[yolanda,r=james-page] Add postgresql support

This commit is contained in:
James Page 2014-03-31 11:36:19 +01:00
commit 2acf85568d
13 changed files with 130 additions and 20 deletions

View File

@ -147,7 +147,8 @@ class SharedDBContext(OSContextGenerator):
'database_host': rdata.get('db_host'), 'database_host': rdata.get('db_host'),
'database': self.database, 'database': self.database,
'database_user': self.user, 'database_user': self.user,
'database_password': rdata.get(password_setting) 'database_password': rdata.get(password_setting),
'database_type': 'mysql'
} }
if context_complete(ctxt): if context_complete(ctxt):
db_ssl(rdata, ctxt, self.ssl_dir) db_ssl(rdata, ctxt, self.ssl_dir)
@ -155,6 +156,35 @@ class SharedDBContext(OSContextGenerator):
return {} 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): def db_ssl(rdata, ctxt, ssl_dir):
if 'ssl_ca' in rdata and ssl_dir: if 'ssl_ca' in rdata and ssl_dir:
ca_path = os.path.join(ssl_dir, 'db-client.ca') ca_path = os.path.join(ssl_dir, 'db-client.ca')

View File

@ -0,0 +1 @@
quantum_hooks.py

View File

@ -0,0 +1 @@
quantum_hooks.py

View File

@ -5,6 +5,7 @@ from base64 import b64decode
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
log, ERROR, WARNING, log, ERROR, WARNING,
config, config,
is_relation_made,
relation_get, relation_get,
relation_set, relation_set,
relation_ids, relation_ids,
@ -77,6 +78,8 @@ def config_changed():
# Re-run joined hooks as config might have changed # Re-run joined hooks as config might have changed
for r_id in relation_ids('shared-db'): for r_id in relation_ids('shared-db'):
db_joined(relation_id=r_id) 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'): for r_id in relation_ids('amqp'):
amqp_joined(relation_id=r_id) amqp_joined(relation_id=r_id)
if valid_plugin(): if valid_plugin():
@ -95,12 +98,29 @@ def upgrade_charm():
@hooks.hook('shared-db-relation-joined') @hooks.hook('shared-db-relation-joined')
def db_joined(relation_id=None): 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'), relation_set(username=config('database-user'),
database=config('database'), database=config('database'),
hostname=unit_get('private-address'), hostname=unit_get('private-address'),
relation_id=relation_id) 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') @hooks.hook('amqp-relation-joined')
def amqp_joined(relation_id=None): def amqp_joined(relation_id=None):
relation_set(relation_id=relation_id, relation_set(relation_id=relation_id,
@ -118,6 +138,7 @@ def amqp_departed():
@hooks.hook('shared-db-relation-changed', @hooks.hook('shared-db-relation-changed',
'pgsql-db-relation-changed',
'amqp-relation-changed', 'amqp-relation-changed',
'cluster-relation-changed', 'cluster-relation-changed',
'cluster-relation-joined') 'cluster-relation-joined')

View File

@ -77,12 +77,14 @@ QUANTUM_GATEWAY_PKGS = {
"quantum-l3-agent", "quantum-l3-agent",
"quantum-dhcp-agent", "quantum-dhcp-agent",
'python-mysqldb', 'python-mysqldb',
'python-psycopg2',
"nova-api-metadata" "nova-api-metadata"
], ],
NVP: [ NVP: [
"openvswitch-switch", "openvswitch-switch",
"quantum-dhcp-agent", "quantum-dhcp-agent",
'python-mysqldb', 'python-mysqldb',
'python-psycopg2',
"nova-api-metadata" "nova-api-metadata"
] ]
} }
@ -94,6 +96,7 @@ NEUTRON_GATEWAY_PKGS = {
"neutron-l3-agent", "neutron-l3-agent",
"neutron-dhcp-agent", "neutron-dhcp-agent",
'python-mysqldb', 'python-mysqldb',
'python-psycopg2',
'python-oslo.config', # Force upgrade 'python-oslo.config', # Force upgrade
"nova-api-metadata", "nova-api-metadata",
"neutron-plugin-metering-agent", "neutron-plugin-metering-agent",
@ -103,6 +106,7 @@ NEUTRON_GATEWAY_PKGS = {
NVP: [ NVP: [
"neutron-dhcp-agent", "neutron-dhcp-agent",
'python-mysqldb', 'python-mysqldb',
'python-psycopg2',
'python-oslo.config', # Force upgrade 'python-oslo.config', # Force upgrade
"nova-api-metadata" "nova-api-metadata"
] ]
@ -163,6 +167,7 @@ NOVA_CONFIG_FILES = {
NOVA_CONF: { NOVA_CONF: {
'hook_contexts': [context.AMQPContext(ssl_dir=NOVA_CONF_DIR), 'hook_contexts': [context.AMQPContext(ssl_dir=NOVA_CONF_DIR),
context.SharedDBContext(ssl_dir=NOVA_CONF_DIR), context.SharedDBContext(ssl_dir=NOVA_CONF_DIR),
context.PostgresqlDBContext(),
NetworkServiceContext(), NetworkServiceContext(),
QuantumGatewayContext()], QuantumGatewayContext()],
'services': ['nova-api-metadata'] 'services': ['nova-api-metadata']

View File

@ -21,6 +21,8 @@ provides:
requires: requires:
shared-db: shared-db:
interface: mysql-shared interface: mysql-shared
pgsql-db:
interface: pgsql
amqp: amqp:
interface: rabbitmq interface: rabbitmq
peers: peers:

View File

@ -1 +1 @@
63 64

View File

@ -7,7 +7,7 @@ verbose=True
api_paste_config=/etc/nova/api-paste.ini api_paste_config=/etc/nova/api-paste.ini
enabled_apis=metadata enabled_apis=metadata
multi_host=True 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 }} quantum_metadata_proxy_shared_secret={{ shared_secret }}
service_quantum_metadata_proxy=True service_quantum_metadata_proxy=True
# Access to message bus # Access to message bus

View File

@ -7,7 +7,7 @@ verbose=True
api_paste_config=/etc/nova/api-paste.ini api_paste_config=/etc/nova/api-paste.ini
enabled_apis=metadata enabled_apis=metadata
multi_host=True 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 }} quantum_metadata_proxy_shared_secret={{ shared_secret }}
service_quantum_metadata_proxy=True service_quantum_metadata_proxy=True
# Access to message bus # Access to message bus

View File

@ -7,7 +7,7 @@ verbose= {{ verbose }}
api_paste_config=/etc/nova/api-paste.ini api_paste_config=/etc/nova/api-paste.ini
enabled_apis=metadata enabled_apis=metadata
multi_host=True 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 }} neutron_metadata_proxy_shared_secret={{ shared_secret }}
service_neutron_metadata_proxy=True service_neutron_metadata_proxy=True
# Access to message bus # Access to message bus

1
templates/parts/database Normal file
View File

@ -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 %}

View File

@ -39,6 +39,7 @@ TO_PATCH = [
'lsb_release', 'lsb_release',
'stop_services', 'stop_services',
'b64decode', 'b64decode',
'is_relation_made'
] ]
@ -115,6 +116,7 @@ class TestQuantumHooks(CharmTestCase):
self.assertTrue(_config_changed.called) self.assertTrue(_config_changed.called)
def test_db_joined(self): def test_db_joined(self):
self.is_relation_made.return_value = False
self.unit_get.return_value = 'myhostname' self.unit_get.return_value = 'myhostname'
self._call_hook('shared-db-relation-joined') self._call_hook('shared-db-relation-joined')
self.relation_set.assert_called_with( self.relation_set.assert_called_with(
@ -124,6 +126,32 @@ class TestQuantumHooks(CharmTestCase):
relation_id=None 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): def test_amqp_joined(self):
self._call_hook('amqp-relation-joined') self._call_hook('amqp-relation-joined')
self.relation_set.assert_called_with( self.relation_set.assert_called_with(
@ -140,6 +168,10 @@ class TestQuantumHooks(CharmTestCase):
self._call_hook('shared-db-relation-changed') self._call_hook('shared-db-relation-changed')
self.CONFIGS.write_all.assert_called() 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): def test_nm_changed(self):
self.relation_get.return_value = "cert" self.relation_get.return_value = "cert"
self._call_hook('quantum-network-service-relation-changed') self._call_hook('quantum-network-service-relation-changed')

View File

@ -1,4 +1,5 @@
from mock import MagicMock, call, patch from mock import MagicMock, call, patch
import collections
import charmhelpers.contrib.openstack.templating as templating import charmhelpers.contrib.openstack.templating as templating
@ -46,6 +47,16 @@ TO_PATCH = [
class TestQuantumUtils(CharmTestCase): 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): def setUp(self):
super(TestQuantumUtils, self).setUp(quantum_utils, TO_PATCH) super(TestQuantumUtils, self).setUp(quantum_utils, TO_PATCH)
self.networking_name.return_value = 'neutron' self.networking_name.return_value = 'neutron'
@ -144,7 +155,6 @@ class TestQuantumUtils(CharmTestCase):
quantum_utils.NEUTRON_L3_AGENT_CONF, quantum_utils.NEUTRON_L3_AGENT_CONF,
quantum_utils.NEUTRON_OVS_PLUGIN_CONF, quantum_utils.NEUTRON_OVS_PLUGIN_CONF,
quantum_utils.EXT_PORT_CONF] quantum_utils.EXT_PORT_CONF]
print configs.register.calls()
for conf in confs: for conf in confs:
configs.register.assert_any_call( configs.register.assert_any_call(
conf, conf,
@ -155,26 +165,33 @@ class TestQuantumUtils(CharmTestCase):
def test_restart_map_ovs(self): def test_restart_map_ovs(self):
self.config.return_value = 'ovs' self.config.return_value = 'ovs'
ex_map = { 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', quantum_utils.NEUTRON_CONF: ['neutron-l3-agent',
'neutron-dhcp-agent', 'neutron-dhcp-agent',
'neutron-metadata-agent', 'neutron-metadata-agent',
'neutron-plugin-openvswitch-agent', 'neutron-plugin-openvswitch-agent',
'neutron-plugin-metering-agent',
'neutron-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): def test_register_configs_nvp(self):
self.config.return_value = 'nvp' self.config.return_value = 'nvp'