Merge ~xiaofengw/cloud-init:xiaofengw-fix-post-script into cloud-init:master

Proposed by Xiaofeng Wang
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)
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

To post a comment you must log in.
Revision history for this message
Xiaofeng Wang (xiaofengw) wrote :

Test launched:
1. tox test passed.
2. Deploy cloud-init and perform the guest customization(specify a custom script), make sure the custom script is run and only run once.The guest customization finished successfully.
3. Deploy cloud-init and perform the guest customization(without custom script), make sure the guest customization finished successfully.
4. Repeat test 2 with sh, bash, dash custom script.

Revision history for this message
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.

review: Needs Information
Revision history for this message
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://bugs.launchpad.net/ubuntu/+source/evince/+bug/1833192
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

Revision history for this message
Ryan Harper (raharper) wrote :

One minor change requested.

review: Needs Fixing
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:ecac298c51dd758d7757834f3fc41f28e2b5a071
https://jenkins.ubuntu.com/server/job/cloud-init-ci/743/
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://jenkins.ubuntu.com/server/job/cloud-init-ci/743/rebuild

review: Approve (continuous-integration)
5e1dac2... by Xiaofeng Wang

Address comments to move variable definition to the begining

Revision history for this message
Xiaofeng Wang (xiaofengw) wrote :

> One minor change requested.
Fixed, PTAL. Thanks.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:5e1dac265dabf225718d8bcd8383b8760ca2f27f
https://jenkins.ubuntu.com/server/job/cloud-init-ci/779/
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://jenkins.ubuntu.com/server/job/cloud-init-ci/779/rebuild

review: Approve (continuous-integration)
Revision history for this message
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://jenkins.ubuntu.com/server/job/cloud-init-autoland-test/260/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    IN_PROGRESS: Declarative: Post Actions

review: Needs Fixing (continuous-integration)
Revision history for this message
Ryan Harper (raharper) wrote :

Thanks!

