Merge lp:~xianghui/charms/trusty/nova-cloud-controller/lp1476455 into lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/next
- Trusty Tahr (14.04)
- lp1476455
- Merge into next
Status: | Superseded |
---|---|
Proposed branch: | lp:~xianghui/charms/trusty/nova-cloud-controller/lp1476455 |
Merge into: | lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/next |
Diff against target: |
441 lines (+277/-10) 11 files modified
config.yaml (+14/-0) hooks/nova_cc_context.py (+55/-0) hooks/nova_cc_hooks.py (+23/-4) hooks/nova_cc_utils.py (+2/-1) templates/icehouse/nova.conf (+2/-0) templates/juno/nova.conf (+2/-0) templates/kilo/nova.conf (+2/-0) templates/parts/novnc (+3/-0) templates/parts/spice (+4/-0) unit_tests/test_nova_cc_contexts.py (+148/-0) unit_tests/test_nova_cc_hooks.py (+22/-5) |
To merge this branch: | bzr merge lp:~xianghui/charms/trusty/nova-cloud-controller/lp1476455 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Edward Hope-Morley | Needs Fixing | ||
OpenStack Charmers | Pending | ||
Review via email: mp+265496@code.launchpad.net |
This proposal has been superseded by a proposal from 2015-07-24.
Commit message
Description of the change
Support nova console session to be encrypted without nova API SSL.
uosci-testing-bot (uosci-testing-bot) wrote : | # |
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #6247 nova-cloud-
UNIT OK: passed
Edward Hope-Morley (hopem) wrote : | # |
See inline
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #6618 nova-cloud-
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #6250 nova-cloud-
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #6626 nova-cloud-
LINT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #6253 nova-cloud-
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5255 nova-cloud-
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
Edward Hope-Morley (hopem) wrote : | # |
This all lgtm apart from one small comment. I'm gonna have a go at deploying it with your bundle before I +1 it tho. I also see that amulet failed but not sure if that is related.
Xiang Hui (xianghui) : | # |
Edward Hope-Morley (hopem) : | # |
Unmerged revisions
Preview Diff
1 | === modified file 'config.yaml' |
2 | --- config.yaml 2015-07-10 14:14:23 +0000 |
3 | +++ config.yaml 2015-07-22 12:35:41 +0000 |
4 | @@ -381,3 +381,17 @@ |
5 | |
6 | If memcached is being used to store the tokens, then it's recommended to |
7 | change this configuration to False. |
8 | + console-access-ssl-cert: |
9 | + type: string |
10 | + default: |
11 | + description: | |
12 | + Used for encrypting console connections. This differs from the SSL certificate |
13 | + used for API endpoints and is used for console access session only. Setting |
14 | + this value along with console-access-ssl-key will enable encrypted console |
15 | + sessions. This has nothing to do with Nova API SSL and can be used |
16 | + independently. This can be used in conjunction with console-access-protocol |
17 | + set to 'novnc' or 'spice'. |
18 | + console-access-ssl-key: |
19 | + type: string |
20 | + default: |
21 | + description: SSL key to use with certificate specified as console-access-ssl-cert. |
22 | |
23 | === modified file 'hooks/nova_cc_context.py' |
24 | --- hooks/nova_cc_context.py 2015-04-08 12:10:09 +0000 |
25 | +++ hooks/nova_cc_context.py 2015-07-22 12:35:41 +0000 |
26 | @@ -1,5 +1,6 @@ |
27 | import os |
28 | |
29 | +from base64 import b64decode |
30 | from charmhelpers.core.hookenv import ( |
31 | config, |
32 | relation_ids, |
33 | @@ -9,6 +10,8 @@ |
34 | related_units, |
35 | relations_for_id, |
36 | relation_get, |
37 | + DEBUG, |
38 | + unit_get, |
39 | ) |
40 | from charmhelpers.fetch import ( |
41 | apt_install, |
42 | @@ -23,6 +26,7 @@ |
43 | determine_apache_port, |
44 | determine_api_port, |
45 | https, |
46 | + is_clustered, |
47 | ) |
48 | from charmhelpers.contrib.network.ip import ( |
49 | format_ipv6_addr, |
50 | @@ -30,6 +34,7 @@ |
51 | from charmhelpers.contrib.openstack.ip import ( |
52 | resolve_address, |
53 | INTERNAL, |
54 | + PUBLIC, |
55 | ) |
56 | |
57 | |
58 | @@ -350,3 +355,53 @@ |
59 | ctxt['ssl_key'] = key |
60 | |
61 | return ctxt |
62 | + |
63 | + |
64 | +class ConsoleSSLContext(context.OSContextGenerator): |
65 | + interfaces = [] |
66 | + |
67 | + def __call__(self): |
68 | + ctxt = {} |
69 | + from nova_cc_utils import console_attributes |
70 | + |
71 | + if config('console-access-ssl-cert') \ |
72 | + and config('console-access-ssl-key') \ |
73 | + and config('console-access-protocol'): |
74 | + ssl_dir = '/etc/nova/ssl/' |
75 | + if not os.path.exists(ssl_dir): |
76 | + log('Creating %s.' % ssl_dir, level=DEBUG) |
77 | + os.mkdir(ssl_dir) |
78 | + |
79 | + cert_path = os.path.join(ssl_dir, 'nova_cert.pem') |
80 | + decode_ssl_cert = b64decode(config('console-access-ssl-cert')) |
81 | + with open(cert_path, 'w') as fh: |
82 | + fh.write(decode_ssl_cert) |
83 | + |
84 | + key_path = os.path.join(ssl_dir, 'nova_key.pem') |
85 | + decode_ssl_key = b64decode(config('console-access-ssl-key')) |
86 | + with open(key_path, 'w') as fh: |
87 | + fh.write(decode_ssl_key) |
88 | + |
89 | + ctxt['ssl_only'] = True |
90 | + ctxt['ssl_cert'] = cert_path |
91 | + ctxt['ssl_key'] = key_path |
92 | + |
93 | + if is_clustered(): |
94 | + ip_addr = resolve_address(endpoint_type=PUBLIC) |
95 | + else: |
96 | + ip_addr = unit_get('private-address') |
97 | + |
98 | + ip_addr = format_ipv6_addr(ip_addr) or ip_addr |
99 | + |
100 | + _proto = config('console-access-protocol') |
101 | + url = "https://%s:%s%s" % ( |
102 | + ip_addr, |
103 | + console_attributes('proxy-port', proto=_proto), |
104 | + console_attributes('proxy-page', proto=_proto)) |
105 | + |
106 | + if _proto == 'novnc': |
107 | + ctxt['novncproxy_base_url'] = url |
108 | + elif _proto == 'spice': |
109 | + ctxt['html5proxy_base_url'] = url |
110 | + |
111 | + return ctxt |
112 | |
113 | === modified file 'hooks/nova_cc_hooks.py' |
114 | --- hooks/nova_cc_hooks.py 2015-06-12 13:03:50 +0000 |
115 | +++ hooks/nova_cc_hooks.py 2015-07-22 12:35:41 +0000 |
116 | @@ -106,13 +106,15 @@ |
117 | from charmhelpers.contrib.hahelpers.cluster import ( |
118 | is_elected_leader, |
119 | get_hacluster_config, |
120 | + https, |
121 | ) |
122 | |
123 | from charmhelpers.payload.execd import execd_preinstall |
124 | |
125 | from charmhelpers.contrib.openstack.ip import ( |
126 | canonical_url, |
127 | - PUBLIC, INTERNAL, ADMIN |
128 | + PUBLIC, INTERNAL, ADMIN, |
129 | + resolve_address, |
130 | ) |
131 | |
132 | from charmhelpers.contrib.network.ip import ( |
133 | @@ -120,7 +122,8 @@ |
134 | get_netmask_for_address, |
135 | get_address_in_network, |
136 | get_ipv6_addr, |
137 | - is_ipv6 |
138 | + is_ipv6, |
139 | + format_ipv6_addr, |
140 | ) |
141 | |
142 | from charmhelpers.contrib.openstack.context import ADDRESS_TYPES |
143 | @@ -511,10 +514,26 @@ |
144 | return {} |
145 | rel_settings['console_keymap'] = config('console-keymap') |
146 | rel_settings['console_access_protocol'] = proto |
147 | + |
148 | + console_ssl = False |
149 | + if config('console-access-ssl-cert') and config('console-access-ssl-key'): |
150 | + console_ssl = True |
151 | + |
152 | if config('console-proxy-ip') == 'local': |
153 | - proxy_base_addr = canonical_url(CONFIGS, PUBLIC) |
154 | + if console_ssl: |
155 | + address = resolve_address(endpoint_type=PUBLIC) |
156 | + address = format_ipv6_addr(address) or address |
157 | + proxy_base_addr = 'https://%s' % address |
158 | + else: |
159 | + # canonical_url will only return 'https:' if API SSL are enabled. |
160 | + proxy_base_addr = canonical_url(CONFIGS, PUBLIC) |
161 | else: |
162 | - proxy_base_addr = "http://" + config('console-proxy-ip') |
163 | + if console_ssl or https(): |
164 | + schema = "https" |
165 | + else: |
166 | + schema = "http" |
167 | + proxy_base_addr = "%s://%s" % (schema, config('console-proxy-ip')) |
168 | + |
169 | if proto == 'vnc': |
170 | protocols = ['novnc', 'xvpvnc'] |
171 | else: |
172 | |
173 | === modified file 'hooks/nova_cc_utils.py' |
174 | --- hooks/nova_cc_utils.py 2015-07-16 20:51:15 +0000 |
175 | +++ hooks/nova_cc_utils.py 2015-07-22 12:35:41 +0000 |
176 | @@ -194,7 +194,8 @@ |
177 | nova_cc_context.NovaIPv6Context(), |
178 | nova_cc_context.NeutronCCContext(), |
179 | nova_cc_context.NovaConfigContext(), |
180 | - nova_cc_context.InstanceConsoleContext()], |
181 | + nova_cc_context.InstanceConsoleContext(), |
182 | + nova_cc_context.ConsoleSSLContext()], |
183 | }), |
184 | (NOVA_API_PASTE, { |
185 | 'services': [s for s in BASE_SERVICES if 'api' in s], |
186 | |
187 | === modified file 'templates/icehouse/nova.conf' |
188 | --- templates/icehouse/nova.conf 2015-04-10 02:39:10 +0000 |
189 | +++ templates/icehouse/nova.conf 2015-07-22 12:35:41 +0000 |
190 | @@ -165,3 +165,5 @@ |
191 | |
192 | [conductor] |
193 | workers = {{ workers }} |
194 | + |
195 | +{% include "parts/spice" %} |
196 | |
197 | === modified file 'templates/juno/nova.conf' |
198 | --- templates/juno/nova.conf 2015-04-13 08:49:59 +0000 |
199 | +++ templates/juno/nova.conf 2015-07-22 12:35:41 +0000 |
200 | @@ -160,3 +160,5 @@ |
201 | |
202 | [conductor] |
203 | workers = {{ workers }} |
204 | + |
205 | +{% include "parts/spice" %} |
206 | |
207 | === modified file 'templates/kilo/nova.conf' |
208 | --- templates/kilo/nova.conf 2015-04-13 08:49:59 +0000 |
209 | +++ templates/kilo/nova.conf 2015-07-22 12:35:41 +0000 |
210 | @@ -155,3 +155,5 @@ |
211 | |
212 | [oslo_concurrency] |
213 | lock_path=/var/lock/nova |
214 | + |
215 | +{% include "parts/spice" %} |
216 | |
217 | === modified file 'templates/parts/novnc' |
218 | --- templates/parts/novnc 2015-04-08 12:10:09 +0000 |
219 | +++ templates/parts/novnc 2015-07-22 12:35:41 +0000 |
220 | @@ -7,3 +7,6 @@ |
221 | {% if ssl_key -%} |
222 | key={{ ssl_key }} |
223 | {% endif %} |
224 | +{% if novncproxy_base_url -%} |
225 | +novncproxy_base_url={{ novncproxy_base_url }} |
226 | +{% endif %} |
227 | |
228 | === added file 'templates/parts/spice' |
229 | --- templates/parts/spice 1970-01-01 00:00:00 +0000 |
230 | +++ templates/parts/spice 2015-07-22 12:35:41 +0000 |
231 | @@ -0,0 +1,4 @@ |
232 | +[spice] |
233 | +{% if html5proxy_base_url -%} |
234 | +html5proxy_base_url = {{ html5proxy_base_url }} |
235 | +{% endif -%} |
236 | |
237 | === modified file 'unit_tests/test_nova_cc_contexts.py' |
238 | --- unit_tests/test_nova_cc_contexts.py 2015-06-11 13:33:42 +0000 |
239 | +++ unit_tests/test_nova_cc_contexts.py 2015-07-22 12:35:41 +0000 |
240 | @@ -148,3 +148,151 @@ |
241 | self.assertTrue(context.use_local_neutron_api()) |
242 | self.related_units.return_value = ['unit/0'] |
243 | self.assertFalse(context.use_local_neutron_api()) |
244 | + |
245 | + @mock.patch.object(context, 'config') |
246 | + def test_console_access_ssl_disabled(self, mock_config): |
247 | + config = {'console-access-ssl_cert': 'LS0tLS1CRUdJTiBDRV', |
248 | + 'console-access-ssl_key': 'LS0tLS1CRUdJTiBQUk'} |
249 | + mock_config.side_effect = lambda key: config.get(key) |
250 | + |
251 | + ctxt = context.ConsoleSSLContext()() |
252 | + self.assertEqual(ctxt, None) |
253 | + |
254 | + config = {'console-access-ssl_cert': None, |
255 | + 'console-access-ssl_key': None} |
256 | + mock_config.side_effect = lambda key: config.get(key) |
257 | + |
258 | + ctxt = context.ConsoleSSLContext()() |
259 | + self.assertEqual(ctxt, None) |
260 | + |
261 | + config = {'console-access-protocol': 'novnc', |
262 | + 'console-access-ssl_cert': None, |
263 | + 'console-access-ssl_key': None} |
264 | + mock_config.side_effect = lambda key: config.get(key) |
265 | + |
266 | + ctxt = context.ConsoleSSLContext()() |
267 | + self.assertEqual(ctxt, None) |
268 | + |
269 | + @mock.patch('__builtin__.open') |
270 | + @mock.patch('os.path.exists') |
271 | + @mock.patch.object(context, 'config') |
272 | + @mock.patch.object(context, 'unit_get') |
273 | + @mock.patch.object(context, 'is_clustered') |
274 | + @mock.patch.object(context, 'resolve_address') |
275 | + @mock.patch.object(context, 'b64decode') |
276 | + def test_noVNC_ssl_enabled(self, mock_b64decode, |
277 | + mock_resolve_address, |
278 | + mock_is_clustered, mock_unit_get, |
279 | + mock_config, mock_exists, mock_open): |
280 | + config = {'console-access-ssl-cert': 'LS0tLS1CRUdJTiBDRV', |
281 | + 'console-access-ssl-key': 'LS0tLS1CRUdJTiBQUk', |
282 | + 'console-access-protocol': 'novnc'} |
283 | + mock_config.side_effect = lambda key: config.get(key) |
284 | + mock_exists.return_value = True |
285 | + mock_unit_get.return_value = '127.0.0.1' |
286 | + mock_is_clustered.return_value = True |
287 | + mock_resolve_address.return_value = '10.5.100.1' |
288 | + mock_b64decode.return_value = 'decode_success' |
289 | + |
290 | + mock_open.return_value.__enter__ = lambda s: s |
291 | + mock_open.return_value.__exit__ = mock.Mock() |
292 | + |
293 | + ctxt = context.ConsoleSSLContext()() |
294 | + self.assertTrue(ctxt['ssl_only']) |
295 | + self.assertEqual(ctxt['ssl_cert'], '/etc/nova/ssl/nova_cert.pem') |
296 | + self.assertEqual(ctxt['ssl_key'], '/etc/nova/ssl/nova_key.pem') |
297 | + self.assertEqual(ctxt['novncproxy_base_url'], |
298 | + 'https://10.5.100.1:6080/vnc_auto.html') |
299 | + |
300 | + @mock.patch('__builtin__.open') |
301 | + @mock.patch('os.path.exists') |
302 | + @mock.patch.object(context, 'config') |
303 | + @mock.patch.object(context, 'unit_get') |
304 | + @mock.patch.object(context, 'is_clustered') |
305 | + @mock.patch.object(context, 'resolve_address') |
306 | + @mock.patch.object(context, 'b64decode') |
307 | + def test_noVNC_ssl_enabled_no_cluster(self, mock_b64decode, |
308 | + mock_resolve_address, |
309 | + mock_is_clustered, mock_unit_get, |
310 | + mock_config, mock_exists, mock_open): |
311 | + config = {'console-access-ssl-cert': 'LS0tLS1CRUdJTiBDRV', |
312 | + 'console-access-ssl-key': 'LS0tLS1CRUdJTiBQUk', |
313 | + 'console-access-protocol': 'novnc'} |
314 | + mock_config.side_effect = lambda key: config.get(key) |
315 | + mock_exists.return_value = True |
316 | + mock_unit_get.return_value = '10.5.0.1' |
317 | + mock_is_clustered.return_value = False |
318 | + mock_b64decode.return_value = 'decode_success' |
319 | + |
320 | + mock_open.return_value.__enter__ = lambda s: s |
321 | + mock_open.return_value.__exit__ = mock.Mock() |
322 | + |
323 | + ctxt = context.ConsoleSSLContext()() |
324 | + self.assertTrue(ctxt['ssl_only']) |
325 | + self.assertEqual(ctxt['ssl_cert'], '/etc/nova/ssl/nova_cert.pem') |
326 | + self.assertEqual(ctxt['ssl_key'], '/etc/nova/ssl/nova_key.pem') |
327 | + self.assertEqual(ctxt['novncproxy_base_url'], |
328 | + 'https://10.5.0.1:6080/vnc_auto.html') |
329 | + |
330 | + @mock.patch('__builtin__.open') |
331 | + @mock.patch('os.path.exists') |
332 | + @mock.patch.object(context, 'config') |
333 | + @mock.patch.object(context, 'unit_get') |
334 | + @mock.patch.object(context, 'is_clustered') |
335 | + @mock.patch.object(context, 'resolve_address') |
336 | + @mock.patch.object(context, 'b64decode') |
337 | + def test_spice_html5_ssl_enabled(self, mock_b64decode, |
338 | + mock_resolve_address, |
339 | + mock_is_clustered, mock_unit_get, |
340 | + mock_config, mock_exists, mock_open): |
341 | + config = {'console-access-ssl-cert': 'LS0tLS1CRUdJTiBDRV', |
342 | + 'console-access-ssl-key': 'LS0tLS1CRUdJTiBQUk', |
343 | + 'console-access-protocol': 'spice'} |
344 | + mock_config.side_effect = lambda key: config.get(key) |
345 | + mock_exists.return_value = True |
346 | + mock_unit_get.return_value = '127.0.0.1' |
347 | + mock_is_clustered.return_value = True |
348 | + mock_resolve_address.return_value = '10.5.100.1' |
349 | + mock_b64decode.return_value = 'decode_success' |
350 | + |
351 | + mock_open.return_value.__enter__ = lambda s: s |
352 | + mock_open.return_value.__exit__ = mock.Mock() |
353 | + |
354 | + ctxt = context.ConsoleSSLContext()() |
355 | + self.assertTrue(ctxt['ssl_only']) |
356 | + self.assertEqual(ctxt['ssl_cert'], '/etc/nova/ssl/nova_cert.pem') |
357 | + self.assertEqual(ctxt['ssl_key'], '/etc/nova/ssl/nova_key.pem') |
358 | + self.assertEqual(ctxt['html5proxy_base_url'], |
359 | + 'https://10.5.100.1:6082/spice_auto.html') |
360 | + |
361 | + @mock.patch('__builtin__.open') |
362 | + @mock.patch('os.path.exists') |
363 | + @mock.patch.object(context, 'config') |
364 | + @mock.patch.object(context, 'unit_get') |
365 | + @mock.patch.object(context, 'is_clustered') |
366 | + @mock.patch.object(context, 'resolve_address') |
367 | + @mock.patch.object(context, 'b64decode') |
368 | + def test_spice_html5_ssl_enabled_no_cluster(self, mock_b64decode, |
369 | + mock_resolve_address, |
370 | + mock_is_clustered, |
371 | + mock_unit_get, |
372 | + mock_config, mock_exists, |
373 | + mock_open): |
374 | + config = {'console-access-ssl-cert': 'LS0tLS1CRUdJTiBDRV', |
375 | + 'console-access-ssl-key': 'LS0tLS1CRUdJTiBQUk', |
376 | + 'console-access-protocol': 'spice'} |
377 | + mock_config.side_effect = lambda key: config.get(key) |
378 | + mock_exists.return_value = True |
379 | + mock_unit_get.return_value = '10.5.0.1' |
380 | + mock_is_clustered.return_value = False |
381 | + mock_b64decode.return_value = 'decode_success' |
382 | + |
383 | + mock_open.return_value.__enter__ = lambda s: s |
384 | + mock_open.return_value.__exit__ = mock.Mock() |
385 | + |
386 | + ctxt = context.ConsoleSSLContext()() |
387 | + self.assertTrue(ctxt['ssl_only']) |
388 | + self.assertEqual(ctxt['ssl_cert'], '/etc/nova/ssl/nova_cert.pem') |
389 | + self.assertEqual(ctxt['ssl_key'], '/etc/nova/ssl/nova_key.pem') |
390 | + self.assertEqual(ctxt['html5proxy_base_url'], |
391 | + 'https://10.5.0.1:6082/spice_auto.html') |
392 | |
393 | === modified file 'unit_tests/test_nova_cc_hooks.py' |
394 | --- unit_tests/test_nova_cc_hooks.py 2015-06-12 13:03:50 +0000 |
395 | +++ unit_tests/test_nova_cc_hooks.py 2015-07-22 12:35:41 +0000 |
396 | @@ -681,16 +681,14 @@ |
397 | } |
398 | self.assertEqual(_con_sets, console_settings) |
399 | |
400 | - @patch.object(hooks, 'canonical_url') |
401 | + @patch.object(hooks, 'https') |
402 | @patch.object(utils, 'config') |
403 | - def test_console_settings_explicit_ip(self, _utils_config, |
404 | - _canonical_url): |
405 | + def test_console_settings_explicit_ip(self, _utils_config, _https): |
406 | _utils_config.return_value = 'spice' |
407 | + _https.return_value = False |
408 | _cc_public_host = "public-host" |
409 | - _cc_private_host = "private-host" |
410 | self.test_config.set('console-proxy-ip', _cc_public_host) |
411 | _con_sets = hooks.console_settings() |
412 | - _canonical_url.return_value = 'http://' + _cc_private_host |
413 | console_settings = { |
414 | 'console_proxy_spice_address': 'http://%s:6082/spice_auto.html' % |
415 | (_cc_public_host), |
416 | @@ -701,6 +699,25 @@ |
417 | } |
418 | self.assertEqual(_con_sets, console_settings) |
419 | |
420 | + @patch.object(hooks, 'https') |
421 | + @patch.object(utils, 'config') |
422 | + def test_console_settings_explicit_ip_with_https(self, _utils_config, |
423 | + _https): |
424 | + _utils_config.return_value = 'spice' |
425 | + _https.return_value = True |
426 | + _cc_public_host = "public-host" |
427 | + self.test_config.set('console-proxy-ip', _cc_public_host) |
428 | + _con_sets = hooks.console_settings() |
429 | + console_settings = { |
430 | + 'console_proxy_spice_address': 'https://%s:6082/spice_auto.html' % |
431 | + (_cc_public_host), |
432 | + 'console_proxy_spice_host': _cc_public_host, |
433 | + 'console_proxy_spice_port': 6082, |
434 | + 'console_access_protocol': 'spice', |
435 | + 'console_keymap': 'en-us' |
436 | + } |
437 | + self.assertEqual(_con_sets, console_settings) |
438 | + |
439 | def test_conditional_neutron_migration(self): |
440 | self.os_release.return_value = 'juno' |
441 | self.services.return_value = ['neutron-server'] |
charm_lint_check #6615 nova-cloud- controller- next for xianghui mp265496
LINT OK: passed
Build: http:// 10.245. 162.77: 8080/job/ charm_lint_ check/6615/