commit 89a83cd8df8a2cfa1c0d5cc51a063f15da56a4ff Author: James Page Date: Fri Oct 26 17:56:42 2012 +0200 Baseline eod pre-UDS diff --git a/.project b/.project new file mode 100644 index 00000000..528f4924 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + quantum + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/.pydevproject b/.pydevproject new file mode 100644 index 00000000..6ab714cf --- /dev/null +++ b/.pydevproject @@ -0,0 +1,10 @@ + + + + +python 2.7 +Default + +/quantum/hooks + + diff --git a/config.yaml b/config.yaml new file mode 100644 index 00000000..9e94e094 --- /dev/null +++ b/config.yaml @@ -0,0 +1,36 @@ +options: + plugin: + default: ovs + type: string + description: | + Network configuration plugin to use to manage + the quantum network across quantum and nova-compute + nodes. Supported values include: + . + ovs - OpenVSwitch + nvp - Nicera + ext-port: + type: string + description: | + External port to use for routing of instance + traffic to the external public network. + source: + type: string + description: | + Optional configuration to support use of additional sources such as: + . + - ppa:myteam/ppa + - cloud:folsom-proposed + - http://my.archive.com/ubuntu main + . + The last option should be used in conjunction with the key configuration + option. + . + Note that a minimum ceph version of 0.48.2 is required for use with this + charm which is NOT provided by the packages in the main Ubuntu archive + for precise. + key: + type: string + description: | + Key ID to import to the apt keyring to support use with arbitary source + configuration from outside of Launchpad archives or PPA's. \ No newline at end of file diff --git a/hooks/config-changed b/hooks/config-changed new file mode 120000 index 00000000..9416ca6a --- /dev/null +++ b/hooks/config-changed @@ -0,0 +1 @@ +hooks.py \ No newline at end of file diff --git a/hooks/hooks.py b/hooks/hooks.py new file mode 100755 index 00000000..4c65e1e9 --- /dev/null +++ b/hooks/hooks.py @@ -0,0 +1,129 @@ +#!/usr/bin/python + +import utils +import sys +import quantum_utils + + +PLUGIN_PKGS = { + "ovs": [ # TODO: Assumes Quantum Provider Gateway + "quantum-plugin-openvswitch", + "quantum-plugin-openvswitch-agent", + "quantum-l3-agent", + "quantum-dhcp-agent" + ], + "nvp": ["quantum-plugin-nicira"] # No agent required + } + + +def install(): + utils.configure_source() + # TODO: when using the nicira plugin /etc/default/quantum-server + # will also need to be updated to point to the correct configuration + plugin = utils.config_get('plugin') + if plugin in PLUGIN_PKGS.keys(): + if plugin == "ovs": + # Install OVS DKMS first to ensure that the ovs module + # loaded supports GRE tunnels + utils.install('openvswitch-datapath-dkms') + utils.install(['quantum-server'].extend(PLUGIN_PKGS[plugin])) + else: + utils.juju_log('ERROR', 'Please provide a valid plugin config') + sys.exit(1) + + +def config_changed(): + plugin = utils.config_get('plugin') + if plugin in PLUGIN_PKGS.keys(): + if plugin == "ovs": + # TODO: Defaults to Quantum Provider Router + quantum_utils.add_bridge('br-int') + quantum_utils.add_bridge('br-ext') + ext_port = utils.config_get('ext-port') + if ext_port: + quantum_utils.add_bridge_port('br-ex', ext_port) + quantum_utils.configure_core_plugin(plugin) + quantum_utils.configure_local_ip(plugin, + utils.unit_get('private-address')) + else: + utils.juju_log('ERROR', + 'Please provide a valid plugin config') + sys.exit(1) + + +def keystone_joined(): + url = "http://{}:9696/".format(utils.unit_get('private-address')) + utils.relation_set(service="quantum", + region="RegionOne", + public_url=url, + admin_url=url, + internal_url=url) + + +def keystone_changed(): + token = utils.relation_get('admin_token') + service_port = utils.relation_get('service_port') + auth_port = utils.relation_get('auth_port') + service_username = utils.relation_get('service_username') + service_password = utils.relation_get('service_password') + service_tenant = utils.relation_get('service_tenant') + if not (token and + service_port and + auth_port and + service_username and + service_password and + service_tenant): + utils.juju_log('INFO', + 'keystone peer not ready yet') + return + if token == "-1": + utils.juju_log('ERROR', + 'Admin token error') + sys.exit(1) + keystone_host = utils.relation_get('private-address') + utils.juju_log('INFO', 'Configuring quantum for keystone authentication') + quantum_utils.configure_keystone(keystone_host, + token, + service_port, + auth_port, + service_username, + service_password, + service_tenant) + + +def db_joined(): + utils.relation_set(username=quantum_utils.DB_USER, + database=quantum_utils.QUANTUM_DB, + hostname=utils.unit_get('private-address')) + + +def db_changed(): + host = utils.relation_get('private-address') + password = utils.relation_get('password') + if not (host and password): + return + else: + quantum_utils.configure_db_connection(utils.config_get('plugin'), + host, password) + + +def amqp_joined(): + pass + + +def amqp_changed(): + pass + + +utils.do_hooks({ + "install": install, + "config-changed": config_changed, + "identity-service-relation-joined": keystone_joined, + "identity-service-relation-changed": keystone_changed, + "shared-db-relation-joined": db_joined, + "shared-db-relation-changed": db_changed, + "amqp-relation-joined": amqp_joined, + "amqp-relation-changed": amqp_changed + }) + +sys.exit(0) diff --git a/hooks/identity-service-relation-changed b/hooks/identity-service-relation-changed new file mode 120000 index 00000000..9416ca6a --- /dev/null +++ b/hooks/identity-service-relation-changed @@ -0,0 +1 @@ +hooks.py \ No newline at end of file diff --git a/hooks/identity-service-relation-joined b/hooks/identity-service-relation-joined new file mode 120000 index 00000000..9416ca6a --- /dev/null +++ b/hooks/identity-service-relation-joined @@ -0,0 +1 @@ +hooks.py \ No newline at end of file diff --git a/hooks/install b/hooks/install new file mode 120000 index 00000000..9416ca6a --- /dev/null +++ b/hooks/install @@ -0,0 +1 @@ +hooks.py \ No newline at end of file diff --git a/hooks/packages b/hooks/packages new file mode 100644 index 00000000..c7b8e626 --- /dev/null +++ b/hooks/packages @@ -0,0 +1,47 @@ +Quantum Server +============== + +quantum-server + +# OVS +quantum-plugin-openvswitch quantum-plugin-openvswitch-agent + +# Configure connection to the database + -> store in sql_connection in plugins. + +# OVS + +/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini + +enable_tunneling=True +tenant_network_type=gre +tunnel_id_ranges=1:1000 +# only if node is running the agent +local_ip=$(unit-get private-address) # data network + +service quantum-server restart + +# Add internal bridge + +ovs-vsctl add-br br-int + +# restart openvswitch plugin + +# Same everywhere + +quantum-dhcp-agent +quantum-l3-agent + +ovs-vsctl add-br br-ex +ovs-vsctl add-port br-ex $(config-get external-port) + +# Keystone - need to review keystone charm to see how this works. + +keystone service-create --name quantum --type network --description 'OpenStack Networking Service' +keystone endpoint-create --region $REGION --service-id $ID --publicurl \ + 'http://$IP:9696/' --adminurl 'http://$IP:9696/' --internalurl 'http://$IP:9696/' + + +keystone user-create --name=quantum --pass=$SERVICE_PASSWORD --tenant-id service +keystone user-role-add --user_id 45e9461fa61e48f99de1adcd0b38eae7 --role_id e45af7cf33be4dac8070aa8310144ce3 \ + --tenant_id 950fe8e5ed5f4659a8556ac836e8943d diff --git a/hooks/quantum-hooks b/hooks/quantum-hooks new file mode 100755 index 00000000..09bb5aff --- /dev/null +++ b/hooks/quantum-hooks @@ -0,0 +1,131 @@ +#!/bin/bash -e +# vim: set ts=2:set et: + +[ -f hooks/openstack-common ] && . hooks/openstack-common + +ARG0=${0##*/} +PLUGIN=$(config-get plugin) +EXT_PORT=$(config-get ext-port) +PRIVATE_ADDRESS=$(unit-get private-address) +DB_USER=quantum +QUANTUM_DB=quantum + +OVS_PLUGIN_CONF="/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini" +NVP_PLUGIN_CONF="/etc/quantum/plugins/nicira/nvp.ini" + +case $PLUGIN in + "ovs") PLUGIN_CONF=$OVS_PLUGIN_CONF ;; + "nvp") PLUGIN_CONF=$NVP_PLUGIN_CONF ;; +esac + +function install_hook() { + case $PLUGIN in + "ovs") + PLUGIN_PKGS="quantum-plugin-openvswitch quantum-plugin-openvswitch-agent" + ;; + "nvp") + # XXX implement nvp + ;; + *) + juju-log "Unsupported plugin specified" + exit 1 + ;; + esac + + apt-get install quantum-server $PLUGIN_PKGS \ + quantum-dhcp-agent \ + quantum-l3-agent \ + openvswitch-datapath-dkms + + ovs-vsctl add-br br-int + ovs-vsctl add-br br-ex +} + +function config_changed_hook() { + ovs-vsctl add-br br-int + ovs-vsctl add-br br-ex + if [ -n "$EXT_PORT" ]; then + juju-log "Adding $EXT_PORT to external bridge for external access" + ovs-vsctl add-port br-ex $EXT_PORT + fi + + case $PLUGIN in + "ovs") + CORE_PLUGIN="quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPluginV2" + ;; + "nvp") + CORE_PLUGIN="quantum.plugins.nicira.nicira_nvp_plugin.QuantumPlugin.NvpPluginV2" + ;; + esac + juju-log "Configuring quantum with core_plugin: $CORE_PLUGIN" + set_or_update core_plugin $CORE_PLUGIN /etc/quantum/quantum.conf +} + +function start() { + +} + +function stop() { + +} + +function keystone_joined { + # advertise our API endpoint to keystone + url="http://$(unit-get private-address):9696/v1" + public_url="http://$(unit-get public-address):9696/v1" + relation-set service="quantum" \ + region="RegionOne" public_url=$public_url admin_url=$url internal_url=$url +} + +function keystone_changed { + # we hopefully get a token in return. configure middleware accordingly + token=$(relation-get admin_token) + service_port=$(relation-get service_port) + auth_port=$(relation-get auth_port) + service_username=$(relation-get service_username) + service_password=$(relation-get service_password) + service_tenant=$(relation-get service_tenant) + [[ -z "$token" ]] || [[ -z "$service_port" ]] || [[ -z "$auth_port" ]] || + [[ -z "$service_username" ]] || [[ -z "$service_password" ]] || + [[ -z "$service_tenant" ]] && juju-log "Peer not ready" && + exit 0 + [[ "$token" == "-1" ]] && + juju-log "admin token error" && exit 1 + juju-log "Acquired admin. token" + keystone_host=$(relation-get private-address) +} + +function db_joined { + juju-log "Requesting access to $QUANTUM_DB for $DB_USER@$PRIVATE_ADDRESS" + relation-set database=$QUANTUM_DB username=$DB_USER hostname=$PRIVATE_ADDRESS +} + +function db_changed { + DB_HOST=$(relation-get private-address) + DB_PASSWORD=$(relation-get password) + if [ -z "$DB_HOST" ] || [ -z "$DB_PASSWORD" ] ; then + echo "DB_HOST || DB_PASSWORD not yet set. Exit 0 and retry" + exit 0 + else + echo "Received password from $DB_HOST" + fi + juju-log "Configuring registry for access to $QUANTUM_DB@$DB_HOST" + set_or_update sql_connection \ + "mysql://$DB_USER:$DB_PASSWORD@$DB_HOST/$QUANTUM_DB?charset=utf8" \ + $PLUGIN_CONF +} + + +case $ARG0 in + "install") install_hook ;; + "config-changed") config_changed_hook ;; + "identity-service-relation-joined") keystone_joined ;; + "identity-service-relation-changed") keystone_changed ;; + "shared-db-relation-joined") db_joined ;; + "shared-db-relation-changed") db_changed;; +esac + +# TODO: Add hooks for rabbitmq integration - dhcpagent seems to require: +# /etc/quantum/quantum.conf: +# rabbit_password = openstack +# rabbit_host = 100.1.1.10 diff --git a/hooks/quantum_utils.py b/hooks/quantum_utils.py new file mode 100644 index 00000000..30777254 --- /dev/null +++ b/hooks/quantum_utils.py @@ -0,0 +1,163 @@ + +import utils +import subprocess +import os + +OVS_PLUGIN = \ + "quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPluginV2" +NVP_PLUGIN = \ + "quantum.plugins.nicira.nicira_nvp_plugin.QuantumPlugin.NvpPluginV2" +CORE_PLUGIN = { + "ovs": OVS_PLUGIN, + "nvp": NVP_PLUGIN + } + +OVS_PLUGIN_CONF = \ + "/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini" +NVP_PLUGIN_CONF = \ + "/etc/quantum/plugins/nicira/nvp.ini" +PLUGIN_CONF = { + "ovs": OVS_PLUGIN_CONF, + "nvp": NVP_PLUGIN_CONF + } + +DB_USER = "quantum" +QUANTUM_DB = "quantum" + +QUANTUM_CONF = "/etc/quantum/quantum.conf" +L3_AGENT_CONF = "/etc/quantum/l3_agent.ini" +QUANTUM_API_CONF = "/etc/quantum/api-paste.ini" + +MYSQL_CS = "mysql://%(user)s:%(password)s@%(host)s/%(db)s?charset=utf8" + + +def update_config_block(block, conf, **kwargs): + """ + Updates configuration file blocks given kwargs. + Can be used to update driver settings for a particular backend, + setting the sql connection, etc. + + Parses block heading as '[block]' + + If block does not exist, a new block will be created at end of file with + given kwargs + """ + f = open(conf, "r+") + orig = f.readlines() + new = [] + heading = "[{}]\n".format(block) + + lines = len(orig) + ln = 0 + + def update_block(block): + for k, v in kwargs.iteritems(): + for l in block: + if l.strip().split(" ")[0] == k: + block[block.index(l)] = "{} = {}\n".format(k, v) + return + block.append('{} = {}\n'.format(k, v)) + block.append('\n') + + found = False + while ln < lines: + if orig[ln] != heading: + new.append(orig[ln]) + ln += 1 + else: + new.append(orig[ln]) + ln += 1 + block = [] + while orig[ln].strip() != '': + block.append(orig[ln]) + ln += 1 + update_block(block) + new += block + found = True + + if not found: + if new[(len(new) - 1)].strip() != '': + new.append('\n') + new.append('{}'.format(heading)) + for k, v in kwargs.iteritems(): + new.append('{} = {}\n'.format(k, v)) + new.append('\n') + + # backup original config + backup = open(conf + '.juju-back', 'w+') + for l in orig: + backup.write(l) + backup.close() + + # update config + f.seek(0) + f.truncate() + for l in new: + f.write(l) + + +def configure_core_plugin(plugin): + update_config_block("DEFAULT", QUANTUM_CONF, + core_plugin=CORE_PLUGIN[plugin]) + + +def configure_db_connection(plugin, host, password): + update_config_block( + "DATABASE", PLUGIN_CONF[plugin], + sql_connection=MYSQL_CS.format(host=host, + user=DB_USER, + password=password, + db=QUANTUM_DB) + ) + + +def configure_local_ip(plugin, address): + update_config_block("OVS", PLUGIN_CONF[plugin], local_ip=address) + + +def configure_keystone(keystone_host, + token, + service_port, + auth_port, + username, + password, + tenant): + if os.path.exists(L3_AGENT_CONF): # Indicated OVS model is in use. + update_config_block("DEFAULT", L3_AGENT_CONF, + auth_url="http://{}:{}/v2.0".format(keystone_host, + auth_port), + auth_region="RegionOne", + admin_tenant_name=tenant, + admin_user=username, + admin_password=password) + update_config_block("filter:authtoken", QUANTUM_API_CONF, + auth_host=keystone_host, + auth_port=auth_port, + admin_tenant_name=tenant, + admin_user=username, + admin_password=password) + + +def add_bridge(name): + status = subprocess.check_output(["ovs-vsctl", "show"]) + if "Bridge {}".format(name) not in status: + subprocess.check_call(["ovs-vsctl", "add-br", name]) + + +def del_bridge(name): + status = subprocess.check_output(["ovs-vsctl", "show"]) + if "Bridge {}".format(name) in status: + subprocess.check_call(["ovs-vsctl", "del-br", name]) + + +def add_bridge_port(name, port): + status = subprocess.check_output(["ovs-vsctl", "show"]) + if "Bridge {}".format(name) in status: + subprocess.check_call(["ovs-vsctl", "add-port", name, port]) + + +def del_bridge_port(name, port): + status = subprocess.check_output(["ovs-vsctl", "show"]) + if ("Bridge {}".format(name) in status and + "Interface \"{}\"".format(port) in status): + subprocess.check_call(["ovs-vsctl", "del-port", name, port]) diff --git a/hooks/shared-db-relation-changed b/hooks/shared-db-relation-changed new file mode 120000 index 00000000..9416ca6a --- /dev/null +++ b/hooks/shared-db-relation-changed @@ -0,0 +1 @@ +hooks.py \ No newline at end of file diff --git a/hooks/shared-db-relation-joined b/hooks/shared-db-relation-joined new file mode 120000 index 00000000..9416ca6a --- /dev/null +++ b/hooks/shared-db-relation-joined @@ -0,0 +1 @@ +hooks.py \ No newline at end of file diff --git a/hooks/start b/hooks/start new file mode 120000 index 00000000..9416ca6a --- /dev/null +++ b/hooks/start @@ -0,0 +1 @@ +hooks.py \ No newline at end of file diff --git a/hooks/stop b/hooks/stop new file mode 120000 index 00000000..9416ca6a --- /dev/null +++ b/hooks/stop @@ -0,0 +1 @@ +hooks.py \ No newline at end of file diff --git a/hooks/utils.py b/hooks/utils.py new file mode 100644 index 00000000..d1a14411 --- /dev/null +++ b/hooks/utils.py @@ -0,0 +1,185 @@ + +# +# Copyright 2012 Canonical Ltd. +# +# Authors: +# James Page +# Paul Collins +# + +import os +import subprocess +import socket +import sys + + +def do_hooks(hooks): + hook = os.path.basename(sys.argv[0]) + + try: + hooks[hook]() + except KeyError: + juju_log('INFO', + "This charm doesn't know how to handle '{}'.".format(hook)) + + +def install(*pkgs): + cmd = [ + 'apt-get', + '-y', + 'install' + ] + for pkg in pkgs: + cmd.append(pkg) + subprocess.check_call(cmd) + +TEMPLATES_DIR = 'templates' + +try: + import jinja2 +except ImportError: + install('python-jinja2') + import jinja2 + + +def render_template(template_name, context, template_dir=TEMPLATES_DIR): + templates = jinja2.Environment( + loader=jinja2.FileSystemLoader(template_dir) + ) + template = templates.get_template(template_name) + return template.render(context) + + +def configure_source(): + source = config_get('source') + if not source: + return + if (source.startswith('ppa:') or + source.startswith('cloud:')): + cmd = [ + 'add-apt-repository', + source + ] + subprocess.check_call(cmd) + if source.startswith('http:'): + with open('/etc/apt/sources.list.d/ceph.list', 'w') as apt: + apt.write("deb " + source + "\n") + key = config_get('key') + if key != "": + cmd = [ + 'apt-key', + 'adv', '--keyserver keyserver.ubuntu.com', + '--recv-keys', key + ] + subprocess.check_call(cmd) + cmd = [ + 'apt-get', + 'update' + ] + subprocess.check_call(cmd) + +# Protocols +TCP = 'TCP' +UDP = 'UDP' + + +def expose(port, protocol='TCP'): + cmd = [ + 'open-port', + '{}/{}'.format(port, protocol) + ] + subprocess.check_call(cmd) + + +def juju_log(severity, message): + cmd = [ + 'juju-log', + '--log-level', severity, + message + ] + subprocess.check_call(cmd) + + +def relation_ids(relation): + cmd = [ + 'relation-ids', + relation + ] + return subprocess.check_output(cmd).split() # IGNORE:E1103 + + +def relation_list(rid): + cmd = [ + 'relation-list', + '-r', rid, + ] + return subprocess.check_output(cmd).split() # IGNORE:E1103 + + +def relation_get(attribute, unit=None, rid=None): + cmd = [ + 'relation-get', + ] + if rid: + cmd.append('-r') + cmd.append(rid) + cmd.append(attribute) + if unit: + cmd.append(unit) + value = subprocess.check_output(cmd).strip() # IGNORE:E1103 + if value == "": + return None + else: + return value + + +def relation_set(**kwargs): + cmd = [ + 'relation-set' + ] + args = [] + for k, v in kwargs.items(): + if k == 'rid': + cmd.append('-r') + cmd.append(v) + else: + args.append('{}={}'.format(k, v)) + cmd += args + subprocess.check_call(cmd) + + +def unit_get(attribute): + cmd = [ + 'unit-get', + attribute + ] + value = subprocess.check_output(cmd).strip() # IGNORE:E1103 + if value == "": + return None + else: + return value + + +def config_get(attribute): + cmd = [ + 'config-get', + attribute + ] + value = subprocess.check_output(cmd).strip() # IGNORE:E1103 + if value == "": + return None + else: + return value + + +def get_unit_hostname(): + return socket.gethostname() + + +def get_host_ip(hostname=unit_get('private-address')): + cmd = [ + 'dig', + '+short', + hostname + ] + return subprocess.check_output(cmd).strip() # IGNORE:E1103 diff --git a/metadata.yaml b/metadata.yaml new file mode 100644 index 00000000..8333af66 --- /dev/null +++ b/metadata.yaml @@ -0,0 +1,26 @@ +name: quantum +summary: Virtual Networking for OpenStack +maintainer: James Page +description: | + Quantum is a virtual network service for Openstack, and a part of + Netstack. Just like OpenStack Nova provides an API to dynamically + request and configure virtual servers, Quantum provides an API to + dynamically request and configure virtual networks. These networks + connect "interfaces" from other OpenStack services (e.g., virtual NICs + from Nova VMs). The Quantum API supports extensions to provide + advanced network capabilities (e.g., QoS, ACLs, network monitoring, + etc.) +provides: + network-manager: + interface: quantum +requires: + cloud-compute: + interface: nova-compute + shared-db: + interface: mysql-shared + amqp: + interface: rabbitmq + identity-service: + interface: keystone + cloud-controller: + interface: nova diff --git a/revision b/revision new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/revision @@ -0,0 +1 @@ +1