[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
lint:
@flake8 --exclude hooks/charmhelpers hooks
@flake8 --exclude hooks/charmhelpers unit_tests
@flake8 --exclude hooks/charmhelpers hooks unit_tests
@charm proof
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
description: |
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
issued by Keystone (if it is configured to do so).
ssl_key:
type: string
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:
type: boolean
default: False
description: |
If set to True, supporting services will log to syslog.
# Neutron NVP Plugin configuration
nvp-controllers:

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import json
import os
import time
from base64 import b64decode
@ -113,7 +114,8 @@ class OSContextGenerator(object):
class SharedDBContext(OSContextGenerator):
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.
This is useful for parsing access for multiple databases returned via
@ -122,6 +124,7 @@ class SharedDBContext(OSContextGenerator):
self.relation_prefix = relation_prefix
self.database = database
self.user = user
self.ssl_dir = ssl_dir
def __call__(self):
self.database = self.database or config('database')
@ -139,19 +142,74 @@ class SharedDBContext(OSContextGenerator):
for rid in relation_ids('shared-db'):
for unit in related_units(rid):
passwd = relation_get(password_setting, rid=rid, unit=unit)
rdata = relation_get(rid=rid, unit=unit)
ctxt = {
'database_host': relation_get('db_host', rid=rid,
unit=unit),
'database_host': rdata.get('db_host'),
'database': self.database,
'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):
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')
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):
interfaces = ['identity-service']
@ -161,24 +219,25 @@ class IdentityServiceContext(OSContextGenerator):
for rid in relation_ids('identity-service'):
for unit in related_units(rid):
rdata = relation_get(rid=rid, unit=unit)
ctxt = {
'service_port': relation_get('service_port', rid=rid,
unit=unit),
'service_host': relation_get('service_host', rid=rid,
unit=unit),
'auth_host': relation_get('auth_host', rid=rid, unit=unit),
'auth_port': relation_get('auth_port', rid=rid, unit=unit),
'admin_tenant_name': relation_get('service_tenant',
rid=rid, unit=unit),
'admin_user': relation_get('service_username', rid=rid,
unit=unit),
'admin_password': relation_get('service_password', rid=rid,
unit=unit),
# XXX: Hard-coded http.
'service_protocol': 'http',
'auth_protocol': 'http',
'service_port': rdata.get('service_port'),
'service_host': rdata.get('service_host'),
'auth_host': rdata.get('auth_host'),
'auth_port': rdata.get('auth_port'),
'admin_tenant_name': rdata.get('service_tenant'),
'admin_user': rdata.get('service_username'),
'admin_password': rdata.get('service_password'),
'service_protocol':
rdata.get('service_protocol') or 'http',
'auth_protocol':
rdata.get('auth_protocol') or 'http',
}
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 {}
@ -186,6 +245,9 @@ class IdentityServiceContext(OSContextGenerator):
class AMQPContext(OSContextGenerator):
interfaces = ['amqp']
def __init__(self, ssl_dir=None):
self.ssl_dir = ssl_dir
def __call__(self):
log('Generating template context for amqp')
conf = config()
@ -196,7 +258,6 @@ class AMQPContext(OSContextGenerator):
log('Could not generate shared_db context. '
'Missing required charm config options: %s.' % e)
raise OSContextError
ctxt = {}
for rid in relation_ids('amqp'):
ha_vip_only = False
@ -214,6 +275,14 @@ class AMQPContext(OSContextGenerator):
unit=unit),
'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:
ctxt['rabbitmq_ha_queues'] = True
@ -221,6 +290,16 @@ class AMQPContext(OSContextGenerator):
rid=rid, unit=unit) is not None
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!
break
# Used for active/active rabbitmq >= grizzly
@ -391,6 +470,8 @@ class ApacheSSLContext(OSContextGenerator):
'private_address': unit_get('private-address'),
'endpoints': []
}
if is_clustered():
ctxt['private_address'] = config('vip')
for api_port in self.external_ports:
ext_port = determine_apache_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()
return 'linux-headers-%s' % kver
QUANTUM_CONF_DIR = '/etc/quantum'
def kernel_version():
""" Retrieve the current major kernel version as a tuple e.g. (3, 13) """
@ -35,6 +37,8 @@ def determine_dkms_package():
# legacy
def quantum_plugins():
from charmhelpers.contrib.openstack import context
return {
@ -46,7 +50,8 @@ def quantum_plugins():
'contexts': [
context.SharedDBContext(user=config('neutron-database-user'),
database=config('neutron-database'),
relation_prefix='neutron')],
relation_prefix='neutron',
ssl_dir=QUANTUM_CONF_DIR)],
'services': ['quantum-plugin-openvswitch-agent'],
'packages': [[headers_package()] + determine_dkms_package(),
['quantum-plugin-openvswitch-agent']],
@ -61,7 +66,8 @@ def quantum_plugins():
'contexts': [
context.SharedDBContext(user=config('neutron-database-user'),
database=config('neutron-database'),
relation_prefix='neutron')],
relation_prefix='neutron',
ssl_dir=QUANTUM_CONF_DIR)],
'services': [],
'packages': [],
'server_packages': ['quantum-server',
@ -70,6 +76,8 @@ def quantum_plugins():
}
}
NEUTRON_CONF_DIR = '/etc/neutron'
def neutron_plugins():
from charmhelpers.contrib.openstack import context
@ -83,7 +91,8 @@ def neutron_plugins():
'contexts': [
context.SharedDBContext(user=config('neutron-database-user'),
database=config('neutron-database'),
relation_prefix='neutron')],
relation_prefix='neutron',
ssl_dir=NEUTRON_CONF_DIR)],
'services': ['neutron-plugin-openvswitch-agent'],
'packages': [[headers_package()] + determine_dkms_package(),
['neutron-plugin-openvswitch-agent']],
@ -98,7 +107,8 @@ def neutron_plugins():
'contexts': [
context.SharedDBContext(user=config('neutron-database-user'),
database=config('neutron-database'),
relation_prefix='neutron')],
relation_prefix='neutron',
ssl_dir=NEUTRON_CONF_DIR)],
'services': [],
'packages': [],
'server_packages': ['neutron-server',

View File

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

View File

@ -2,7 +2,9 @@ from os import stat
from stat import S_ISBLK
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.
'''
check_call(['sgdisk', '--zap-all', '--clear',
'--mbrtogpt', block_device])
# sometimes sgdisk exits non-zero; this is OK, dd will clean up
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 (
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.contrib.openstack import context, neutron, utils
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):
@ -112,6 +117,24 @@ class HAProxyContext(context.HAProxyContext):
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):
interfaces = []
@ -148,10 +171,12 @@ class NeutronCCContext(context.NeutronContext):
','.join(_config['nvp-controllers'].split())
ctxt['nvp_controllers_list'] = \
_config['nvp-controllers'].split()
ctxt['nova_url'] = "{}:8774/v2".format(canonical_url())
return ctxt
class IdentityServiceContext(context.IdentityServiceContext):
def __call__(self):
ctxt = super(IdentityServiceContext, self).__call__()
if not ctxt:
@ -159,7 +184,23 @@ class IdentityServiceContext(context.IdentityServiceContext):
# the ec2 api needs to know the location of the keystone ec2
# tokens endpoint, set in nova.conf
ec2_tokens = 'http://%s:%s/v2.0/ec2tokens' % (ctxt['service_host'],
ctxt['service_port'])
ec2_tokens = '%s://%s:%s/v2.0/ec2tokens' % (
ctxt['service_protocol'] or 'http',
ctxt['service_host'],
ctxt['service_port']
)
ctxt['keystone_ec2_url'] = ec2_tokens
ctxt['region'] = config('region')
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,
config,
charm_dir,
is_relation_made,
log,
ERROR,
relation_get,
relation_ids,
relation_set,
@ -96,8 +98,9 @@ def install():
@hooks.hook('config-changed')
@restart_on_change(restart_map(), stopstart=True)
def config_changed():
global CONFIGS
if openstack_upgrade_available('nova-common'):
do_openstack_upgrade(configs=CONFIGS)
CONFIGS = do_openstack_upgrade()
save_script_rc()
configure_https()
CONFIGS.write_all()
@ -110,6 +113,7 @@ def amqp_joined(relation_id=None):
@hooks.hook('amqp-relation-changed')
@hooks.hook('amqp-relation-departed')
@restart_on_change(restart_map())
def amqp_changed():
if 'amqp' not in CONFIGS.complete_contexts():
@ -124,6 +128,14 @@ def amqp_changed():
@hooks.hook('shared-db-relation-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'),
nova_username=config('database-user'),
nova_hostname=unit_get('private-address'))
@ -134,18 +146,37 @@ def db_joined():
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')
@restart_on_change(restart_map())
def db_changed():
if 'shared-db' not in CONFIGS.complete_contexts():
log('shared-db relation incomplete. Peer not ready?')
return
CONFIGS.write(NOVA_CONF)
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'))
CONFIGS.write_all()
if eligible_leader(CLUSTER_RES):
migrate_database()
@ -154,6 +185,30 @@ def db_changed():
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')
@restart_on_change(restart_map())
def image_service_changed():
@ -211,6 +266,8 @@ def _auth_config():
cfg = {
'auth_host': ks_auth_host,
'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_username': auth_token_config('admin_user'),
'service_password': auth_token_config('admin_password'),
@ -226,7 +283,8 @@ def _auth_config():
def save_novarc():
auth = _auth_config()
# 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:
out.write('export OS_USERNAME=%s\n' % auth['service_username'])
out.write('export OS_PASSWORD=%s\n' % auth['service_password'])
@ -385,7 +443,9 @@ def ha_changed():
'identity-service-relation-broken',
'image-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')
def relation_broken():
CONFIGS.write_all()
@ -436,6 +496,8 @@ def nova_vmware_relation_changed():
def upgrade_charm():
for r_id in relation_ids('amqp'):
amqp_joined(relation_id=r_id)
for r_id in relation_ids('identity-service'):
identity_joined(rid=r_id)
def main():

View File

@ -22,8 +22,9 @@ from charmhelpers.contrib.openstack.utils import (
save_script_rc as _save_script_rc)
from charmhelpers.fetch import (
apt_install,
apt_upgrade,
apt_update,
apt_install,
)
from charmhelpers.core.hookenv import (
@ -36,6 +37,10 @@ from charmhelpers.core.hookenv import (
ERROR,
)
from charmhelpers.core.host import (
service_start
)
import nova_cc_context
@ -49,6 +54,7 @@ BASE_PACKAGES = [
'haproxy',
'python-keystoneclient',
'python-mysqldb',
'python-psycopg2',
'uuid',
]
@ -69,11 +75,15 @@ API_PORTS = {
'quantum-server': 9696,
}
NOVA_CONF = '/etc/nova/nova.conf'
NOVA_API_PASTE = '/etc/nova/api-paste.ini'
QUANTUM_CONF = '/etc/quantum/quantum.conf'
QUANTUM_API_PASTE = '/etc/quantum/api-paste.ini'
NEUTRON_CONF = '/etc/neutron/neutron.conf'
NOVA_CONF_DIR = "/etc/nova"
QUANTUM_CONF_DIR = "/etc/quantum"
NEUTRON_CONF_DIR = "/etc/neutron"
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'
APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend'
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([
(NOVA_CONF, {
'services': BASE_SERVICES,
'contexts': [context.AMQPContext(),
context.SharedDBContext(relation_prefix='nova'),
'contexts': [context.AMQPContext(ssl_dir=NOVA_CONF_DIR),
context.SharedDBContext(
relation_prefix='nova', ssl_dir=NOVA_CONF_DIR),
nova_cc_context.NovaPostgresqlDBContext(),
context.ImageServiceContext(),
context.OSConfigFlagContext(),
context.SubordinateConfigContext(
@ -104,11 +116,17 @@ BASE_RESOURCE_MAP = OrderedDict([
}),
(QUANTUM_CONF, {
'services': ['quantum-server'],
'contexts': [context.AMQPContext(),
context.SyslogContext(),
'contexts': [context.AMQPContext(ssl_dir=QUANTUM_CONF_DIR),
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.IdentityServiceContext(),
nova_cc_context.NeutronCCContext()],
nova_cc_context.NeutronCCContext()
context.SyslogContext()],
}),
(QUANTUM_DEFAULT, {
'services': ['quantum-server'],
@ -120,11 +138,17 @@ BASE_RESOURCE_MAP = OrderedDict([
}),
(NEUTRON_CONF, {
'services': ['neutron-server'],
'contexts': [context.AMQPContext(),
context.SyslogContext(),
'contexts': [context.AMQPContext(ssl_dir=NEUTRON_CONF_DIR),
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.NeutronCCContext(),
nova_cc_context.HAProxyContext()],
nova_cc_context.HAProxyContext()
context.SyslogContext()],
}),
(NEUTRON_DEFAULT, {
'services': ['neutron-server'],
@ -195,6 +219,10 @@ def resource_map():
resource_map[conf]['contexts'].append(
nova_cc_context.NeutronCCContext())
# update for postgres
resource_map[conf]['contexts'].append(
nova_cc_context.NeutronPostgresqlDBContext())
# nova-conductor for releases >= G.
if os_release('nova-common') not in ['essex', 'folsom']:
resource_map['/etc/nova/nova.conf']['services'] += ['nova-conductor']
@ -211,8 +239,8 @@ def resource_map():
return resource_map
def register_configs():
release = os_release('nova-common')
def register_configs(release=None):
release = release or os_release('nova-common')
configs = templating.OSConfigRenderer(templates_dir=TEMPLATES,
openstack_release=release)
for cfg, rscs in resource_map().iteritems():
@ -226,10 +254,18 @@ def restart_map():
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():
'''Assemble a list of API ports for services we are managing'''
ports = []
for cfg, services in restart_map().iteritems():
for services in restart_map().values():
for service in services:
try:
ports.append(API_PORTS[service])
@ -245,7 +281,7 @@ def api_port(service):
def determine_packages():
# currently all packages match service names
packages = [] + BASE_PACKAGES
for k, v in resource_map().iteritems():
for v in resource_map().values():
packages.extend(v['services'])
if network_manager() in ['neutron', 'quantum']:
pkgs = neutron_plugin_attribute(neutron_plugin(), 'server_packages',
@ -273,27 +309,162 @@ def save_script_rc():
_save_script_rc(**env_vars)
def do_openstack_upgrade(configs):
new_src = config('openstack-origin')
def get_step_upgrade_source(new_src):
'''
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)
log('Performing OpenStack upgrade to %s.' % (new_os_rel))
configure_installation_source(new_src)
apt_update()
dpkg_opts = [
'--option', 'Dpkg::Options::=--force-confnew',
'--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
configs.set_release(openstack_release=new_os_rel)
configs.write_all()
apt_update(fatal=True)
apt_upgrade(options=dpkg_opts, fatal=True, dist=True)
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):
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():
@ -315,10 +486,10 @@ def migrate_database():
def auth_token_config(setting):
'''
"""
Returns currently configured value for setting in api-paste.ini's
authtoken section, or None.
'''
"""
config = ConfigParser.RawConfigParser()
config.read('/etc/nova/api-paste.ini')
try:
@ -363,7 +534,10 @@ def authorized_keys(user=None):
def ssh_known_host_key(host, user=None):
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):
@ -459,10 +633,14 @@ def determine_endpoints(url):
'''Generates a dictionary containing all relevant endpoints to be
passed to keystone as relation settings.'''
region = config('region')
os_rel = os_release('nova-common')
# TODO: Configurable nova API version.
nova_url = ('%s:%s/v1.1/$(tenant_id)s' %
(url, api_port('nova-api-os-compute')))
if os_rel >= 'grizzly':
nova_url = ('%s:%s/v2/$(tenant_id)s' %
(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'))
nova_volume_url = ('%s:%s/v1/$(tenant_id)s' %
(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:
shared-db:
interface: mysql-shared
pgsql-nova-db:
interface: pgsql
pgsql-neutron-db:
interface: pgsql
amqp:
interface: rabbitmq
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 }}
--ec2_private_dns_show_ip
{% 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 -%}
{% if rabbitmq_host -%}
--rabbit_host={{ rabbitmq_host }}

View File

@ -25,11 +25,16 @@ compute_driver=libvirt.LibvirtDriver
keystone_ec2_url = {{ keystone_ec2_url }}
{% endif -%}
{% if database_host -%}
sql_connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}
{% endif -%}
{% include "parts/database" %}
{% 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_userid = {{ rabbitmq_user }}
rabbit_password = {{ rabbitmq_password }}

View File

@ -11,7 +11,7 @@ local_ip = {{ local_ip }}
[DATABASE]
{% 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
{% else -%}
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 -%}
{% if core_plugin -%}
core_plugin = {{ core_plugin }}
{% if neutron_plugin in ['ovs', 'ml2'] -%}
service_plugins = 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 -%}
{% if rabbitmq_host -%}
rabbit_host = {{ rabbitmq_host }}
rabbit_userid = {{ rabbitmq_user }}
rabbit_password = {{ rabbitmq_password }}
rabbit_virtual_host = {{ rabbitmq_virtual_host }}
{% endif -%}
{% include "parts/rabbitmq" %}
[quotas]
quota_driver = neutron.db.quota_db.DbQuotaDriver
@ -51,5 +50,7 @@ admin_user = {{ admin_user }}
admin_password = {{ admin_password }}
{% endif -%}
{% include "parts/section-database" %}
[lbaas]
[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 }}
default_tz_uuid = {{ nvp_tz_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
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]
{% if neutron_security_groups -%}
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
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
_map = utils.restart_map
@ -15,7 +16,6 @@ import nova_cc_hooks as hooks
utils.register_configs = _reg
utils.restart_map = _map
TO_PATCH = [
'api_port',
'apt_update',
@ -29,8 +29,11 @@ TO_PATCH = [
'determine_packages',
'determine_ports',
'open_port',
'is_relation_made',
'log',
'relation_get',
'relation_set',
'relation_ids',
'ssh_compute_add',
'ssh_known_hosts_b64',
'ssh_authorized_keys_b64',
@ -42,6 +45,7 @@ TO_PATCH = [
'eligible_leader',
'keystone_ca_cert_b64',
'neutron_plugin',
'migrate_database',
]
@ -63,6 +67,7 @@ class NovaCCHooksTests(CharmTestCase):
def setUp(self):
super(NovaCCHooksTests, self).setUp(hooks, TO_PATCH)
self.config.side_effect = self.test_config.get
self.relation_get.side_effect = self.test_relation.get
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',
relation_id=None,
**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
TO_PATCH = [
'apt_update',
'apt_upgrade',
'apt_install',
'config',
'configure_installation_source',
'disable_policy_rcd',
'eligible_leader',
'enable_policy_rcd',
'get_os_codename_install_source',
'log',
'ml2_migration',
'network_manager',
'neutron_db_manage',
'neutron_plugin',
'neutron_plugin_attribute',
'os_release',
'register_configs',
'relation_ids',
'remote_unit',
'_save_script_rc',
'service_start',
'services'
]
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):
if plugin in PLUGIN_ATTRIBUTES:
try:
@ -345,7 +364,9 @@ class NovaCCUtilsTests(CharmTestCase):
ssh_dir.return_value = '/tmp/foo'
self.assertEquals(utils.authorized_keys(), '/tmp/foo/authorized_keys')
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')
@patch.object(utils, 'known_hosts')
@ -374,7 +395,8 @@ class NovaCCUtilsTests(CharmTestCase):
@patch.object(utils, 'known_hosts')
@patch.object(utils, 'authorized_keys')
@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
removed_key = AUTHORIZED_KEYS.split('\n')[2]
@ -454,3 +476,82 @@ class NovaCCUtilsTests(CharmTestCase):
_known_hosts.assert_called_with(None)
utils.remove_known_host('test', '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)