Refactored into quantum gateway

This commit is contained in:
James Page 2012-12-03 15:16:55 +00:00
parent e578fd74f5
commit be10baf149
13 changed files with 64 additions and 486 deletions

View File

@ -6,6 +6,5 @@
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/quantum/hooks</path>
<path>/quantum/files</path>
</pydev_pathproperty>
</pydev_project>

View File

@ -3,9 +3,9 @@ Overview
Quantum provides flexible software defined networking (SDN) for OpenStack.
This charm is designed to be used in conjunction with the 'quantum-agent'
charm (and the rest of the OpenStack related charms in the charm store) to
virtualized the network that Nova Compute instances plug into.
This charm is designed to be used in conjunction with the rest of the OpenStack
related charms in the charm store) to virtualized the network that Nova Compute
instances plug into.
Its designed as a replacement for nova-network; however it does not yet
support all of the features as nova-network (such as multihost) so may not
@ -14,8 +14,8 @@ be suitable for all.
Quantum supports a rich plugin/extension framework for propriety networking
solutions and supports (in core) Nicira NVP, NEC, Cisco and others...
The charm currently only supports the fully free OpenvSwitch plugin and
implements the 'Provider Router with Private Networks' use case.
The Openstack charms currently only support the fully free OpenvSwitch plugin
and implements the 'Provider Router with Private Networks' use case.
See the upstream [Quantum documentation](http://docs.openstack.org/trunk/openstack-network/admin/content/use_cases_single_router.html)
for more details.
@ -24,70 +24,32 @@ for more details.
Usage
-----
Assumming that you have already deployed OpenStack using Juju, Quantum can be
added to the mix:
In order to use Quantum with Openstack, you will need to deploy the
nova-compute and nova-cloud-controller charms with the network-manager
configuration set to 'Quantum':
juju deploy quantum
juju add-relation quantum mysql
juju add-relation quantum rabbitmq-server
juju add-relation keystone
juju add-relation nova-cloud-controller
nova-compute:
network-manager: Quantum
nova-cloud-controller:
network-manager: Quantum
This will setup a Quantum API server and the DHCP and L3 routing agents on the
deployed servce unit. ATM it does not support multiple units (WIP).
This decision must be made prior to deploying Openstack with Juju as
Quantum is deployed baked into these charms from install onwards:
To then integrate Quantum with nova-compute do:
juju deploy --config config.yaml nova-compute
juju deploy --config config.yaml nova-cloud-controller
juju add-relation nova-compute nova-cloud-controller
juju deploy quantum-agent
juju add-relation quantum-agent mysql
juju add-relation quantum-agent rabbitmq-server
juju add-relation quantum-agent nova-compute
The Quantum Gateway can then be added to the deploying:
All of the units supporting nova-compute will now be reconfigured to support
use of Quantum instead of nova-network.
juju deploy quantum-gateway
juju add-relation quantum-gateway mysql
juju add-relation quantum-gateway rabbitmq-server
juju add-relation quantum-gateway nova-cloud-controller
Configuration
-------------
The gateway provides two key services; L3 network routing and DHCP services.
External Network Configuration
==============================
The quantum charm supports a number of configuration options; at a minimum you
will need to specify the external network configuration for you environment.
These are used to configure the 'external network' in quantum which provides
outbound public network access from tenant private networks and handles the
allocation of floating IP's for inbound public network access.
You will also need to provide the 'ext-port' configuration element; this should
be the port on the server which should be used for routing external/public
network traffic. This does of course mean that you need a server with more than
one network interface to deploy the quantum charm.
Example minimal configuration:
quantum:
ext-port: eth1
conf-ext-net: yes
ext-net-cidr: 192.168.21.0/24
ext-net-gateway: 192.168.21.1
pool-floating-start: 192.168.21.130
pool-floating-end: 192.168.21.200
The IP addresses above are for illustrative purposes only; in a real environment
these would be configured with actual routable public addresses.
Tenant Network Configuration
============================
The quantum charm provides a helper script for creating tenant networks:
quantum-net-create -t admin -r provider-router \
-N 192.168.21.1 adminnet 10.5.5.0/24
will create a new network for the admin tenant called 'adminnet' with a
default gateway of 10.5.5.1, a DNS nameserver at 192.168.21.1 and a dhcp
allocation range of 10.5.5.2 to 10.5.5.254; external network access is
provided through the 'provider-router' (created by the charm itself).
These are both required in a fully functional Quantum Openstack deployment.
TODO
----
@ -96,4 +58,3 @@ TODO
* Support VLAN in addition to GRE+OpenFlow for L2 separation.
* High Avaliability.
* Support for propriety plugins for Quantum.

View File

@ -18,55 +18,12 @@ options:
default: RegionOne
description: |
OpenStack region that this quantum service supports.
conf-ext-net:
type: string
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
description: |
IP address to assign to external bridge for external network
access. Only use this when configuraing a single instance
quantum gateway deployment.
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:
openstack-origin:
type: string
description: |
Optional configuration to support use of additional sources such as:
.
- ppa:myteam/ppa
- cloud:precise-proposed/folsom
- http://my.archive.com/ubuntu main
.
The last option should be used in conjunction with the key configuration
option.
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.
- cloud:precise-folsom/proposed
- cloud:precise-folsom
- deb http://my.archive.com/ubuntu main|KEYID

View File

@ -1,143 +0,0 @@
#!/usr/bin/python
from quantumclient.v2_0 import client
from keystoneclient.v2_0 import client as ks_client
import optparse
import os
import sys
import logging
usage = """Usage: %prog [options] name cidr
For example:
%prog -t admin -r provider-router admin_net 10.5.5.0/24
will create a new network for the admin tenant called 'admin_net' with a
default gateway of 10.5.5.1 and a dhcp allocation range of
10.5.5.2->10.5.5.254
"""
if __name__ == '__main__':
parser = optparse.OptionParser(usage)
parser.add_option('-t', '--tenant',
help='Tenant name to create network for',
dest='tenant', action='store',
default=None)
parser.add_option('-s', '--shared',
help='Create a shared rather than private network',
dest='shared', action='store_true', default=False)
parser.add_option('-r', '--router',
help='Router to plug new network into',
dest='router', action='store', default=None)
parser.add_option("-d", "--debug",
help="Enable debug logging",
dest="debug", action="store_true", default=False)
parser.add_option("-D", "--disable-dhcp",
help="Disable dhcp on network",
dest="dhcp", action="store_false", default=True)
parser.add_option("-N", "--dns-nameservers",
help="Comma separated list of dns servers to use.",
dest="dns_servers", action="store", default=None)
(opts, args) = parser.parse_args()
if len(args) != 2:
parser.print_help()
sys.exit(1)
if opts.debug:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
net_name = args[0]
subnet_name = "{}_subnet".format(net_name)
cidr = args[1]
keystone = ks_client.Client(username=os.environ['OS_USERNAME'],
password=os.environ['OS_PASSWORD'],
tenant_name=os.environ['OS_TENANT_NAME'],
auth_url=os.environ['OS_AUTH_URL'])
quantum = client.Client(username=os.environ['OS_USERNAME'],
password=os.environ['OS_PASSWORD'],
tenant_name=os.environ['OS_TENANT_NAME'],
auth_url=os.environ['OS_AUTH_URL'])
# Resolve tenant id
tenant_id = None
for tenant in [t._info for t in keystone.tenants.list()]:
if (tenant['name'] ==
(opts.tenant or os.environ['OS_TENANT_NAME'])):
tenant_id = tenant['id']
break # Tenant ID found - stop looking
if not tenant_id:
logging.error("Unable to locate tenant id for %s.", opts.tenant)
sys.exit(1)
# Create network
networks = quantum.list_networks(name=net_name)
if len(networks['networks']) == 0:
logging.info('Creating network: %s',
net_name)
network_msg = {
'network': {
'name': net_name,
'shared': opts.shared,
'tenant_id': tenant_id
}
}
network = quantum.create_network(network_msg)['network']
else:
logging.warning('Network %s already exists.', net_name)
network = networks['networks'][0]
# Create subnet
subnets = quantum.list_subnets(name=subnet_name)
if len(subnets['subnets']) == 0:
logging.info('Creating subnet for %s',
net_name)
subnet_msg = {
'subnet': {
'name': subnet_name,
'network_id': network['id'],
'enable_dhcp': opts.dhcp,
'cidr': cidr,
'ip_version': 4,
'tenant_id': tenant_id
}
}
subnet = quantum.create_subnet(subnet_msg)['subnet']
else:
logging.warning('Subnet %s already exists.', subnet_name)
subnet = subnets['subnets'][0]
# Update dns_nameservers
if opts.dns_servers:
msg = {
'subnet': {
'dns_nameservers': opts.dns_servers.split(',')
}
}
logging.info('Updating dns_nameservers (%s) for subnet %s',
opts.dns_servers,
subnet_name)
quantum.update_subnet(subnet['id'], msg)
# Plug subnet into router if provided
if opts.router:
routers = quantum.list_routers(name=opts.router)
if len(routers['routers']) == 0:
logging.error('Unable to locate provider router %s', opts.router)
sys.exit(1)
else:
# Check to see if subnet already plugged into router
ports = quantum.list_ports(device_owner='network:router_interface',
network_id=network['id'])
if len(ports['ports']) == 0:
logging.info('Adding interface from %s to %s',
opts.router, subnet_name)
router = routers['routers'][0]
quantum.add_interface_router(router['id'],
{'subnet_id': subnet['id']})
else:
logging.warning('Router already connected to subnet')

View File

@ -4,15 +4,13 @@ import utils
import sys
import quantum_utils as qutils
import os
import shutil
PLUGIN = utils.config_get('plugin')
def install():
utils.configure_source()
shutil.copy('files/create_tenant_net.py', '/usr/bin/quantum-net-create')
if PLUGIN in qutils.PLUGIN_PKGS.keys():
if PLUGIN in qutils.GATEWAY_PKGS.keys():
if PLUGIN == qutils.OVS:
# Install OVS DKMS first to ensure that the ovs module
# loaded supports GRE tunnels
@ -24,12 +22,10 @@ def install():
def config_changed():
if PLUGIN in qutils.PLUGIN_PKGS.keys():
render_api_paste_conf()
if PLUGIN in qutils.GATEWAY_PKGS.keys():
render_quantum_conf()
render_plugin_conf()
render_l3_agent_conf()
render_novarc()
if PLUGIN == qutils.OVS:
qutils.add_bridge(qutils.INT_BRIDGE)
qutils.add_bridge(qutils.EXT_BRIDGE)
@ -42,31 +38,6 @@ def config_changed():
'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()
@ -85,25 +56,6 @@ def render_l3_agent_conf():
)
def render_api_paste_conf():
context = get_keystone_conf()
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_novarc():
context = get_keystone_conf()
if context:
with open('/etc/quantum/novarc', "w") as conf:
conf.write(utils.render_template('novarc', context))
def render_quantum_conf():
context = get_rabbit_conf()
if (context and
@ -132,31 +84,12 @@ def render_plugin_conf():
)
def keystone_joined():
url = "http://{}:9696/".format(utils.unit_get('private-address'))
utils.relation_set(service=qutils.KEYSTONE_SERVICE,
region=utils.config_get('region'),
public_url=url,
admin_url=url,
internal_url=url)
def keystone_changed():
render_l3_agent_conf()
render_api_paste_conf()
render_novarc()
utils.restart(*qutils.GATEWAY_AGENTS[PLUGIN])
notify_agents()
configure_networking()
def get_keystone_conf():
for relid in utils.relation_ids('identity-service'):
for relid in utils.relation_ids('quantum-network-service'):
for unit in utils.relation_list(relid):
conf = {
"keystone_host": utils.relation_get('private-address',
"keystone_host": utils.relation_get('keystone_host',
unit, relid),
"token": utils.relation_get('admin_token', unit, relid),
"service_port": utils.relation_get('service_port',
unit, relid),
"auth_port": utils.relation_get('auth_port', unit, relid),
@ -181,7 +114,6 @@ def db_joined():
def db_changed():
render_plugin_conf()
utils.restart(*qutils.GATEWAY_AGENTS[PLUGIN])
configure_networking()
def get_db_conf():
@ -226,33 +158,20 @@ def get_rabbit_conf():
return None
def nm_joined():
keystone_conf = get_keystone_conf()
if keystone_conf:
utils.relation_set(**keystone_conf) # IGNORE:W0142
utils.relation_set(plugin=PLUGIN)
def notify_agents():
keystone_conf = get_keystone_conf()
if keystone_conf:
for relid in utils.relation_ids('quantum-network-service'):
utils.relation_set(rid=relid, # IGNORE:W0142
plugin=PLUGIN,
**keystone_conf)
def nm_changed():
render_l3_agent_conf()
utils.restart(*qutils.GATEWAY_AGENTS[PLUGIN])
utils.do_hooks({
"install": install,
"config-changed": config_changed,
"upgrade-charm": upgrade_charm,
"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,
"quantum-network-service-relation-joined": nm_joined,
"quantum-network-service-relation-changed": nm_changed,
})
sys.exit(0)

View File

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

View File

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

View File

@ -1,12 +1,6 @@
import subprocess
import shutil
from utils import juju_log as log, install, start, stop
from utils import juju_log as log
try:
from quantumclient.v2_0 import client
except ImportError:
install('python-quantumclient')
from quantumclient.v2_0 import client
OVS = "ovs"
NVP = "nvp"
@ -31,11 +25,9 @@ PLUGIN_CONF = {
GATEWAY_PKGS = {
OVS: [
"quantum-plugin-openvswitch",
"quantum-plugin-openvswitch-agent",
"quantum-l3-agent",
"quantum-dhcp-agent",
'quantum-server',
'python-mysqldb'
],
NVP: [
@ -43,22 +35,9 @@ GATEWAY_PKGS = {
]
}
PLUGIN_PKGS = {
OVS: [
"quantum-plugin-openvswitch-agent"
]
}
PLUGIN_AGENT = {
OVS: [
"quantum-plugin-openvswitch-agent"
]
}
GATEWAY_AGENTS = {
OVS: [
"quantum-plugin-openvswitch-agent",
"quantum-server",
"quantum-l3-agent",
"quantum-dhcp-agent"
]
@ -70,12 +49,14 @@ KEYSTONE_SERVICE = "quantum"
QUANTUM_CONF = "/etc/quantum/quantum.conf"
L3_AGENT_CONF = "/etc/quantum/l3_agent.ini"
QUANTUM_API_CONF = "/etc/quantum/api-paste.ini"
DHCP_AGENT_CONF = "/etc/quantum/dhcp_agent.ini"
RABBIT_USER = "nova"
RABBIT_VHOST = "nova"
INT_BRIDGE = "br-int"
EXT_BRIDGE = "br-ex"
def add_bridge(name):
status = subprocess.check_output(["ovs-vsctl", "show"])
@ -109,104 +90,3 @@ def del_bridge_port(name, port):
'Deleting port {} from bridge {}'.format(port, name))
subprocess.check_call(["ovs-vsctl", "del-port", name, port])
subprocess.check_call(["ip", "link", "set", port, "down"])
QEMU_CONF = '/etc/libvirt/qemu.conf'
def configure_libvirt():
log('INFO',
'Configuring default permissions in libvirt-bin')
shutil.copyfile('files/qemu.conf',
QEMU_CONF)
stop('libvirt-bin')
start('libvirt-bin')
EXT_BRIDGE = 'br-ex'
INT_BRIDGE = 'br-int'
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:
log('INFO',
'Configuring external bridge')
network_msg = {
'network': {
'name': ext_net_name,
'router:external': True
}
}
log('INFO',
'Creating new external network definition: {}'
.format(ext_net_name))
network = quantum.create_network(network_msg)
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
}
]
}
}
log('INFO',
'Creating new subnet for {}'.format(ext_net_name))
subnet = quantum.create_subnet(subnet_msg)
log('INFO',
'New subnet created: {}'.format(subnet['subnet']['id']))
log('INFO',
'Creating provider router for external network access')
router = quantum.create_router({'router': {'name': 'provider-router'}})
log('INFO',
'New router created: {}'.format(router['router']['id']))
log('INFO',
'Plugging router into ext_net')
router = \
quantum.add_gateway_router(
router=router['router']['id'],
body={'network_id': network['network']['id']}
)
log('INFO',
'Router connected to ext_net')
if gateway_ip:
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', 'link', 'set',
EXT_BRIDGE, 'up'])

