Merge ~smoser/cloud-init:fix/cii-kvmimage-preserve-original into cloud-init:master

Proposed by Scott Moser on 2017-11-22
Status: Merged
Approved by: Scott Moser on 2017-11-29
Approved revision: 27ff24127c0bd5074817a199eb373cae5ab83d29
Merged at revision: 88368f9851b29dddb5a12e4b21868cbdef906c5c
Proposed branch: ~smoser/cloud-init:fix/cii-kvmimage-preserve-original
Merge into: cloud-init:master
Diff against target: 200 lines (+42/-26)
4 files modified
tests/cloud_tests/images/nocloudkvm.py (+15/-7)
tests/cloud_tests/instances/nocloudkvm.py (+5/-3)
tests/cloud_tests/platforms/nocloudkvm.py (+11/-10)
tests/cloud_tests/snapshots/nocloudkvm.py (+11/-6)
Reviewer Review Type Date Requested Status
Ryan Harper 2017-11-22 Approve on 2017-11-29
Server Team CI bot continuous-integration Approve on 2017-11-27
Joshua Powers (community) Approve on 2017-11-22
Review via email: mp+334147@code.launchpad.net

Commit Message

tests: NoCloudKVMImage do not modify the original local cache image.

The NoCloudKVMImage.execute() would modify the image in /srv/citest
that meant that after the first time you ran a test, the image was
dirty.

The change here is to make the image operate on a qcow backed image.

Also modify Snapshot to then copy the qcow rather
than creating another chained qcow. The reason being that the image
might go away but the snapshot stick around.

Also
 * drop use of 'override_templates' which was only relevant to LXD.
 * NoCloudKVM.create_image() returned an instance before
   now it has create_instance which creates an instance.
 * NoCloudKVMInstance has a 'disk' attribute separate from 'name'

To post a comment you must log in.

PASSED: Continuous integration, rev:b82bed1db430c94df690261f720cceda2e58a235
https://jenkins.ubuntu.com/server/job/cloud-init-ci/539/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/539/rebuild

review: Approve (continuous-integration)
Joshua Powers (powersj) wrote :

+1 This also makes sure we are able to properly use --preserve-data now with qcow2 files that are ready to boot in the event triage is needed, even if the original file is deleted.

Test with:

python3 -m tests.cloud_tests run --platform nocloud-kvm \
    --verbose --preserve-data --data-dir out --os-name xenial \
    -t modules/write_files -t modules/final_message \
    --deb cloud-init_17.1-41-g76243487-0ubuntu1_all.deb

I appreciate the cleanup of the variable names to be more specific as to what each is.

review: Approve

PASSED: Continuous integration, rev:86255ca411f0ea99a13bb96aec6625e61c07008b
https://jenkins.ubuntu.com/server/job/cloud-init-ci/542/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/542/rebuild

review: Approve (continuous-integration)
Ryan Harper (raharper) wrote :

What's the use case for the "destroy an image without breaking a snapshot" that introduces the copy of the image.

Some more comments inline as well.

Scott Moser (smoser) wrote :

An image is a mutable thing.
General flow of tests is:
 - get an image
 - update the image (install a new deb)
 - create a snapshot
 - launch a snapshot multiple times

After creating a snapshot of the image the image can reasonably be destroyed.
Just like in a "real cloud".
 - get an image (download a cloud image or boot an instance we can update/modify)
 - update the image
 - create a snapshot (publish to a new ami id)

At the point where we've created a new snapshot we no longer need the image.

As it was previously, if we deleted the Image that the snapshot was created from, then the snapshot (a qcow image) was broken.

Scott Moser (smoser) :
Ryan Harper (raharper) wrote :

> An image is a mutable thing.
> General flow of tests is:
> - get an image
> - update the image (install a new deb)
> - create a snapshot
> - launch a snapshot multiple times
>
> After creating a snapshot of the image the image can reasonably be destroyed.
> Just like in a "real cloud".
> - get an image (download a cloud image or boot an instance we can
> update/modify)
> - update the image
> - create a snapshot (publish to a new ami id)
>
> At the point where we've created a new snapshot we no longer need the image.

Sure, but where in the workflow of our nightly tests will the images be purged,
or where in a developer workflow would the images actually get removed.
Does part of the make cloud-tests or whatever remove images?

>
> As it was previously, if we deleted the Image that the snapshot was created
> from, then the snapshot (a qcow image) was broken.

I'm not terribly concerned about manual deletion of images; Is there some
automatic removal of images that would let a tree rot such that invoking
a cloud-test at a later time would break without the copy?

Ryan Harper (raharper) :
27ff241... by Scott Moser on 2017-11-27

better error messages on multiple images found

Scott Moser (smoser) wrote :

the Image object has a destroy() method which is valid to call, and should be called whne you're "done" with the object.
  i = Image(foo)
  i.execute(['apt-get', 'dist-upgrade'])
  s = i.snapshot()
  i.destroy()
  s.launch()
  ...

the result of snapshot needs to be stand-alone, and before this change it was not.
As used above, s.launch() would have failed as it was qcow backed by i.

FAILED: Continuous integration, rev:cc9905002cd5956d01c5aa31676be719b0170f5d
https://jenkins.ubuntu.com/server/job/cloud-init-ci/546/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/546/rebuild

review: Needs Fixing (continuous-integration)

PASSED: Continuous integration, rev:27ff24127c0bd5074817a199eb373cae5ab83d29
https://jenkins.ubuntu.com/server/job/cloud-init-ci/547/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/547/rebuild

review: Approve (continuous-integration)
Ryan Harper (raharper) wrote :

> the Image object has a destroy() method which is valid to call, and should be
> called whne you're "done" with the object.
> i = Image(foo)
> i.execute(['apt-get', 'dist-upgrade'])
> s = i.snapshot()
> i.destroy()
> s.launch()
> ...
>
> the result of snapshot needs to be stand-alone, and before this change it was
> not.
> As used above, s.launch() would have failed as it was qcow backed by i.

But is this called in this way via how users and CI interact with the test-suite?
I suspect it's not; and nothing is doing the above or the test-suite would have broken.

Scott Moser (smoser) wrote :

It looks like as it is right now we do not call destroy on an image before using a snapshot.
But having NoCloud work like other clouds makes sense.

As it is right now we only ever call snapshot wrapped in a context handler of an image.

Ryan Harper (raharper) wrote :

On Mon, Nov 27, 2017 at 4:31 PM, Scott Moser <email address hidden>
wrote:

>
> It looks like as it is right now we do not call destroy on an image before
> using a snapshot.
> But having NoCloud work like other clouds makes sense.
>

I don't disagree with the goal; however; just as I don't want to upload a
new snapshot
each time I launch a VM, I don't want to copy over the image each time I
launch one in
NoCloud.

Can we model the image manipulation like we would the clouds?

i = Image(foo)
i.execute("modifications here")
s = i.snapshot()
new_image = cloud.upload(s)
new_image.launch()

I think that makes it clear that one *could* delete the original image
second the upload allows for NoCloud to do a copy and return a new path to
the image
which hopefully we could re-use on subsequent runs

i = cloud.get_image("foo")
i.launch()

After the upload, I think it'd be fair for cloud.get_image() to return a
qcow2 image backed against the "uploaded" snapshot which would
avoid the copy and allow the original image to be deleted (not that we have
a use-case for that).

>
> As it is right now we only ever call snapshot wrapped in a context handler
> of an image.
>
> --
> https://code.launchpad.net/~smoser/cloud-init/+git/cloud-
> init/+merge/334147
> Your team cloud-init commiters is requested to review the proposed merge
> of ~smoser/cloud-init:fix/cii-kvmimage-preserve-original into
> cloud-init:master.
>
> _______________________________________________
> Mailing list: https://launchpad.net/~cloud-init-dev
> Post to : <email address hidden>
> Unsubscribe : https://launchpad.net/~cloud-init-dev
> More help : https://help.launchpad.net/ListHelp
>

Ryan Harper (raharper) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/tests/cloud_tests/images/nocloudkvm.py b/tests/cloud_tests/images/nocloudkvm.py
2index 1e7962c..8678b07 100644
3--- a/tests/cloud_tests/images/nocloudkvm.py
4+++ b/tests/cloud_tests/images/nocloudkvm.py
5@@ -4,6 +4,10 @@
6
7 from cloudinit import util as c_util
8
9+import os
10+import shutil
11+import tempfile
12+
13 from tests.cloud_tests.images import base
14 from tests.cloud_tests.snapshots import nocloudkvm as nocloud_kvm_snapshot
15
16@@ -13,7 +17,7 @@ class NoCloudKVMImage(base.Image):
17
18 platform_name = "nocloud-kvm"
19
20- def __init__(self, platform, config, img_path):
21+ def __init__(self, platform, config, orig_img_path):
22 """Set up image.
23
24 @param platform: platform object
25@@ -21,7 +25,13 @@ class NoCloudKVMImage(base.Image):
26 @param img_path: path to the image
27 """
28 self.modified = False
29- self._img_path = img_path
30+ self._workd = tempfile.mkdtemp(prefix='NoCloudKVMImage')
31+ self._orig_img_path = orig_img_path
32+ self._img_path = os.path.join(self._workd,
33+ os.path.basename(self._orig_img_path))
34+
35+ c_util.subp(['qemu-img', 'create', '-f', 'qcow2',
36+ '-b', orig_img_path, self._img_path])
37
38 super(NoCloudKVMImage, self).__init__(platform, config)
39
40@@ -61,13 +71,9 @@ class NoCloudKVMImage(base.Image):
41 if not self._img_path:
42 raise RuntimeError()
43
44- instance = self.platform.create_image(
45- self.properties, self.config, self.features,
46- self._img_path, image_desc=str(self), use_desc='snapshot')
47-
48 return nocloud_kvm_snapshot.NoCloudKVMSnapshot(
49 self.platform, self.properties, self.config,
50- self.features, instance)
51+ self.features, self._img_path)
52
53 def destroy(self):
54 """Unset path to signal image is no longer used.
55@@ -77,6 +83,8 @@ class NoCloudKVMImage(base.Image):
56 framework decide whether to keep or destroy everything.
57 """
58 self._img_path = None
59+ shutil.rmtree(self._workd)
60+
61 super(NoCloudKVMImage, self).destroy()
62
63 # vi: ts=4 expandtab
64diff --git a/tests/cloud_tests/instances/nocloudkvm.py b/tests/cloud_tests/instances/nocloudkvm.py
65index cc82580..bc06a79 100644
66--- a/tests/cloud_tests/instances/nocloudkvm.py
67+++ b/tests/cloud_tests/instances/nocloudkvm.py
68@@ -25,12 +25,13 @@ class NoCloudKVMInstance(base.Instance):
69 platform_name = "nocloud-kvm"
70 _ssh_client = None
71
72- def __init__(self, platform, name, properties, config, features,
73- user_data, meta_data):
74+ def __init__(self, platform, name, image_path, properties, config,
75+ features, user_data, meta_data):
76 """Set up instance.
77
78 @param platform: platform object
79 @param name: image path
80+ @param image_path: path to disk image to boot.
81 @param properties: dictionary of properties
82 @param config: dictionary of configuration values
83 @param features: dictionary of supported feature flags
84@@ -43,6 +44,7 @@ class NoCloudKVMInstance(base.Instance):
85 self.pid = None
86 self.pid_file = None
87 self.console_file = None
88+ self.disk = image_path
89
90 super(NoCloudKVMInstance, self).__init__(
91 platform, name, properties, config, features)
92@@ -145,7 +147,7 @@ class NoCloudKVMInstance(base.Instance):
93 self.ssh_port = self.get_free_port()
94
95 cmd = ['./tools/xkvm',
96- '--disk', '%s,cache=unsafe' % self.name,
97+ '--disk', '%s,cache=unsafe' % self.disk,
98 '--disk', '%s,cache=unsafe' % seed,
99 '--netdev', ','.join(['user',
100 'hostfwd=tcp::%s-:22' % self.ssh_port,
101diff --git a/tests/cloud_tests/platforms/nocloudkvm.py b/tests/cloud_tests/platforms/nocloudkvm.py
102index f1f8187..76cd83a 100644
103--- a/tests/cloud_tests/platforms/nocloudkvm.py
104+++ b/tests/cloud_tests/platforms/nocloudkvm.py
105@@ -55,19 +55,20 @@ class NoCloudKVMPlatform(base.Platform):
106 for fname in glob.iglob(search_d, recursive=True):
107 images.append(fname)
108
109- if len(images) != 1:
110- raise Exception('No unique images found')
111+ if len(images) < 1:
112+ raise RuntimeError("No images found under '%s'" % search_d)
113+ if len(images) > 1:
114+ raise RuntimeError(
115+ "Multiple images found in '%s': %s" % (search_d,
116+ ' '.join(images)))
117
118 image = nocloud_kvm_image.NoCloudKVMImage(self, img_conf, images[0])
119- if img_conf.get('override_templates', False):
120- image.update_templates(self.config.get('template_overrides', {}),
121- self.config.get('template_files', {}))
122 return image
123
124- def create_image(self, properties, config, features,
125- src_img_path, image_desc=None, use_desc=None,
126- user_data=None, meta_data=None):
127- """Create an image
128+ def create_instance(self, properties, config, features,
129+ src_img_path, image_desc=None, use_desc=None,
130+ user_data=None, meta_data=None):
131+ """Create an instance
132
133 @param src_img_path: image path to launch from
134 @param properties: image properties
135@@ -82,7 +83,7 @@ class NoCloudKVMPlatform(base.Platform):
136 c_util.subp(['qemu-img', 'create', '-f', 'qcow2',
137 '-b', src_img_path, img_path])
138
139- return nocloud_kvm_instance.NoCloudKVMInstance(self, img_path,
140+ return nocloud_kvm_instance.NoCloudKVMInstance(self, name, img_path,
141 properties, config,
142 features, user_data,
143 meta_data)
144diff --git a/tests/cloud_tests/snapshots/nocloudkvm.py b/tests/cloud_tests/snapshots/nocloudkvm.py
145index 0999834..21e908d 100644
146--- a/tests/cloud_tests/snapshots/nocloudkvm.py
147+++ b/tests/cloud_tests/snapshots/nocloudkvm.py
148@@ -2,6 +2,8 @@
149
150 """Base NoCloud KVM snapshot."""
151 import os
152+import shutil
153+import tempfile
154
155 from tests.cloud_tests.snapshots import base
156
157@@ -11,16 +13,19 @@ class NoCloudKVMSnapshot(base.Snapshot):
158
159 platform_name = "nocloud-kvm"
160
161- def __init__(self, platform, properties, config, features,
162- instance):
163+ def __init__(self, platform, properties, config, features, image_path):
164 """Set up snapshot.
165
166 @param platform: platform object
167 @param properties: image properties
168 @param config: image config
169 @param features: supported feature flags
170+ @param image_path: image file to snapshot.
171 """
172- self.instance = instance
173+ self._workd = tempfile.mkdtemp(prefix='NoCloudKVMSnapshot')
174+ snapshot = os.path.join(self._workd, 'snapshot')
175+ shutil.copyfile(image_path, snapshot)
176+ self._image_path = snapshot
177
178 super(NoCloudKVMSnapshot, self).__init__(
179 platform, properties, config, features)
180@@ -40,9 +45,9 @@ class NoCloudKVMSnapshot(base.Snapshot):
181 self.platform.config['public_key'])
182 user_data = self.inject_ssh_key(user_data, key_file)
183
184- instance = self.platform.create_image(
185+ instance = self.platform.create_instance(
186 self.properties, self.config, self.features,
187- self.instance.name, image_desc=str(self), use_desc=use_desc,
188+ self._image_path, image_desc=str(self), use_desc=use_desc,
189 user_data=user_data, meta_data=meta_data)
190
191 if start:
192@@ -68,7 +73,7 @@ class NoCloudKVMSnapshot(base.Snapshot):
193
194 def destroy(self):
195 """Clean up snapshot data."""
196- self.instance.destroy()
197+ shutil.rmtree(self._workd)
198 super(NoCloudKVMSnapshot, self).destroy()
199
200 # vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches

to all changes: