From d5a60ecd53c6746e7fa67112dea5523344d430e9 Mon Sep 17 00:00:00 2001 From: James Page Date: Wed, 7 Nov 2012 12:11:38 +0000 Subject: [PATCH] Refactoring to include external network setup as part of charm --- config.yaml | 43 ++++++- hooks/hooks.py | 159 ++++++++++++++----------- hooks/network-manager-relation-changed | 1 - hooks/quantum_utils.py | 121 ++++++++++++++++++- hooks/utils.py | 20 ++-- revision | 2 +- 6 files changed, 268 insertions(+), 78 deletions(-) delete mode 120000 hooks/network-manager-relation-changed diff --git a/config.yaml b/config.yaml index 9e94e094..5223f673 100644 --- a/config.yaml +++ b/config.yaml @@ -8,12 +8,53 @@ options: 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. + region: + type: string + default: RegionOne + description: | + OpenStack region that this quantum service supports. + conf-ext-net: + type: string + default: doti + description: Configure external network for quantum using + network configuration below. + ext-net-name: + type: string + default: ext_net + description: | + Name of external network configuration to create for + public access to instances/floating IP's. + ext-gw-ip: + type: string + default: 192.168.21.100 + description: | + IP address to assign to external bridge for external network + access. + ext-net-cidr: + type: string + default: 192.168.21.0/24 + description: | + External network addressing + ext-net-gateway: + type: string + default: 192.168.21.1 + description: | + IP of the public network gateway (i.e. external router) + pool-floating-start: + type: string + default: 192.168.21.130 + description: | + Start of default floating IP range. + pool-floating-end: + type: string + default: 192.168.21.200 + description: | + End of default floating IP range. source: type: string description: | diff --git a/hooks/hooks.py b/hooks/hooks.py index 13947bf7..979447f8 100755 --- a/hooks/hooks.py +++ b/hooks/hooks.py @@ -2,62 +2,73 @@ import utils import sys -import quantum_utils +import quantum_utils as qutils import os - -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 - } +PLUGIN = utils.config_get('plugin') 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": + if PLUGIN in qutils.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', 'python-mysqldb', - *PLUGIN_PKGS[plugin]) + *qutils.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 in qutils.PLUGIN_PKGS.keys(): render_api_paste_conf() render_quantum_conf() render_plugin_conf() - if plugin == "ovs": - # TODO: Defaults to Quantum Provider Router - quantum_utils.add_bridge('br-int') - quantum_utils.add_bridge('br-ex') + render_l3_agent_conf() + if PLUGIN == "ovs": + qutils.add_bridge('br-int') + qutils.add_bridge('br-ex') ext_port = utils.config_get('ext-port') if ext_port: - quantum_utils.add_bridge_port('br-ex', ext_port) - render_l3_agent_conf() - utils.restart('quantum-l3-agent', - 'quantum-plugin-openvswitch-agent', - 'quantum-dhcp-agent') - utils.restart('quantum-server') + qutils.add_bridge_port('br-ex', ext_port) + utils.restart(*(qutils.PLUGIN_AGENT[PLUGIN] + \ + qutils.GATEWAY_AGENTS[PLUGIN])) else: utils.juju_log('ERROR', 'Please provide a valid plugin config') sys.exit(1) + configure_networking() + + +def configure_networking(): + keystone_conf = get_keystone_conf() + db_conf = get_db_conf() + if (utils.config_get('conf-ext-net') and + keystone_conf and + db_conf): + qutils.configure_ext_net( + username=keystone_conf['service_username'], + password=keystone_conf['service_password'], + tenant=keystone_conf['service_tenant'], + url="http://{}:{}/v2.0/".format( + keystone_conf['keystone_host'], + keystone_conf['auth_port'] + ), + ext_net_name=utils.config_get('ext-net-name'), + gateway_ip=utils.config_get('ext-gw-ip'), + default_gateway=utils.config_get('ext-net-gateway'), + cidr=utils.config_get('ext-net-cidr'), + start_floating_ip=utils.config_get('pool-floating-start'), + end_floating_ip=utils.config_get('pool-floating-end') + ) + def upgrade_charm(): install() @@ -66,56 +77,71 @@ def upgrade_charm(): def render_l3_agent_conf(): context = get_keystone_conf() - if context: - with open(quantum_utils.L3_AGENT_CONF, "w") as conf: - conf.write(utils.render_template("l3_agent.ini", context)) + if (context and + os.path.exists(qutils.L3_AGENT_CONF)): + with open(qutils.L3_AGENT_CONF, "w") as conf: + conf.write(utils.render_template( + os.path.basename(qutils.L3_AGENT_CONF), + context + ) + ) def render_api_paste_conf(): context = get_keystone_conf() - if context: - with open(quantum_utils.QUANTUM_API_CONF, "w") as conf: - conf.write(utils.render_template("api-paste.ini", context)) + if (context and + os.path.exists(qutils.QUANTUM_API_CONF)): + with open(qutils.QUANTUM_API_CONF, "w") as conf: + conf.write(utils.render_template( + os.path.basename(qutils.QUANTUM_API_CONF), + context + ) + ) def render_quantum_conf(): context = get_rabbit_conf() - if context: + if (context and + os.path.exists(qutils.QUANTUM_CONF)): context['core_plugin'] = \ - quantum_utils.CORE_PLUGIN[utils.config_get('plugin')] - with open(quantum_utils.QUANTUM_CONF, "w") as conf: - conf.write(utils.render_template("quantum.conf", context)) + qutils.CORE_PLUGIN[PLUGIN] + with open(qutils.QUANTUM_CONF, "w") as conf: + conf.write(utils.render_template( + os.path.basename(qutils.QUANTUM_CONF), + context + ) + ) def render_plugin_conf(): context = get_db_conf() - if context: + if (context and + os.path.exists(qutils.PLUGIN_CONF[PLUGIN])): context['local_ip'] = utils.get_host_ip() - plugin = utils.config_get('plugin') - conf_file = quantum_utils.PLUGIN_CONF[plugin] + conf_file = qutils.PLUGIN_CONF[PLUGIN] with open(conf_file, "w") as conf: - conf.write(utils.render_template(os.path.basename(conf_file), - context)) + conf.write(utils.render_template( + os.path.basename(conf_file), + context + ) + ) def keystone_joined(): url = "http://{}:9696/".format(utils.unit_get('private-address')) - utils.relation_set(service="quantum", - region="RegionOne", + utils.relation_set(service=qutils.KEYSTONE_SERVICE, + region=utils.config_get('region'), public_url=url, admin_url=url, internal_url=url) def keystone_changed(): - if os.path.exists(quantum_utils.L3_AGENT_CONF): - render_l3_agent_conf() # Restart quantum_l3_agent - utils.restart('quantum-l3-agent') - render_api_paste_conf() # Restart quantum server - utils.restart('quantum-server') - if os.path.exists(quantum_utils.DHCP_AGENT_CONF): - utils.restart('quantum-dhcp-agent') + render_l3_agent_conf() + render_api_paste_conf() + utils.restart(*qutils.GATEWAY_AGENTS[PLUGIN]) notify_agents() + configure_networking() def get_keystone_conf(): @@ -141,16 +167,16 @@ def get_keystone_conf(): def db_joined(): - utils.relation_set(username=quantum_utils.DB_USER, - database=quantum_utils.QUANTUM_DB, + utils.relation_set(username=qutils.DB_USER, + database=qutils.QUANTUM_DB, hostname=utils.unit_get('private-address')) def db_changed(): render_plugin_conf() - utils.restart('quantum-server') - if utils.config_get('plugin') == 'ovs': - utils.restart('quantum-plugin-openvswitch-agent') + utils.restart(*(qutils.GATEWAY_AGENTS[PLUGIN] + \ + qutils.PLUGIN_AGENT[PLUGIN])) + configure_networking() def get_db_conf(): @@ -159,10 +185,10 @@ def get_db_conf(): conf = { "host": utils.relation_get('private-address', unit, relid), - "user": quantum_utils.DB_USER, + "user": qutils.DB_USER, "password": utils.relation_get('password', unit, relid), - "db": quantum_utils.QUANTUM_DB + "db": qutils.QUANTUM_DB } if None not in conf.itervalues(): return conf @@ -170,15 +196,14 @@ def get_db_conf(): def amqp_joined(): - utils.relation_set(username=quantum_utils.RABBIT_USER, - vhost=quantum_utils.RABBIT_VHOST) + utils.relation_set(username=qutils.RABBIT_USER, + vhost=qutils.RABBIT_VHOST) def amqp_changed(): render_quantum_conf() - utils.restart('quantum-server', 'quantum-dhcp-agent') - if utils.config_get('plugin') == 'ovs': - utils.restart('quantum-plugin-openvswitch-agent') + utils.restart(*(qutils.GATEWAY_AGENTS[PLUGIN] + \ + qutils.PLUGIN_AGENT[PLUGIN])) def get_rabbit_conf(): @@ -187,8 +212,8 @@ def get_rabbit_conf(): conf = { "rabbit_host": utils.relation_get('private-address', unit, relid), - "rabbit_virtual_host": quantum_utils.RABBIT_VHOST, - "rabbit_userid": quantum_utils.RABBIT_USER, + "rabbit_virtual_host": qutils.RABBIT_VHOST, + "rabbit_userid": qutils.RABBIT_USER, "rabbit_password": utils.relation_get('password', unit, relid) } @@ -201,7 +226,7 @@ def nm_joined(): keystone_conf = get_keystone_conf() if keystone_conf: utils.relation_set(**keystone_conf) # IGNORE:W0142 - utils.relation_set(plugin=utils.config_get('plugin')) + utils.relation_set(plugin=PLUGIN) def notify_agents(): @@ -209,7 +234,7 @@ def notify_agents(): if keystone_conf: for relid in utils.relation_ids('network-manager'): utils.relation_set(relid=relid, - plugin=utils.config_get('plugin'), + plugin=PLUGIN, **keystone_conf) diff --git a/hooks/network-manager-relation-changed b/hooks/network-manager-relation-changed deleted file mode 120000 index 9416ca6a..00000000 --- a/hooks/network-manager-relation-changed +++ /dev/null @@ -1 +0,0 @@ -hooks.py \ No newline at end of file diff --git a/hooks/quantum_utils.py b/hooks/quantum_utils.py index 5c3da020..3dcfe5ad 100644 --- a/hooks/quantum_utils.py +++ b/hooks/quantum_utils.py @@ -1,6 +1,13 @@ - +import utils import subprocess +try: + from quantumclient.v2_0 import client +except ImportError: + utils.install('python-quantumclient') + from quantumclient.v2_0 import client + + OVS_PLUGIN = \ "quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPluginV2" NVP_PLUGIN = \ @@ -19,8 +26,35 @@ PLUGIN_CONF = { "nvp": NVP_PLUGIN_CONF } +PLUGIN_PKGS = { + "ovs": [ + "quantum-plugin-openvswitch", + "quantum-plugin-openvswitch-agent", + "quantum-l3-agent", + "quantum-dhcp-agent" + ], + "nvp": [ # No agent required + "quantum-plugin-nicira" + ] + } + +PLUGIN_AGENT = { + "ovs": [ + "quantum-plugin-openvswitch-agent", + ] + } + +GATEWAY_AGENTS = { + "ovs": [ + "quantum-server", + "quantum-l3-agent", + "quantum-dhcp-agent" + ] + } + DB_USER = "quantum" QUANTUM_DB = "quantum" +KEYSTONE_SERVICE = "quantum" QUANTUM_CONF = "/etc/quantum/quantum.conf" L3_AGENT_CONF = "/etc/quantum/l3_agent.ini" @@ -30,16 +64,23 @@ DHCP_AGENT_CONF = "/etc/quantum/dhcp_agent.ini" RABBIT_USER = "nova" RABBIT_VHOST = "nova" +EXT_BRIDGE = 'br-ex' +INT_BRIDGE = 'br-int' + def add_bridge(name): status = subprocess.check_output(["ovs-vsctl", "show"]) if "Bridge {}".format(name) not in status: + utils.juju_log('INFO', + 'Creating bridge {}'.format(name)) 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: + utils.juju_log('INFO', + 'Deleting bridge {}'.format(name)) subprocess.check_call(["ovs-vsctl", "del-br", name]) @@ -47,11 +88,89 @@ def add_bridge_port(name, port): status = subprocess.check_output(["ovs-vsctl", "show"]) if ("Bridge {}".format(name) in status and "Interface \"{}\"".format(port) not in status): + utils.juju_log('INFO', + 'Adding port {} to bridge {}' + .format(port, name)) subprocess.check_call(["ovs-vsctl", "add-port", name, port]) + subprocess.check_call(["ip", "link", "set", port, "up"]) 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): + utils.juju_log('INFO', + 'Deleting port {} from bridge {}' + .format(port, name)) subprocess.check_call(["ovs-vsctl", "del-port", name, port]) + subprocess.check_call(["ip", "link", "set", port, "down"]) + + +def configure_ext_net(username, + password, + tenant, + url, + ext_net_name, + gateway_ip, + default_gateway, + cidr, + start_floating_ip, + end_floating_ip): + + ext_net_len = cidr.split('/')[1] + quantum = client.Client(username=username, + password=password, + tenant_name=tenant, + auth_url=url) + + networks = quantum.list_networks(name=ext_net_name) + if len(networks['networks']) == 0: + utils.juju_log('INFO', + 'Configuring external bridge') + network_msg = { + 'network': { + 'name': ext_net_name, + 'router:external': True + } + } + utils.juju_log('INFO', + 'Creating new external network definition: {}' + .format(ext_net_name)) + network = quantum.create_network(network_msg) + utils.juju_log('INFO', + 'New external network created: {}' + .format(network['network']['id'])) + + subnet_msg = { + 'subnet': { + 'name': '{}_subnet'.format(ext_net_name), + 'network_id': network['network']['id'], + 'enable_dhcp': False, + 'gateway_ip': default_gateway, + 'cidr': cidr, + 'ip_version': 4, + 'allocation_pools': [ + { + 'start': start_floating_ip, + 'end': end_floating_ip + } + ] + } + } + utils.juju_log('INFO', + 'Creating new subnet for {}' + .format(ext_net_name)) + subnet = quantum.create_subnet(subnet_msg) + utils.juju_log('INFO', + 'New subnet created: {}' + .format(subnet['subnet']['id'])) + + utils.juju_log('INFO', + 'Configuring external bridge connectivity') + subprocess.check_call(['ip', 'addr', 'flush', + 'dev', EXT_BRIDGE]) + subprocess.check_call(['ip', 'addr', 'add', + '{}/{}'.format(gateway_ip, ext_net_len), + 'dev', EXT_BRIDGE]) + subprocess.check_call(['ip', 'addr', 'set', + EXT_BRIDGE, 'up']) diff --git a/hooks/utils.py b/hooks/utils.py index d79b6635..c397e7ae 100644 --- a/hooks/utils.py +++ b/hooks/utils.py @@ -41,16 +41,13 @@ except ImportError: install('python-jinja2') import jinja2 - try: import dns.resolver import dns.ipv4 - import dns.ipv6 except ImportError: install('python-dnspython') import dns.resolver import dns.ipv4 - import dns.ipv6 def render_template(template_name, context, template_dir=TEMPLATES_DIR): @@ -60,20 +57,29 @@ def render_template(template_name, context, template_dir=TEMPLATES_DIR): template = templates.get_template(template_name) return template.render(context) +CLOUD_ARCHIVE = \ +""" # Ubuntu Cloud Archive +deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main +""" + def configure_source(): - source = config_get('source') + source = str(config_get('source')) if not source: return - if (source.startswith('ppa:') or - source.startswith('cloud:')): + if source.startswith('ppa:'): cmd = [ 'add-apt-repository', source ] subprocess.check_call(cmd) + if source.startswith('cloud:'): + install('ubuntu-cloud-keyring') + pocket = source.split(':')[1] + with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt: + apt.write(CLOUD_ARCHIVE.format(pocket)) if source.startswith('http:'): - with open('/etc/apt/sources.list.d/ceph.list', 'w') as apt: + with open('/etc/apt/sources.list.d/quantum.list', 'w') as apt: apt.write("deb " + source + "\n") key = config_get('key') if key: diff --git a/revision b/revision index 40994076..e85087af 100644 --- a/revision +++ b/revision @@ -1 +1 @@ -23 +31