Skip to content

Fix unpickle for source paths missing run_dir#863

Merged
OddBloke merged 3 commits into
canonical:masterfrom
lucasmoura:fix-pickle-for-paths-run-dir-attribute
Apr 12, 2021
Merged

Fix unpickle for source paths missing run_dir#863
OddBloke merged 3 commits into
canonical:masterfrom
lucasmoura:fix-pickle-for-paths-run-dir-attribute

Conversation

@lucasmoura
Copy link
Copy Markdown
Contributor

@lucasmoura lucasmoura commented Apr 7, 2021

Proposed Commit Message

Fix unpickle for source paths missing run_dir

On the source class, we require the use of paths.run_dir to perform some operations. On older cloud-init version, the Paths class does not have the run_dir attribute. To fix that, we are now manually adding that attribute in the source paths
object if doesn't exist in the unpickle operation.

LP: #1899299

Test Steps

To reproduce this issue, run the following script:

#!/bin/sh
set -x

series=trusty
name=$series-upgrade

lxc delete --force $name
lxc launch ubuntu-daily:$series $name
sleep 10

lxc exec $name -- add-apt-repository ppa:ua-client/stable -y
lxc exec $name -- sudo apt-get update
lxc exec $name -- sudo apt-get install ubuntu-advantage-tools -y
lxc exec $name -- ua version
lxc exec $name -- sudo ua attach <uaclient-token>
lxc exec $name -- sudo ua status
lxc exec $name -- sudo add-apt-repository ppa:cloud-init-dev/daily -y
lxc exec $name -- sudo apt-get update
lxc exec $name -- sudo mkdir -p /etc/update-manager/release-upgrades.d  
lxc exec $name -- sh -c "echo '[Sources]\nAllowThirdParty=yes' > allow.cfg"
lxc exec $name -- sudo mv allow.cfg /etc/update-manager/release-upgrades.d   
lxc exec $name -- sudo apt-get upgrade -y
lxc exec $name -- sudo apt-get dist-upgrade -y
lxc exec $name -- sudo do-release-upgrade --frontend DistUpgradeViewNonInteractive
lxc exec $name -- sudo ua status --wait

lxc exec $name -- sudo reboot
sleep 60
lxc exec $name -- sudo ua status --wait

set +x

By looking at the /var/log/ubuntu-advantage.log we will see that the error is because cloud-id fails with the error:

AttributeError: 'Paths' object has no attribute 'run_dir'

We can confirm that by running cloud-id on the container directly.

To verify that the fix works, run the following commands:

lxc file push cloudinit/helpers.py trusty-upgrade/usr/lib/python3/dist-packages/cloudinit/helpers.py
lxc exec trusty-upgrade -- reboot
sleep 30
lxc exec trusty-upgrade -- cloud-id
lxc exec trusty-upgrade -- ua status

Checklist:

  • My code follows the process laid out in the documentation
  • I have updated or added any unit tests accordingly
  • I have updated or added any documentation accordingly

Copy link
Copy Markdown
Collaborator

@OddBloke OddBloke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Lucas! It's good to see this framework seems to be up to the second challenge it's presented with! I have some inline thoughts, but this is a good first pass.

Comment thread cloudinit/sources/__init__.py Outdated

def _unpickle(self, ci_pkl_version: int) -> None:
"""Perform deserialization fixes for Distro."""
if "run_dir" not in self.paths.__dict__:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if "run_dir" not in self.paths.__dict__:
if not hasattr(self.paths, "run_dir"):

because it handles properties appropriately:

In [1]: class Foo:
   ...:     @property
   ...:     def run_dir(self):
   ...:         pass
   ...: 

In [2]: "run_dir" in Foo().__dict__
Out[2]: False

In [3]: hasattr(Foo(), "run_dir")
Out[3]: True

Comment thread cloudinit/sources/__init__.py Outdated
# when loading the pickle object on newer versions of cloud-init
# we will rely on this attribute. To fix that, we are now
# manually adding that attribute here.
self.paths.run_dir = "/run/cloud-init"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is likely safe, but run_dir is configurable. It's possible that someone could have configured a non-default run_dir even without a cloud-init that supports it (e.g. by applying the same configuration file to all their images). To ensure that we pick such configuration up, I think we should probably reinstantiate Paths with the same configuration as the pickled one, and then pull run_dir out of it. Roughly:

self.paths.run_dir = Paths(self.paths.cfgs, ds=self.paths.datasource).run_dir

Does that make sense? (Do you agree?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does make sense. I missed that this could configured by the user. So doing that is the safest approach.

Comment thread cloudinit/sources/__init__.py Outdated
def __str__(self):
return type_utils.obj_name(self)

def _unpickle(self, ci_pkl_version: int) -> None:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be on DataSource? Could it instead be on Paths itself?

As background, the networking fix had to be in Distro._unpickle because affected Networking instances don't have any instance state: this means that they get unpickled differently, so the hooks we use don't get called. In this case, however, all Paths instances do have instance state, so the hooks we use should get called.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are totally right here. I missed that the Paths object can be instantiated directly. I will move the code there

Comment thread cloudinit/sources/__init__.py Outdated
return type_utils.obj_name(self)

def _unpickle(self, ci_pkl_version: int) -> None:
"""Perform deserialization fixes for Distro."""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs updating.

On the source class, we require the use of paths.run_dir to
perform some operations. On older cloud-init version, the
Paths class does not have the run_dir attribute. To fix that,
we are now manually adding that attribute in the Paths
object if doesn't exist in the unpickle operation.

LP: #1899299
@lucasmoura lucasmoura force-pushed the fix-pickle-for-paths-run-dir-attribute branch from 91bc12b to c02a628 Compare April 7, 2021 18:03
@lucasmoura
Copy link
Copy Markdown
Contributor Author

@OddBloke Thank you for the review and the context here. I have updated the code

@lucasmoura lucasmoura requested a review from OddBloke April 7, 2021 18:03
Copy link
Copy Markdown
Collaborator

@OddBloke OddBloke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Lucas! One minor nit which I'll apply now.

Comment thread cloudinit/helpers.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants