diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index d254de18..8429d832 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -147,7 +147,8 @@ class SharedDBContext(OSContextGenerator): 'database_host': rdata.get('db_host'), 'database': self.database, 'database_user': self.user, - 'database_password': rdata.get(password_setting) + 'database_password': rdata.get(password_setting), + 'database_type': 'mysql' } if context_complete(ctxt): db_ssl(rdata, ctxt, self.ssl_dir) @@ -155,6 +156,35 @@ class SharedDBContext(OSContextGenerator): return {} +class PostgresqlDBContext(OSContextGenerator): + interfaces = ['pgsql-db'] + + def __init__(self, database=None): + self.database = database + + def __call__(self): + self.database = self.database or config('database') + if self.database is None: + log('Could not generate postgresql_db context. ' + 'Missing required charm config options. ' + '(database name)') + raise OSContextError + ctxt = {} + + for rid in relation_ids(self.interfaces[0]): + for unit in related_units(rid): + ctxt = { + 'database_host': relation_get('host', rid=rid, unit=unit), + 'database': self.database, + 'database_user': relation_get('user', rid=rid, unit=unit), + 'database_password': relation_get('password', rid=rid, unit=unit), + 'database_type': 'postgresql', + } + if context_complete(ctxt): + return ctxt + return {} + + def db_ssl(rdata, ctxt, ssl_dir): if 'ssl_ca' in rdata and ssl_dir: ca_path = os.path.join(ssl_dir, 'db-client.ca') diff --git a/hooks/pgsql-db-relation-changed b/hooks/pgsql-db-relation-changed new file mode 120000 index 00000000..9a2da58e --- /dev/null +++ b/hooks/pgsql-db-relation-changed @@ -0,0 +1 @@ +quantum_hooks.py \ No newline at end of file diff --git a/hooks/pgsql-db-relation-joined b/hooks/pgsql-db-relation-joined new file mode 120000 index 00000000..9a2da58e --- /dev/null +++ b/hooks/pgsql-db-relation-joined @@ -0,0 +1 @@ +quantum_hooks.py \ No newline at end of file diff --git a/hooks/quantum_hooks.py b/hooks/quantum_hooks.py index f9901c22..db8831d5 100755 --- a/hooks/quantum_hooks.py +++ b/hooks/quantum_hooks.py @@ -5,6 +5,7 @@ from base64 import b64decode from charmhelpers.core.hookenv import ( log, ERROR, WARNING, config, + is_relation_made, relation_get, relation_set, relation_ids, @@ -77,6 +78,8 @@ def config_changed(): # Re-run joined hooks as config might have changed for r_id in relation_ids('shared-db'): db_joined(relation_id=r_id) + for r_id in relation_ids('pgsql-db'): + pgsql_db_joined(relation_id=r_id) for r_id in relation_ids('amqp'): amqp_joined(relation_id=r_id) if valid_plugin(): @@ -95,12 +98,29 @@ def upgrade_charm(): @hooks.hook('shared-db-relation-joined') def db_joined(relation_id=None): + if is_relation_made('pgsql-db'): + # raise error + e = ('Attempting to associate a mysql database when there is already ' + 'associated a postgresql one') + log(e, level=ERROR) + raise Exception(e) relation_set(username=config('database-user'), database=config('database'), hostname=unit_get('private-address'), relation_id=relation_id) +@hooks.hook('pgsql-db-relation-joined') +def pgsql_db_joined(): + if is_relation_made('shared-db'): + # raise error + e = ('Attempting to associate a postgresql database when there is already ' + 'associated a mysql one') + log(e, level=ERROR) + raise Exception(e) + relation_set(database=config('database')) + + @hooks.hook('amqp-relation-joined') def amqp_joined(relation_id=None): relation_set(relation_id=relation_id, @@ -118,6 +138,7 @@ def amqp_departed(): @hooks.hook('shared-db-relation-changed', + 'pgsql-db-relation-changed', 'amqp-relation-changed', 'cluster-relation-changed', 'cluster-relation-joined') diff --git a/hooks/quantum_utils.py b/hooks/quantum_utils.py index 67235d9c..3c4763fa 100644 --- a/hooks/quantum_utils.py +++ b/hooks/quantum_utils.py @@ -77,12 +77,14 @@ QUANTUM_GATEWAY_PKGS = { "quantum-l3-agent", "quantum-dhcp-agent", 'python-mysqldb', + 'python-psycopg2', "nova-api-metadata" ], NVP: [ "openvswitch-switch", "quantum-dhcp-agent", 'python-mysqldb', + 'python-psycopg2', "nova-api-metadata" ] } @@ -94,6 +96,7 @@ NEUTRON_GATEWAY_PKGS = { "neutron-l3-agent", "neutron-dhcp-agent", 'python-mysqldb', + 'python-psycopg2', 'python-oslo.config', # Force upgrade "nova-api-metadata", "neutron-plugin-metering-agent", @@ -103,6 +106,7 @@ NEUTRON_GATEWAY_PKGS = { NVP: [ "neutron-dhcp-agent", 'python-mysqldb', + 'python-psycopg2', 'python-oslo.config', # Force upgrade "nova-api-metadata" ] @@ -163,6 +167,7 @@ NOVA_CONFIG_FILES = { NOVA_CONF: { 'hook_contexts': [context.AMQPContext(ssl_dir=NOVA_CONF_DIR), context.SharedDBContext(ssl_dir=NOVA_CONF_DIR), + context.PostgresqlDBContext(), NetworkServiceContext(), QuantumGatewayContext()], 'services': ['nova-api-metadata'] diff --git a/metadata.yaml b/metadata.yaml index 296e34fe..637f8953 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -21,6 +21,8 @@ provides: requires: shared-db: interface: mysql-shared + pgsql-db: + interface: pgsql amqp: interface: rabbitmq peers: diff --git a/revision b/revision index 4b9026d8..900731ff 100644 --- a/revision +++ b/revision @@ -1 +1 @@ -63 +64 diff --git a/templates/folsom/nova.conf b/templates/folsom/nova.conf index baaab6f7..f78687c1 100644 --- a/templates/folsom/nova.conf +++ b/templates/folsom/nova.conf @@ -7,7 +7,7 @@ verbose=True api_paste_config=/etc/nova/api-paste.ini enabled_apis=metadata multi_host=True -sql_connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} +{% include "parts/database" %} quantum_metadata_proxy_shared_secret={{ shared_secret }} service_quantum_metadata_proxy=True # Access to message bus diff --git a/templates/grizzly/nova.conf b/templates/grizzly/nova.conf index 2e27fe64..dc43ca87 100644 --- a/templates/grizzly/nova.conf +++ b/templates/grizzly/nova.conf @@ -7,7 +7,7 @@ verbose=True api_paste_config=/etc/nova/api-paste.ini enabled_apis=metadata multi_host=True -sql_connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} +{% include "parts/database" %} quantum_metadata_proxy_shared_secret={{ shared_secret }} service_quantum_metadata_proxy=True # Access to message bus diff --git a/templates/havana/nova.conf b/templates/havana/nova.conf index 993df91d..282cc07b 100644 --- a/templates/havana/nova.conf +++ b/templates/havana/nova.conf @@ -7,7 +7,7 @@ verbose= {{ verbose }} api_paste_config=/etc/nova/api-paste.ini enabled_apis=metadata multi_host=True -sql_connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} +{% include "parts/database" %} neutron_metadata_proxy_shared_secret={{ shared_secret }} service_neutron_metadata_proxy=True # Access to message bus diff --git a/templates/parts/database b/templates/parts/database new file mode 100644 index 00000000..1512f27f --- /dev/null +++ b/templates/parts/database @@ -0,0 +1 @@ +sql_connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} diff --git a/unit_tests/test_quantum_hooks.py b/unit_tests/test_quantum_hooks.py index 09338d3a..a60e49c7 100644 --- a/unit_tests/test_quantum_hooks.py +++ b/unit_tests/test_quantum_hooks.py @@ -39,6 +39,7 @@ TO_PATCH = [ 'lsb_release', 'stop_services', 'b64decode', + 'is_relation_made' ] @@ -115,6 +116,7 @@ class TestQuantumHooks(CharmTestCase): self.assertTrue(_config_changed.called) def test_db_joined(self): + self.is_relation_made.return_value = False self.unit_get.return_value = 'myhostname' self._call_hook('shared-db-relation-joined') self.relation_set.assert_called_with( @@ -124,6 +126,32 @@ class TestQuantumHooks(CharmTestCase): relation_id=None ) + def test_db_joined_with_postgresql(self): + self.is_relation_made.return_value = True + + with self.assertRaises(Exception) as context: + hooks.db_joined() + self.assertEqual(context.exception.message, + 'Attempting to associate a mysql database when there ' + 'is already associated a postgresql one') + + def test_postgresql_db_joined(self): + self.unit_get.return_value = 'myhostname' + self.is_relation_made.return_value = False + self._call_hook('pgsql-db-relation-joined') + self.relation_set.assert_called_with( + database='nova' + ) + + def test_postgresql_joined_with_db(self): + self.is_relation_made.return_value = True + + with self.assertRaises(Exception) as context: + hooks.pgsql_db_joined() + self.assertEqual(context.exception.message, + 'Attempting to associate a postgresql database when there ' + 'is already associated a mysql one') + def test_amqp_joined(self): self._call_hook('amqp-relation-joined') self.relation_set.assert_called_with( @@ -140,6 +168,10 @@ class TestQuantumHooks(CharmTestCase): self._call_hook('shared-db-relation-changed') self.CONFIGS.write_all.assert_called() + def test_pgsql_db_changed(self): + self._call_hook('pgsql-db-relation-changed') + self.CONFIGS.write_all.assert_called() + def test_nm_changed(self): self.relation_get.return_value = "cert" self._call_hook('quantum-network-service-relation-changed') diff --git a/unit_tests/test_quantum_utils.py b/unit_tests/test_quantum_utils.py index 2824cf40..c590876b 100644 --- a/unit_tests/test_quantum_utils.py +++ b/unit_tests/test_quantum_utils.py @@ -1,4 +1,5 @@ from mock import MagicMock, call, patch +import collections import charmhelpers.contrib.openstack.templating as templating @@ -46,6 +47,16 @@ TO_PATCH = [ class TestQuantumUtils(CharmTestCase): + def assertDictEqual(self, d1, d2, msg=None): # assertEqual uses for dicts + for k,v1 in d1.iteritems(): + self.assertIn(k, d2, msg) + v2 = d2[k] + if(isinstance(v1, collections.Iterable) and + not isinstance(v1, basestring)): + self.assertItemsEqual(v1, v2, msg) + else: + self.assertEqual(v1, v2, msg) + def setUp(self): super(TestQuantumUtils, self).setUp(quantum_utils, TO_PATCH) self.networking_name.return_value = 'neutron' @@ -144,7 +155,6 @@ class TestQuantumUtils(CharmTestCase): quantum_utils.NEUTRON_L3_AGENT_CONF, quantum_utils.NEUTRON_OVS_PLUGIN_CONF, quantum_utils.EXT_PORT_CONF] - print configs.register.calls() for conf in confs: configs.register.assert_any_call( conf, @@ -155,26 +165,33 @@ class TestQuantumUtils(CharmTestCase): def test_restart_map_ovs(self): self.config.return_value = 'ovs' ex_map = { - quantum_utils.NEUTRON_L3_AGENT_CONF: ['neutron-l3-agent'], - quantum_utils.NEUTRON_METERING_AGENT_CONF: - ['neutron-metering-agent'], - quantum_utils.NEUTRON_LBAAS_AGENT_CONF: - ['neutron-lbaas-agent'], - quantum_utils.NEUTRON_OVS_PLUGIN_CONF: - ['neutron-plugin-openvswitch-agent'], - quantum_utils.NOVA_CONF: ['nova-api-metadata'], - quantum_utils.NEUTRON_METADATA_AGENT_CONF: - ['neutron-metadata-agent'], - quantum_utils.NEUTRON_DHCP_AGENT_CONF: ['neutron-dhcp-agent'], - quantum_utils.NEUTRON_DNSMASQ_CONF: ['neutron-dhcp-agent'], quantum_utils.NEUTRON_CONF: ['neutron-l3-agent', 'neutron-dhcp-agent', 'neutron-metadata-agent', 'neutron-plugin-openvswitch-agent', + 'neutron-plugin-metering-agent', 'neutron-metering-agent', - 'neutron-lbaas-agent'] + 'neutron-lbaas-agent', + 'neutron-plugin-vpn-agent', + 'neutron-vpn-agent'], + quantum_utils.NEUTRON_DNSMASQ_CONF: ['neutron-dhcp-agent'], + quantum_utils.NEUTRON_LBAAS_AGENT_CONF: + ['neutron-lbaas-agent'], + quantum_utils.NEUTRON_OVS_PLUGIN_CONF: + ['neutron-plugin-openvswitch-agent'], + quantum_utils.NEUTRON_METADATA_AGENT_CONF: + ['neutron-metadata-agent'], + quantum_utils.NEUTRON_VPNAAS_AGENT_CONF: ['neutron-plugin-vpn-agent', + 'neutron-vpn-agent'], + quantum_utils.NEUTRON_L3_AGENT_CONF: ['neutron-l3-agent'], + quantum_utils.NEUTRON_DHCP_AGENT_CONF: ['neutron-dhcp-agent'], + quantum_utils.NEUTRON_FWAAS_CONF: ['neutron-l3-agent'], + quantum_utils.NEUTRON_METERING_AGENT_CONF: + ['neutron-metering-agent', 'neutron-plugin-metering-agent'], + quantum_utils.NOVA_CONF: ['nova-api-metadata'], } - self.assertEquals(quantum_utils.restart_map(), ex_map) + + self.assertDictEqual(quantum_utils.restart_map(), ex_map) def test_register_configs_nvp(self): self.config.return_value = 'nvp'