[james-page,hazmat,ivoks,coreycb,yolanda.robla,r=james-page,t=*]

Support for Icehouse on 12.04 and 14.04
Support for Active/Active and SSL RabbitMQ
Support for SSL MySQL
Support for SSL endpoints
Support for PostgreSQL

Fixes for upgrades on 12.04 grizzly->havana->icehouse
This commit is contained in:
James Page 2014-04-16 09:25:14 +01:00
commit f1ecf4d81d
38 changed files with 1381 additions and 116 deletions

View File

@ -2,8 +2,7 @@
PYTHON := /usr/bin/env python PYTHON := /usr/bin/env python
lint: lint:
@flake8 --exclude hooks/charmhelpers hooks @flake8 --exclude hooks/charmhelpers hooks unit_tests
@flake8 --exclude hooks/charmhelpers unit_tests
@charm proof @charm proof
test: test:

15
README.txt Normal file
View File

@ -0,0 +1,15 @@
=====================
nova-cloud-controller
=====================
Cloud controller node for Openstack nova. Contains nova-schedule, nova-api, nova-network and nova-objectstore.
******************************************************
Special considerations to be deployed using Postgresql
******************************************************
juju deploy nova-cloud-controller
juju deploy postgresql
juju add-relation "nova-cloud-controller:pgsql-nova-db" "postgresql:db"
juju add-relation "nova-cloud-controller:pgsql-neutron-db" "postgresql:db"

View File

@ -120,16 +120,20 @@ options:
type: string type: string
description: | description: |
SSL certificate to install and use for API ports. Setting this value SSL certificate to install and use for API ports. Setting this value
and ssl_key will enable reverse proxying, point Glance's entry in the and ssl_key will enable reverse proxying, point Nova's entry in the
Keystone catalog to use https, and override any certficiate and key Keystone catalog to use https, and override any certficiate and key
issued by Keystone (if it is configured to do so). issued by Keystone (if it is configured to do so).
ssl_key: ssl_key:
type: string type: string
description: SSL key to use with certificate specified as ssl_cert. description: SSL key to use with certificate specified as ssl_cert.
ssl_ca:
type: string
description: |
SSL CA to use with the certificate and key provided - this is only
required if you are providing a privately signed ssl_cert and ssl_key.
use-syslog: use-syslog:
type: boolean type: boolean
default: False default: False
description: |
If set to True, supporting services will log to syslog. If set to True, supporting services will log to syslog.
# Neutron NVP Plugin configuration # Neutron NVP Plugin configuration
nvp-controllers: nvp-controllers:

View File

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

View File

@ -39,14 +39,15 @@ def get_cert():
def get_ca_cert(): def get_ca_cert():
ca_cert = None ca_cert = config_get('ssl_ca')
log("Inspecting identity-service relations for CA SSL certificate.", if ca_cert is None:
level=INFO) log("Inspecting identity-service relations for CA SSL certificate.",
for r_id in relation_ids('identity-service'): level=INFO)
for unit in relation_list(r_id): for r_id in relation_ids('identity-service'):
if not ca_cert: for unit in relation_list(r_id):
ca_cert = relation_get('ca_cert', if ca_cert is None:
rid=r_id, unit=unit) ca_cert = relation_get('ca_cert',
rid=r_id, unit=unit)
return ca_cert return ca_cert

View File

@ -1,5 +1,6 @@
import json import json
import os import os
import time
from base64 import b64decode from base64 import b64decode
@ -113,7 +114,8 @@ class OSContextGenerator(object):
class SharedDBContext(OSContextGenerator): class SharedDBContext(OSContextGenerator):
interfaces = ['shared-db'] interfaces = ['shared-db']
def __init__(self, database=None, user=None, relation_prefix=None): def __init__(self,
database=None, user=None, relation_prefix=None, ssl_dir=None):
''' '''
Allows inspecting relation for settings prefixed with relation_prefix. Allows inspecting relation for settings prefixed with relation_prefix.
This is useful for parsing access for multiple databases returned via This is useful for parsing access for multiple databases returned via
@ -122,6 +124,7 @@ class SharedDBContext(OSContextGenerator):
self.relation_prefix = relation_prefix self.relation_prefix = relation_prefix
self.database = database self.database = database
self.user = user self.user = user
self.ssl_dir = ssl_dir
def __call__(self): def __call__(self):
self.database = self.database or config('database') self.database = self.database or config('database')
@ -139,19 +142,74 @@ class SharedDBContext(OSContextGenerator):
for rid in relation_ids('shared-db'): for rid in relation_ids('shared-db'):
for unit in related_units(rid): for unit in related_units(rid):
passwd = relation_get(password_setting, rid=rid, unit=unit) rdata = relation_get(rid=rid, unit=unit)
ctxt = { ctxt = {
'database_host': relation_get('db_host', rid=rid, 'database_host': rdata.get('db_host'),
unit=unit),
'database': self.database, 'database': self.database,
'database_user': self.user, 'database_user': self.user,
'database_password': passwd, 'database_password': rdata.get(password_setting),
'database_type': 'mysql'
}
if context_complete(ctxt):
db_ssl(rdata, ctxt, self.ssl_dir)
return ctxt
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): if context_complete(ctxt):
return ctxt return ctxt
return {} 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')
with open(ca_path, 'w') as fh:
fh.write(b64decode(rdata['ssl_ca']))
ctxt['database_ssl_ca'] = ca_path
elif 'ssl_ca' in rdata:
log("Charm not setup for ssl support but ssl ca found")
return ctxt
if 'ssl_cert' in rdata:
cert_path = os.path.join(
ssl_dir, 'db-client.cert')
if not os.path.exists(cert_path):
log("Waiting 1m for ssl client cert validity")
time.sleep(60)
with open(cert_path, 'w') as fh:
fh.write(b64decode(rdata['ssl_cert']))
ctxt['database_ssl_cert'] = cert_path
key_path = os.path.join(ssl_dir, 'db-client.key')
with open(key_path, 'w') as fh:
fh.write(b64decode(rdata['ssl_key']))
ctxt['database_ssl_key'] = key_path
return ctxt
class IdentityServiceContext(OSContextGenerator): class IdentityServiceContext(OSContextGenerator):
interfaces = ['identity-service'] interfaces = ['identity-service']
@ -161,24 +219,25 @@ class IdentityServiceContext(OSContextGenerator):
for rid in relation_ids('identity-service'): for rid in relation_ids('identity-service'):
for unit in related_units(rid): for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit)
ctxt = { ctxt = {
'service_port': relation_get('service_port', rid=rid, 'service_port': rdata.get('service_port'),
unit=unit), 'service_host': rdata.get('service_host'),
'service_host': relation_get('service_host', rid=rid, 'auth_host': rdata.get('auth_host'),
unit=unit), 'auth_port': rdata.get('auth_port'),
'auth_host': relation_get('auth_host', rid=rid, unit=unit), 'admin_tenant_name': rdata.get('service_tenant'),
'auth_port': relation_get('auth_port', rid=rid, unit=unit), 'admin_user': rdata.get('service_username'),
'admin_tenant_name': relation_get('service_tenant', 'admin_password': rdata.get('service_password'),
rid=rid, unit=unit), 'service_protocol':
'admin_user': relation_get('service_username', rid=rid, rdata.get('service_protocol') or 'http',
unit=unit), 'auth_protocol':
'admin_password': relation_get('service_password', rid=rid, rdata.get('auth_protocol') or 'http',
unit=unit),
# XXX: Hard-coded http.
'service_protocol': 'http',
'auth_protocol': 'http',
} }
if context_complete(ctxt): if context_complete(ctxt):
# NOTE(jamespage) this is required for >= icehouse
# so a missing value just indicates keystone needs
# upgrading
ctxt['admin_tenant_id'] = rdata.get('service_tenant_id')
return ctxt return ctxt
return {} return {}
@ -186,6 +245,9 @@ class IdentityServiceContext(OSContextGenerator):
class AMQPContext(OSContextGenerator): class AMQPContext(OSContextGenerator):
interfaces = ['amqp'] interfaces = ['amqp']
def __init__(self, ssl_dir=None):
self.ssl_dir = ssl_dir
def __call__(self): def __call__(self):
log('Generating template context for amqp') log('Generating template context for amqp')
conf = config() conf = config()
@ -196,7 +258,6 @@ class AMQPContext(OSContextGenerator):
log('Could not generate shared_db context. ' log('Could not generate shared_db context. '
'Missing required charm config options: %s.' % e) 'Missing required charm config options: %s.' % e)
raise OSContextError raise OSContextError
ctxt = {} ctxt = {}
for rid in relation_ids('amqp'): for rid in relation_ids('amqp'):
ha_vip_only = False ha_vip_only = False
@ -214,6 +275,14 @@ class AMQPContext(OSContextGenerator):
unit=unit), unit=unit),
'rabbitmq_virtual_host': vhost, 'rabbitmq_virtual_host': vhost,
}) })
ssl_port = relation_get('ssl_port', rid=rid, unit=unit)
if ssl_port:
ctxt['rabbit_ssl_port'] = ssl_port
ssl_ca = relation_get('ssl_ca', rid=rid, unit=unit)
if ssl_ca:
ctxt['rabbit_ssl_ca'] = ssl_ca
if relation_get('ha_queues', rid=rid, unit=unit) is not None: if relation_get('ha_queues', rid=rid, unit=unit) is not None:
ctxt['rabbitmq_ha_queues'] = True ctxt['rabbitmq_ha_queues'] = True
@ -221,6 +290,16 @@ class AMQPContext(OSContextGenerator):
rid=rid, unit=unit) is not None rid=rid, unit=unit) is not None
if context_complete(ctxt): if context_complete(ctxt):
if 'rabbit_ssl_ca' in ctxt:
if not self.ssl_dir:
log(("Charm not setup for ssl support "
"but ssl ca found"))
break
ca_path = os.path.join(
self.ssl_dir, 'rabbit-client-ca.pem')
with open(ca_path, 'w') as fh:
fh.write(b64decode(ctxt['rabbit_ssl_ca']))
ctxt['rabbit_ssl_ca'] = ca_path
# Sufficient information found = break out! # Sufficient information found = break out!
break break
# Used for active/active rabbitmq >= grizzly # Used for active/active rabbitmq >= grizzly
@ -391,6 +470,8 @@ class ApacheSSLContext(OSContextGenerator):
'private_address': unit_get('private-address'), 'private_address': unit_get('private-address'),
'endpoints': [] 'endpoints': []
} }
if is_clustered():
ctxt['private_address'] = config('vip')
for api_port in self.external_ports: for api_port in self.external_ports:
ext_port = determine_apache_port(api_port) ext_port = determine_apache_port(api_port)
int_port = determine_api_port(api_port) int_port = determine_api_port(api_port)

View File

@ -17,6 +17,8 @@ def headers_package():
kver = check_output(['uname', '-r']).strip() kver = check_output(['uname', '-r']).strip()
return 'linux-headers-%s' % kver return 'linux-headers-%s' % kver
QUANTUM_CONF_DIR = '/etc/quantum'
def kernel_version(): def kernel_version():
""" Retrieve the current major kernel version as a tuple e.g. (3, 13) """ """ Retrieve the current major kernel version as a tuple e.g. (3, 13) """
@ -35,6 +37,8 @@ def determine_dkms_package():
# legacy # legacy
def quantum_plugins(): def quantum_plugins():
from charmhelpers.contrib.openstack import context from charmhelpers.contrib.openstack import context
return { return {
@ -46,7 +50,8 @@ def quantum_plugins():
'contexts': [ 'contexts': [
context.SharedDBContext(user=config('neutron-database-user'), context.SharedDBContext(user=config('neutron-database-user'),
database=config('neutron-database'), database=config('neutron-database'),
relation_prefix='neutron')], relation_prefix='neutron',
ssl_dir=QUANTUM_CONF_DIR)],
'services': ['quantum-plugin-openvswitch-agent'], 'services': ['quantum-plugin-openvswitch-agent'],
'packages': [[headers_package()] + determine_dkms_package(), 'packages': [[headers_package()] + determine_dkms_package(),
['quantum-plugin-openvswitch-agent']], ['quantum-plugin-openvswitch-agent']],
@ -61,7 +66,8 @@ def quantum_plugins():
'contexts': [ 'contexts': [
context.SharedDBContext(user=config('neutron-database-user'), context.SharedDBContext(user=config('neutron-database-user'),
database=config('neutron-database'), database=config('neutron-database'),
relation_prefix='neutron')], relation_prefix='neutron',
ssl_dir=QUANTUM_CONF_DIR)],
'services': [], 'services': [],
'packages': [], 'packages': [],
'server_packages': ['quantum-server', 'server_packages': ['quantum-server',
@ -70,6 +76,8 @@ def quantum_plugins():
} }
} }
NEUTRON_CONF_DIR = '/etc/neutron'
def neutron_plugins(): def neutron_plugins():
from charmhelpers.contrib.openstack import context from charmhelpers.contrib.openstack import context
@ -83,7 +91,8 @@ def neutron_plugins():
'contexts': [ 'contexts': [
context.SharedDBContext(user=config('neutron-database-user'), context.SharedDBContext(user=config('neutron-database-user'),
database=config('neutron-database'), database=config('neutron-database'),
relation_prefix='neutron')], relation_prefix='neutron',
ssl_dir=NEUTRON_CONF_DIR)],
'services': ['neutron-plugin-openvswitch-agent'], 'services': ['neutron-plugin-openvswitch-agent'],
'packages': [[headers_package()] + determine_dkms_package(), 'packages': [[headers_package()] + determine_dkms_package(),
['neutron-plugin-openvswitch-agent']], ['neutron-plugin-openvswitch-agent']],
@ -98,7 +107,8 @@ def neutron_plugins():
'contexts': [ 'contexts': [
context.SharedDBContext(user=config('neutron-database-user'), context.SharedDBContext(user=config('neutron-database-user'),
database=config('neutron-database'), database=config('neutron-database'),
relation_prefix='neutron')], relation_prefix='neutron',
ssl_dir=NEUTRON_CONF_DIR)],
'services': [], 'services': [],
'packages': [], 'packages': [],
'server_packages': ['neutron-server', 'server_packages': ['neutron-server',

View File

@ -65,6 +65,7 @@ SWIFT_CODENAMES = OrderedDict([
('1.10.0', 'havana'), ('1.10.0', 'havana'),
('1.9.1', 'havana'), ('1.9.1', 'havana'),
('1.9.0', 'havana'), ('1.9.0', 'havana'),
('1.13.1', 'icehouse'),
('1.13.0', 'icehouse'), ('1.13.0', 'icehouse'),
('1.12.0', 'icehouse'), ('1.12.0', 'icehouse'),
('1.11.0', 'icehouse'), ('1.11.0', 'icehouse'),

View File

@ -2,7 +2,9 @@ from os import stat
from stat import S_ISBLK from stat import S_ISBLK
from subprocess import ( from subprocess import (
check_call check_call,
check_output,
call
) )
@ -22,5 +24,12 @@ def zap_disk(block_device):
:param block_device: str: Full path of block device to clean. :param block_device: str: Full path of block device to clean.
''' '''
check_call(['sgdisk', '--zap-all', '--clear', # sometimes sgdisk exits non-zero; this is OK, dd will clean up
'--mbrtogpt', block_device]) call(['sgdisk', '--zap-all', '--mbrtogpt',
'--clear', block_device])
dev_end = check_output(['blockdev', '--getsz', block_device])
gpt_end = int(dev_end.split()[0]) - 100
check_call(['dd', 'if=/dev/zero', 'of=%s'%(block_device),
'bs=1M', 'count=1'])
check_call(['dd', 'if=/dev/zero', 'of=%s'%(block_device),
'bs=512', 'count=100', 'seek=%s'%(gpt_end)])

View File

@ -1,12 +1,17 @@
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
config, relation_ids, relation_set, log, ERROR) config, relation_ids, relation_set, log, ERROR,
unit_get)
from charmhelpers.fetch import apt_install, filter_installed_packages from charmhelpers.fetch import apt_install, filter_installed_packages
from charmhelpers.contrib.openstack import context, neutron, utils from charmhelpers.contrib.openstack import context, neutron, utils
from charmhelpers.contrib.hahelpers.cluster import ( from charmhelpers.contrib.hahelpers.cluster import (
determine_apache_port, determine_api_port) determine_apache_port,
determine_api_port,
https,
is_clustered
)
class ApacheSSLContext(context.ApacheSSLContext): class ApacheSSLContext(context.ApacheSSLContext):
@ -112,6 +117,24 @@ class HAProxyContext(context.HAProxyContext):
return ctxt return ctxt
def canonical_url(vip_setting='vip'):
'''
Returns the correct HTTP URL to this host given the state of HTTPS
configuration and hacluster.
:vip_setting: str: Setting in charm config that specifies
VIP address.
'''
scheme = 'http'
if https():
scheme = 'https'
if is_clustered():
addr = config(vip_setting)
else:
addr = unit_get('private-address')
return '%s://%s' % (scheme, addr)
class NeutronCCContext(context.NeutronContext): class NeutronCCContext(context.NeutronContext):
interfaces = [] interfaces = []
@ -148,10 +171,12 @@ class NeutronCCContext(context.NeutronContext):
','.join(_config['nvp-controllers'].split()) ','.join(_config['nvp-controllers'].split())
ctxt['nvp_controllers_list'] = \ ctxt['nvp_controllers_list'] = \
_config['nvp-controllers'].split() _config['nvp-controllers'].split()
ctxt['nova_url'] = "{}:8774/v2".format(canonical_url())
return ctxt return ctxt
class IdentityServiceContext(context.IdentityServiceContext): class IdentityServiceContext(context.IdentityServiceContext):
def __call__(self): def __call__(self):
ctxt = super(IdentityServiceContext, self).__call__() ctxt = super(IdentityServiceContext, self).__call__()
if not ctxt: if not ctxt:
@ -159,7 +184,23 @@ class IdentityServiceContext(context.IdentityServiceContext):
# the ec2 api needs to know the location of the keystone ec2 # the ec2 api needs to know the location of the keystone ec2
# tokens endpoint, set in nova.conf # tokens endpoint, set in nova.conf
ec2_tokens = 'http://%s:%s/v2.0/ec2tokens' % (ctxt['service_host'], ec2_tokens = '%s://%s:%s/v2.0/ec2tokens' % (
ctxt['service_port']) ctxt['service_protocol'] or 'http',
ctxt['service_host'],
ctxt['service_port']
)
ctxt['keystone_ec2_url'] = ec2_tokens ctxt['keystone_ec2_url'] = ec2_tokens
ctxt['region'] = config('region')
return ctxt return ctxt
class NovaPostgresqlDBContext(context.PostgresqlDBContext):
interfaces = ['pgsql-nova-db']
class NeutronPostgresqlDBContext(context.PostgresqlDBContext):
interfaces = ['pgsql-neutron-db']
def __init__(self):
super(NeutronPostgresqlDBContext,
self).__init__(config('neutron-database'))

View File

@ -13,7 +13,9 @@ from charmhelpers.core.hookenv import (
UnregisteredHookError, UnregisteredHookError,
config, config,
charm_dir, charm_dir,
is_relation_made,
log, log,
ERROR,
relation_get, relation_get,
relation_ids, relation_ids,
relation_set, relation_set,
@ -96,8 +98,9 @@ def install():
@hooks.hook('config-changed') @hooks.hook('config-changed')
@restart_on_change(restart_map(), stopstart=True) @restart_on_change(restart_map(), stopstart=True)
def config_changed(): def config_changed():
global CONFIGS
if openstack_upgrade_available('nova-common'): if openstack_upgrade_available('nova-common'):
do_openstack_upgrade(configs=CONFIGS) CONFIGS = do_openstack_upgrade()
save_script_rc() save_script_rc()
configure_https() configure_https()
CONFIGS.write_all() CONFIGS.write_all()
@ -110,6 +113,7 @@ def amqp_joined(relation_id=None):
@hooks.hook('amqp-relation-changed') @hooks.hook('amqp-relation-changed')
@hooks.hook('amqp-relation-departed')
@restart_on_change(restart_map()) @restart_on_change(restart_map())
def amqp_changed(): def amqp_changed():
if 'amqp' not in CONFIGS.complete_contexts(): if 'amqp' not in CONFIGS.complete_contexts():
@ -124,6 +128,14 @@ def amqp_changed():
@hooks.hook('shared-db-relation-joined') @hooks.hook('shared-db-relation-joined')
def db_joined(): def db_joined():
if is_relation_made('pgsql-nova-db') or \
is_relation_made('pgsql-neutron-db'):
# error, postgresql is used
e = ('Attempting to associate a mysql database when there is already '
'associated a postgresql one')
log(e, level=ERROR)
raise Exception(e)
relation_set(nova_database=config('database'), relation_set(nova_database=config('database'),
nova_username=config('database-user'), nova_username=config('database-user'),
nova_hostname=unit_get('private-address')) nova_hostname=unit_get('private-address'))
@ -134,18 +146,37 @@ def db_joined():
neutron_hostname=unit_get('private-address')) neutron_hostname=unit_get('private-address'))
@hooks.hook('pgsql-nova-db-relation-joined')
def pgsql_nova_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('pgsql-neutron-db-relation-joined')
def pgsql_neutron_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('neutron-database'))
@hooks.hook('shared-db-relation-changed') @hooks.hook('shared-db-relation-changed')
@restart_on_change(restart_map()) @restart_on_change(restart_map())
def db_changed(): def db_changed():
if 'shared-db' not in CONFIGS.complete_contexts(): if 'shared-db' not in CONFIGS.complete_contexts():
log('shared-db relation incomplete. Peer not ready?') log('shared-db relation incomplete. Peer not ready?')
return return
CONFIGS.write(NOVA_CONF) CONFIGS.write_all()
if network_manager() in ['neutron', 'quantum']:
plugin = neutron_plugin()
# DB config might have been moved to main neutron.conf in H?
CONFIGS.write(neutron_plugin_attribute(plugin, 'config'))
if eligible_leader(CLUSTER_RES): if eligible_leader(CLUSTER_RES):
migrate_database() migrate_database()
@ -154,6 +185,30 @@ def db_changed():
for rid in relation_ids('cloud-compute')] for rid in relation_ids('cloud-compute')]
@hooks.hook('pgsql-nova-db-relation-changed')
@restart_on_change(restart_map())
def postgresql_nova_db_changed():
if 'pgsql-nova-db' not in CONFIGS.complete_contexts():
log('pgsql-nova-db relation incomplete. Peer not ready?')
return
CONFIGS.write_all()
if eligible_leader(CLUSTER_RES):
migrate_database()
log('Triggering remote cloud-compute restarts.')
[compute_joined(rid=rid, remote_restart=True)
for rid in relation_ids('cloud-compute')]
@hooks.hook('pgsql-neutron-db-relation-changed')
@restart_on_change(restart_map())
def postgresql_neutron_db_changed():
if network_manager() in ['neutron', 'quantum']:
plugin = neutron_plugin()
# DB config might have been moved to main neutron.conf in H?
CONFIGS.write(neutron_plugin_attribute(plugin, 'config'))
@hooks.hook('image-service-relation-changed') @hooks.hook('image-service-relation-changed')
@restart_on_change(restart_map()) @restart_on_change(restart_map())
def image_service_changed(): def image_service_changed():
@ -211,6 +266,8 @@ def _auth_config():
cfg = { cfg = {
'auth_host': ks_auth_host, 'auth_host': ks_auth_host,
'auth_port': auth_token_config('auth_port'), 'auth_port': auth_token_config('auth_port'),
'auth_protocol': auth_token_config('auth_protocol'),
'service_protocol': auth_token_config('service_protocol'),
'service_port': auth_token_config('service_port'), 'service_port': auth_token_config('service_port'),
'service_username': auth_token_config('admin_user'), 'service_username': auth_token_config('admin_user'),
'service_password': auth_token_config('admin_password'), 'service_password': auth_token_config('admin_password'),
@ -226,7 +283,8 @@ def _auth_config():
def save_novarc(): def save_novarc():
auth = _auth_config() auth = _auth_config()
# XXX hard-coded http # XXX hard-coded http
ks_url = 'http://%s:%s/v2.0' % (auth['auth_host'], auth['auth_port']) ks_url = '%s://%s:%s/v2.0' % (auth['auth_protocol'],
auth['auth_host'], auth['auth_port'])
with open('/etc/quantum/novarc', 'wb') as out: with open('/etc/quantum/novarc', 'wb') as out:
out.write('export OS_USERNAME=%s\n' % auth['service_username']) out.write('export OS_USERNAME=%s\n' % auth['service_username'])
out.write('export OS_PASSWORD=%s\n' % auth['service_password']) out.write('export OS_PASSWORD=%s\n' % auth['service_password'])
@ -385,7 +443,9 @@ def ha_changed():
'identity-service-relation-broken', 'identity-service-relation-broken',
'image-service-relation-broken', 'image-service-relation-broken',
'nova-volume-service-relation-broken', 'nova-volume-service-relation-broken',
'shared-db-relation-broken' 'shared-db-relation-broken',
'pgsql-nova-db-relation-broken',
'pgsql-neutron-db-relation-broken',
'quantum-network-service-relation-broken') 'quantum-network-service-relation-broken')
def relation_broken(): def relation_broken():
CONFIGS.write_all() CONFIGS.write_all()
@ -436,6 +496,8 @@ def nova_vmware_relation_changed():
def upgrade_charm(): def upgrade_charm():
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)
for r_id in relation_ids('identity-service'):
identity_joined(rid=r_id)
def main(): def main():