View File

@ -62,9 +62,15 @@ CLOUD_ARCHIVE = \
deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
"""
CLOUD_ARCHIVE_POCKETS = {
'folsom': 'precise-updates/folsom',
'folsom/updates': 'precise-updates/folsom',
'folsom/proposed': 'precise-proposed/folsom'
}
def configure_source():
source = str(config_get('source'))
source = str(config_get('openstack-origin'))
if not source:
return
if source.startswith('ppa:'):
@ -77,18 +83,22 @@ def configure_source():
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/quantum.list', 'w') as apt:
apt.write("deb " + source + "\n")
key = config_get('key')
if key:
apt.write(CLOUD_ARCHIVE.format(CLOUD_ARCHIVE_POCKETS[pocket]))
if source.startswith('deb'):
l = len(source.split('|'))
if l == 2:
(apt_line, key) = source.split('|')
cmd = [
'apt-key',
'adv', '--keyserver keyserver.ubuntu.com',
'--recv-keys', key
]
subprocess.check_call(cmd)
elif l == 1:
apt_line = source
with open('/etc/apt/sources.list.d/quantum.list', 'w') as apt:
apt.write(apt_line + "\n")
cmd = [
'apt-get',
'update'

View File

@ -1,5 +1,5 @@
name: quantum
summary: Virtual Networking for OpenStack
name: quantum-gateway
summary: Virtual Networking for OpenStack - Quantum Gateway
maintainer: James Page <james.page@ubuntu.com>
description: |
Quantum is a virtual network service for Openstack, and a part of
@ -10,6 +10,9 @@ description: |
from Nova VMs). The Quantum API supports extensions to provide
advanced network capabilities (e.g., QoS, ACLs, network monitoring,
etc.)
.
This charm provides central Quantum networking services as part
of a Quantum based Openstack deployment
provides:
quantum-network-service:
interface: quantum
@ -17,6 +20,4 @@ requires:
shared-db:
interface: mysql-shared
amqp:
interface: rabbitmq
identity-service:
interface: keystone
interface: rabbitmq

View File

@ -1 +1 @@
32
33

View File

@ -1,4 +0,0 @@
export OS_USERNAME={{ service_username }}
export OS_PASSWORD={{ service_password }}
export OS_TENANT_NAME={{ service_tenant }}
export OS_AUTH_URL=http://{{ keystone_host }}:{{ auth_port }}/v2.0/