Merge ~xiaofengw/cloud-init:xiaofengw-fix-post-script into cloud-init:master
- Git
- lp:~xiaofengw/cloud-init
- xiaofengw-fix-post-script
- Merge into master
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Ryan Harper | ||||
Approved revision: | 5e1dac265dabf225718d8bcd8383b8760ca2f27f | ||||
Merge reported by: | Server Team CI bot | ||||
Merged at revision: | not available | ||||
Proposed branch: | ~xiaofengw/cloud-init:xiaofengw-fix-post-script | ||||
Merge into: | cloud-init:master | ||||
Diff against target: |
363 lines (+111/-155) 3 files modified
cloudinit/sources/DataSourceOVF.py (+6/-1) cloudinit/sources/helpers/vmware/imc/config_custom_script.py (+42/-101) tests/unittests/test_vmware/test_custom_script.py (+63/-53) |
||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Ryan Harper | Approve | ||
Review via email: mp+368902@code.launchpad.net |
Commit message
VMWare: Trigger the post customization script via cc_scripts module.
cloud-init does not trigger reboots of a VM therefore adding custom
scripts to rc.local does not execute the post scripts. This patch
moves post-scripts into per-instance scripts dir and has cc_scripts
module run the post-scripts.
Also in this branch:
- Remove the sh interpreter and execute the customization script
directly.
- Update the unit test.
LP: #1833192
Description of the change
Xiaofeng Wang (xiaofengw) wrote : | # |
Ryan Harper (raharper) wrote : | # |
This looks like a bug; did you file a bug to track the issue? If not, could you do that please?
I've added some comments in-line and some questions so I can provide better feedback.
Xiaofeng Wang (xiaofengw) wrote : | # |
> This looks like a bug; did you file a bug to track the issue? If not, could
> you do that please?
>
> I've added some comments in-line and some questions so I can provide better
> feedback.
The bug is created: https:/
Thanks.
- ecac298... by Xiaofeng Wang
-
Address the review comments from Ryan.
Changes to be committed:
modified: cloudinit/sources/ DataSourceOVF. py
modified: cloudinit/sources/ helpers/ vmware/ imc/config_ custom_ script. py
modified: tests/unittests/test_vmware/ test_custom_ script. py
Ryan Harper (raharper) wrote : | # |
One minor change requested.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:ecac298c51d
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
- 5e1dac2... by Xiaofeng Wang
-
Address comments to move variable definition to the begining
Xiaofeng Wang (xiaofengw) wrote : | # |
> One minor change requested.
Fixed, PTAL. Thanks.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:5e1dac265da
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Autolanding.
Approved revid is not set in launchpad. This is most likely a launchpad issue and re-approve should fix it. There is also a chance (although a very small one) this is a permission problem of the ps-jenkins bot.
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Server Team CI bot (server-team-bot) : | # |
Preview Diff
1 | diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py |
2 | index 70e7a5c..dd941d2 100644 |
3 | --- a/cloudinit/sources/DataSourceOVF.py |
4 | +++ b/cloudinit/sources/DataSourceOVF.py |
5 | @@ -148,6 +148,9 @@ class DataSourceOVF(sources.DataSource): |
6 | product_marker, os.path.join(self.paths.cloud_dir, 'data')) |
7 | special_customization = product_marker and not hasmarkerfile |
8 | customscript = self._vmware_cust_conf.custom_script_name |
9 | + ccScriptsDir = os.path.join( |
10 | + self.paths.get_cpath("scripts"), |
11 | + "per-instance") |
12 | except Exception as e: |
13 | _raise_error_status( |
14 | "Error parsing the customization Config File", |
15 | @@ -201,7 +204,9 @@ class DataSourceOVF(sources.DataSource): |
16 | |
17 | if customscript: |
18 | try: |
19 | - postcust = PostCustomScript(customscript, imcdirpath) |
20 | + postcust = PostCustomScript(customscript, |
21 | + imcdirpath, |
22 | + ccScriptsDir) |
23 | postcust.execute() |
24 | except Exception as e: |
25 | _raise_error_status( |
26 | diff --git a/cloudinit/sources/helpers/vmware/imc/config_custom_script.py b/cloudinit/sources/helpers/vmware/imc/config_custom_script.py |
27 | index a7d4ad9..9f14770 100644 |
28 | --- a/cloudinit/sources/helpers/vmware/imc/config_custom_script.py |
29 | +++ b/cloudinit/sources/helpers/vmware/imc/config_custom_script.py |
30 | @@ -1,5 +1,5 @@ |
31 | # Copyright (C) 2017 Canonical Ltd. |
32 | -# Copyright (C) 2017 VMware Inc. |
33 | +# Copyright (C) 2017-2019 VMware Inc. |
34 | # |
35 | # Author: Maitreyee Saikia <msaikia@vmware.com> |
36 | # |
37 | @@ -8,7 +8,6 @@ |
38 | import logging |
39 | import os |
40 | import stat |
41 | -from textwrap import dedent |
42 | |
43 | from cloudinit import util |
44 | |
45 | @@ -20,12 +19,15 @@ class CustomScriptNotFound(Exception): |
46 | |
47 | |
48 | class CustomScriptConstant(object): |
49 | - RC_LOCAL = "/etc/rc.local" |
50 | - POST_CUST_TMP_DIR = "/root/.customization" |
51 | - POST_CUST_RUN_SCRIPT_NAME = "post-customize-guest.sh" |
52 | - POST_CUST_RUN_SCRIPT = os.path.join(POST_CUST_TMP_DIR, |
53 | - POST_CUST_RUN_SCRIPT_NAME) |
54 | - POST_REBOOT_PENDING_MARKER = "/.guest-customization-post-reboot-pending" |
55 | + CUSTOM_TMP_DIR = "/root/.customization" |
56 | + |
57 | + # The user defined custom script |
58 | + CUSTOM_SCRIPT_NAME = "customize.sh" |
59 | + CUSTOM_SCRIPT = os.path.join(CUSTOM_TMP_DIR, |
60 | + CUSTOM_SCRIPT_NAME) |
61 | + POST_CUSTOM_PENDING_MARKER = "/.guest-customization-post-reboot-pending" |
62 | + # The cc_scripts_per_instance script to launch custom script |
63 | + POST_CUSTOM_SCRIPT_NAME = "post-customize-guest.sh" |
64 | |
65 | |
66 | class RunCustomScript(object): |
67 | @@ -39,10 +41,19 @@ class RunCustomScript(object): |
68 | raise CustomScriptNotFound("Script %s not found!! " |
69 | "Cannot execute custom script!" |
70 | % self.scriptpath) |
71 | + |
72 | + util.ensure_dir(CustomScriptConstant.CUSTOM_TMP_DIR) |
73 | + |
74 | + LOG.debug("Copying custom script to %s", |
75 | + CustomScriptConstant.CUSTOM_SCRIPT) |
76 | + util.copy(self.scriptpath, CustomScriptConstant.CUSTOM_SCRIPT) |
77 | + |
78 | # Strip any CR characters from the decoded script |
79 | - util.load_file(self.scriptpath).replace("\r", "") |
80 | - st = os.stat(self.scriptpath) |
81 | - os.chmod(self.scriptpath, st.st_mode | stat.S_IEXEC) |
82 | + content = util.load_file( |
83 | + CustomScriptConstant.CUSTOM_SCRIPT).replace("\r", "") |
84 | + util.write_file(CustomScriptConstant.CUSTOM_SCRIPT, |
85 | + content, |
86 | + mode=0o544) |
87 | |
88 | |
89 | class PreCustomScript(RunCustomScript): |
90 | @@ -50,104 +61,34 @@ class PreCustomScript(RunCustomScript): |
91 | """Executing custom script with precustomization argument.""" |
92 | LOG.debug("Executing pre-customization script") |
93 | self.prepare_script() |
94 | - util.subp(["/bin/sh", self.scriptpath, "precustomization"]) |
95 | + util.subp([CustomScriptConstant.CUSTOM_SCRIPT, "precustomization"]) |
96 | |
97 | |
98 | class PostCustomScript(RunCustomScript): |
99 | - def __init__(self, scriptname, directory): |
100 | + def __init__(self, scriptname, directory, ccScriptsDir): |
101 | super(PostCustomScript, self).__init__(scriptname, directory) |
102 | - # Determine when to run custom script. When postreboot is True, |
103 | - # the user uploaded script will run as part of rc.local after |
104 | - # the machine reboots. This is determined by presence of rclocal. |
105 | - # When postreboot is False, script will run as part of cloud-init. |
106 | - self.postreboot = False |
107 | - |
108 | - def _install_post_reboot_agent(self, rclocal): |
109 | - """ |
110 | - Install post-reboot agent for running custom script after reboot. |
111 | - As part of this process, we are editing the rclocal file to run a |
112 | - VMware script, which in turn is resposible for handling the user |
113 | - script. |
114 | - @param: path to rc local. |
115 | - """ |
116 | - LOG.debug("Installing post-reboot customization from %s to %s", |
117 | - self.directory, rclocal) |
118 | - if not self.has_previous_agent(rclocal): |
119 | - LOG.info("Adding post-reboot customization agent to rc.local") |
120 | - new_content = dedent(""" |
121 | - # Run post-reboot guest customization |
122 | - /bin/sh %s |
123 | - exit 0 |
124 | - """) % CustomScriptConstant.POST_CUST_RUN_SCRIPT |
125 | - existing_rclocal = util.load_file(rclocal).replace('exit 0\n', '') |
126 | - st = os.stat(rclocal) |
127 | - # "x" flag should be set |
128 | - mode = st.st_mode | stat.S_IEXEC |
129 | - util.write_file(rclocal, existing_rclocal + new_content, mode) |
130 | - |
131 | - else: |
132 | - # We don't need to update rclocal file everytime a customization |
133 | - # is requested. It just needs to be done for the first time. |
134 | - LOG.info("Post-reboot guest customization agent is already " |
135 | - "registered in rc.local") |
136 | - LOG.debug("Installing post-reboot customization agent finished: %s", |
137 | - self.postreboot) |
138 | - |
139 | - def has_previous_agent(self, rclocal): |
140 | - searchstring = "# Run post-reboot guest customization" |
141 | - if searchstring in open(rclocal).read(): |
142 | - return True |
143 | - return False |
144 | - |
145 | - def find_rc_local(self): |
146 | - """ |
147 | - Determine if rc local is present. |
148 | - """ |
149 | - rclocal = "" |
150 | - if os.path.exists(CustomScriptConstant.RC_LOCAL): |
151 | - LOG.debug("rc.local detected.") |
152 | - # resolving in case of symlink |
153 | - rclocal = os.path.realpath(CustomScriptConstant.RC_LOCAL) |
154 | - LOG.debug("rc.local resolved to %s", rclocal) |
155 | - else: |
156 | - LOG.warning("Can't find rc.local, post-customization " |
157 | - "will be run before reboot") |
158 | - return rclocal |
159 | - |
160 | - def install_agent(self): |
161 | - rclocal = self.find_rc_local() |
162 | - if rclocal: |
163 | - self._install_post_reboot_agent(rclocal) |
164 | - self.postreboot = True |
165 | + self.ccScriptsDir = ccScriptsDir |
166 | + self.ccScriptPath = os.path.join( |
167 | + ccScriptsDir, |
168 | + CustomScriptConstant.POST_CUSTOM_SCRIPT_NAME) |
169 | |
170 | def execute(self): |
171 | """ |
172 | - This method executes post-customization script before or after reboot |
173 | - based on the presence of rc local. |
174 | + This method copy the post customize run script to |
175 | + cc_scripts_per_instance directory and let this |
176 | + module to run post custom script. |
177 | """ |
178 | self.prepare_script() |
179 | - self.install_agent() |
180 | - if not self.postreboot: |
181 | - LOG.warning("Executing post-customization script inline") |
182 | - util.subp(["/bin/sh", self.scriptpath, "postcustomization"]) |
183 | - else: |
184 | - LOG.debug("Scheduling custom script to run post reboot") |
185 | - if not os.path.isdir(CustomScriptConstant.POST_CUST_TMP_DIR): |
186 | - os.mkdir(CustomScriptConstant.POST_CUST_TMP_DIR) |
187 | - # Script "post-customize-guest.sh" and user uploaded script are |
188 | - # are present in the same directory and needs to copied to a temp |
189 | - # directory to be executed post reboot. User uploaded script is |
190 | - # saved as customize.sh in the temp directory. |
191 | - # post-customize-guest.sh excutes customize.sh after reboot. |
192 | - LOG.debug("Copying post-customization script") |
193 | - util.copy(self.scriptpath, |
194 | - CustomScriptConstant.POST_CUST_TMP_DIR + "/customize.sh") |
195 | - LOG.debug("Copying script to run post-customization script") |
196 | - util.copy( |
197 | - os.path.join(self.directory, |
198 | - CustomScriptConstant.POST_CUST_RUN_SCRIPT_NAME), |
199 | - CustomScriptConstant.POST_CUST_RUN_SCRIPT) |
200 | - LOG.info("Creating post-reboot pending marker") |
201 | - util.ensure_file(CustomScriptConstant.POST_REBOOT_PENDING_MARKER) |
202 | + |
203 | + LOG.debug("Copying post customize run script to %s", |
204 | + self.ccScriptPath) |
205 | + util.copy( |
206 | + os.path.join(self.directory, |
207 | + CustomScriptConstant.POST_CUSTOM_SCRIPT_NAME), |
208 | + self.ccScriptPath) |
209 | + st = os.stat(self.ccScriptPath) |
210 | + os.chmod(self.ccScriptPath, st.st_mode | stat.S_IEXEC) |
211 | + LOG.info("Creating post customization pending marker") |
212 | + util.ensure_file(CustomScriptConstant.POST_CUSTOM_PENDING_MARKER) |
213 | |
214 | # vi: ts=4 expandtab |
215 | diff --git a/tests/unittests/test_vmware/test_custom_script.py b/tests/unittests/test_vmware/test_custom_script.py |
216 | index 2d9519b..f89f815 100644 |
217 | --- a/tests/unittests/test_vmware/test_custom_script.py |
218 | +++ b/tests/unittests/test_vmware/test_custom_script.py |
219 | @@ -1,10 +1,12 @@ |
220 | # Copyright (C) 2015 Canonical Ltd. |
221 | -# Copyright (C) 2017 VMware INC. |
222 | +# Copyright (C) 2017-2019 VMware INC. |
223 | # |
224 | # Author: Maitreyee Saikia <msaikia@vmware.com> |
225 | # |
226 | # This file is part of cloud-init. See LICENSE file for license information. |
227 | |
228 | +import os |
229 | +import stat |
230 | from cloudinit import util |
231 | from cloudinit.sources.helpers.vmware.imc.config_custom_script import ( |
232 | CustomScriptConstant, |
233 | @@ -18,6 +20,10 @@ from cloudinit.tests.helpers import CiTestCase, mock |
234 | class TestVmwareCustomScript(CiTestCase): |
235 | def setUp(self): |
236 | self.tmpDir = self.tmp_dir() |
237 | + # Mock the tmpDir as the root dir in VM. |
238 | + self.execDir = os.path.join(self.tmpDir, ".customization") |
239 | + self.execScript = os.path.join(self.execDir, |
240 | + ".customize.sh") |
241 | |
242 | def test_prepare_custom_script(self): |
243 | """ |
244 | @@ -37,63 +43,67 @@ class TestVmwareCustomScript(CiTestCase): |
245 | |
246 | # Custom script exists. |
247 | custScript = self.tmp_path("test-cust", self.tmpDir) |
248 | - util.write_file(custScript, "test-CR-strip/r/r") |
249 | - postCust = PostCustomScript("test-cust", self.tmpDir) |
250 | - self.assertEqual("test-cust", postCust.scriptname) |
251 | - self.assertEqual(self.tmpDir, postCust.directory) |
252 | - self.assertEqual(custScript, postCust.scriptpath) |
253 | - self.assertFalse(postCust.postreboot) |
254 | - postCust.prepare_script() |
255 | - # Check if all carraige returns are stripped from script. |
256 | - self.assertFalse("/r" in custScript) |
257 | + util.write_file(custScript, "test-CR-strip\r\r") |
258 | + with mock.patch.object(CustomScriptConstant, |
259 | + "CUSTOM_TMP_DIR", |
260 | + self.execDir): |
261 | + with mock.patch.object(CustomScriptConstant, |
262 | + "CUSTOM_SCRIPT", |
263 | + self.execScript): |
264 | + postCust = PostCustomScript("test-cust", |
265 | + self.tmpDir, |
266 | + self.tmpDir) |
267 | + self.assertEqual("test-cust", postCust.scriptname) |
268 | + self.assertEqual(self.tmpDir, postCust.directory) |
269 | + self.assertEqual(custScript, postCust.scriptpath) |
270 | + postCust.prepare_script() |
271 | |
272 | - def test_rc_local_exists(self): |
273 | - """ |
274 | - This test is designed to verify the different scenarios associated |
275 | - with the presence of rclocal. |
276 | - """ |
277 | - # test when rc local does not exist |
278 | - postCust = PostCustomScript("test-cust", self.tmpDir) |
279 | - with mock.patch.object(CustomScriptConstant, "RC_LOCAL", "/no/path"): |
280 | - rclocal = postCust.find_rc_local() |
281 | - self.assertEqual("", rclocal) |
282 | - |
283 | - # test when rc local exists |
284 | - rclocalFile = self.tmp_path("vmware-rclocal", self.tmpDir) |
285 | - util.write_file(rclocalFile, "# Run post-reboot guest customization", |
286 | - omode="w") |
287 | - with mock.patch.object(CustomScriptConstant, "RC_LOCAL", rclocalFile): |
288 | - rclocal = postCust.find_rc_local() |
289 | - self.assertEqual(rclocalFile, rclocal) |
290 | - self.assertTrue(postCust.has_previous_agent, rclocal) |
291 | - |
292 | - # test when rc local is a symlink |
293 | - rclocalLink = self.tmp_path("dummy-rclocal-link", self.tmpDir) |
294 | - util.sym_link(rclocalFile, rclocalLink, True) |
295 | - with mock.patch.object(CustomScriptConstant, "RC_LOCAL", rclocalLink): |
296 | - rclocal = postCust.find_rc_local() |
297 | - self.assertEqual(rclocalFile, rclocal) |
298 | + # Custom script is copied with exec privilege |
299 | + self.assertTrue(os.path.exists(self.execScript)) |
300 | + st = os.stat(self.execScript) |
301 | + self.assertTrue(st.st_mode & stat.S_IEXEC) |
302 | + with open(self.execScript, "r") as f: |
303 | + content = f.read() |
304 | + self.assertEqual(content, "test-CR-strip") |
305 | + # Check if all carraige returns are stripped from script. |
306 | + self.assertFalse("\r" in content) |
307 | |
308 | def test_execute_post_cust(self): |
309 | """ |
310 | - This test is to identify if rclocal was properly populated to be |
311 | - run after reboot. |
312 | + This test is designed to verify the behavior after execute post |
313 | + customization. |
314 | """ |
315 | - customscript = self.tmp_path("vmware-post-cust-script", self.tmpDir) |
316 | - rclocal = self.tmp_path("vmware-rclocal", self.tmpDir) |
317 | - # Create a temporary rclocal file |
318 | - open(customscript, "w") |
319 | - util.write_file(rclocal, "tests\nexit 0", omode="w") |
320 | - postCust = PostCustomScript("vmware-post-cust-script", self.tmpDir) |
321 | - with mock.patch.object(CustomScriptConstant, "RC_LOCAL", rclocal): |
322 | - # Test that guest customization agent is not installed initially. |
323 | - self.assertFalse(postCust.postreboot) |
324 | - self.assertIs(postCust.has_previous_agent(rclocal), False) |
325 | - postCust.install_agent() |
326 | + # Prepare the customize package |
327 | + postCustRun = self.tmp_path("post-customize-guest.sh", self.tmpDir) |
328 | + util.write_file(postCustRun, "This is the script to run post cust") |
329 | + userScript = self.tmp_path("test-cust", self.tmpDir) |
330 | + util.write_file(userScript, "This is the post cust script") |
331 | |
332 | - # Assert rclocal has been modified to have guest customization |
333 | - # agent. |
334 | - self.assertTrue(postCust.postreboot) |
335 | - self.assertTrue(postCust.has_previous_agent, rclocal) |
336 | + # Mock the cc_scripts_per_instance dir and marker file. |
337 | + # Create another tmp dir for cc_scripts_per_instance. |
338 | + ccScriptDir = self.tmp_dir() |
339 | + ccScript = os.path.join(ccScriptDir, "post-customize-guest.sh") |
340 | + markerFile = os.path.join(self.tmpDir, ".markerFile") |
341 | + with mock.patch.object(CustomScriptConstant, |
342 | + "CUSTOM_TMP_DIR", |
343 | + self.execDir): |
344 | + with mock.patch.object(CustomScriptConstant, |
345 | + "CUSTOM_SCRIPT", |
346 | + self.execScript): |
347 | + with mock.patch.object(CustomScriptConstant, |
348 | + "POST_CUSTOM_PENDING_MARKER", |
349 | + markerFile): |
350 | + postCust = PostCustomScript("test-cust", |
351 | + self.tmpDir, |
352 | + ccScriptDir) |
353 | + postCust.execute() |
354 | + # Check cc_scripts_per_instance and marker file |
355 | + # are created. |
356 | + self.assertTrue(os.path.exists(ccScript)) |
357 | + with open(ccScript, "r") as f: |
358 | + content = f.read() |
359 | + self.assertEqual(content, |
360 | + "This is the script to run post cust") |
361 | + self.assertTrue(os.path.exists(markerFile)) |
362 | |
363 | # vi: ts=4 expandtab |
Test launched: specify a custom script), make sure the custom script is run and only run once.The guest customization finished successfully. without custom script), make sure the guest customization finished successfully.
1. tox test passed.
2. Deploy cloud-init and perform the guest customization(
3. Deploy cloud-init and perform the guest customization(
4. Repeat test 2 with sh, bash, dash custom script.