Merge ~chad.smith/cloud-init:bug/1776701-openstack-local-no-probe-on-ec2 into cloud-init:master
- Git
- lp:~chad.smith/cloud-init
- bug/1776701-openstack-local-no-probe-on-ec2
- Merge into master
Status: | Merged |
---|---|
Approved by: | Chad Smith |
Approved revision: | 69b62008c859790f94dddd93f8bd704edd8bd0c6 |
Merge reported by: | Chad Smith |
Merged at revision: | 1efa8a0a030794cec68197100f31a856d0d264ab |
Proposed branch: | ~chad.smith/cloud-init:bug/1776701-openstack-local-no-probe-on-ec2 |
Merge into: | cloud-init:master |
Diff against target: |
333 lines (+182/-16) 5 files modified
cloudinit/sources/DataSourceOpenStack.py (+23/-0) cloudinit/util.py (+11/-2) doc/rtd/topics/datasources/openstack.rst (+15/-0) tests/unittests/test_datasource/test_openstack.py (+110/-14) tests/unittests/test_util.py (+23/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Chad Smith | Abstain | ||
Scott Moser | Approve | ||
Server Team CI bot | continuous-integration | Approve | |
Review via email: mp+347937@code.launchpad.net |
Commit message
openstack: avoid unneeded metadata probe on non-openstack platforms
OpenStack datasource is now discovered in init-local stage. In order to
probe whether OpenStack metadata is present, it performs a costly
sandboxed dhclient setup and metadata probe against http://
for openstack data.
Cloud-init properly detects non-OpenStack on EC2, but it spends precious
time probing the metadata service also resulting in a confusing WARNING
log about 'metadata not present'. To avoid the wasted cycles, and
confusing warning, get_data will call a detect_openstack function to
quickly determine whether the platform looks like OpenStack before trying
to setup network to probe and crawl the metadata service.
LP: #1776701
Description of the change
detect_openstack function is basically logic adapted from ./tools/ds-identify for detecting OpenStack.
Jenkins failures which showcased this warning message
https:/
Server Team CI bot (server-team-bot) wrote : | # |
Ryan Harper (raharper) wrote : | # |
If not already done so, we should update the OpenStack datasource docs to indicate which product strings/chassis ids are used to positively identify OpenStack datasource on VMs and containers.
Scott Moser (smoser) wrote : | # |
some minor things.
- c84f679... by Chad Smith
-
address review comments
- magic variables at the top of DataSourceOpenStack
- use util.get_proc_env instead of rolling our own
- add util.is_x86 with efficient string checks for matching
architecture use is_x86 in detect_openstack
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:c84f6798829
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
- 9c9f6ab... by Chad Smith
-
add Discovery section to OpenStack datasource RTD
- 9a475d0... by Chad Smith
-
doc tweak
- 2effe5c... by Chad Smith
-
fix unit tests given use of get_proc_env
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:2effe5c3a1d
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
- 44caa4a... by Chad Smith
-
pyflakes and pycodestyle
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:44caa4afd58
https:/
Executed test runs:
SUCCESS: Checkout
FAILED: Unit & Style Tests
Click here to trigger a rebuild:
https:/
- 760279f... by Chad Smith
-
fix unit tests to mock is_x86 instead of uname
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:760279fb794
https:/
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:/
Scott Moser (smoser) wrote : | # |
trivial suggestions.
Ryan Harper (raharper) : | # |
- 4055f7d... by Chad Smith
-
use is_x86 in read_dmi_data and add unit test for it
- c3183e9... by Chad Smith
-
define top-level constants VALID_DMI_
PRODUCT_ NAMES and VALID_DMI_ ASSET_TAGS
Chad Smith (chad.smith) : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:c3183e99372
https:/
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:/
- 69b6200... by Chad Smith
-
doc update
Chad Smith (chad.smith) wrote : | # |
Thank you for your merge proposal.
Your branch has been set to 'Work in progress'.
Please set the branch back to 'Needs Review' after resolving the issues below.
Thanks again,
Your friendly neighborhood cloud-init robot.
Please fix the following issues:
-------
Commit message lints:
- Line #2 has 506 too many characters. Line starts with: "OpenStack datasource"...
-------
For more information, see commit message guidelines at
https:/
Chad Smith (chad.smith) wrote : | # |
Thank you for your merge proposal.
Your branch has been set to 'Work in progress'.
Please set the branch back to 'Needs Review' after resolving the issues below.
Thanks again,
Your friendly neighborhood cloud-init robot.
Please fix the following issues:
-------
Commit message lints:
- Line #2 has 3 too many characters. Line starts with: "OpenStack datasource"... - Line #3 has 4 too many characters. Line starts with: "whether OpenStack metadata"... - Line #4 has 1 too many characters. Line starts with: "setup and metadata probe"... - Line #6 has 3 too many characters. Line starts with: "Cloud-init properly detects"... - Line #7 has 2 too many characters. Line starts with: "probing the metadata"... - Line #9 has 5 too many characters. Line starts with: "get_data will call a"... - Line #10 has 5 too many characters. Line starts with: "platform looks like OpenStack"...
-------
For more information, see commit message guidelines at
https:/
Scott Moser (smoser) : | # |
- 8266327... by Chad Smith
-
proper mock of get_proc_env in unit tests
Chad Smith (chad.smith) : | # |
Chad Smith (chad.smith) wrote : | # |
An upstream commit landed for this bug.
To view that commit see the following URL:
https:/
Preview Diff
1 | diff --git a/cloudinit/sources/DataSourceOpenStack.py b/cloudinit/sources/DataSourceOpenStack.py |
2 | index 1a12a3f..365af96 100644 |
3 | --- a/cloudinit/sources/DataSourceOpenStack.py |
4 | +++ b/cloudinit/sources/DataSourceOpenStack.py |
5 | @@ -23,6 +23,13 @@ DEFAULT_METADATA = { |
6 | "instance-id": DEFAULT_IID, |
7 | } |
8 | |
9 | +# OpenStack DMI constants |
10 | +DMI_PRODUCT_NOVA = 'OpenStack Nova' |
11 | +DMI_PRODUCT_COMPUTE = 'OpenStack Compute' |
12 | +VALID_DMI_PRODUCT_NAMES = [DMI_PRODUCT_NOVA, DMI_PRODUCT_COMPUTE] |
13 | +DMI_ASSET_TAG_OPENTELEKOM = 'OpenTelekomCloud' |
14 | +VALID_DMI_ASSET_TAGS = [DMI_ASSET_TAG_OPENTELEKOM] |
15 | + |
16 | |
17 | class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource): |
18 | |
19 | @@ -114,6 +121,8 @@ class DataSourceOpenStack(openstack.SourceMixin, sources.DataSource): |
20 | False when unable to contact metadata service or when metadata |
21 | format is invalid or disabled. |
22 | """ |
23 | + if not detect_openstack(): |
24 | + return False |
25 | if self.perform_dhcp_setup: # Setup networking in init-local stage. |
26 | try: |
27 | with EphemeralDHCPv4(self.fallback_interface): |
28 | @@ -205,6 +214,20 @@ def read_metadata_service(base_url, ssl_details=None, |
29 | return reader.read_v2() |
30 | |
31 | |
32 | +def detect_openstack(): |
33 | + """Return True when a potential OpenStack platform is detected.""" |
34 | + if not util.is_x86(): |
35 | + return True # Non-Intel cpus don't properly report dmi product names |
36 | + product_name = util.read_dmi_data('system-product-name') |
37 | + if product_name in VALID_DMI_PRODUCT_NAMES: |
38 | + return True |
39 | + elif util.read_dmi_data('chassis-asset-tag') in VALID_DMI_ASSET_TAGS: |
40 | + return True |
41 | + elif util.get_proc_env(1).get('product_name') == DMI_PRODUCT_NOVA: |
42 | + return True |
43 | + return False |
44 | + |
45 | + |
46 | # Used to match classes to dependencies |
47 | datasources = [ |
48 | (DataSourceOpenStackLocal, (sources.DEP_FILESYSTEM,)), |
49 | diff --git a/cloudinit/util.py b/cloudinit/util.py |
50 | index 26a4112..6da9511 100644 |
51 | --- a/cloudinit/util.py |
52 | +++ b/cloudinit/util.py |
53 | @@ -2629,6 +2629,16 @@ def _call_dmidecode(key, dmidecode_path): |
54 | return None |
55 | |
56 | |
57 | +def is_x86(uname_arch=None): |
58 | + """Return True if platform is x86-based""" |
59 | + if uname_arch is None: |
60 | + uname_arch = os.uname()[4] |
61 | + x86_arch_match = ( |
62 | + uname_arch == 'x86_64' or |
63 | + (uname_arch[0] == 'i' and uname_arch[2:] == '86')) |
64 | + return x86_arch_match |
65 | + |
66 | + |
67 | def read_dmi_data(key): |
68 | """ |
69 | Wrapper for reading DMI data. |
70 | @@ -2656,8 +2666,7 @@ def read_dmi_data(key): |
71 | |
72 | # running dmidecode can be problematic on some arches (LP: #1243287) |
73 | uname_arch = os.uname()[4] |
74 | - if not (uname_arch == "x86_64" or |
75 | - (uname_arch.startswith("i") and uname_arch[2:] == "86") or |
76 | + if not (is_x86(uname_arch) or |
77 | uname_arch == 'aarch64' or |
78 | uname_arch == 'amd64'): |
79 | LOG.debug("dmidata is not supported on %s", uname_arch) |
80 | diff --git a/doc/rtd/topics/datasources/openstack.rst b/doc/rtd/topics/datasources/openstack.rst |
81 | index 0ea8994..421da08 100644 |
82 | --- a/doc/rtd/topics/datasources/openstack.rst |
83 | +++ b/doc/rtd/topics/datasources/openstack.rst |
84 | @@ -7,6 +7,21 @@ This datasource supports reading data from the |
85 | `OpenStack Metadata Service |
86 | <https://docs.openstack.org/nova/latest/admin/networking-nova.html#metadata-service>`_. |
87 | |
88 | +Discovery |
89 | +------------- |
90 | +To determine whether a platform looks like it may be OpenStack, cloud-init |
91 | +checks the following environment attributes as a potential OpenStack platform: |
92 | + |
93 | + * Maybe OpenStack if |
94 | + |
95 | + * **non-x86 cpu architecture**: because DMI data is buggy on some arches |
96 | + * Is OpenStack **if x86 architecture and ANY** of the following |
97 | + |
98 | + * **/proc/1/environ**: Nova-lxd contains *product_name=OpenStack Nova* |
99 | + * **DMI product_name**: Either *Openstack Nova* or *OpenStack Compute* |
100 | + * **DMI chassis_asset_tag** is *OpenTelekomCloud* |
101 | + |
102 | + |
103 | Configuration |
104 | ------------- |
105 | The following configuration can be set for the datasource in system |
106 | diff --git a/tests/unittests/test_datasource/test_openstack.py b/tests/unittests/test_datasource/test_openstack.py |
107 | index fad73b2..585acc3 100644 |
108 | --- a/tests/unittests/test_datasource/test_openstack.py |
109 | +++ b/tests/unittests/test_datasource/test_openstack.py |
110 | @@ -69,6 +69,8 @@ EC2_VERSIONS = [ |
111 | 'latest', |
112 | ] |
113 | |
114 | +MOCK_PATH = 'cloudinit.sources.DataSourceOpenStack.' |
115 | + |
116 | |
117 | # TODO _register_uris should leverage test_ec2.register_mock_metaserver. |
118 | def _register_uris(version, ec2_files, ec2_meta, os_files): |
119 | @@ -231,7 +233,10 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase): |
120 | ds_os = ds.DataSourceOpenStack( |
121 | settings.CFG_BUILTIN, None, helpers.Paths({'run_dir': self.tmp})) |
122 | self.assertIsNone(ds_os.version) |
123 | - found = ds_os.get_data() |
124 | + mock_path = MOCK_PATH + 'detect_openstack' |
125 | + with test_helpers.mock.patch(mock_path) as m_detect_os: |
126 | + m_detect_os.return_value = True |
127 | + found = ds_os.get_data() |
128 | self.assertTrue(found) |
129 | self.assertEqual(2, ds_os.version) |
130 | md = dict(ds_os.metadata) |
131 | @@ -260,7 +265,10 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase): |
132 | 'broadcast-address': '192.168.2.255'}] |
133 | |
134 | self.assertIsNone(ds_os_local.version) |
135 | - found = ds_os_local.get_data() |
136 | + mock_path = MOCK_PATH + 'detect_openstack' |
137 | + with test_helpers.mock.patch(mock_path) as m_detect_os: |
138 | + m_detect_os.return_value = True |
139 | + found = ds_os_local.get_data() |
140 | self.assertTrue(found) |
141 | self.assertEqual(2, ds_os_local.version) |
142 | md = dict(ds_os_local.metadata) |
143 | @@ -284,7 +292,10 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase): |
144 | None, |
145 | helpers.Paths({'run_dir': self.tmp})) |
146 | self.assertIsNone(ds_os.version) |
147 | - found = ds_os.get_data() |
148 | + mock_path = MOCK_PATH + 'detect_openstack' |
149 | + with test_helpers.mock.patch(mock_path) as m_detect_os: |
150 | + m_detect_os.return_value = True |
151 | + found = ds_os.get_data() |
152 | self.assertFalse(found) |
153 | self.assertIsNone(ds_os.version) |
154 | self.assertIn( |
155 | @@ -306,15 +317,16 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase): |
156 | 'timeout': 0, |
157 | } |
158 | self.assertIsNone(ds_os.version) |
159 | - found = ds_os.get_data() |
160 | + mock_path = MOCK_PATH + 'detect_openstack' |
161 | + with test_helpers.mock.patch(mock_path) as m_detect_os: |
162 | + m_detect_os.return_value = True |
163 | + found = ds_os.get_data() |
164 | self.assertFalse(found) |
165 | self.assertIsNone(ds_os.version) |
166 | |
167 | def test_network_config_disabled_by_datasource_config(self): |
168 | """The network_config can be disabled from datasource config.""" |
169 | - mock_path = ( |
170 | - 'cloudinit.sources.DataSourceOpenStack.openstack.' |
171 | - 'convert_net_json') |
172 | + mock_path = MOCK_PATH + 'openstack.convert_net_json' |
173 | ds_os = ds.DataSourceOpenStack( |
174 | settings.CFG_BUILTIN, None, helpers.Paths({'run_dir': self.tmp})) |
175 | ds_os.ds_cfg = {'apply_network_config': False} |
176 | @@ -327,9 +339,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase): |
177 | |
178 | def test_network_config_from_network_json(self): |
179 | """The datasource gets network_config from network_data.json.""" |
180 | - mock_path = ( |
181 | - 'cloudinit.sources.DataSourceOpenStack.openstack.' |
182 | - 'convert_net_json') |
183 | + mock_path = MOCK_PATH + 'openstack.convert_net_json' |
184 | example_cfg = {'version': 1, 'config': []} |
185 | ds_os = ds.DataSourceOpenStack( |
186 | settings.CFG_BUILTIN, None, helpers.Paths({'run_dir': self.tmp})) |
187 | @@ -345,9 +355,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase): |
188 | |
189 | def test_network_config_cached(self): |
190 | """The datasource caches the network_config property.""" |
191 | - mock_path = ( |
192 | - 'cloudinit.sources.DataSourceOpenStack.openstack.' |
193 | - 'convert_net_json') |
194 | + mock_path = MOCK_PATH + 'openstack.convert_net_json' |
195 | example_cfg = {'version': 1, 'config': []} |
196 | ds_os = ds.DataSourceOpenStack( |
197 | settings.CFG_BUILTIN, None, helpers.Paths({'run_dir': self.tmp})) |
198 | @@ -374,7 +382,10 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase): |
199 | 'timeout': 0, |
200 | } |
201 | self.assertIsNone(ds_os.version) |
202 | - found = ds_os.get_data() |
203 | + mock_path = MOCK_PATH + 'detect_openstack' |
204 | + with test_helpers.mock.patch(mock_path) as m_detect_os: |
205 | + m_detect_os.return_value = True |
206 | + found = ds_os.get_data() |
207 | self.assertFalse(found) |
208 | self.assertIsNone(ds_os.version) |
209 | |
210 | @@ -438,4 +449,89 @@ class TestVendorDataLoading(test_helpers.TestCase): |
211 | data = {'foo': 'bar', 'cloud-init': ['VD_1', 'VD_2']} |
212 | self.assertEqual(self.cvj(data), data['cloud-init']) |
213 | |
214 | + |
215 | +@test_helpers.mock.patch(MOCK_PATH + 'util.is_x86') |
216 | +class TestDetectOpenStack(test_helpers.CiTestCase): |
217 | + |
218 | + def test_detect_openstack_non_intel_x86(self, m_is_x86): |
219 | + """Return True on non-intel platforms because dmi isn't conclusive.""" |
220 | + m_is_x86.return_value = False |
221 | + self.assertTrue( |
222 | + ds.detect_openstack(), 'Expected detect_openstack == True') |
223 | + |
224 | + @test_helpers.mock.patch(MOCK_PATH + 'util.get_proc_env') |
225 | + @test_helpers.mock.patch(MOCK_PATH + 'util.read_dmi_data') |
226 | + def test_not_detect_openstack_intel_x86_ec2(self, m_dmi, m_proc_env, |
227 | + m_is_x86): |
228 | + """Return False on EC2 platforms.""" |
229 | + m_is_x86.return_value = True |
230 | + # No product_name in proc/1/environ |
231 | + m_proc_env.return_value = {'HOME': '/'} |
232 | + |
233 | + def fake_dmi_read(dmi_key): |
234 | + if dmi_key == 'system-product-name': |
235 | + return 'HVM domU' # Nothing 'openstackish' on EC2 |
236 | + if dmi_key == 'chassis-asset-tag': |
237 | + return '' # Empty string on EC2 |
238 | + assert False, 'Unexpected dmi read of %s' % dmi_key |
239 | + |
240 | + m_dmi.side_effect = fake_dmi_read |
241 | + self.assertFalse( |
242 | + ds.detect_openstack(), 'Expected detect_openstack == False on EC2') |
243 | + m_proc_env.assert_called_with(1) |
244 | + |
245 | + @test_helpers.mock.patch(MOCK_PATH + 'util.read_dmi_data') |
246 | + def test_detect_openstack_intel_product_name_compute(self, m_dmi, |
247 | + m_is_x86): |
248 | + """Return True on OpenStack compute and nova instances.""" |
249 | + m_is_x86.return_value = True |
250 | + openstack_product_names = ['OpenStack Nova', 'OpenStack Compute'] |
251 | + |
252 | + for product_name in openstack_product_names: |
253 | + m_dmi.return_value = product_name |
254 | + self.assertTrue( |
255 | + ds.detect_openstack(), 'Failed to detect_openstack') |
256 | + |
257 | + @test_helpers.mock.patch(MOCK_PATH + 'util.read_dmi_data') |
258 | + def test_detect_openstack_opentelekomcloud_chassis_asset_tag(self, m_dmi, |
259 | + m_is_x86): |
260 | + """Return True on OpenStack reporting OpenTelekomCloud asset-tag.""" |
261 | + m_is_x86.return_value = True |
262 | + |
263 | + def fake_dmi_read(dmi_key): |
264 | + if dmi_key == 'system-product-name': |
265 | + return 'HVM domU' # Nothing 'openstackish' on OpenTelekomCloud |
266 | + if dmi_key == 'chassis-asset-tag': |
267 | + return 'OpenTelekomCloud' |
268 | + assert False, 'Unexpected dmi read of %s' % dmi_key |
269 | + |
270 | + m_dmi.side_effect = fake_dmi_read |
271 | + self.assertTrue( |
272 | + ds.detect_openstack(), |
273 | + 'Expected detect_openstack == True on OpenTelekomCloud') |
274 | + |
275 | + @test_helpers.mock.patch(MOCK_PATH + 'util.get_proc_env') |
276 | + @test_helpers.mock.patch(MOCK_PATH + 'util.read_dmi_data') |
277 | + def test_detect_openstack_by_proc_1_environ(self, m_dmi, m_proc_env, |
278 | + m_is_x86): |
279 | + """Return True when nova product_name specified in /proc/1/environ.""" |
280 | + m_is_x86.return_value = True |
281 | + # Nova product_name in proc/1/environ |
282 | + m_proc_env.return_value = { |
283 | + 'HOME': '/', 'product_name': 'OpenStack Nova'} |
284 | + |
285 | + def fake_dmi_read(dmi_key): |
286 | + if dmi_key == 'system-product-name': |
287 | + return 'HVM domU' # Nothing 'openstackish' |
288 | + if dmi_key == 'chassis-asset-tag': |
289 | + return '' # Nothin 'openstackish' |
290 | + assert False, 'Unexpected dmi read of %s' % dmi_key |
291 | + |
292 | + m_dmi.side_effect = fake_dmi_read |
293 | + self.assertTrue( |
294 | + ds.detect_openstack(), |
295 | + 'Expected detect_openstack == True on OpenTelekomCloud') |
296 | + m_proc_env.assert_called_with(1) |
297 | + |
298 | + |
299 | # vi: ts=4 expandtab |
300 | diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py |
301 | index 20479f6..7a203ce 100644 |
302 | --- a/tests/unittests/test_util.py |
303 | +++ b/tests/unittests/test_util.py |
304 | @@ -468,6 +468,29 @@ class TestMountinfoParsing(helpers.ResourceUsingTestCase): |
305 | self.assertIsNone(ret) |
306 | |
307 | |
308 | +class TestIsX86(helpers.CiTestCase): |
309 | + |
310 | + def test_is_x86_matches_x86_types(self): |
311 | + """is_x86 returns True if CPU architecture matches.""" |
312 | + matched_arches = ['x86_64', 'i386', 'i586', 'i686'] |
313 | + for arch in matched_arches: |
314 | + self.assertTrue( |
315 | + util.is_x86(arch), 'Expected is_x86 for arch "%s"' % arch) |
316 | + |
317 | + def test_is_x86_unmatched_types(self): |
318 | + """is_x86 returns Fale on non-intel x86 architectures.""" |
319 | + unmatched_arches = ['ia64', '9000/800', 'arm64v71'] |
320 | + for arch in unmatched_arches: |
321 | + self.assertFalse( |
322 | + util.is_x86(arch), 'Expected not is_x86 for arch "%s"' % arch) |
323 | + |
324 | + @mock.patch('cloudinit.util.os.uname') |
325 | + def test_is_x86_calls_uname_for_architecture(self, m_uname): |
326 | + """is_x86 returns True if platform from uname matches.""" |
327 | + m_uname.return_value = [0, 1, 2, 3, 'x86_64'] |
328 | + self.assertTrue(util.is_x86()) |
329 | + |
330 | + |
331 | class TestReadDMIData(helpers.FilesystemMockingTestCase): |
332 | |
333 | def setUp(self): |
PASSED: Continuous integration, rev:28a042285ad 04512a03f611731 b455a870c14961 /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 80/
https:/
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: /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 80/rebuild
https:/