Merge lp:~xianghui/charms/trusty/nova-cloud-controller/lp1476455 into lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/next

Proposed by Xiang Hui
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
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.

Description of the change

Support nova console session to be encrypted without nova API SSL.

To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

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/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #6247 nova-cloud-controller-next for xianghui mp265496
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/6247/

Revision history for this message
Edward Hope-Morley (hopem) wrote :

See inline

review: Needs Fixing
185. By Xiang Hui

Fix comments.

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #6618 nova-cloud-controller-next for xianghui mp265496
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/6618/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #6250 nova-cloud-controller-next for xianghui mp265496
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/6250/

186. By Xiang Hui

Fix template format err.

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #6626 nova-cloud-controller-next for xianghui mp265496
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/6626/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #6253 nova-cloud-controller-next for xianghui mp265496
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/6253/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #5255 nova-cloud-controller-next for xianghui mp265496
    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://paste.ubuntu.com/11921798/
Build: http://10.245.162.77:8080/job/charm_amulet_test/5255/

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

Revision history for this message
Xiang Hui (xianghui) :
Revision history for this message
Edward Hope-Morley (hopem) :
187. By Xiang Hui

decode before write file

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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']

Subscribers

People subscribed via source and target branches