Merge ~xiaofengw/cloud-init:xiaofengw-user-defined-script into cloud-init:master
- Git
- lp:~xiaofengw/cloud-init
- xiaofengw-user-defined-script
- Merge into master
Status: | Work in progress |
---|---|
Proposed branch: | ~xiaofengw/cloud-init:xiaofengw-user-defined-script |
Merge into: | cloud-init:master |
Diff against target: |
243 lines (+160/-1) (has conflicts) 5 files modified
cloudinit/sources/DataSourceOVF.py (+25/-1) cloudinit/sources/helpers/vmware/imc/guestcust_error.py (+1/-0) cloudinit/sources/helpers/vmware/imc/guestcust_util.py (+33/-0) tests/unittests/test_datasource/test_ovf.py (+42/-0) tests/unittests/test_vmware/test_guestcust_util.py (+59/-0) Conflict in cloudinit/sources/DataSourceOVF.py |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ryan Harper | Needs Information | ||
Server Team CI bot | continuous-integration | Approve | |
Review via email: mp+367889@code.launchpad.net |
Commit message
VMWware: add option into VMTools configuration to enable/disable custom script.
VMWware customization has already support to run a custom script during the VM customization. We add this option so that VM administrator could prevent custom script to run, then the customization will fail and customization status is set to GUESTCUST_
Description of the change
Xiaofeng Wang (xiaofengw) wrote : | # |
Hi all,
The unit tests are added as required, please take a look. Thanks.
Ryan Harper (raharper) wrote : | # |
I've pointed CI at this branch.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:50d8b8497e5
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:/
Ryan Harper (raharper) wrote : | # |
One question about the unittest. I'm marking this WIP, when you've replied, please change the state back to Needs Review.
Xiaofeng Wang (xiaofengw) wrote : | # |
> One question about the unittest. I'm marking this WIP, when you've replied,
> please change the state back to Needs Review.
Thanks, please check my answer in line.
Xiaofeng Wang (xiaofengw) wrote : | # |
> > One question about the unittest. I'm marking this WIP, when you've replied,
> > please change the state back to Needs Review.
> Thanks, please check my answer in line.
This is still needed. I removed this line and the test failed with "called subp. set allowed_subp=True to allow subp" error.
I checked the _fake_subp logic, it require to set allowed_subp to True if we call util.subp function no matter util.subp is mocked or not.
Ryan Harper (raharper) wrote : | # |
I can't reproduce the failure; so I wonder if it's because we're not mocking out calls to vmware tools? On what platform are you running unittests on and does it have some of the vmware tools installed?
(neipa) ~ % mkdir /tmp/test-ci
(neipa) ~ % cd /tmp/test-ci/
(neipa) test-ci % git clone -b xiaofengw-
(neipa) test-ci % git clone -b xiaofengw-
Cloning into 'cloud-init'...
remote: Counting objects: 29358, done.
remote: Compressing objects: 100% (11052/11052), done.
remote: Total 29358 (delta 21821), reused 25008 (delta 18013)
Receiving objects: 100% (29358/29358), 17.82 MiB | 4.03 MiB/s, done.
Resolving deltas: 100% (21821/21821), done.
Checking connectivity... done.
(neipa) test-ci % cd cloud-init/
(neipa) cloud-init % git remote add upstream https:/
(neipa) cloud-init % git fetch upstream --tags &>/dev/null
(neipa) cloud-init % git describe
18.5-72-g50d8b84
(neipa) cloud-init % sed -i '/self.allowed_subp = True/d' tests/unittests
(neipa) cloud-init % git diff
diff --git a/tests/
index d2964e5..9059ea3 100644
--- a/tests/
+++ b/tests/
@@ -122,7 +122,6 @@ class TestDatasourceO
self.tdir = self.tmp_dir()
- self.allowed_subp = True
def test_get_
"""When dmi for system-product-name is None, get_data returns False."""
(neipa) cloud-init % tox
....
py27: commands succeeded
py3: commands succeeded
xenial: commands succeeded
pycodestyle: commands succeeded
pyflakes: commands succeeded
pylint: commands succeeded
congratulations :)
Xiaofeng Wang (xiaofengw) wrote : | # |
I run the unit test on Ubuntu 16.04 and the vmware tools is installed. And yes, I didn't mock out calls to vmware tools, but I has mocked the calls to subp().
In file cloudinit/
> I can't reproduce the failure; so I wonder if it's because we're not mocking
> out calls to vmware tools? On what platform are you running unittests on and
> does it have some of the vmware tools installed?
>
> (neipa) ~ % mkdir /tmp/test-ci
> (neipa) ~ % cd /tmp/test-ci/
> (neipa) test-ci % git clone -b xiaofengw-
> https:/
> (neipa) test-ci % git clone -b xiaofengw-
> https:/
> Cloning into 'cloud-init'...
> remote: Counting objects: 29358, done.
> remote: Compressing objects: 100% (11052/11052), done.
> remote: Total 29358 (delta 21821), reused 25008 (delta 18013)
> Receiving objects: 100% (29358/29358), 17.82 MiB | 4.03 MiB/s, done.
> Resolving deltas: 100% (21821/21821), done.
> Checking connectivity... done.
> (neipa) test-ci % cd cloud-init/
> (neipa) cloud-init % git remote add upstream https:/
> init
> (neipa) cloud-init % git fetch upstream --tags &>/dev/null
> (neipa) cloud-init % git describe
> 18.5-72-g50d8b84
> (neipa) cloud-init % sed -i '/self.allowed_subp = True/d'
> tests/unittests
> (neipa) cloud-init % git diff
> diff --git a/tests/
> b/tests/
> index d2964e5..9059ea3 100644
> --- a/tests/
> +++ b/tests/
> @@ -122,7 +122,6 @@ class TestDatasourceO
> super(TestDatas
> self.datasource = dsovf.DataSourceOVF
> self.tdir = self.tmp_dir()
> - self.allowed_subp = True
>
> def test_get_
> """When dmi for system-product-name is None, get_data returns
> False."""
> (neipa) cloud-init % tox
> ....
> py27: commands succeeded
> py3: commands succeeded
> xenial: commands succeeded
> pycodestyle: commands succeeded
> pyflakes: commands succeeded
> pylint: commands succeeded
> congratulations :)
- 35d5d4c... by Xiaofeng Wang
-
Add the vmware-toolbox-cmd into allowed_subp command other than
set the allowed_subp to True.O
Changes to be committed:
modified: tests/unittests/test_datasourc e/test_ ovf.py
Xiaofeng Wang (xiaofengw) wrote : | # |
Hi Ryan,
I update the unittest by adding "vmware-
Please review and comments. Thanks.
> I can't reproduce the failure; so I wonder if it's because we're not mocking
> out calls to vmware tools? On what platform are you running unittests on and
> does it have some of the vmware tools installed?
>
> (neipa) ~ % mkdir /tmp/test-ci
> (neipa) ~ % cd /tmp/test-ci/
> (neipa) test-ci % git clone -b xiaofengw-
> https:/
> (neipa) test-ci % git clone -b xiaofengw-
> https:/
> Cloning into 'cloud-init'...
> remote: Counting objects: 29358, done.
> remote: Compressing objects: 100% (11052/11052), done.
> remote: Total 29358 (delta 21821), reused 25008 (delta 18013)
> Receiving objects: 100% (29358/29358), 17.82 MiB | 4.03 MiB/s, done.
> Resolving deltas: 100% (21821/21821), done.
> Checking connectivity... done.
> (neipa) test-ci % cd cloud-init/
> (neipa) cloud-init % git remote add upstream https:/
> init
> (neipa) cloud-init % git fetch upstream --tags &>/dev/null
> (neipa) cloud-init % git describe
> 18.5-72-g50d8b84
> (neipa) cloud-init % sed -i '/self.allowed_subp = True/d'
> tests/unittests
> (neipa) cloud-init % git diff
> diff --git a/tests/
> b/tests/
> index d2964e5..9059ea3 100644
> --- a/tests/
> +++ b/tests/
> @@ -122,7 +122,6 @@ class TestDatasourceO
> super(TestDatas
> self.datasource = dsovf.DataSourceOVF
> self.tdir = self.tmp_dir()
> - self.allowed_subp = True
>
> def test_get_
> """When dmi for system-product-name is None, get_data returns
> False."""
> (neipa) cloud-init % tox
> ....
> py27: commands succeeded
> py3: commands succeeded
> xenial: commands succeeded
> pycodestyle: commands succeeded
> pyflakes: commands succeeded
> pylint: commands succeeded
> congratulations :)
Ryan Harper (raharper) wrote : | # |
I'm running on Ubuntu 16.04 as well, but I do not have vmware-toolbox-cmd installed.
We don't want the host environment to affect the test results; so the testing
should mock out calls to the host.
Xiaofeng Wang (xiaofengw) wrote : | # |
Thanks Ryan,
I update the codes and stop setting allowed_subp to Yes, also mock out the calls to the host. Pleae take another review. Thanks.
> I'm running on Ubuntu 16.04 as well, but I do not have vmware-toolbox-cmd
> installed.
>
> We don't want the host environment to affect the test results; so the testing
> should mock out calls to the host.
Ryan Harper (raharper) wrote : | # |
I think you need to push the changes?
Xiaofeng Wang (xiaofengw) wrote : | # |
Please check this commit, thanks.
https:/
> I think you need to push the changes?
Xiaofeng Wang (xiaofengw) wrote : | # |
And the call to subp is already mocked in the original commit.
> Please check this commit, thanks.
> https:/
> init/commit/
>
> > I think you need to push the changes?
Xiaofeng Wang (xiaofengw) wrote : | # |
> I think you need to push the changes?
Hi Ryan,
I followed your suggestion to mock the guestcust_
Could you give me some help on this issue? Thanks.
File "/home/
"true")
File "/home/
(outText, _) = util.subp(cmd)
File "/home/
["%s=%s" % (k, repr(v)) for k, v in kwargs.items()]))
Exception: called subp. set self.allowed_
subp([
Ryan Harper (raharper) wrote : | # |
Let's get this branch rebased onto master to clean up the merge conflicts. Once you've got that done, please move the branch back to Needs Review.
Unmerged commits
- 35d5d4c... by Xiaofeng Wang
-
Add the vmware-toolbox-cmd into allowed_subp command other than
set the allowed_subp to True.O
Changes to be committed:
modified: tests/unittests/test_datasourc e/test_ ovf.py - 0ed0d32... by Xiaofeng Wang
-
add unit tests for the custom script option
- 9a7c180... by Xiaofeng Wang
-
VMWware: add option into VMTools configuration to enable/disable custom script.
VMWware customization has already support to run a custom script during the VM customization.
We add this option so that VM administrator could prevent custom script to run, then the
customization will fail and customization status is set to GUESTCUST_ERROR_SCRIPT_ DISABLED.
Preview Diff
1 | diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py |
2 | index dd941d2..793aa7f 100644 |
3 | --- a/cloudinit/sources/DataSourceOVF.py |
4 | +++ b/cloudinit/sources/DataSourceOVF.py |
5 | @@ -40,11 +40,15 @@ from cloudinit.sources.helpers.vmware.imc.guestcust_state \ |
6 | from cloudinit.sources.helpers.vmware.imc.guestcust_util import ( |
7 | enable_nics, |
8 | get_nics_to_enable, |
9 | - set_customization_status |
10 | + set_customization_status, |
11 | + get_tools_config |
12 | ) |
13 | |
14 | LOG = logging.getLogger(__name__) |
15 | |
16 | +CONFGROUPNAME_GUESTCUSTOMIZATION = "deployPkg" |
17 | +GUESTCUSTOMIZATION_ENABLE_CUST_SCRIPTS = "enable-custom-scripts" |
18 | + |
19 | |
20 | class DataSourceOVF(sources.DataSource): |
21 | |
22 | @@ -148,9 +152,29 @@ class DataSourceOVF(sources.DataSource): |
23 | product_marker, os.path.join(self.paths.cloud_dir, 'data')) |
24 | special_customization = product_marker and not hasmarkerfile |
25 | customscript = self._vmware_cust_conf.custom_script_name |
26 | +<<<<<<< cloudinit/sources/DataSourceOVF.py |
27 | ccScriptsDir = os.path.join( |
28 | self.paths.get_cpath("scripts"), |
29 | "per-instance") |
30 | +======= |
31 | + |
32 | + custScriptConfig = get_tools_config( |
33 | + CONFGROUPNAME_GUESTCUSTOMIZATION, |
34 | + GUESTCUSTOMIZATION_ENABLE_CUST_SCRIPTS, |
35 | + "true") |
36 | + if custScriptConfig.lower() == "false": |
37 | + # Update the customization status if there is a |
38 | + # custom script is disabled |
39 | + if special_customization and customscript: |
40 | + LOG.debug( |
41 | + "Custom script is disabled by VM Administrator") |
42 | + set_customization_status( |
43 | + GuestCustStateEnum.GUESTCUST_STATE_RUNNING, |
44 | + GuestCustErrorEnum.GUESTCUST_ERROR_SCRIPT_DISABLED) |
45 | + raise RuntimeError( |
46 | + "Custom script is disabled by VM Administrator") |
47 | + |
48 | +>>>>>>> cloudinit/sources/DataSourceOVF.py |
49 | except Exception as e: |
50 | _raise_error_status( |
51 | "Error parsing the customization Config File", |
52 | diff --git a/cloudinit/sources/helpers/vmware/imc/guestcust_error.py b/cloudinit/sources/helpers/vmware/imc/guestcust_error.py |
53 | index db5a00d..65ae739 100644 |
54 | --- a/cloudinit/sources/helpers/vmware/imc/guestcust_error.py |
55 | +++ b/cloudinit/sources/helpers/vmware/imc/guestcust_error.py |
56 | @@ -10,5 +10,6 @@ class GuestCustErrorEnum(object): |
57 | """Specifies different errors of Guest Customization engine""" |
58 | |
59 | GUESTCUST_ERROR_SUCCESS = 0 |
60 | + GUESTCUST_ERROR_SCRIPT_DISABLED = 6 |
61 | |
62 | # vi: ts=4 expandtab |
63 | diff --git a/cloudinit/sources/helpers/vmware/imc/guestcust_util.py b/cloudinit/sources/helpers/vmware/imc/guestcust_util.py |
64 | index a590f32..6c5398d 100644 |
65 | --- a/cloudinit/sources/helpers/vmware/imc/guestcust_util.py |
66 | +++ b/cloudinit/sources/helpers/vmware/imc/guestcust_util.py |
67 | @@ -7,6 +7,7 @@ |
68 | |
69 | import logging |
70 | import os |
71 | +import re |
72 | import time |
73 | |
74 | from cloudinit import util |
75 | @@ -117,4 +118,36 @@ def enable_nics(nics): |
76 | logger.warning("Can't connect network interfaces after %d attempts", |
77 | enableNicsWaitRetries) |
78 | |
79 | + |
80 | +def get_tools_config(section, key, defaultVal): |
81 | + """ Return the value of [section] key from VMTools configuration. |
82 | + |
83 | + @param section: String of section to read from VMTools config |
84 | + @returns: String value from key in [section] or defaultVal if |
85 | + [section] is not present or vmware-toolbox-cmd is |
86 | + not installed. |
87 | + """ |
88 | + |
89 | + if not util.which('vmware-toolbox-cmd'): |
90 | + logger.debug( |
91 | + 'vmware-toolbox-cmd not installed, returning default value') |
92 | + return defaultVal |
93 | + |
94 | + retValue = defaultVal |
95 | + cmd = ['vmware-toolbox-cmd', 'config', 'get', section, key] |
96 | + |
97 | + try: |
98 | + (outText, _) = util.subp(cmd) |
99 | + m = re.match(r'(.+)=(.*)', outText) |
100 | + if m: |
101 | + retValue = m.group(2).strip() |
102 | + logger.debug("Get tools config: [%s] %s = %s", |
103 | + section, key, retValue) |
104 | + except util.ProcessExecutionError as e: |
105 | + logger.error("Failed running %s[%s]", cmd, e.exit_code) |
106 | + logger.exception(e) |
107 | + |
108 | + return retValue |
109 | + |
110 | + |
111 | # vi: ts=4 expandtab |
112 | diff --git a/tests/unittests/test_datasource/test_ovf.py b/tests/unittests/test_datasource/test_ovf.py |
113 | index 349d54c..4248512 100644 |
114 | --- a/tests/unittests/test_datasource/test_ovf.py |
115 | +++ b/tests/unittests/test_datasource/test_ovf.py |
116 | @@ -16,6 +16,7 @@ from cloudinit.helpers import Paths |
117 | from cloudinit.sources import DataSourceOVF as dsovf |
118 | from cloudinit.sources.helpers.vmware.imc.config_custom_script import ( |
119 | CustomScriptNotFound) |
120 | +from cloudinit.sources.helpers.vmware.imc import guestcust_util |
121 | |
122 | MPATH = 'cloudinit.sources.DataSourceOVF.' |
123 | |
124 | @@ -116,6 +117,7 @@ class TestMarkerFiles(CiTestCase): |
125 | class TestDatasourceOVF(CiTestCase): |
126 | |
127 | with_logs = True |
128 | + allowed_subp = ['vmware-toolbox-cmd'] |
129 | |
130 | def setUp(self): |
131 | super(TestDatasourceOVF, self).setUp() |
132 | @@ -182,6 +184,46 @@ class TestDatasourceOVF(CiTestCase): |
133 | self.assertIn('Script %s not found!!' % customscript, |
134 | str(context.exception)) |
135 | |
136 | + def test_get_data_cust_script_disabled(self): |
137 | + """If custom script is disabled by VMware tools configuration, |
138 | + raise a RuntimeError. |
139 | + """ |
140 | + paths = Paths({'cloud_dir': self.tdir}) |
141 | + ds = self.datasource( |
142 | + sys_cfg={'disable_vmware_customization': False}, distro={}, |
143 | + paths=paths) |
144 | + # Prepare the conf file |
145 | + conf_file = self.tmp_path('test-cust', self.tdir) |
146 | + conf_content = dedent("""\ |
147 | + [CUSTOM-SCRIPT] |
148 | + SCRIPT-NAME = test-script |
149 | + [MISC] |
150 | + MARKER-ID = 12345345 |
151 | + """) |
152 | + util.write_file(conf_file, conf_content) |
153 | + # Prepare the custom sript |
154 | + customscript = self.tmp_path('test-script', self.tdir) |
155 | + util.write_file(customscript, "This is the post cust script") |
156 | + |
157 | + with mock.patch.object(util, 'which', return_value='/dummy/path'): |
158 | + # Mock the cust script is disabeld (FALSE). |
159 | + with mock.patch.object(util, 'subp', |
160 | + return_value=(' key = FALSE', b'')): |
161 | + with mock.patch.object(guestcust_util, |
162 | + 'set_customization_status', |
163 | + return_value=('msg', b'')): |
164 | + with self.assertRaises(RuntimeError) as context: |
165 | + wrap_and_call( |
166 | + 'cloudinit.sources.DataSourceOVF', |
167 | + {'util.read_dmi_data': 'vmware', |
168 | + 'util.del_dir': True, |
169 | + 'search_file': self.tdir, |
170 | + 'wait_for_imc_cfg_file': conf_file, |
171 | + 'get_nics_to_enable': ''}, |
172 | + ds.get_data) |
173 | + self.assertIn('Custom script is disabled by VM Administrator', |
174 | + str(context.exception)) |
175 | + |
176 | def test_get_data_non_vmware_seed_platform_info(self): |
177 | """Platform info properly reports when on non-vmware platforms.""" |
178 | paths = Paths({'cloud_dir': self.tdir, 'run_dir': self.tdir}) |
179 | diff --git a/tests/unittests/test_vmware/test_guestcust_util.py b/tests/unittests/test_vmware/test_guestcust_util.py |
180 | new file mode 100644 |
181 | index 0000000..345b5ff |
182 | --- /dev/null |
183 | +++ b/tests/unittests/test_vmware/test_guestcust_util.py |
184 | @@ -0,0 +1,59 @@ |
185 | +# Copyright (C) 2019 Canonical Ltd. |
186 | +# Copyright (C) 2019 VMware INC. |
187 | +# |
188 | +# Author: Xiaofeng Wang <xiaofengw@vmware.com> |
189 | +# |
190 | +# This file is part of cloud-init. See LICENSE file for license information. |
191 | + |
192 | +from cloudinit import util |
193 | +from cloudinit.sources.helpers.vmware.imc.guestcust_util import ( |
194 | + get_tools_config, |
195 | +) |
196 | +from cloudinit.tests.helpers import CiTestCase, mock |
197 | + |
198 | + |
199 | +class TestGuestCustUtil(CiTestCase): |
200 | + def test_get_tools_config_not_installed(self): |
201 | + """ |
202 | + This test is designed to verify the behavior if vmware-toolbox-cmd |
203 | + is not installed. |
204 | + """ |
205 | + with mock.patch.object(util, 'which', return_value=None): |
206 | + self.assertEqual( |
207 | + get_tools_config('section', 'key', 'defaultVal'), 'defaultVal') |
208 | + |
209 | + def test_get_tools_config_internal_exception(self): |
210 | + """ |
211 | + This test is designed to verify the behavior if internal exception |
212 | + is raised. |
213 | + """ |
214 | + with mock.patch.object(util, 'which', return_value='/dummy/path'): |
215 | + with mock.patch.object(util, 'subp', |
216 | + return_value=('key=value', b''), |
217 | + side_effect=util.ProcessExecutionError( |
218 | + "subp failed", exit_code=99)): |
219 | + # verify return value is 'defaultVal', not 'value'. |
220 | + self.assertEqual( |
221 | + get_tools_config('section', 'key', 'defaultVal'), |
222 | + 'defaultVal') |
223 | + |
224 | + def test_get_tools_config_normal(self): |
225 | + """ |
226 | + This test is designed to verify the value could be parsed from |
227 | + key = value of the given [section] |
228 | + """ |
229 | + with mock.patch.object(util, 'which', return_value='/dummy/path'): |
230 | + # value is not blank |
231 | + with mock.patch.object(util, 'subp', |
232 | + return_value=('key = value ', b'')): |
233 | + self.assertEqual( |
234 | + get_tools_config('section', 'key', 'defaultVal'), |
235 | + 'value') |
236 | + # value is blank |
237 | + with mock.patch.object(util, 'subp', |
238 | + return_value=('key = ', b'')): |
239 | + self.assertEqual( |
240 | + get_tools_config('section', 'key', 'defaultVal'), |
241 | + '') |
242 | + |
243 | +# vi: ts=4 expandtab |
Hi Ryan, toolbox- cmd" to be installed first. It's hard for us to simulate different VMTools configuration. custom- script is not set in tools.conf, then custom script is executed as expected. custom- script is "true"t in tools.conf, then custom script is executed as expected custom- script is "false"t in tools.conf, then custom script is not executed as expected. custom- script is set to "", then custom script is executed as expected.
I update the codes according to your comments. But I found it's hard for me to add unit test for these codes. Since this change is about to add option into VMTools configuration to enable/disable custom script, it requires command "vmware-
Hence, I test below test cases manually:
1. [deployPkg] enable-
2. [deployPkg] enable-
3. [deployPkg] enable-
4. vmware-toolbox-cmd is not installed, then custom script is executed as expected.
5. [deployPkg] enable-
6. Customization doesn't require to run custom script, then customization could finish sucessfully.