View File

@ -22,8 +22,9 @@ from charmhelpers.contrib.openstack.utils import (
save_script_rc as _save_script_rc) save_script_rc as _save_script_rc)
from charmhelpers.fetch import ( from charmhelpers.fetch import (
apt_install, apt_upgrade,
apt_update, apt_update,
apt_install,
) )
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
@ -36,6 +37,10 @@ from charmhelpers.core.hookenv import (
ERROR, ERROR,
) )
from charmhelpers.core.host import (
service_start
)
import nova_cc_context import nova_cc_context
@ -49,6 +54,7 @@ BASE_PACKAGES = [
'haproxy', 'haproxy',
'python-keystoneclient', 'python-keystoneclient',
'python-mysqldb', 'python-mysqldb',
'python-psycopg2',
'uuid', 'uuid',
] ]
@ -69,11 +75,15 @@ API_PORTS = {
'quantum-server': 9696, 'quantum-server': 9696,
} }
NOVA_CONF = '/etc/nova/nova.conf' NOVA_CONF_DIR = "/etc/nova"
NOVA_API_PASTE = '/etc/nova/api-paste.ini' QUANTUM_CONF_DIR = "/etc/quantum"
QUANTUM_CONF = '/etc/quantum/quantum.conf' NEUTRON_CONF_DIR = "/etc/neutron"
QUANTUM_API_PASTE = '/etc/quantum/api-paste.ini'
NEUTRON_CONF = '/etc/neutron/neutron.conf' NOVA_CONF = '%s/nova.conf' % NOVA_CONF_DIR
NOVA_API_PASTE = '%s/api-paste.ini' % NOVA_CONF_DIR
QUANTUM_CONF = '%s/quantum.conf' % QUANTUM_CONF_DIR
QUANTUM_API_PASTE = '%s/api-paste.ini' % QUANTUM_CONF_DIR
NEUTRON_CONF = '%s/neutron.conf' % NEUTRON_CONF_DIR
HAPROXY_CONF = '/etc/haproxy/haproxy.cfg' HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'
APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend' APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend'
APACHE_24_CONF = '/etc/apache2/sites-available/openstack_https_frontend.conf' APACHE_24_CONF = '/etc/apache2/sites-available/openstack_https_frontend.conf'
@ -83,8 +93,10 @@ QUANTUM_DEFAULT = '/etc/default/quantum-server'
BASE_RESOURCE_MAP = OrderedDict([ BASE_RESOURCE_MAP = OrderedDict([
(NOVA_CONF, { (NOVA_CONF, {
'services': BASE_SERVICES, 'services': BASE_SERVICES,
'contexts': [context.AMQPContext(), 'contexts': [context.AMQPContext(ssl_dir=NOVA_CONF_DIR),
context.SharedDBContext(relation_prefix='nova'), context.SharedDBContext(
relation_prefix='nova', ssl_dir=NOVA_CONF_DIR),
nova_cc_context.NovaPostgresqlDBContext(),
context.ImageServiceContext(), context.ImageServiceContext(),
context.OSConfigFlagContext(), context.OSConfigFlagContext(),
context.SubordinateConfigContext( context.SubordinateConfigContext(
@ -104,11 +116,17 @@ BASE_RESOURCE_MAP = OrderedDict([
}), }),
(QUANTUM_CONF, { (QUANTUM_CONF, {
'services': ['quantum-server'], 'services': ['quantum-server'],
'contexts': [context.AMQPContext(), 'contexts': [context.AMQPContext(ssl_dir=QUANTUM_CONF_DIR),
context.SyslogContext(), context.SharedDBContext(
user=config('neutron-database-user'),
database=config('neutron-database'),
relation_prefix='neutron',
ssl_dir=QUANTUM_CONF_DIR),
nova_cc_context.NeutronPostgresqlDBContext(),
nova_cc_context.HAProxyContext(), nova_cc_context.HAProxyContext(),
nova_cc_context.IdentityServiceContext(), nova_cc_context.IdentityServiceContext(),
nova_cc_context.NeutronCCContext()], nova_cc_context.NeutronCCContext()
context.SyslogContext()],
}), }),
(QUANTUM_DEFAULT, { (QUANTUM_DEFAULT, {
'services': ['quantum-server'], 'services': ['quantum-server'],
@ -120,11 +138,17 @@ BASE_RESOURCE_MAP = OrderedDict([
}), }),
(NEUTRON_CONF, { (NEUTRON_CONF, {
'services': ['neutron-server'], 'services': ['neutron-server'],
'contexts': [context.AMQPContext(), 'contexts': [context.AMQPContext(ssl_dir=NEUTRON_CONF_DIR),
context.SyslogContext(), context.SharedDBContext(
user=config('neutron-database-user'),
database=config('neutron-database'),
relation_prefix='neutron',
ssl_dir=NEUTRON_CONF_DIR),
nova_cc_context.NeutronPostgresqlDBContext(),
nova_cc_context.IdentityServiceContext(), nova_cc_context.IdentityServiceContext(),
nova_cc_context.NeutronCCContext(), nova_cc_context.NeutronCCContext(),
nova_cc_context.HAProxyContext()], nova_cc_context.HAProxyContext()
context.SyslogContext()],
}), }),
(NEUTRON_DEFAULT, { (NEUTRON_DEFAULT, {
'services': ['neutron-server'], 'services': ['neutron-server'],
@ -195,6 +219,10 @@ def resource_map():
resource_map[conf]['contexts'].append( resource_map[conf]['contexts'].append(
nova_cc_context.NeutronCCContext()) nova_cc_context.NeutronCCContext())
# update for postgres
resource_map[conf]['contexts'].append(
nova_cc_context.NeutronPostgresqlDBContext())
# nova-conductor for releases >= G. # nova-conductor for releases >= G.
if os_release('nova-common') not in ['essex', 'folsom']: if os_release('nova-common') not in ['essex', 'folsom']:
resource_map['/etc/nova/nova.conf']['services'] += ['nova-conductor'] resource_map['/etc/nova/nova.conf']['services'] += ['nova-conductor']
@ -211,8 +239,8 @@ def resource_map():
return resource_map return resource_map
def register_configs(): def register_configs(release=None):
release = os_release('nova-common') release = release or os_release('nova-common')
configs = templating.OSConfigRenderer(templates_dir=TEMPLATES, configs = templating.OSConfigRenderer(templates_dir=TEMPLATES,
openstack_release=release) openstack_release=release)
for cfg, rscs in resource_map().iteritems(): for cfg, rscs in resource_map().iteritems():
@ -226,10 +254,18 @@ def restart_map():
if v['services']]) if v['services']])
def services():
''' Returns a list of services associate with this charm '''
_services = []
for v in restart_map().values():
_services = _services + v
return list(set(_services))
def determine_ports(): def determine_ports():
'''Assemble a list of API ports for services we are managing''' '''Assemble a list of API ports for services we are managing'''
ports = [] ports = []
for cfg, services in restart_map().iteritems(): for services in restart_map().values():
for service in services: for service in services:
try: try:
ports.append(API_PORTS[service]) ports.append(API_PORTS[service])
@ -245,7 +281,7 @@ def api_port(service):
def determine_packages(): def determine_packages():
# currently all packages match service names # currently all packages match service names
packages = [] + BASE_PACKAGES packages = [] + BASE_PACKAGES
for k, v in resource_map().iteritems(): for v in resource_map().values():
packages.extend(v['services']) packages.extend(v['services'])
if network_manager() in ['neutron', 'quantum']: if network_manager() in ['neutron', 'quantum']:
pkgs = neutron_plugin_attribute(neutron_plugin(), 'server_packages', pkgs = neutron_plugin_attribute(neutron_plugin(), 'server_packages',
@ -273,27 +309,162 @@ def save_script_rc():
_save_script_rc(**env_vars) _save_script_rc(**env_vars)
def do_openstack_upgrade(configs): def get_step_upgrade_source(new_src):
new_src = config('openstack-origin') '''
Determine if upgrade skips a release and, if so, return source
of skipped release.
'''
sources = {
# target_src: (cur_pocket, step_src)
'cloud:precise-icehouse':
('precise-updates/grizzly', 'cloud:precise-havana'),
'cloud:precise-icehouse/proposed':
('precise-proposed/grizzly', 'cloud:precise-havana/proposed')
}
with open('/etc/apt/sources.list.d/cloud-archive.list', 'r') as f:
line = f.readline()
for target_src, (cur_pocket, step_src) in sources.items():
if target_src != new_src:
continue
if cur_pocket in line:
return step_src
return None
POLICY_RC_D = """#!/bin/bash
set -e
case $1 in
neutron-server|quantum-server|nova-*)
[ $2 = "start" ] && exit 101
;;
*)
;;
esac
exit 0
"""
def enable_policy_rcd():
with open('/usr/sbin/policy-rc.d', 'w') as policy:
policy.write(POLICY_RC_D)
os.chmod('/usr/sbin/policy-rc.d', 0o755)
def disable_policy_rcd():
os.unlink('/usr/sbin/policy-rc.d')
QUANTUM_DB_MANAGE = "/usr/bin/quantum-db-manage"
NEUTRON_DB_MANAGE = "/usr/bin/neutron-db-manage"
def reset_os_release():
# Ugly hack to make os_release re-read versions
import charmhelpers.contrib.openstack.utils as utils
utils.os_rel = None
def neutron_db_manage(actions):
net_manager = network_manager()
if net_manager in ['neutron', 'quantum']:
plugin = neutron_plugin()
conf = neutron_plugin_attribute(plugin, 'config', net_manager)
if net_manager == 'quantum':
cmd = QUANTUM_DB_MANAGE
else:
cmd = NEUTRON_DB_MANAGE
subprocess.check_call([
cmd, '--config-file=/etc/{mgr}/{mgr}.conf'.format(mgr=net_manager),
'--config-file={}'.format(conf)] + actions
)
def get_db_connection():
config = ConfigParser.RawConfigParser()
config.read('/etc/neutron/neutron.conf')
try:
return config.get('database', 'connection')
except:
return None
def ml2_migration():
reset_os_release()
net_manager = network_manager()
if net_manager == 'neutron':
plugin = neutron_plugin()
if plugin == 'ovs':
log('Migrating from openvswitch to ml2 plugin')
cmd = [
'python',
'/usr/lib/python2.7/dist-packages/neutron'
'/db/migration/migrate_to_ml2.py',
'--tunnel-type', 'gre',
'--release', 'icehouse',
'openvswitch', get_db_connection()
]
subprocess.check_call(cmd)
def _do_openstack_upgrade(new_src):
enable_policy_rcd()
cur_os_rel = os_release('nova-common')
new_os_rel = get_os_codename_install_source(new_src) new_os_rel = get_os_codename_install_source(new_src)
log('Performing OpenStack upgrade to %s.' % (new_os_rel)) log('Performing OpenStack upgrade to %s.' % (new_os_rel))
configure_installation_source(new_src) configure_installation_source(new_src)
apt_update()
dpkg_opts = [ dpkg_opts = [
'--option', 'Dpkg::Options::=--force-confnew', '--option', 'Dpkg::Options::=--force-confnew',
'--option', 'Dpkg::Options::=--force-confdef', '--option', 'Dpkg::Options::=--force-confdef',
] ]
apt_install(packages=determine_packages(), options=dpkg_opts, fatal=True) # NOTE(jamespage) pre-stamp neutron database before upgrade from grizzly
if cur_os_rel == 'grizzly':
neutron_db_manage(['stamp', 'grizzly'])
# set CONFIGS to load templates from new release and regenerate config apt_update(fatal=True)
configs.set_release(openstack_release=new_os_rel) apt_upgrade(options=dpkg_opts, fatal=True, dist=True)
configs.write_all() apt_install(determine_packages(), fatal=True)
if cur_os_rel == 'grizzly':
# NOTE(jamespage) when upgrading from grizzly->havana, config
# files need to be generated prior to performing the db upgrade
reset_os_release()
configs = register_configs(release=new_os_rel)
configs.write_all()
neutron_db_manage(['upgrade', 'head'])
else:
# NOTE(jamespage) upgrade with existing config files as the
# havana->icehouse migration enables new service_plugins which
# create issues with db upgrades
neutron_db_manage(['upgrade', 'head'])
reset_os_release()
configs = register_configs(release=new_os_rel)
configs.write_all()
if new_os_rel == 'icehouse':
# NOTE(jamespage) default plugin switch to ml2@icehouse
ml2_migration()
if eligible_leader(CLUSTER_RES): if eligible_leader(CLUSTER_RES):
migrate_database() migrate_database()
[service_start(s) for s in services()]
disable_policy_rcd()
return configs
def do_openstack_upgrade():
new_src = config('openstack-origin')
step_src = get_step_upgrade_source(new_src)
if step_src is not None:
_do_openstack_upgrade(step_src)
return _do_openstack_upgrade(new_src)
def volume_service(): def volume_service():
@ -315,10 +486,10 @@ def migrate_database():
def auth_token_config(setting): def auth_token_config(setting):
''' """
Returns currently configured value for setting in api-paste.ini's Returns currently configured value for setting in api-paste.ini's
authtoken section, or None. authtoken section, or None.
''' """
config = ConfigParser.RawConfigParser() config = ConfigParser.RawConfigParser()
config.read('/etc/nova/api-paste.ini') config.read('/etc/nova/api-paste.ini')
try: try:
@ -363,7 +534,10 @@ def authorized_keys(user=None):
def ssh_known_host_key(host, user=None): def ssh_known_host_key(host, user=None):
cmd = ['ssh-keygen', '-f', known_hosts(user), '-H', '-F', host] cmd = ['ssh-keygen', '-f', known_hosts(user), '-H', '-F', host]
return subprocess.check_output(cmd).strip() try:
return subprocess.check_output(cmd).strip()
except subprocess.CalledProcessError:
return None
def remove_known_host(host, user=None): def remove_known_host(host, user=None):
@ -459,10 +633,14 @@ def determine_endpoints(url):
'''Generates a dictionary containing all relevant endpoints to be '''Generates a dictionary containing all relevant endpoints to be
passed to keystone as relation settings.''' passed to keystone as relation settings.'''
region = config('region') region = config('region')
os_rel = os_release('nova-common')
# TODO: Configurable nova API version. if os_rel >= 'grizzly':
nova_url = ('%s:%s/v1.1/$(tenant_id)s' % nova_url = ('%s:%s/v2/$(tenant_id)s' %
(url, api_port('nova-api-os-compute'))) (url, api_port('nova-api-os-compute')))
else:
nova_url = ('%s:%s/v1.1/$(tenant_id)s' %
(url, api_port('nova-api-os-compute')))
ec2_url = '%s:%s/services/Cloud' % (url, api_port('nova-api-ec2')) ec2_url = '%s:%s/services/Cloud' % (url, api_port('nova-api-ec2'))
nova_volume_url = ('%s:%s/v1/$(tenant_id)s' % nova_volume_url = ('%s:%s/v1/$(tenant_id)s' %
(url, api_port('nova-api-os-compute'))) (url, api_port('nova-api-os-compute')))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,10 @@ provides:
requires: requires:
shared-db: shared-db:
interface: mysql-shared interface: mysql-shared
pgsql-nova-db:
interface: pgsql
pgsql-neutron-db:
interface: pgsql
amqp: amqp:
interface: rabbitmq interface: rabbitmq
image-service: image-service:

5
setup.cfg Normal file
View File

@ -0,0 +1,5 @@
[nosetests]
verbosity=2
with-coverage=1
cover-erase=1
cover-package=hooks

View File

@ -17,7 +17,7 @@
--use_syslog={{ use_syslog }} --use_syslog={{ use_syslog }}
--ec2_private_dns_show_ip --ec2_private_dns_show_ip
{% if database_host -%} {% if database_host -%}
--sql_connection=mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }} --sql_connection={{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}
{% endif -%} {% endif -%}
{% if rabbitmq_host -%} {% if rabbitmq_host -%}
--rabbit_host={{ rabbitmq_host }} --rabbit_host={{ rabbitmq_host }}

View File

@ -25,11 +25,16 @@ compute_driver=libvirt.LibvirtDriver
keystone_ec2_url = {{ keystone_ec2_url }} keystone_ec2_url = {{ keystone_ec2_url }}
{% endif -%} {% endif -%}
{% if database_host -%} {% include "parts/database" %}
sql_connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}
{% endif -%}
{% if rabbitmq_host -%} {% if rabbitmq_host -%}
{% if rabbit_ssl_port %}
rabbit_use_ssl=True
rabbit_port={{ rabbit_ssl_port }}
{% if rabbit_ssl_ca %}
kombu_ssl_ca_certs={{rabbit_ssl_ca}}
{% endif %}
{% endif %}
rabbit_host = {{ rabbitmq_host }} rabbit_host = {{ rabbitmq_host }}
rabbit_userid = {{ rabbitmq_user }} rabbit_userid = {{ rabbitmq_user }}
rabbit_password = {{ rabbitmq_password }} rabbit_password = {{ rabbitmq_password }}

View File

@ -11,7 +11,7 @@ local_ip = {{ local_ip }}
[DATABASE] [DATABASE]
{% if database_host -%} {% if database_host -%}
sql_connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}?quantum?charset=utf8 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 %}
reconnect_interval = 2 reconnect_interval = 2
{% else -%} {% else -%}
connection = sqlite:////var/lib/quantum/quantum.sqlite connection = sqlite:////var/lib/quantum/quantum.sqlite

115
templates/grizzly/nova.conf Normal file
View File

@ -0,0 +1,115 @@
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[DEFAULT]
dhcpbridge_flagfile=/etc/nova/nova.conf
dhcpbridge=/usr/bin/nova-dhcpbridge
logdir=/var/log/nova
state_path=/var/lib/nova
lock_path=/var/lock/nova
force_dhcp_release=True
iscsi_helper=tgtadm
libvirt_use_virtio_for_bridges=True
connection_type=libvirt
root_helper=sudo nova-rootwrap /etc/nova/rootwrap.conf
verbose=True
ec2_private_dns_show_ip=True
api_paste_config=/etc/nova/api-paste.ini
volumes_path=/var/lib/nova/volumes
enabled_apis=ec2,osapi_compute,metadata
auth_strategy=keystone
compute_driver=libvirt.LibvirtDriver
{% if keystone_ec2_url -%}
keystone_ec2_url = {{ keystone_ec2_url }}
{% endif -%}
{% include "parts/database" %}
{% include "parts/rabbitmq" %}
{% if glance_api_servers -%}
glance_api_servers = {{ glance_api_servers }}
{% endif -%}
{% if rbd_pool -%}
rbd_pool = {{ rbd_pool }}
rbd_user = {{ rbd_user }}
rbd_secret_uuid = {{ rbd_secret_uuid }}
{% endif -%}
{% if neutron_plugin and neutron_plugin == 'ovs' -%}
libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtGenericVIFDriver
libvirt_user_virtio_for_bridges = True
{% if neutron_security_groups -%}
security_group_api = {{ network_manager }}
nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver
{% endif -%}
{% if external_network -%}
default_floating_pool = {{ external_network }}
{% endif -%}
{% endif -%}
{% if neutron_plugin and neutron_plugin == 'nvp' -%}
security_group_api = neutron
nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver
{% if external_network -%}
default_floating_pool = {{ external_network }}
{% endif -%}
{% endif -%}
{% if network_manager_config -%}
{% for key, value in network_manager_config.iteritems() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if network_manager and network_manager == 'quantum' -%}
network_api_class = nova.network.quantumv2.api.API
quantum_url = {{ neutron_url }}
{% if auth_host -%}
quantum_auth_strategy = keystone
quantum_admin_tenant_name = {{ admin_tenant_name }}
quantum_admin_username = {{ admin_user }}
quantum_admin_password = {{ admin_password }}
quantum_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0
{% endif -%}
{% elif network_manager and network_manager == 'neutron' -%}
network_api_class = nova.network.neutronv2.api.API
neutron_url = {{ neutron_url }}
{% if auth_host -%}
neutron_auth_strategy = keystone
neutron_admin_tenant_name = {{ admin_tenant_name }}
neutron_admin_username = {{ admin_user }}
neutron_admin_password = {{ admin_password }}
neutron_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0
{% endif -%}
{% else -%}
network_manager = nova.network.manager.FlatDHCPManager
{% endif -%}
{% if default_floating_pool -%}
default_floating_pool = {{ default_floating_pool }}
{% endif -%}
{% if volume_service -%}
volume_api_class=nova.volume.cinder.API
{% endif -%}
{% if user_config_flags -%}
{% for key, value in user_config_flags.iteritems() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if listen_ports -%}
{% for key, value in listen_ports.iteritems() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if sections and 'DEFAULT' in sections -%}
{% for key, value in sections['DEFAULT'] -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}

View File

@ -0,0 +1,45 @@
# grizzly
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[DEFAULT]
state_path = /var/lib/quantum
lock_path = $state_path/lock
bind_host = 0.0.0.0
{% if neutron_bind_port -%}
bind_port = {{ neutron_bind_port }}
{% else -%}
bind_port = 9696
{% endif -%}
{% if core_plugin -%}
core_plugin = {{ core_plugin }}
{% endif -%}
api_paste_config = /etc/quantum/api-paste.ini
auth_strategy = keystone
control_exchange = quantum
notification_driver = quantum.openstack.common.notifier.rpc_notifier
default_notification_level = INFO
notification_topics = notifications
{% include "parts/database" %}
{% include "parts/rabbitmq" %}
{% if neutron_security_groups -%}
allow_overlapping_ips = True
{% endif -%}
[QUOTAS]
quota_driver = quantum.db.quota_db.DbQuotaDriver
{% if neutron_security_groups -%}
quota_items = network,subnet,port,security_group,security_group_rule
{% endif -%}
[DEFAULT_SERVICETYPE]
[AGENT]
root_helper = sudo quantum-rootwrap /etc/quantum/rootwrap.conf
[keystone_authtoken]
# auth_token middleware currently set in /etc/quantum/api-paste.ini

View File

@ -16,17 +16,16 @@ bind_port = 9696
{% endif -%} {% endif -%}
{% if core_plugin -%} {% if core_plugin -%}
core_plugin = {{ core_plugin }} core_plugin = {{ core_plugin }}
{% if neutron_plugin in ['ovs', 'ml2'] -%}
service_plugins = neutron.services.metering.metering_plugin.MeteringPlugin
{% endif -%}
{% endif -%} {% endif -%}
{% if neutron_security_groups -%} {% if neutron_security_groups -%}
allow_overlapping_ips = True allow_overlapping_ips = True
neutron_firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver neutron_firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver
{% endif -%} {% endif -%}
{% if rabbitmq_host -%}
rabbit_host = {{ rabbitmq_host }} {% include "parts/rabbitmq" %}
rabbit_userid = {{ rabbitmq_user }}
rabbit_password = {{ rabbitmq_password }}
rabbit_virtual_host = {{ rabbitmq_virtual_host }}
{% endif -%}
[quotas] [quotas]
quota_driver = neutron.db.quota_db.DbQuotaDriver quota_driver = neutron.db.quota_db.DbQuotaDriver
@ -51,5 +50,7 @@ admin_user = {{ admin_user }}
admin_password = {{ admin_password }} admin_password = {{ admin_password }}
{% endif -%} {% endif -%}
{% include "parts/section-database" %}
[lbaas] [lbaas]
[service_providers] [service_providers]

119
templates/havana/nova.conf Normal file
View File

@ -0,0 +1,119 @@
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[DEFAULT]
dhcpbridge_flagfile=/etc/nova/nova.conf
dhcpbridge=/usr/bin/nova-dhcpbridge
logdir=/var/log/nova
state_path=/var/lib/nova
lock_path=/var/lock/nova
force_dhcp_release=True
iscsi_helper=tgtadm
libvirt_use_virtio_for_bridges=True
connection_type=libvirt
root_helper=sudo nova-rootwrap /etc/nova/rootwrap.conf
verbose=True
ec2_private_dns_show_ip=True
api_paste_config=/etc/nova/api-paste.ini
volumes_path=/var/lib/nova/volumes
enabled_apis=ec2,osapi_compute,metadata
auth_strategy=keystone
compute_driver=libvirt.LibvirtDriver
{% if keystone_ec2_url -%}
keystone_ec2_url = {{ keystone_ec2_url }}
{% endif -%}
{% include "parts/database" %}
{% include "parts/rabbitmq" %}
{% if glance_api_servers -%}
glance_api_servers = {{ glance_api_servers }}
{% endif -%}
{% if rbd_pool -%}
rbd_pool = {{ rbd_pool }}
rbd_user = {{ rbd_user }}
rbd_secret_uuid = {{ rbd_secret_uuid }}
{% endif -%}
{% if neutron_plugin and neutron_plugin == 'ovs' -%}
libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtGenericVIFDriver
libvirt_user_virtio_for_bridges = True
{% if neutron_security_groups -%}
security_group_api = {{ network_manager }}
nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver
{% endif -%}
{% if external_network -%}
default_floating_pool = {{ external_network }}
{% endif -%}
{% endif -%}
{% if neutron_plugin and neutron_plugin == 'nvp' -%}
security_group_api = neutron
nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver
{% if external_network -%}
default_floating_pool = {{ external_network }}
{% endif -%}
{% endif -%}
{% if network_manager_config -%}
{% for key, value in network_manager_config.iteritems() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if network_manager and network_manager == 'quantum' -%}
network_api_class = nova.network.quantumv2.api.API
quantum_url = {{ neutron_url }}
{% if auth_host -%}
quantum_auth_strategy = keystone
quantum_admin_tenant_name = {{ admin_tenant_name }}
quantum_admin_username = {{ admin_user }}
quantum_admin_password = {{ admin_password }}
quantum_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0
{% endif -%}
{% elif network_manager and network_manager == 'neutron' -%}
network_api_class = nova.network.neutronv2.api.API
neutron_url = {{ neutron_url }}
{% if auth_host -%}
neutron_auth_strategy = keystone
neutron_admin_tenant_name = {{ admin_tenant_name }}
neutron_admin_username = {{ admin_user }}
neutron_admin_password = {{ admin_password }}
neutron_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0
{% endif -%}
{% else -%}
network_manager = nova.network.manager.FlatDHCPManager
{% endif -%}
{% if default_floating_pool -%}
default_floating_pool = {{ default_floating_pool }}
{% endif -%}
{% if volume_service -%}
volume_api_class=nova.volume.cinder.API
{% endif -%}
{% if user_config_flags -%}
{% for key, value in user_config_flags.iteritems() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if listen_ports -%}
{% for key, value in listen_ports.iteritems() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if sections and 'DEFAULT' in sections -%}
{% for key, value in sections['DEFAULT'] -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
[osapi_v3]
enabled=True

View File

@ -9,10 +9,3 @@ nvp_password = {{ nvp_password }}
nvp_controllers = {{ nvp_controllers }} nvp_controllers = {{ nvp_controllers }}
default_tz_uuid = {{ nvp_tz_uuid }} default_tz_uuid = {{ nvp_tz_uuid }}
default_l3_gw_service_uuid = {{ nvp_l3_uuid }} default_l3_gw_service_uuid = {{ nvp_l3_uuid }}
[database]
{% if database_host -%}
connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}
{% else -%}
connection = sqlite:////var/lib/neutron/neutron.sqlite
{% endif -%}

View File

@ -4,13 +4,6 @@ tenant_network_type = gre
enable_tunneling = True enable_tunneling = True
local_ip = {{ local_ip }} local_ip = {{ local_ip }}
[database]
{% if database_host -%}
connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}
{% else -%}
connection = sqlite:////var/lib/neutron/neutron.sqlite
{% endif -%}
[securitygroup] [securitygroup]
{% if neutron_security_groups -%} {% if neutron_security_groups -%}
firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver

View File

@ -0,0 +1,124 @@
############
# Metadata #
############
[composite:metadata]
use = egg:Paste#urlmap
/: meta
[pipeline:meta]
pipeline = ec2faultwrap logrequest metaapp
[app:metaapp]
paste.app_factory = nova.api.metadata.handler:MetadataRequestHandler.factory
#######
# EC2 #
#######
[composite:ec2]
use = egg:Paste#urlmap
/services/Cloud: ec2cloud
[composite:ec2cloud]
use = call:nova.api.auth:pipeline_factory
noauth = ec2faultwrap logrequest ec2noauth cloudrequest validator ec2executor
keystone = ec2faultwrap logrequest ec2keystoneauth cloudrequest validator ec2executor
[filter:ec2faultwrap]
paste.filter_factory = nova.api.ec2:FaultWrapper.factory
[filter:logrequest]
paste.filter_factory = nova.api.ec2:RequestLogging.factory
[filter:ec2lockout]
paste.filter_factory = nova.api.ec2:Lockout.factory
[filter:ec2keystoneauth]
paste.filter_factory = nova.api.ec2:EC2KeystoneAuth.factory
[filter:ec2noauth]
paste.filter_factory = nova.api.ec2:NoAuth.factory
[filter:cloudrequest]
controller = nova.api.ec2.cloud.CloudController
paste.filter_factory = nova.api.ec2:Requestify.factory
[filter:authorizer]
paste.filter_factory = nova.api.ec2:Authorizer.factory
[filter:validator]
paste.filter_factory = nova.api.ec2:Validator.factory
[app:ec2executor]
paste.app_factory = nova.api.ec2:Executor.factory
#############
# OpenStack #
#############
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v1.1: openstack_compute_api_v2
/v2: openstack_compute_api_v2
/v3: openstack_compute_api_v3
[composite:openstack_compute_api_v2]
use = call:nova.api.auth:pipeline_factory
noauth = faultwrap sizelimit noauth ratelimit osapi_compute_app_v2
keystone = faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2
keystone_nolimit = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2
[composite:openstack_compute_api_v3]
use = call:nova.api.auth:pipeline_factory_v3
noauth = faultwrap sizelimit noauth_v3 osapi_compute_app_v3
keystone = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3
[filter:faultwrap]
paste.filter_factory = nova.api.openstack:FaultWrapper.factory
[filter:noauth]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory
[filter:noauth_v3]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddlewareV3.factory
[filter:ratelimit]
paste.filter_factory = nova.api.openstack.compute.limits:RateLimitingMiddleware.factory
[filter:sizelimit]
paste.filter_factory = nova.api.sizelimit:RequestBodySizeLimiter.factory
[app:osapi_compute_app_v2]
paste.app_factory = nova.api.openstack.compute:APIRouter.factory
[app:osapi_compute_app_v3]
paste.app_factory = nova.api.openstack.compute:APIRouterV3.factory
[pipeline:oscomputeversions]
pipeline = faultwrap oscomputeversionapp
[app:oscomputeversionapp]
paste.app_factory = nova.api.openstack.compute.versions:Versions.factory
##########
# Shared #
##########
[filter:keystonecontext]
paste.filter_factory = nova.api.auth:NovaKeystoneContext.factory
[filter:authtoken]
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory
{% if service_host -%}
# NOTE(jamespage) - not used - but required for relation to nova-compute
service_protocol = {{ service_protocol }}
service_host = {{ service_host }}
service_port = {{ service_port }}
auth_host = {{ auth_host }}
auth_port = {{ auth_port }}
auth_protocol = {{ auth_protocol }}
admin_tenant_name = {{ admin_tenant_name }}
admin_user = {{ admin_user }}
admin_password = {{ admin_password }}
{% endif -%}

View File

@ -0,0 +1,30 @@
# icehouse
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[ml2]
type_drivers = gre,vxlan
tenant_network_types = gre,vxlan
mechanism_drivers = openvswitch
[ml2_type_gre]
tunnel_id_ranges = 1:1000
[ml2_type_vxlan]
vni_ranges = 1001:2000
[ovs]
enable_tunneling = True
local_ip = {{ local_ip }}
[agent]
tunnel_types = gre
[securitygroup]
{% if neutron_security_groups -%}
enable_security_group = True
firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver
{% else -%}
enable_security_group = False
{% endif -%}

View File

@ -0,0 +1,71 @@
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[DEFAULT]
state_path = /var/lib/neutron
lock_path = $state_path/lock
bind_host = 0.0.0.0
auth_strategy = keystone
notification_driver = neutron.openstack.common.notifier.rpc_notifier
{% if neutron_bind_port -%}
bind_port = {{ neutron_bind_port }}
{% else -%}
bind_port = 9696
{% endif -%}
{% if core_plugin -%}
core_plugin = {{ core_plugin }}
{% if neutron_plugin in ['ovs', 'ml2'] -%}
service_plugins = neutron.services.l3_router.l3_router_plugin.L3RouterPlugin,neutron.services.firewall.fwaas_plugin.FirewallPlugin,neutron.services.loadbalancer.plugin.LoadBalancerPlugin,neutron.services.vpn.plugin.VPNDriverPlugin,neutron.services.metering.metering_plugin.MeteringPlugin
{% endif -%}
{% endif -%}
{% if neutron_security_groups -%}
allow_overlapping_ips = True
neutron_firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver
{% endif -%}
{% include "parts/rabbitmq" %}
notify_nova_on_port_status_changes = True
notify_nova_on_port_data_changes = True
nova_url = {{ nova_url }}
nova_region_name = {{ region }}
{% if auth_host -%}
nova_admin_username = {{ admin_user }}
nova_admin_tenant_id = {{ admin_tenant_id }}
nova_admin_password = {{ admin_password }}
nova_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0
{% endif -%}
[quotas]
quota_driver = neutron.db.quota_db.DbQuotaDriver
{% if neutron_security_groups -%}
quota_items = network,subnet,port,security_group,security_group_rule
{% endif -%}
[agent]
root_helper = sudo /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf
[keystone_authtoken]
signing_dir = $state_path/keystone-signing
{% if service_host -%}
service_protocol = {{ service_protocol }}
service_host = {{ service_host }}
service_port = {{ service_port }}
auth_host = {{ auth_host }}
auth_port = {{ auth_port }}
auth_protocol = {{ auth_protocol }}
admin_tenant_name = {{ admin_tenant_name }}
admin_user = {{ admin_user }}
admin_password = {{ admin_password }}
{% endif -%}
{% include "parts/section-database" %}
[service_providers]
service_provider=LOADBALANCER:Haproxy:neutron.services.loadbalancer.drivers.haproxy.plugin_driver.HaproxyOnHostPluginDriver:default
service_provider=VPN:openswan:neutron.services.vpn.service_drivers.ipsec.IPsecVPNDriver:default
service_provider=FIREWALL:Iptables:neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver:default

View File

@ -0,0 +1,130 @@
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[DEFAULT]
dhcpbridge_flagfile=/etc/nova/nova.conf
dhcpbridge=/usr/bin/nova-dhcpbridge
logdir=/var/log/nova
state_path=/var/lib/nova
lock_path=/var/lock/nova
force_dhcp_release=True
iscsi_helper=tgtadm
libvirt_use_virtio_for_bridges=True
connection_type=libvirt
root_helper=sudo nova-rootwrap /etc/nova/rootwrap.conf
verbose=True
ec2_private_dns_show_ip=True
api_paste_config=/etc/nova/api-paste.ini
volumes_path=/var/lib/nova/volumes
enabled_apis=ec2,osapi_compute,metadata
auth_strategy=keystone
compute_driver=libvirt.LibvirtDriver
{% if keystone_ec2_url -%}
keystone_ec2_url = {{ keystone_ec2_url }}
{% endif -%}
{% include "parts/database" %}
{% include "parts/rabbitmq" %}
{% if glance_api_servers -%}
glance_api_servers = {{ glance_api_servers }}
{% endif -%}
{% if rbd_pool -%}
rbd_pool = {{ rbd_pool }}
rbd_user = {{ rbd_user }}
rbd_secret_uuid = {{ rbd_secret_uuid }}
{% endif -%}
{% if neutron_plugin and neutron_plugin == 'ovs' -%}
libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtGenericVIFDriver
libvirt_user_virtio_for_bridges = True
{% if neutron_security_groups -%}
security_group_api = {{ network_manager }}
nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver
{% endif -%}
{% if external_network -%}
default_floating_pool = {{ external_network }}
{% endif -%}
{% endif -%}
{% if neutron_plugin and neutron_plugin == 'nvp' -%}
security_group_api = neutron
nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver
{% if external_network -%}
default_floating_pool = {{ external_network }}
{% endif -%}
{% endif -%}
{% if network_manager_config -%}
{% for key, value in network_manager_config.iteritems() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if network_manager and network_manager == 'quantum' -%}
network_api_class = nova.network.quantumv2.api.API
quantum_url = {{ neutron_url }}
{% if auth_host -%}
quantum_auth_strategy = keystone
quantum_admin_tenant_name = {{ admin_tenant_name }}
quantum_admin_username = {{ admin_user }}
quantum_admin_password = {{ admin_password }}
quantum_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0
{% endif -%}
{% elif network_manager and network_manager == 'neutron' -%}
network_api_class = nova.network.neutronv2.api.API
neutron_url = {{ neutron_url }}
{% if auth_host -%}
neutron_auth_strategy = keystone
neutron_admin_tenant_name = {{ admin_tenant_name }}
neutron_admin_username = {{ admin_user }}
neutron_admin_password = {{ admin_password }}
neutron_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0
{% endif -%}
{% else -%}
network_manager = nova.network.manager.FlatDHCPManager
{% endif -%}
{% if default_floating_pool -%}
default_floating_pool = {{ default_floating_pool }}
{% endif -%}
{% if volume_service -%}
volume_api_class=nova.volume.cinder.API
{% endif -%}
{% if user_config_flags -%}
{% for key, value in user_config_flags.iteritems() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if listen_ports -%}
{% for key, value in listen_ports.iteritems() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if sections and 'DEFAULT' in sections -%}
{% for key, value in sections['DEFAULT'] -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if auth_host -%}
[keystone_authtoken]
auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/
auth_host = {{ auth_host }}
auth_port = {{ auth_port }}
auth_protocol = {{ auth_protocol }}
admin_tenant_name = {{ admin_tenant_name }}
admin_user = {{ admin_user }}
admin_password = {{ admin_password }}
{% endif -%}
[osapi_v3]
enabled=True

3
templates/parts/database Normal file
View File

@ -0,0 +1,3 @@
{% if database_host -%}
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 %}
{% endif -%}

21
templates/parts/rabbitmq Normal file
View File

@ -0,0 +1,21 @@
{% if rabbitmq_host or rabbitmq_hosts -%}
rabbit_userid = {{ rabbitmq_user }}
rabbit_virtual_host = {{ rabbitmq_virtual_host }}
rabbit_password = {{ rabbitmq_password }}
{% if rabbitmq_hosts -%}
rabbit_hosts = {{ rabbitmq_hosts }}
{% if rabbitmq_ha_queues -%}
rabbit_ha_queues = True
rabbit_durable_queues = False
{% endif -%}
{% else -%}
rabbit_host = {{ rabbitmq_host }}
{% endif -%}
{% if rabbit_ssl_port -%}
rabbit_use_ssl = True
rabbit_port = {{ rabbit_ssl_port }}
{% if rabbit_ssl_ca -%}
kombu_ssl_ca_certs = {{ rabbit_ssl_ca }}
{% endif -%}
{% endif -%}
{% endif -%}

View File

@ -0,0 +1,4 @@
{% if database_host -%}
[database]
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 %}
{% endif -%}

View File

@ -1,8 +1,9 @@
from mock import MagicMock, patch from mock import call, MagicMock, patch
from test_utils import CharmTestCase from test_utils import CharmTestCase
import nova_cc_utils as utils with patch('charmhelpers.core.hookenv.config') as config:
config.return_value = 'neutron'
import nova_cc_utils as utils
_reg = utils.register_configs _reg = utils.register_configs
_map = utils.restart_map _map = utils.restart_map
@ -15,7 +16,6 @@ import nova_cc_hooks as hooks
utils.register_configs = _reg utils.register_configs = _reg
utils.restart_map = _map utils.restart_map = _map
TO_PATCH = [ TO_PATCH = [
'api_port', 'api_port',
'apt_update', 'apt_update',
@ -29,8 +29,11 @@ TO_PATCH = [
'determine_packages', 'determine_packages',
'determine_ports', 'determine_ports',
'open_port', 'open_port',
'is_relation_made',
'log',
'relation_get', 'relation_get',
'relation_set', 'relation_set',
'relation_ids',
'ssh_compute_add', 'ssh_compute_add',
'ssh_known_hosts_b64', 'ssh_known_hosts_b64',
'ssh_authorized_keys_b64', 'ssh_authorized_keys_b64',
@ -42,6 +45,7 @@ TO_PATCH = [
'eligible_leader', 'eligible_leader',
'keystone_ca_cert_b64', 'keystone_ca_cert_b64',
'neutron_plugin', 'neutron_plugin',
'migrate_database',
] ]
@ -63,6 +67,7 @@ class NovaCCHooksTests(CharmTestCase):
def setUp(self): def setUp(self):
super(NovaCCHooksTests, self).setUp(hooks, TO_PATCH) super(NovaCCHooksTests, self).setUp(hooks, TO_PATCH)
self.config.side_effect = self.test_config.get self.config.side_effect = self.test_config.get
self.relation_get.side_effect = self.test_relation.get self.relation_get.side_effect = self.test_relation.get
self.charm_dir.return_value = '/var/lib/juju/charms/nova/charm' self.charm_dir.return_value = '/var/lib/juju/charms/nova/charm'
@ -138,3 +143,91 @@ class NovaCCHooksTests(CharmTestCase):
quantum_url='http://nova-cc-host1:9696', quantum_plugin='nvp', quantum_url='http://nova-cc-host1:9696', quantum_plugin='nvp',
relation_id=None, relation_id=None,
**FAKE_KS_AUTH_CFG) **FAKE_KS_AUTH_CFG)
def test_db_joined(self):
self.unit_get.return_value = 'nova.foohost.com'
self.is_relation_made.return_value = False
hooks.db_joined()
self.relation_set.assert_called_with(nova_database='nova',
nova_username='nova',
nova_hostname='nova.foohost.com')
self.unit_get.assert_called_with('private-address')
def test_postgresql_nova_db_joined(self):
self.is_relation_made.return_value = False
hooks.pgsql_nova_db_joined()
self.relation_set.assert_called_with(database='nova')
def test_postgresql_neutron_db_joined(self):
self.is_relation_made.return_value = False
hooks.pgsql_neutron_db_joined()
self.relation_set.assert_called_with(database='neutron')
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_nova_joined_with_db(self):
self.is_relation_made.return_value = True
with self.assertRaises(Exception) as context:
hooks.pgsql_nova_db_joined()
self.assertEqual(context.exception.message,
'Attempting to associate a postgresql database when'
' there is already associated a mysql one')
def test_postgresql_neutron_joined_with_db(self):
self.is_relation_made.return_value = True
with self.assertRaises(Exception) as context:
hooks.pgsql_neutron_db_joined()
self.assertEqual(context.exception.message,
'Attempting to associate a postgresql database when'
' there is already associated a mysql one')
@patch.object(hooks, 'CONFIGS')
def test_db_changed_missing_relation_data(self, configs):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = []
hooks.db_changed()
self.log.assert_called_with(
'shared-db relation incomplete. Peer not ready?'
)
@patch.object(hooks, 'CONFIGS')
def test_postgresql_nova_db_changed_missing_relation_data(self, configs):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = []
hooks.postgresql_nova_db_changed()
self.log.assert_called_with(
'pgsql-nova-db relation incomplete. Peer not ready?'
)
def _shared_db_test(self, configs):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = ['shared-db']
configs.write = MagicMock()
hooks.db_changed()
def _postgresql_db_test(self, configs):
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = ['pgsql-nova-db']
configs.write = MagicMock()
hooks.postgresql_nova_db_changed()
@patch.object(hooks, 'CONFIGS')
def test_db_changed(self, configs):
self._shared_db_test(configs)
self.assertTrue(configs.write_all.called)
self.migrate_database.assert_called_with()
@patch.object(hooks, 'CONFIGS')
def test_postgresql_db_changed(self, configs):
self._postgresql_db_test(configs)
self.assertTrue(configs.write_all.called)
self.migrate_database.assert_called_with()

View File

@ -13,15 +13,28 @@ import nova_cc_utils as utils
hookenv.config = _conf hookenv.config = _conf
TO_PATCH = [ TO_PATCH = [
'apt_update',
'apt_upgrade',
'apt_install',
'config', 'config',
'configure_installation_source',
'disable_policy_rcd',
'eligible_leader',
'enable_policy_rcd',
'get_os_codename_install_source',
'log', 'log',
'ml2_migration',
'network_manager', 'network_manager',
'neutron_db_manage',
'neutron_plugin', 'neutron_plugin',
'neutron_plugin_attribute', 'neutron_plugin_attribute',
'os_release', 'os_release',
'register_configs',
'relation_ids', 'relation_ids',
'remote_unit', 'remote_unit',
'_save_script_rc', '_save_script_rc',
'service_start',
'services'
] ]
SCRIPTRC_ENV_VARS = { SCRIPTRC_ENV_VARS = {
@ -103,6 +116,12 @@ PLUGIN_ATTRIBUTES = {
} }
DPKG_OPTS = [
'--option', 'Dpkg::Options::=--force-confnew',
'--option', 'Dpkg::Options::=--force-confdef',
]
def fake_plugin_attribute(plugin, attr, net_manager): def fake_plugin_attribute(plugin, attr, net_manager):
if plugin in PLUGIN_ATTRIBUTES: if plugin in PLUGIN_ATTRIBUTES:
try: try:
@ -345,7 +364,9 @@ class NovaCCUtilsTests(CharmTestCase):
ssh_dir.return_value = '/tmp/foo' ssh_dir.return_value = '/tmp/foo'
self.assertEquals(utils.authorized_keys(), '/tmp/foo/authorized_keys') self.assertEquals(utils.authorized_keys(), '/tmp/foo/authorized_keys')
ssh_dir.assert_called_with(None) ssh_dir.assert_called_with(None)
self.assertEquals(utils.authorized_keys('bar'), '/tmp/foo/authorized_keys') self.assertEquals(
utils.authorized_keys('bar'),
'/tmp/foo/authorized_keys')
ssh_dir.assert_called_with('bar') ssh_dir.assert_called_with('bar')
@patch.object(utils, 'known_hosts') @patch.object(utils, 'known_hosts')
@ -374,7 +395,8 @@ class NovaCCUtilsTests(CharmTestCase):
@patch.object(utils, 'known_hosts') @patch.object(utils, 'known_hosts')
@patch.object(utils, 'authorized_keys') @patch.object(utils, 'authorized_keys')
@patch('os.path.isfile') @patch('os.path.isfile')
def test_ssh_compute_remove(self, isfile, auth_key, known_host): def test_ssh_compute_remove(self, isfile,
auth_key, known_host):
isfile.return_value = False isfile.return_value = False
removed_key = AUTHORIZED_KEYS.split('\n')[2] removed_key = AUTHORIZED_KEYS.split('\n')[2]
@ -454,3 +476,82 @@ class NovaCCUtilsTests(CharmTestCase):
_known_hosts.assert_called_with(None) _known_hosts.assert_called_with(None)
utils.remove_known_host('test', 'bar') utils.remove_known_host('test', 'bar')
_known_hosts.assert_called_with('bar') _known_hosts.assert_called_with('bar')
@patch('subprocess.check_output')
def test_migrate_database(self, check_output):
"Migrate database with nova-manage"
utils.migrate_database()
check_output.assert_called_with(['nova-manage', 'db', 'sync'])
@patch.object(utils, 'get_step_upgrade_source')
@patch.object(utils, 'migrate_database')
@patch.object(utils, 'determine_packages')
def test_upgrade_grizzly_icehouse(self, determine_packages,
migrate_database,
get_step_upgrade_source):
"Simulate a call to do_openstack_upgrade() for grizzly->icehouse"
get_step_upgrade_source.return_value = 'cloud:precise-havana'
self.os_release.side_effect = ['grizzly', 'havana']
self.get_os_codename_install_source.side_effect = [
'havana',
'icehouse']
self.eligible_leader.return_value = True
utils.do_openstack_upgrade()
expected = [call(['stamp', 'grizzly']), call(['upgrade', 'head']),
call(['upgrade', 'head'])]
self.assertEquals(self.neutron_db_manage.call_args_list, expected)
self.apt_update.assert_called_with(fatal=True)
self.apt_upgrade.assert_called_with(options=DPKG_OPTS, fatal=True,
dist=True)
self.apt_install.assert_called_with(determine_packages(), fatal=True)
expected = [call(release='havana'), call(release='icehouse')]
self.assertEquals(self.register_configs.call_args_list, expected)
self.assertEquals(self.ml2_migration.call_count, 1)
self.assertTrue(migrate_database.call_count, 2)
@patch.object(utils, 'get_step_upgrade_source')
@patch.object(utils, 'migrate_database')
@patch.object(utils, 'determine_packages')
def test_upgrade_havana_icehouse(self, determine_packages,
migrate_database,
get_step_upgrade_source):
"Simulate a call to do_openstack_upgrade() for havana->icehouse"
get_step_upgrade_source.return_value = None
self.os_release.return_value = 'havana'
self.get_os_codename_install_source.return_value = 'icehouse'
self.eligible_leader.return_value = True
utils.do_openstack_upgrade()
self.neutron_db_manage.assert_called_with(['upgrade', 'head'])
self.apt_update.assert_called_with(fatal=True)
self.apt_upgrade.assert_called_with(options=DPKG_OPTS, fatal=True,
dist=True)
self.apt_install.assert_called_with(determine_packages(), fatal=True)
self.register_configs.assert_called_with(release='icehouse')
self.assertEquals(self.ml2_migration.call_count, 1)
self.assertTrue(migrate_database.call_count, 1)
@patch.object(utils, '_do_openstack_upgrade')
def test_upgrade_grizzly_icehouse_source(self, _do_openstack_upgrade):
"Verify get_step_upgrade_source() for grizzly->icehouse"
self.config.side_effect = None
self.config.return_value = 'cloud:precise-icehouse'
with patch_open() as (_open, _file):
_file.read = MagicMock()
_file.readline.return_value = ("deb url"
" precise-updates/grizzly main")
utils.do_openstack_upgrade()
expected = [call('cloud:precise-havana'),
call('cloud:precise-icehouse')]
self.assertEquals(_do_openstack_upgrade.call_args_list, expected)
@patch.object(utils, '_do_openstack_upgrade')
def test_upgrade_havana_icehouse_source(self, _do_openstack_upgrade):
"Verify get_step_upgrade_source() for havana->icehouse"
self.config.side_effect = None
self.config.return_value = 'cloud:precise-icehouse'
with patch_open() as (_open, _file):
_file.read = MagicMock()
_file.readline.return_value = "deb url precise-updates/havana main"
utils.do_openstack_upgrade()
expected = [call('cloud:precise-icehouse')]
self.assertEquals(_do_openstack_upgrade.call_args_list, expected)