Merge lp:~gnuoy/charms/trusty/nova-cloud-controller/add-console-access into lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/next

Proposed by Liam Young
Status: Merged
Merged at revision: 92
Proposed branch: lp:~gnuoy/charms/trusty/nova-cloud-controller/add-console-access
Merge into: lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/next
Diff against target: 402 lines (+267/-2)
6 files modified
README.txt (+5/-0)
config.yaml (+17/-0)
hooks/nova_cc_hooks.py (+36/-0)
hooks/nova_cc_utils.py (+48/-0)
unit_tests/test_nova_cc_hooks.py (+93/-2)
unit_tests/test_nova_cc_utils.py (+68/-0)
To merge this branch: bzr merge lp:~gnuoy/charms/trusty/nova-cloud-controller/add-console-access
Reviewer Review Type Date Requested Status
Liam Young (community) Needs Resubmitting
James Page Needs Fixing
Review via email: mp+226319@code.launchpad.net

Description of the change

Added support for console access to guest instances using vnc via browser (novnc), vnc via java client (xvpvnc) or spice via browser.

The bug reports suggests this should be baked into the nova-cloud-controller charm which is what I've done but I'm still sitting on the fence as to whether this would be better as a standalone principle charm.

To post a comment you must log in.
Revision history for this message
James Page (james-page) wrote :

I think this is fine as a function of the nova-cc charm; I've made a couple of comments.

One other question - does this work OK when more that one nova-cc service unit is being used? I'm not familiar with how this part of openstack actually works. Right now, users would always be directed to the unit serving the VIP and would not be load balanced - but this might actually be the right behaviour!

review: Needs Fixing
95. By Liam Young

Remove none as default option for console-access-protocol and tidyup code accordingly

96. By Liam Young

Rebased

97. By Liam Young

Updated README

98. By Liam Young

Set console_settings for compute node if leader or not as leadership is irrelevant and stale settings can persist otherwise

Revision history for this message
Liam Young (gnuoy) wrote :

> I think this is fine as a function of the nova-cc charm; I've made a couple of
> comments.
>

I've made the changes you suggested

> One other question - does this work OK when more that one nova-cc service unit
> is being used? I'm not familiar with how this part of openstack actually
> works. Right now, users would always be directed to the unit serving the VIP
> and would not be load balanced - but this might actually be the right
> behaviour!

I think this is behaviour is fine tbh. I've had a play with an ha setup and the client handshake fails. I haven't looked into to much further (maybe sticky haproxy sessions are needed). If you have no objection I'll create a wishlist bug and grab it to look at ha further but I'm not convinced it will work.

review: Needs Resubmitting
99. By Liam Young

README update, enable ability to explictly set the use of the nova-cc address and use the PUBLIC address

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README.txt'
2--- README.txt 2014-06-11 13:54:39 +0000
3+++ README.txt 2014-07-30 10:50:10 +0000
4@@ -9,6 +9,11 @@
5 neutron-api endpoint in keystone. It will also use the quantum-security-groups setting which is passed to
6 it by the api service rather than its own quantum-security-groups setting.
7
8+If console access is required then console-proxy-ip should be set to a client accessible IP that resolves
9+to the nova-cloud-controller. If running in HA mode then the public vip is used if console-proxy-ip is set
10+to local. Note: The console access protocol is baked into a guest when it is created, if you change it then
11+console access for existing guests will stop working
12+
13 ******************************************************
14 Special considerations to be deployed using Postgresql
15 ******************************************************
16
17=== modified file 'config.yaml'
18--- config.yaml 2014-07-29 11:50:07 +0000
19+++ config.yaml 2014-07-30 10:50:10 +0000
20@@ -203,3 +203,20 @@
21 * shared-db or (pgsql-nova-db, pgsql-neutron-db)
22 * amqp
23 * identity-service
24+ console-access-protocol:
25+ type: string
26+ description: |
27+ Protocol to use when accessing virtual machine console. Supported types
28+ are None, spice, xvpvnc, novnc and vnc (for both xvpvnc and novnc)
29+ console-proxy-ip:
30+ type: string
31+ default: local
32+ description: |
33+ If console-access-protocol != None then this is the ip published to
34+ clients for access to console proxy. Set to local for the ip address of
35+ the nova-cloud-controller serving the request to be used
36+ console-keymap:
37+ type: string
38+ default: 'en-us'
39+ description: |
40+ Console keymap
41
42=== modified file 'hooks/nova_cc_hooks.py'
43--- hooks/nova_cc_hooks.py 2014-07-29 12:25:27 +0000
44+++ hooks/nova_cc_hooks.py 2014-07-30 10:50:10 +0000
45@@ -71,6 +71,7 @@
46 QUANTUM_CONF,
47 NEUTRON_CONF,
48 QUANTUM_API_PASTE,
49+ console_attributes,
50 service_guard,
51 guard_map,
52 )
53@@ -124,6 +125,11 @@
54 save_script_rc()
55 configure_https()
56 CONFIGS.write_all()
57+ if console_attributes('protocol'):
58+ apt_update()
59+ apt_install(console_attributes('packages'), fatal=True)
60+ [compute_joined(rid=rid)
61+ for rid in relation_ids('cloud-compute')]
62 for r_id in relation_ids('identity-service'):
63 identity_joined(rid=r_id)
64
65@@ -377,8 +383,38 @@
66 return rel_settings
67
68
69+def console_settings():
70+ rel_settings = {}
71+ proto = console_attributes('protocol')
72+ if not proto:
73+ return {}
74+ rel_settings['console_keymap'] = config('console-keymap')
75+ rel_settings['console_access_protocol'] = proto
76+ if config('console-proxy-ip') == 'local':
77+ proxy_base_addr = canonical_url(CONFIGS, PUBLIC)
78+ else:
79+ proxy_base_addr = "http://" + config('console-proxy-ip')
80+ if proto == 'vnc':
81+ protocols = ['novnc', 'xvpvnc']
82+ else:
83+ protocols = [proto]
84+ for _proto in protocols:
85+ rel_settings['console_proxy_%s_address' % (_proto)] = \
86+ "%s:%s%s" % (proxy_base_addr,
87+ console_attributes('proxy-port', proto=_proto),
88+ console_attributes('proxy-page', proto=_proto))
89+ rel_settings['console_proxy_%s_host' % (_proto)] = \
90+ urlparse(proxy_base_addr).hostname
91+ rel_settings['console_proxy_%s_port' % (_proto)] = \
92+ console_attributes('proxy-port', proto=_proto)
93+
94+ return rel_settings
95+
96+
97 @hooks.hook('cloud-compute-relation-joined')
98 def compute_joined(rid=None, remote_restart=False):
99+ cons_settings = console_settings()
100+ relation_set(relation_id=rid, **cons_settings)
101 if not eligible_leader(CLUSTER_RES):
102 return
103 rel_settings = {
104
105=== modified file 'hooks/nova_cc_utils.py'
106--- hooks/nova_cc_utils.py 2014-07-29 13:32:55 +0000
107+++ hooks/nova_cc_utils.py 2014-07-30 10:50:10 +0000
108@@ -173,6 +173,27 @@
109
110 NOVA_SSH_DIR = '/etc/nova/compute_ssh/'
111
112+CONSOLE_CONFIG = {
113+ 'spice': {
114+ 'packages': ['nova-spiceproxy', 'nova-consoleauth'],
115+ 'services': ['nova-spiceproxy', 'nova-consoleauth'],
116+ 'proxy-page': '/spice_auto.html',
117+ 'proxy-port': 6082,
118+ },
119+ 'novnc': {
120+ 'packages': ['nova-novncproxy', 'nova-consoleauth'],
121+ 'services': ['nova-novncproxy', 'nova-consoleauth'],
122+ 'proxy-page': '/vnc_auto.html',
123+ 'proxy-port': 6080,
124+ },
125+ 'xvpvnc': {
126+ 'packages': ['nova-xvpvncproxy', 'nova-consoleauth'],
127+ 'services': ['nova-xvpvncproxy', 'nova-consoleauth'],
128+ 'proxy-page': '/console',
129+ 'proxy-port': 6081,
130+ },
131+}
132+
133
134 def resource_map():
135 '''
136@@ -235,6 +256,10 @@
137 if os_release('nova-common') not in ['essex', 'folsom']:
138 resource_map['/etc/nova/nova.conf']['services'] += ['nova-conductor']
139
140+ if console_attributes('services'):
141+ resource_map['/etc/nova/nova.conf']['services'] += \
142+ console_attributes('services')
143+
144 # also manage any configs that are being updated by subordinates.
145 vmware_ctxt = context.SubordinateConfigContext(interface='nova-vmware',
146 service='nova',
147@@ -287,6 +312,27 @@
148 return API_PORTS[service]
149
150
151+def console_attributes(attr, proto=None):
152+ '''Leave proto unset to query attributes of the protocal specified at
153+ runtime'''
154+ if proto:
155+ console_proto = proto
156+ else:
157+ console_proto = config('console-access-protocol')
158+ if attr == 'protocol':
159+ return console_proto
160+ # 'vnc' is a virtual type made up of novnc and xvpvnc
161+ if console_proto == 'vnc':
162+ if attr in ['packages', 'services']:
163+ return list(set(CONSOLE_CONFIG['novnc'][attr] +
164+ CONSOLE_CONFIG['xvpvnc'][attr]))
165+ else:
166+ return None
167+ if console_proto in CONSOLE_CONFIG:
168+ return CONSOLE_CONFIG[console_proto][attr]
169+ return None
170+
171+
172 def determine_packages():
173 # currently all packages match service names
174 packages = [] + BASE_PACKAGES
175@@ -296,6 +342,8 @@
176 pkgs = neutron_plugin_attribute(neutron_plugin(), 'server_packages',
177 network_manager())
178 packages.extend(pkgs)
179+ if console_attributes('packages'):
180+ packages.extend(console_attributes('packages'))
181 return list(set(packages))
182
183
184
185=== modified file 'unit_tests/test_nova_cc_hooks.py'
186--- unit_tests/test_nova_cc_hooks.py 2014-07-29 12:23:46 +0000
187+++ unit_tests/test_nova_cc_hooks.py 2014-07-30 10:50:10 +0000
188@@ -159,8 +159,10 @@
189 self.assertEquals(sorted(self.relation_set.call_args_list),
190 sorted(expected_relations))
191
192+ @patch.object(utils, 'config')
193 @patch.object(hooks, '_auth_config')
194- def test_compute_joined_neutron(self, auth_config):
195+ def test_compute_joined_neutron(self, auth_config, _util_config):
196+ _util_config.return_value = None
197 self.is_relation_made.return_value = False
198 self.network_manager.return_value = 'neutron'
199 self.eligible_leader = True
200@@ -186,15 +188,18 @@
201 quantum_plugin='nvp',
202 network_manager='neutron', **FAKE_KS_AUTH_CFG)
203
204+ @patch.object(utils, 'config')
205 @patch.object(hooks, 'NeutronAPIContext')
206 @patch.object(hooks, '_auth_config')
207- def test_compute_joined_neutron_api_rel(self, auth_config, napi):
208+ def test_compute_joined_neutron_api_rel(self, auth_config, napi,
209+ _util_config):
210 def mock_NeutronAPIContext():
211 return {
212 'neutron_plugin': 'bob',
213 'neutron_security_groups': 'yes',
214 'neutron_url': 'http://nova-cc-host1:9696',
215 }
216+ _util_config.return_value = None
217 napi.return_value = mock_NeutronAPIContext
218 self.is_relation_made.return_value = True
219 self.network_manager.return_value = 'neutron'
220@@ -365,3 +370,89 @@
221 self.assertTrue(configs.write_all.called)
222 self.assertTrue(_compute_joined.called)
223 self.assertTrue(_quantum_joined.called)
224+
225+ @patch.object(utils, 'config')
226+ def test_console_settings_vnc(self, _utils_config):
227+ _utils_config.return_value = 'vnc'
228+ _cc_host = "nova-cc-host1"
229+ self.canonical_url.return_value = 'http://' + _cc_host
230+ _con_sets = hooks.console_settings()
231+ console_settings = {
232+ 'console_proxy_novnc_address': 'http://%s:6080/vnc_auto.html' %
233+ (_cc_host),
234+ 'console_proxy_novnc_port': 6080,
235+ 'console_access_protocol': 'vnc',
236+ 'console_proxy_novnc_host': _cc_host,
237+ 'console_proxy_xvpvnc_port': 6081,
238+ 'console_proxy_xvpvnc_host': _cc_host,
239+ 'console_proxy_xvpvnc_address': 'http://%s:6081/console' %
240+ (_cc_host),
241+ 'console_keymap': 'en-us'
242+ }
243+ self.assertEqual(_con_sets, console_settings)
244+
245+ @patch.object(utils, 'config')
246+ def test_console_settings_xvpvnc(self, _utils_config):
247+ _utils_config.return_value = 'xvpvnc'
248+ _cc_host = "nova-cc-host1"
249+ self.canonical_url.return_value = 'http://' + _cc_host
250+ _con_sets = hooks.console_settings()
251+ console_settings = {
252+ 'console_access_protocol': 'xvpvnc',
253+ 'console_keymap': 'en-us',
254+ 'console_proxy_xvpvnc_port': 6081,
255+ 'console_proxy_xvpvnc_host': _cc_host,
256+ 'console_proxy_xvpvnc_address': 'http://%s:6081/console' %
257+ (_cc_host),
258+ }
259+ self.assertEqual(_con_sets, console_settings)
260+
261+ @patch.object(utils, 'config')
262+ def test_console_settings_novnc(self, _utils_config):
263+ _utils_config.return_value = 'novnc'
264+ _cc_host = "nova-cc-host1"
265+ self.canonical_url.return_value = 'http://' + _cc_host
266+ _con_sets = hooks.console_settings()
267+ console_settings = {
268+ 'console_proxy_novnc_address': 'http://%s:6080/vnc_auto.html' %
269+ (_cc_host),
270+ 'console_proxy_novnc_port': 6080,
271+ 'console_access_protocol': 'novnc',
272+ 'console_proxy_novnc_host': _cc_host,
273+ 'console_keymap': 'en-us'
274+ }
275+ self.assertEqual(_con_sets, console_settings)
276+
277+ @patch.object(utils, 'config')
278+ def test_console_settings_spice(self, _utils_config):
279+ _utils_config.return_value = 'spice'
280+ _cc_host = "nova-cc-host1"
281+ self.canonical_url.return_value = 'http://' + _cc_host
282+ _con_sets = hooks.console_settings()
283+ console_settings = {
284+ 'console_proxy_spice_address': 'http://%s:6082/spice_auto.html' %
285+ (_cc_host),
286+ 'console_proxy_spice_host': _cc_host,
287+ 'console_proxy_spice_port': 6082,
288+ 'console_access_protocol': 'spice',
289+ 'console_keymap': 'en-us'
290+ }
291+ self.assertEqual(_con_sets, console_settings)
292+
293+ @patch.object(utils, 'config')
294+ def test_console_settings_explicit_ip(self, _utils_config):
295+ _utils_config.return_value = 'spice'
296+ _cc_public_host = "public-host"
297+ _cc_private_host = "private-host"
298+ self.test_config.set('console-proxy-ip', _cc_public_host)
299+ _con_sets = hooks.console_settings()
300+ self.canonical_url.return_value = 'http://' + _cc_private_host
301+ console_settings = {
302+ 'console_proxy_spice_address': 'http://%s:6082/spice_auto.html' %
303+ (_cc_public_host),
304+ 'console_proxy_spice_host': _cc_public_host,
305+ 'console_proxy_spice_port': 6082,
306+ 'console_access_protocol': 'spice',
307+ 'console_keymap': 'en-us'
308+ }
309+ self.assertEqual(_con_sets, console_settings)
310
311=== modified file 'unit_tests/test_nova_cc_utils.py'
312--- unit_tests/test_nova_cc_utils.py 2014-07-29 13:44:34 +0000
313+++ unit_tests/test_nova_cc_utils.py 2014-07-30 10:50:10 +0000
314@@ -214,6 +214,48 @@
315 self.assertIn('nova-api-os-volume',
316 _map['/etc/nova/nova.conf']['services'])
317
318+ @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
319+ def test_resource_map_console_xvpvnc(self, subcontext):
320+ self.test_config.set('console-access-protocol', 'xvpvnc')
321+ self.relation_ids.return_value = []
322+ _map = utils.resource_map()
323+ console_services = ['nova-xvpvncproxy', 'nova-consoleauth']
324+ for service in console_services:
325+ self.assertIn(service, _map['/etc/nova/nova.conf']['services'])
326+
327+ @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
328+ def test_resource_map_console_novnc(self, subcontext):
329+ self.test_config.set('console-access-protocol', 'novnc')
330+ self.relation_ids.return_value = []
331+ _map = utils.resource_map()
332+ console_services = ['nova-novncproxy', 'nova-consoleauth']
333+ for service in console_services:
334+ self.assertIn(service, _map['/etc/nova/nova.conf']['services'])
335+
336+ @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
337+ def test_resource_map_console_vnc(self, subcontext):
338+ self.test_config.set('console-access-protocol', 'vnc')
339+ self.relation_ids.return_value = []
340+ _map = utils.resource_map()
341+ console_services = ['nova-novncproxy', 'nova-xvpvncproxy',
342+ 'nova-consoleauth']
343+ for service in console_services:
344+ self.assertIn(service, _map['/etc/nova/nova.conf']['services'])
345+
346+ def test_console_attributes_none(self):
347+ self.test_config.set('console-access-protocol', None)
348+ _proto = utils.console_attributes('protocol')
349+ self.assertEquals(_proto, None)
350+
351+ @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
352+ def test_resource_map_console_spice(self, subcontext):
353+ self.test_config.set('console-access-protocol', 'spice')
354+ self.relation_ids.return_value = []
355+ _map = utils.resource_map()
356+ console_services = ['nova-spiceproxy', 'nova-consoleauth']
357+ for service in console_services:
358+ self.assertIn(service, _map['/etc/nova/nova.conf']['services'])
359+
360 @patch('os.path.exists')
361 @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
362 def test_restart_map_api_before_frontends(self, subcontext, _exists):
363@@ -235,6 +277,23 @@
364 self.assertTrue('/etc/apache2/sites-available/'
365 'openstack_https_frontend' not in _map)
366
367+ def test_console_attributes_spice(self):
368+ _proto = utils.console_attributes('protocol', proto='spice')
369+ self.assertEquals(_proto, 'spice')
370+
371+ def test_console_attributes_vnc(self):
372+ self.test_config.set('console-access-protocol', 'vnc')
373+ _proto = utils.console_attributes('protocol')
374+ _servs = utils.console_attributes('services')
375+ _pkgs = utils.console_attributes('packages')
376+ _proxy_page = utils.console_attributes('proxy-page')
377+ vnc_pkgs = ['nova-novncproxy', 'nova-xvpvncproxy', 'nova-consoleauth']
378+ vnc_servs = ['nova-novncproxy', 'nova-xvpvncproxy', 'nova-consoleauth']
379+ self.assertEquals(_proto, 'vnc')
380+ self.assertEquals(_servs, vnc_servs)
381+ self.assertEquals(_pkgs, vnc_pkgs)
382+ self.assertEquals(_proxy_page, None)
383+
384 @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
385 def test_determine_packages_quantum(self, subcontext):
386 self._resource_map(network_manager='quantum')
387@@ -255,6 +314,15 @@
388 self.assertIn('nova-api-os-volume', pkgs)
389
390 @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
391+ def test_determine_packages_console(self, subcontext):
392+ self.test_config.set('console-access-protocol', 'spice')
393+ self.relation_ids.return_value = []
394+ pkgs = utils.determine_packages()
395+ console_pkgs = ['nova-spiceproxy', 'nova-consoleauth']
396+ for console_pkg in console_pkgs:
397+ self.assertIn(console_pkg, pkgs)
398+
399+ @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
400 def test_determine_packages_base(self, subcontext):
401 self.relation_ids.return_value = []
402 self.os_release.return_value = 'folsom'

Subscribers

People subscribed via source and target branches