Merge lp:~gnuoy/charms/trusty/nova-cloud-controller/add-console-access into lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/next
- Trusty Tahr (14.04)
- add-console-access
- Merge into next
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Liam Young (community) | Needs Resubmitting | ||
James Page | Needs Fixing | ||
Review via email: mp+226319@code.launchpad.net |
Commit message
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-
- 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
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.
- 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
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' |
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!