review: Approve
Revision history for this message
Server Team CI bot (server-team-bot) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py
index 70e7a5c..dd941d2 100644
--- a/cloudinit/sources/DataSourceOVF.py
+++ b/cloudinit/sources/DataSourceOVF.py
@@ -148,6 +148,9 @@ class DataSourceOVF(sources.DataSource):
148 product_marker, os.path.join(self.paths.cloud_dir, 'data'))148 product_marker, os.path.join(self.paths.cloud_dir, 'data'))
149 special_customization = product_marker and not hasmarkerfile149 special_customization = product_marker and not hasmarkerfile
150 customscript = self._vmware_cust_conf.custom_script_name150 customscript = self._vmware_cust_conf.custom_script_name
151 ccScriptsDir = os.path.join(
152 self.paths.get_cpath("scripts"),
153 "per-instance")
151 except Exception as e:154 except Exception as e:
152 _raise_error_status(155 _raise_error_status(
153 "Error parsing the customization Config File",156 "Error parsing the customization Config File",
@@ -201,7 +204,9 @@ class DataSourceOVF(sources.DataSource):
201204
202 if customscript:205 if customscript:
203 try:206 try:
204 postcust = PostCustomScript(customscript, imcdirpath)207 postcust = PostCustomScript(customscript,
208 imcdirpath,
209 ccScriptsDir)
205 postcust.execute()210 postcust.execute()
206 except Exception as e:211 except Exception as e:
207 _raise_error_status(212 _raise_error_status(
diff --git a/cloudinit/sources/helpers/vmware/imc/config_custom_script.py b/cloudinit/sources/helpers/vmware/imc/config_custom_script.py
index a7d4ad9..9f14770 100644
--- a/cloudinit/sources/helpers/vmware/imc/config_custom_script.py
+++ b/cloudinit/sources/helpers/vmware/imc/config_custom_script.py
@@ -1,5 +1,5 @@
1# Copyright (C) 2017 Canonical Ltd.1# Copyright (C) 2017 Canonical Ltd.
2# Copyright (C) 2017 VMware Inc.2# Copyright (C) 2017-2019 VMware Inc.
3#3#
4# Author: Maitreyee Saikia <msaikia@vmware.com>4# Author: Maitreyee Saikia <msaikia@vmware.com>
5#5#
@@ -8,7 +8,6 @@
8import logging8import logging
9import os9import os
10import stat10import stat
11from textwrap import dedent
1211
13from cloudinit import util12from cloudinit import util
1413
@@ -20,12 +19,15 @@ class CustomScriptNotFound(Exception):
2019
2120
22class CustomScriptConstant(object):21class CustomScriptConstant(object):
23 RC_LOCAL = "/etc/rc.local"22 CUSTOM_TMP_DIR = "/root/.customization"
24 POST_CUST_TMP_DIR = "/root/.customization"23
25 POST_CUST_RUN_SCRIPT_NAME = "post-customize-guest.sh"24 # The user defined custom script
26 POST_CUST_RUN_SCRIPT = os.path.join(POST_CUST_TMP_DIR,25 CUSTOM_SCRIPT_NAME = "customize.sh"
27 POST_CUST_RUN_SCRIPT_NAME)26 CUSTOM_SCRIPT = os.path.join(CUSTOM_TMP_DIR,
28 POST_REBOOT_PENDING_MARKER = "/.guest-customization-post-reboot-pending"27 CUSTOM_SCRIPT_NAME)
28 POST_CUSTOM_PENDING_MARKER = "/.guest-customization-post-reboot-pending"
29 # The cc_scripts_per_instance script to launch custom script
30 POST_CUSTOM_SCRIPT_NAME = "post-customize-guest.sh"
2931
3032
31class RunCustomScript(object):33class RunCustomScript(object):
@@ -39,10 +41,19 @@ class RunCustomScript(object):
39 raise CustomScriptNotFound("Script %s not found!! "41 raise CustomScriptNotFound("Script %s not found!! "
40 "Cannot execute custom script!"42 "Cannot execute custom script!"
41 % self.scriptpath)43 % self.scriptpath)
44
45 util.ensure_dir(CustomScriptConstant.CUSTOM_TMP_DIR)
46
47 LOG.debug("Copying custom script to %s",
48 CustomScriptConstant.CUSTOM_SCRIPT)
49 util.copy(self.scriptpath, CustomScriptConstant.CUSTOM_SCRIPT)
50
42 # Strip any CR characters from the decoded script51 # Strip any CR characters from the decoded script
43 util.load_file(self.scriptpath).replace("\r", "")52 content = util.load_file(
44 st = os.stat(self.scriptpath)53 CustomScriptConstant.CUSTOM_SCRIPT).replace("\r", "")
45 os.chmod(self.scriptpath, st.st_mode | stat.S_IEXEC)54 util.write_file(CustomScriptConstant.CUSTOM_SCRIPT,
55 content,
56 mode=0o544)
4657
4758
48class PreCustomScript(RunCustomScript):59class PreCustomScript(RunCustomScript):
@@ -50,104 +61,34 @@ class PreCustomScript(RunCustomScript):
50 """Executing custom script with precustomization argument."""61 """Executing custom script with precustomization argument."""
51 LOG.debug("Executing pre-customization script")62 LOG.debug("Executing pre-customization script")
52 self.prepare_script()63 self.prepare_script()
53 util.subp(["/bin/sh", self.scriptpath, "precustomization"])64 util.subp([CustomScriptConstant.CUSTOM_SCRIPT, "precustomization"])
5465
5566
56class PostCustomScript(RunCustomScript):67class PostCustomScript(RunCustomScript):
57 def __init__(self, scriptname, directory):68 def __init__(self, scriptname, directory, ccScriptsDir):
58 super(PostCustomScript, self).__init__(scriptname, directory)69 super(PostCustomScript, self).__init__(scriptname, directory)
59 # Determine when to run custom script. When postreboot is True,70 self.ccScriptsDir = ccScriptsDir
60 # the user uploaded script will run as part of rc.local after71 self.ccScriptPath = os.path.join(
61 # the machine reboots. This is determined by presence of rclocal.72 ccScriptsDir,
62 # When postreboot is False, script will run as part of cloud-init.73 CustomScriptConstant.POST_CUSTOM_SCRIPT_NAME)
63 self.postreboot = False
64
65 def _install_post_reboot_agent(self, rclocal):
66 """
67 Install post-reboot agent for running custom script after reboot.
68 As part of this process, we are editing the rclocal file to run a
69 VMware script, which in turn is resposible for handling the user
70 script.
71 @param: path to rc local.
72 """
73 LOG.debug("Installing post-reboot customization from %s to %s",
74 self.directory, rclocal)
75 if not self.has_previous_agent(rclocal):
76 LOG.info("Adding post-reboot customization agent to rc.local")
77 new_content = dedent("""
78 # Run post-reboot guest customization
79 /bin/sh %s
80 exit 0
81 """) % CustomScriptConstant.POST_CUST_RUN_SCRIPT
82 existing_rclocal = util.load_file(rclocal).replace('exit 0\n', '')
83 st = os.stat(rclocal)
84 # "x" flag should be set
85 mode = st.st_mode | stat.S_IEXEC
86 util.write_file(rclocal, existing_rclocal + new_content, mode)
87
88 else:
89 # We don't need to update rclocal file everytime a customization
90 # is requested. It just needs to be done for the first time.
91 LOG.info("Post-reboot guest customization agent is already "
92 "registered in rc.local")
93 LOG.debug("Installing post-reboot customization agent finished: %s",
94 self.postreboot)
95
96 def has_previous_agent(self, rclocal):
97 searchstring = "# Run post-reboot guest customization"
98 if searchstring in open(rclocal).read():
99 return True
100 return False
101
102 def find_rc_local(self):
103 """
104 Determine if rc local is present.
105 """
106 rclocal = ""
107 if os.path.exists(CustomScriptConstant.RC_LOCAL):
108 LOG.debug("rc.local detected.")
109 # resolving in case of symlink
110 rclocal = os.path.realpath(CustomScriptConstant.RC_LOCAL)
111 LOG.debug("rc.local resolved to %s", rclocal)
112 else:
113 LOG.warning("Can't find rc.local, post-customization "
114 "will be run before reboot")
115 return rclocal
116
117 def install_agent(self):
118 rclocal = self.find_rc_local()
119 if rclocal:
120 self._install_post_reboot_agent(rclocal)
121 self.postreboot = True
12274
123 def execute(self):75 def execute(self):
124 """76 """
125 This method executes post-customization script before or after reboot77 This method copy the post customize run script to
126 based on the presence of rc local.78 cc_scripts_per_instance directory and let this
79 module to run post custom script.
127 """80 """
128 self.prepare_script()81 self.prepare_script()
129 self.install_agent()82
130 if not self.postreboot:83 LOG.debug("Copying post customize run script to %s",
131 LOG.warning("Executing post-customization script inline")84 self.ccScriptPath)
132 util.subp(["/bin/sh", self.scriptpath, "postcustomization"])85 util.copy(
133 else:86 os.path.join(self.directory,
134 LOG.debug("Scheduling custom script to run post reboot")87 CustomScriptConstant.POST_CUSTOM_SCRIPT_NAME),
135 if not os.path.isdir(CustomScriptConstant.POST_CUST_TMP_DIR):88 self.ccScriptPath)
136 os.mkdir(CustomScriptConstant.POST_CUST_TMP_DIR)89 st = os.stat(self.ccScriptPath)
137 # Script "post-customize-guest.sh" and user uploaded script are90 os.chmod(self.ccScriptPath, st.st_mode | stat.S_IEXEC)
138 # are present in the same directory and needs to copied to a temp91 LOG.info("Creating post customization pending marker")
139 # directory to be executed post reboot. User uploaded script is92 util.ensure_file(CustomScriptConstant.POST_CUSTOM_PENDING_MARKER)
140 # saved as customize.sh in the temp directory.
141 # post-customize-guest.sh excutes customize.sh after reboot.
142 LOG.debug("Copying post-customization script")
143 util.copy(self.scriptpath,
144 CustomScriptConstant.POST_CUST_TMP_DIR + "/customize.sh")
145 LOG.debug("Copying script to run post-customization script")
146 util.copy(
147 os.path.join(self.directory,
148 CustomScriptConstant.POST_CUST_RUN_SCRIPT_NAME),
149 CustomScriptConstant.POST_CUST_RUN_SCRIPT)
150 LOG.info("Creating post-reboot pending marker")
151 util.ensure_file(CustomScriptConstant.POST_REBOOT_PENDING_MARKER)
15293
153# vi: ts=4 expandtab94# vi: ts=4 expandtab
diff --git a/tests/unittests/test_vmware/test_custom_script.py b/tests/unittests/test_vmware/test_custom_script.py
index 2d9519b..f89f815 100644
--- a/tests/unittests/test_vmware/test_custom_script.py
+++ b/tests/unittests/test_vmware/test_custom_script.py
@@ -1,10 +1,12 @@
1# Copyright (C) 2015 Canonical Ltd.1# Copyright (C) 2015 Canonical Ltd.
2# Copyright (C) 2017 VMware INC.2# Copyright (C) 2017-2019 VMware INC.
3#3#
4# Author: Maitreyee Saikia <msaikia@vmware.com>4# Author: Maitreyee Saikia <msaikia@vmware.com>
5#5#
6# This file is part of cloud-init. See LICENSE file for license information.6# This file is part of cloud-init. See LICENSE file for license information.
77
8import os
9import stat
8from cloudinit import util10from cloudinit import util
9from cloudinit.sources.helpers.vmware.imc.config_custom_script import (11from cloudinit.sources.helpers.vmware.imc.config_custom_script import (
10 CustomScriptConstant,12 CustomScriptConstant,
@@ -18,6 +20,10 @@ from cloudinit.tests.helpers import CiTestCase, mock
18class TestVmwareCustomScript(CiTestCase):20class TestVmwareCustomScript(CiTestCase):
19 def setUp(self):21 def setUp(self):
20 self.tmpDir = self.tmp_dir()22 self.tmpDir = self.tmp_dir()
23 # Mock the tmpDir as the root dir in VM.
24 self.execDir = os.path.join(self.tmpDir, ".customization")
25 self.execScript = os.path.join(self.execDir,
26 ".customize.sh")
2127
22 def test_prepare_custom_script(self):28 def test_prepare_custom_script(self):
23 """29 """
@@ -37,63 +43,67 @@ class TestVmwareCustomScript(CiTestCase):
3743
38 # Custom script exists.44 # Custom script exists.
39 custScript = self.tmp_path("test-cust", self.tmpDir)45 custScript = self.tmp_path("test-cust", self.tmpDir)
40 util.write_file(custScript, "test-CR-strip/r/r")46 util.write_file(custScript, "test-CR-strip\r\r")
41 postCust = PostCustomScript("test-cust", self.tmpDir)47 with mock.patch.object(CustomScriptConstant,
42 self.assertEqual("test-cust", postCust.scriptname)48 "CUSTOM_TMP_DIR",
43 self.assertEqual(self.tmpDir, postCust.directory)49 self.execDir):
44 self.assertEqual(custScript, postCust.scriptpath)50 with mock.patch.object(CustomScriptConstant,
45 self.assertFalse(postCust.postreboot)51 "CUSTOM_SCRIPT",
46 postCust.prepare_script()52 self.execScript):
47 # Check if all carraige returns are stripped from script.53 postCust = PostCustomScript("test-cust",
48 self.assertFalse("/r" in custScript)54 self.tmpDir,
55 self.tmpDir)
56 self.assertEqual("test-cust", postCust.scriptname)
57 self.assertEqual(self.tmpDir, postCust.directory)
58 self.assertEqual(custScript, postCust.scriptpath)
59 postCust.prepare_script()
4960
50 def test_rc_local_exists(self):61 # Custom script is copied with exec privilege
51 """62 self.assertTrue(os.path.exists(self.execScript))
52 This test is designed to verify the different scenarios associated63 st = os.stat(self.execScript)
53 with the presence of rclocal.64 self.assertTrue(st.st_mode & stat.S_IEXEC)
54 """65 with open(self.execScript, "r") as f:
55 # test when rc local does not exist66 content = f.read()
56 postCust = PostCustomScript("test-cust", self.tmpDir)67 self.assertEqual(content, "test-CR-strip")
57 with mock.patch.object(CustomScriptConstant, "RC_LOCAL", "/no/path"):68 # Check if all carraige returns are stripped from script.
58 rclocal = postCust.find_rc_local()69 self.assertFalse("\r" in content)
59 self.assertEqual("", rclocal)
60
61 # test when rc local exists
62 rclocalFile = self.tmp_path("vmware-rclocal", self.tmpDir)
63 util.write_file(rclocalFile, "# Run post-reboot guest customization",
64 omode="w")
65 with mock.patch.object(CustomScriptConstant, "RC_LOCAL", rclocalFile):
66 rclocal = postCust.find_rc_local()
67 self.assertEqual(rclocalFile, rclocal)
68 self.assertTrue(postCust.has_previous_agent, rclocal)
69
70 # test when rc local is a symlink
71 rclocalLink = self.tmp_path("dummy-rclocal-link", self.tmpDir)
72 util.sym_link(rclocalFile, rclocalLink, True)
73 with mock.patch.object(CustomScriptConstant, "RC_LOCAL", rclocalLink):
74 rclocal = postCust.find_rc_local()
75 self.assertEqual(rclocalFile, rclocal)
7670
77 def test_execute_post_cust(self):71 def test_execute_post_cust(self):
78 """72 """
79 This test is to identify if rclocal was properly populated to be73 This test is designed to verify the behavior after execute post
80 run after reboot.74 customization.
81 """75 """
82 customscript = self.tmp_path("vmware-post-cust-script", self.tmpDir)76 # Prepare the customize package
83 rclocal = self.tmp_path("vmware-rclocal", self.tmpDir)77 postCustRun = self.tmp_path("post-customize-guest.sh", self.tmpDir)
84 # Create a temporary rclocal file78 util.write_file(postCustRun, "This is the script to run post cust")
85 open(customscript, "w")79 userScript = self.tmp_path("test-cust", self.tmpDir)
86 util.write_file(rclocal, "tests\nexit 0", omode="w")80 util.write_file(userScript, "This is the post cust script")
87 postCust = PostCustomScript("vmware-post-cust-script", self.tmpDir)
88 with mock.patch.object(CustomScriptConstant, "RC_LOCAL", rclocal):
89 # Test that guest customization agent is not installed initially.
90 self.assertFalse(postCust.postreboot)
91 self.assertIs(postCust.has_previous_agent(rclocal), False)
92 postCust.install_agent()
9381
94 # Assert rclocal has been modified to have guest customization82 # Mock the cc_scripts_per_instance dir and marker file.
95 # agent.83 # Create another tmp dir for cc_scripts_per_instance.
96 self.assertTrue(postCust.postreboot)84 ccScriptDir = self.tmp_dir()
97 self.assertTrue(postCust.has_previous_agent, rclocal)85 ccScript = os.path.join(ccScriptDir, "post-customize-guest.sh")
86 markerFile = os.path.join(self.tmpDir, ".markerFile")
87 with mock.patch.object(CustomScriptConstant,
88 "CUSTOM_TMP_DIR",
89 self.execDir):
90 with mock.patch.object(CustomScriptConstant,
91 "CUSTOM_SCRIPT",
92 self.execScript):
93 with mock.patch.object(CustomScriptConstant,
94 "POST_CUSTOM_PENDING_MARKER",
95 markerFile):
96 postCust = PostCustomScript("test-cust",
97 self.tmpDir,
98 ccScriptDir)
99 postCust.execute()
100 # Check cc_scripts_per_instance and marker file
101 # are created.
102 self.assertTrue(os.path.exists(ccScript))
103 with open(ccScript, "r") as f:
104 content = f.read()
105 self.assertEqual(content,
106 "This is the script to run post cust")
107 self.assertTrue(os.path.exists(markerFile))
98108
99# vi: ts=4 expandtab109# vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches