Merge lp:~james-page/charms/trusty/nova-cloud-controller/service-guard-stable into lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/trunk

Proposed by James Page
Status: Merged
Merged at revision: 79
Proposed branch: lp:~james-page/charms/trusty/nova-cloud-controller/service-guard-stable
Merge into: lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/trunk
Diff against target: 390 lines (+228/-7)
6 files modified
config.yaml (+19/-0)
hooks/nova_cc_hooks.py (+25/-1)
hooks/nova_cc_utils.py (+60/-1)
unit_tests/test_nova_cc_hooks.py (+5/-1)
unit_tests/test_nova_cc_utils.py (+116/-1)
unit_tests/test_utils.py (+3/-3)
To merge this branch: bzr merge lp:~james-page/charms/trusty/nova-cloud-controller/service-guard-stable
Reviewer Review Type Date Requested Status
Adam Collard (community) Approve
OpenStack Charmers Pending
Review via email: mp+228699@code.launchpad.net

Description of the change

Backport support for service-guard option to ensure that services are not
started until all required interfaces have been related and fully configured.

To post a comment you must log in.
Revision history for this message
Adam Collard (adam-collard) wrote :

A few style comments inline below, nothing blocking :)

Revision history for this message
Adam Collard (adam-collard) wrote :

Tested successfully fixes the bugs in Landscape that were caused by having services run prematurely ( #1344413 and #1347986).

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'config.yaml'
--- config.yaml 2014-06-17 10:01:21 +0000
+++ config.yaml 2014-07-29 15:06:19 +0000
@@ -165,3 +165,22 @@
165 description: |165 description: |
166 This is uuid of the default NVP/NSX L3 Gateway Service.166 This is uuid of the default NVP/NSX L3 Gateway Service.
167 # end of NVP/NSX configuration167 # end of NVP/NSX configuration
168 service-guard:
169 type: boolean
170 default: false
171 description: |
172 Ensure required relations are made and complete before allowing services
173 to be started
174 .
175 By default, services may be up and accepting API request from install
176 onwards.
177 .
178 Enabling this flag ensures that services will not be started until the
179 minimum 'core relations' have been made between this charm and other
180 charms.
181 .
182 For this charm the following relations must be made:
183 .
184 * shared-db or (pgsql-nova-db, pgsql-neutron-db)
185 * amqp
186 * identity-service
168187
=== modified file 'hooks/nova_cc_hooks.py'
--- hooks/nova_cc_hooks.py 2014-04-11 16:41:42 +0000
+++ hooks/nova_cc_hooks.py 2014-07-29 15:06:19 +0000
@@ -63,7 +63,9 @@
63 NOVA_CONF,63 NOVA_CONF,
64 QUANTUM_CONF,64 QUANTUM_CONF,
65 NEUTRON_CONF,65 NEUTRON_CONF,
66 QUANTUM_API_PASTE66 QUANTUM_API_PASTE,
67 service_guard,
68 guard_map,
67)69)
6870
69from charmhelpers.contrib.hahelpers.cluster import (71from charmhelpers.contrib.hahelpers.cluster import (
@@ -96,6 +98,8 @@
9698
9799
98@hooks.hook('config-changed')100@hooks.hook('config-changed')
101@service_guard(guard_map(), CONFIGS,
102 active=config('service-guard'))
99@restart_on_change(restart_map(), stopstart=True)103@restart_on_change(restart_map(), stopstart=True)
100def config_changed():104def config_changed():
101 global CONFIGS105 global CONFIGS
@@ -114,6 +118,8 @@
114118
115@hooks.hook('amqp-relation-changed')119@hooks.hook('amqp-relation-changed')
116@hooks.hook('amqp-relation-departed')120@hooks.hook('amqp-relation-departed')
121@service_guard(guard_map(), CONFIGS,
122 active=config('service-guard'))
117@restart_on_change(restart_map())123@restart_on_change(restart_map())
118def amqp_changed():124def amqp_changed():
119 if 'amqp' not in CONFIGS.complete_contexts():125 if 'amqp' not in CONFIGS.complete_contexts():
@@ -171,6 +177,8 @@
171177
172178
173@hooks.hook('shared-db-relation-changed')179@hooks.hook('shared-db-relation-changed')
180@service_guard(guard_map(), CONFIGS,
181 active=config('service-guard'))
174@restart_on_change(restart_map())182@restart_on_change(restart_map())
175def db_changed():183def db_changed():
176 if 'shared-db' not in CONFIGS.complete_contexts():184 if 'shared-db' not in CONFIGS.complete_contexts():
@@ -186,6 +194,8 @@
186194
187195
188@hooks.hook('pgsql-nova-db-relation-changed')196@hooks.hook('pgsql-nova-db-relation-changed')
197@service_guard(guard_map(), CONFIGS,
198 active=config('service-guard'))
189@restart_on_change(restart_map())199@restart_on_change(restart_map())
190def postgresql_nova_db_changed():200def postgresql_nova_db_changed():
191 if 'pgsql-nova-db' not in CONFIGS.complete_contexts():201 if 'pgsql-nova-db' not in CONFIGS.complete_contexts():
@@ -201,6 +211,8 @@
201211
202212
203@hooks.hook('pgsql-neutron-db-relation-changed')213@hooks.hook('pgsql-neutron-db-relation-changed')
214@service_guard(guard_map(), CONFIGS,
215 active=config('service-guard'))
204@restart_on_change(restart_map())216@restart_on_change(restart_map())
205def postgresql_neutron_db_changed():217def postgresql_neutron_db_changed():
206 if network_manager() in ['neutron', 'quantum']:218 if network_manager() in ['neutron', 'quantum']:
@@ -210,6 +222,8 @@
210222
211223
212@hooks.hook('image-service-relation-changed')224@hooks.hook('image-service-relation-changed')
225@service_guard(guard_map(), CONFIGS,
226 active=config('service-guard'))
213@restart_on_change(restart_map())227@restart_on_change(restart_map())
214def image_service_changed():228def image_service_changed():
215 if 'image-service' not in CONFIGS.complete_contexts():229 if 'image-service' not in CONFIGS.complete_contexts():
@@ -228,6 +242,8 @@
228242
229243
230@hooks.hook('identity-service-relation-changed')244@hooks.hook('identity-service-relation-changed')
245@service_guard(guard_map(), CONFIGS,
246 active=config('service-guard'))
231@restart_on_change(restart_map())247@restart_on_change(restart_map())
232def identity_changed():248def identity_changed():
233 if 'identity-service' not in CONFIGS.complete_contexts():249 if 'identity-service' not in CONFIGS.complete_contexts():
@@ -249,6 +265,8 @@
249265
250@hooks.hook('nova-volume-service-relation-joined',266@hooks.hook('nova-volume-service-relation-joined',
251 'cinder-volume-service-relation-joined')267 'cinder-volume-service-relation-joined')
268@service_guard(guard_map(), CONFIGS,
269 active=config('service-guard'))
252@restart_on_change(restart_map())270@restart_on_change(restart_map())
253def volume_joined():271def volume_joined():
254 CONFIGS.write(NOVA_CONF)272 CONFIGS.write(NOVA_CONF)
@@ -391,6 +409,8 @@
391409
392@hooks.hook('cluster-relation-changed',410@hooks.hook('cluster-relation-changed',
393 'cluster-relation-departed')411 'cluster-relation-departed')
412@service_guard(guard_map(), CONFIGS,
413 active=config('service-guard'))
394@restart_on_change(restart_map(), stopstart=True)414@restart_on_change(restart_map(), stopstart=True)
395def cluster_changed():415def cluster_changed():
396 CONFIGS.write_all()416 CONFIGS.write_all()
@@ -447,6 +467,8 @@
447 'pgsql-nova-db-relation-broken',467 'pgsql-nova-db-relation-broken',
448 'pgsql-neutron-db-relation-broken',468 'pgsql-neutron-db-relation-broken',
449 'quantum-network-service-relation-broken')469 'quantum-network-service-relation-broken')
470@service_guard(guard_map(), CONFIGS,
471 active=config('service-guard'))
450def relation_broken():472def relation_broken():
451 CONFIGS.write_all()473 CONFIGS.write_all()
452474
@@ -487,6 +509,8 @@
487509
488510
489@hooks.hook('nova-vmware-relation-changed')511@hooks.hook('nova-vmware-relation-changed')
512@service_guard(guard_map(), CONFIGS,
513 active=config('service-guard'))
490@restart_on_change(restart_map())514@restart_on_change(restart_map())
491def nova_vmware_relation_changed():515def nova_vmware_relation_changed():
492 CONFIGS.write('/etc/nova/nova.conf')516 CONFIGS.write('/etc/nova/nova.conf')
493517
=== modified file 'hooks/nova_cc_utils.py'
--- hooks/nova_cc_utils.py 2014-05-21 10:03:01 +0000
+++ hooks/nova_cc_utils.py 2014-07-29 15:06:19 +0000
@@ -33,12 +33,15 @@
33 relation_get,33 relation_get,
34 relation_ids,34 relation_ids,
35 remote_unit,35 remote_unit,
36 is_relation_made,
36 INFO,37 INFO,
37 ERROR,38 ERROR,
38)39)
3940
40from charmhelpers.core.host import (41from charmhelpers.core.host import (
41 service_start42 service_start,
43 service_stop,
44 service_running
42)45)
4346
4447
@@ -695,3 +698,59 @@
695 # quantum-plugin config setting can be safely overriden698 # quantum-plugin config setting can be safely overriden
696 # as we only supported OVS in G/neutron699 # as we only supported OVS in G/neutron
697 return config('neutron-plugin') or config('quantum-plugin')700 return config('neutron-plugin') or config('quantum-plugin')
701
702
703def guard_map():
704 '''Map of services and required interfaces that must be present before
705 the service should be allowed to start'''
706 gmap = {}
707 nova_services = deepcopy(BASE_SERVICES)
708 if os_release('nova-common') not in ['essex', 'folsom']:
709 nova_services.append('nova-conductor')
710
711 nova_interfaces = ['identity-service', 'amqp']
712 if relation_ids('pgsql-nova-db'):
713 nova_interfaces.append('pgsql-nova-db')
714 else:
715 nova_interfaces.append('shared-db')
716
717 for svc in nova_services:
718 gmap[svc] = nova_interfaces
719
720 net_manager = network_manager()
721 if net_manager in ['neutron', 'quantum'] and \
722 not is_relation_made('neutron-api'):
723 neutron_interfaces = ['identity-service', 'amqp']
724 if relation_ids('pgsql-neutron-db'):
725 neutron_interfaces.append('pgsql-neutron-db')
726 else:
727 neutron_interfaces.append('shared-db')
728 if network_manager() == 'quantum':
729 gmap['quantum-server'] = neutron_interfaces
730 else:
731 gmap['neutron-server'] = neutron_interfaces
732
733 return gmap
734
735
736def service_guard(guard_map, contexts, active=False):
737 '''Inhibit services in guard_map from running unless
738 required interfaces are found complete in contexts.'''
739 def wrap(f):
740 def wrapped_f(*args):
741 if active is True:
742 incomplete_services = []
743 for svc in guard_map:
744 for interface in guard_map[svc]:
745 if interface not in contexts.complete_contexts():
746 incomplete_services.append(svc)
747 f(*args)
748 for svc in incomplete_services:
749 if service_running(svc):
750 log('Service {} has unfulfilled '
751 'interface requirements, stopping.'.format(svc))
752 service_stop(svc)
753 else:
754 f(*args)
755 return wrapped_f
756 return wrap
698757
=== modified file 'unit_tests/test_nova_cc_hooks.py'
--- unit_tests/test_nova_cc_hooks.py 2014-05-21 10:03:01 +0000
+++ unit_tests/test_nova_cc_hooks.py 2014-07-29 15:06:19 +0000
@@ -11,7 +11,11 @@
11utils.register_configs = MagicMock()11utils.register_configs = MagicMock()
12utils.restart_map = MagicMock()12utils.restart_map = MagicMock()
1313
14import nova_cc_hooks as hooks14with patch('nova_cc_utils.guard_map') as gmap:
15 with patch('charmhelpers.core.hookenv.config') as config:
16 config.return_value = False
17 gmap.return_value = {}
18 import nova_cc_hooks as hooks
1519
16utils.register_configs = _reg20utils.register_configs = _reg
17utils.restart_map = _map21utils.restart_map = _map
1822
=== modified file 'unit_tests/test_nova_cc_utils.py'
--- unit_tests/test_nova_cc_utils.py 2014-05-02 10:06:23 +0000
+++ unit_tests/test_nova_cc_utils.py 2014-07-29 15:06:19 +0000
@@ -31,10 +31,13 @@
31 'os_release',31 'os_release',
32 'register_configs',32 'register_configs',
33 'relation_ids',33 'relation_ids',
34 'is_relation_made',
34 'remote_unit',35 'remote_unit',
35 '_save_script_rc',36 '_save_script_rc',
36 'service_start',37 'service_start',
37 'services'38 'services',
39 'service_running',
40 'service_stop'
38]41]
3942
40SCRIPTRC_ENV_VARS = {43SCRIPTRC_ENV_VARS = {
@@ -555,3 +558,115 @@
555 utils.do_openstack_upgrade()558 utils.do_openstack_upgrade()
556 expected = [call('cloud:precise-icehouse')]559 expected = [call('cloud:precise-icehouse')]
557 self.assertEquals(_do_openstack_upgrade.call_args_list, expected)560 self.assertEquals(_do_openstack_upgrade.call_args_list, expected)
561
562 def test_guard_map_nova(self):
563 self.relation_ids.return_value = []
564 self.os_release.return_value = 'havana'
565 self.assertEqual(
566 {'nova-api-ec2': ['identity-service', 'amqp', 'shared-db'],
567 'nova-api-os-compute': ['identity-service', 'amqp', 'shared-db'],
568 'nova-cert': ['identity-service', 'amqp', 'shared-db'],
569 'nova-conductor': ['identity-service', 'amqp', 'shared-db'],
570 'nova-objectstore': ['identity-service', 'amqp', 'shared-db'],
571 'nova-scheduler': ['identity-service', 'amqp', 'shared-db']},
572 utils.guard_map()
573 )
574 self.os_release.return_value = 'essex'
575 self.assertEqual(
576 {'nova-api-ec2': ['identity-service', 'amqp', 'shared-db'],
577 'nova-api-os-compute': ['identity-service', 'amqp', 'shared-db'],
578 'nova-cert': ['identity-service', 'amqp', 'shared-db'],
579 'nova-objectstore': ['identity-service', 'amqp', 'shared-db'],
580 'nova-scheduler': ['identity-service', 'amqp', 'shared-db']},
581 utils.guard_map()
582 )
583
584 def test_guard_map_neutron(self):
585 self.relation_ids.return_value = []
586 self.network_manager.return_value = 'neutron'
587 self.os_release.return_value = 'icehouse'
588 self.is_relation_made.return_value = False
589 self.assertEqual(
590 {'neutron-server': ['identity-service', 'amqp', 'shared-db'],
591 'nova-api-ec2': ['identity-service', 'amqp', 'shared-db'],
592 'nova-api-os-compute': ['identity-service', 'amqp', 'shared-db'],
593 'nova-cert': ['identity-service', 'amqp', 'shared-db'],
594 'nova-conductor': ['identity-service', 'amqp', 'shared-db'],
595 'nova-objectstore': ['identity-service', 'amqp', 'shared-db'],
596 'nova-scheduler': ['identity-service', 'amqp', 'shared-db'], },
597 utils.guard_map()
598 )
599 self.network_manager.return_value = 'quantum'
600 self.os_release.return_value = 'grizzly'
601 self.assertEqual(
602 {'quantum-server': ['identity-service', 'amqp', 'shared-db'],
603 'nova-api-ec2': ['identity-service', 'amqp', 'shared-db'],
604 'nova-api-os-compute': ['identity-service', 'amqp', 'shared-db'],
605 'nova-cert': ['identity-service', 'amqp', 'shared-db'],
606 'nova-conductor': ['identity-service', 'amqp', 'shared-db'],
607 'nova-objectstore': ['identity-service', 'amqp', 'shared-db'],
608 'nova-scheduler': ['identity-service', 'amqp', 'shared-db'], },
609 utils.guard_map()
610 )
611
612 def test_guard_map_pgsql(self):
613 self.relation_ids.return_value = ['pgsql:1']
614 self.network_manager.return_value = 'neutron'
615 self.is_relation_made.return_value = False
616 self.os_release.return_value = 'icehouse'
617 self.assertEqual(
618 {'neutron-server': ['identity-service', 'amqp',
619 'pgsql-neutron-db'],
620 'nova-api-ec2': ['identity-service', 'amqp', 'pgsql-nova-db'],
621 'nova-api-os-compute': ['identity-service', 'amqp',
622 'pgsql-nova-db'],
623 'nova-cert': ['identity-service', 'amqp', 'pgsql-nova-db'],
624 'nova-conductor': ['identity-service', 'amqp', 'pgsql-nova-db'],
625 'nova-objectstore': ['identity-service', 'amqp',
626 'pgsql-nova-db'],
627 'nova-scheduler': ['identity-service', 'amqp',
628 'pgsql-nova-db'], },
629 utils.guard_map()
630 )
631
632 def test_service_guard_inactive(self):
633 '''Ensure that if disabled, service guards nothing'''
634 contexts = MagicMock()
635
636 @utils.service_guard({'test': ['interfacea', 'interfaceb']},
637 contexts, False)
638 def dummy_func():
639 pass
640 dummy_func()
641 self.assertFalse(self.service_running.called)
642 self.assertFalse(contexts.complete_contexts.called)
643
644 def test_service_guard_active_guard(self):
645 '''Ensure services with incomplete interfaces are stopped'''
646 contexts = MagicMock()
647 contexts.complete_contexts.return_value = ['interfacea']
648 self.service_running.return_value = True
649
650 @utils.service_guard({'test': ['interfacea', 'interfaceb']},
651 contexts, True)
652 def dummy_func():
653 pass
654 dummy_func()
655 self.service_running.assert_called_with('test')
656 self.service_stop.assert_called_with('test')
657 self.assertTrue(contexts.complete_contexts.called)
658
659 def test_service_guard_active_release(self):
660 '''Ensure services with complete interfaces are not stopped'''
661 contexts = MagicMock()
662 contexts.complete_contexts.return_value = ['interfacea',
663 'interfaceb']
664
665 @utils.service_guard({'test': ['interfacea', 'interfaceb']},
666 contexts, True)
667 def dummy_func():
668 pass
669 dummy_func()
670 self.assertFalse(self.service_running.called)
671 self.assertFalse(self.service_stop.called)
672 self.assertTrue(contexts.complete_contexts.called)
558673
=== modified file 'unit_tests/test_utils.py'
--- unit_tests/test_utils.py 2013-11-08 05:41:39 +0000
+++ unit_tests/test_utils.py 2014-07-29 15:06:19 +0000
@@ -82,9 +82,9 @@
82 return self.config82 return self.config
8383
84 def set(self, attr, value):84 def set(self, attr, value):
85 if attr not in self.config:85 if attr not in self.config:
86 raise KeyError86 raise KeyError
87 self.config[attr] = value87 self.config[attr] = value
8888
8989
90class TestRelation(object):90class TestRelation(object):

Subscribers

People subscribed via source and target branches