# Change to "sudo quantum-rootwrap" to limit commands that can be run
# as root.
root_helper = sudo
+# Use Quantumv2 API
+target_v2_api = False
#-----------------------------------------------------------------------------
# Sample Configurations.
# root_helper = sudo
# Add the following setting, if you want to log to a file
# log_file = /var/log/quantum/ovs_quantum_agent.log
+# Use Quantumv2 API
+# target_v2_api = False
#
# 2. With tunneling.
# [DATABASE]
import logging
import os
import subprocess
+import uuid
from quantum.common import exceptions as exception
from quantum.common import flags
cfg_file = os.path.join(cfg_dir, config_file)
if os.path.exists(cfg_file):
return cfg_file
+
+
+def str_uuid():
+ """Return a uuid as a string"""
+ return str(uuid.uuid4())
# See the License for the specific language governing permissions and
# limitations under the License.
-import uuid
-
import sqlalchemy as sa
from sqlalchemy.ext import declarative
from sqlalchemy import orm
-def str_uuid():
- return str(uuid.uuid4())
-
-
class QuantumBase(object):
"""Base class for Quantum Models."""
class QuantumBaseV2(QuantumBase):
- id = sa.Column(sa.String(36), primary_key=True, default=str_uuid)
@declarative.declared_attr
def __tablename__(cls):
import sqlalchemy as sa
from sqlalchemy import orm
+from quantum.common import utils
from quantum.db import model_base
tenant_id = sa.Column(sa.String(255))
-class IPAllocationRange(model_base.BASEV2):
+class HasId(object):
+ """id mixin, add to subclasses that have an id."""
+ id = sa.Column(sa.String(36), primary_key=True, default=utils.str_uuid)
+
+
+class IPAllocationRange(model_base.BASEV2, HasId):
"""Internal representation of a free IP address range in a Quantum
subnet. The range of available ips is [first_ip..last_ip]. The
allocation retrieves the first entry from the range. If the first
nullable=False, primary_key=True)
-class Port(model_base.BASEV2, HasTenant):
+class Port(model_base.BASEV2, HasId, HasTenant):
"""Represents a port on a quantum v2 network."""
network_id = sa.Column(sa.String(36), sa.ForeignKey("networks.id"),
nullable=False)
device_id = sa.Column(sa.String(255), nullable=False)
-class Subnet(model_base.BASEV2):
+class Subnet(model_base.BASEV2, HasId):
"""Represents a quantum subnet.
When a subnet is created the first and last entries will be created. These
# - additional_routes
-class Network(model_base.BASEV2, HasTenant):
+class Network(model_base.BASEV2, HasId, HasTenant):
"""Represents a v2 quantum network."""
name = sa.Column(sa.String(255))
ports = orm.relationship(Port, backref='networks')
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Dan Wendlandt, Nicira Networks, Inc.
# @author: Dave Lapsley, Nicira Networks, Inc.
+# @author: Aaron Rosen, Nicira Networks, Inc.
import logging
from optparse import OptionParser
class Port(object):
- '''class stores port data in an ORM-free way,
- so attributes are still available even if a
- row has been deleted.
- '''
+ """Represents a quantum port.
+
+ Class stores port data in a ORM-free way, so attributres are
+ still available even if a row has been deleted.
+ """
def __init__(self, p):
self.uuid = p.uuid
self.network_id = p.network_id
self.interface_id = p.interface_id
self.state = p.state
- self.op_status = p.op_status
+ self.status = p.op_status
def __eq__(self, other):
- '''compare only fields that will cause us to re-wire
- '''
+ '''Compare only fields that will cause us to re-wire.'''
try:
return (self and other
and self.interface_id == other.interface_id
return hash(self.uuid)
+class Portv2(object):
+ """Represents a quantumv2 port.
+
+ Class stores port data in a ORM-free way, so attributres are
+ still available even if a row has been deleted.
+ """
+
+ def __init__(self, p):
+ self.id = p.id
+ self.network_id = p.network_id
+ self.device_id = p.device_id
+ self.admin_state_up = p.admin_state_up
+ self.status = p.status
+
+ def __eq__(self, other):
+ '''Compare only fields that will cause us to re-wire.'''
+ try:
+ return (self and other
+ and self.id == other.id
+ and self.admin_state_up == other.admin_state_up)
+ except:
+ return False
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __hash__(self):
+ return hash(self.id)
+
+
class OVSQuantumAgent(object):
- def __init__(self, integ_br, root_helper,
- polling_interval, reconnect_interval):
+ def __init__(self, integ_br, root_helper, polling_interval,
+ reconnect_interval, target_v2_api=False):
self.root_helper = root_helper
self.setup_integration_br(integ_br)
self.polling_interval = polling_interval
self.reconnect_interval = reconnect_interval
+ self.target_v2_api = target_v2_api
def port_bound(self, port, vlan_id):
self.int_br.set_db_attribute("Port", port.port_name,
continue
for port in ports:
- all_bindings[port.interface_id] = port
+ if self.target_v2_api:
+ all_bindings[port.id] = port
+ else:
+ all_bindings[port.interface_id] = port
vlan_bindings = {}
try:
% (old_b, str(p)))
self.port_unbound(p, True)
if p.vif_id in all_bindings:
- all_bindings[p.vif_id].op_status = OP_STATUS_DOWN
+ all_bindings[p.vif_id].status = OP_STATUS_DOWN
if new_b is not None:
# If we don't have a binding we have to stick it on
# the dead vlan
vlan_id = vlan_bindings.get(net_id, DEAD_VLAN_TAG)
self.port_bound(p, vlan_id)
if p.vif_id in all_bindings:
- all_bindings[p.vif_id].op_status = OP_STATUS_UP
+ all_bindings[p.vif_id].status = OP_STATUS_UP
LOG.info(("Adding binding to net-id = %s "
"for %s on vlan %s") %
(new_b, str(p), vlan_id))
old_b = old_local_bindings[vif_id]
self.port_unbound(old_vif_ports[vif_id], False)
if vif_id in all_bindings:
- all_bindings[vif_id].op_status = OP_STATUS_DOWN
+ all_bindings[vif_id].status = OP_STATUS_DOWN
old_vif_ports = new_vif_ports
old_local_bindings = new_local_bindings
MAX_VLAN_TAG = 4094
def __init__(self, integ_br, tun_br, local_ip, root_helper,
- polling_interval, reconnect_interval):
+ polling_interval, reconnect_interval, target_v2_api=False):
'''Constructor.
:param integ_br: name of the integration bridge.
:param local_ip: local IP address of this hypervisor.
:param root_helper: utility to use when running shell cmds.
:param polling_interval: interval (secs) to poll DB.
- :param reconnect_internal: retry interval (secs) on DB error.'''
+ :param reconnect_internal: retry interval (secs) on DB error.
+ :param target_v2_api: if True use v2 api.
+ '''
self.root_helper = root_helper
self.available_local_vlans = set(
xrange(OVSQuantumTunnelAgent.MIN_VLAN_TAG,
self.local_ip = local_ip
self.tunnel_count = 0
self.setup_tunnel_br(tun_br)
+ self.target_v2_api = target_v2_api
def provision_local_vlan(self, net_uuid, lsw_id):
'''Provisions a local VLAN.
self.available_local_vlans.add(lvm.vlan)
def port_bound(self, port, net_uuid, lsw_id):
- '''Bind port to net_uuid/lsw_id.
+ '''Bind port to net_uuid/lsw_id and install flow for inbound traffic
+ to vm.
:param port: a ovslib.VifPort object.
:param net_uuid: the net_uuid this port is to be associated with.
while True:
try:
- all_bindings = dict((p.interface_id, Port(p))
- for p in db.ports.all())
+ if self.target_v2_api:
+ all_bindings = dict((p.id, Portv2(p))
+ for p in db.ports.all())
+ else:
+ all_bindings = dict((p.interface_id, Port(p))
+ for p in db.ports.all())
all_bindings_vif_port_ids = set(all_bindings)
lsw_id_bindings = dict((bind.network_id, bind.vlan_id)
for bind in db.vlan_bindings.all())
old_net_uuid + " for " + str(p)
+ " added to dead vlan")
self.port_unbound(p, old_net_uuid)
- all_bindings[p.vif_id].op_status = OP_STATUS_DOWN
+ if p.vif_id in all_bindings:
+ all_bindings[p.vif_id].status = OP_STATUS_DOWN
if not new_port:
self.port_dead(p)
lsw_id = lsw_id_bindings[new_net_uuid]
self.port_bound(p, new_net_uuid, lsw_id)
- all_bindings[p.vif_id].op_status = OP_STATUS_UP
+ all_bindings[p.vif_id].status = OP_STATUS_UP
LOG.info("Port %s on net-id = %s bound to %s " % (
str(p), new_net_uuid,
str(self.local_vlan_map[new_net_uuid])))
for vif_id in disappeared_vif_ports_ids:
LOG.info("Port Disappeared: " + vif_id)
if vif_id in all_bindings:
- all_bindings[vif_id].op_status = OP_STATUS_DOWN
+ all_bindings[vif_id].status = OP_STATUS_DOWN
old_port = old_local_bindings.get(vif_id)
if old_port:
self.port_unbound(old_vif_ports[vif_id],
reconnect_interval = conf.DATABASE.reconnect_interval
root_helper = conf.AGENT.root_helper
+ # Determine API Version to use
+ target_v2_api = conf.AGENT.target_v2_api
+
if enable_tunneling:
# Get parameters for OVSQuantumTunnelAgent
tun_br = conf.OVS.tunnel_bridge
# Mandatory parameter.
local_ip = conf.OVS.local_ip
plugin = OVSQuantumTunnelAgent(integ_br, tun_br, local_ip, root_helper,
- polling_interval, reconnect_interval)
+ polling_interval, reconnect_interval,
+ target_v2_api)
else:
# Get parameters for OVSQuantumAgent.
- plugin = OVSQuantumAgent(integ_br, root_helper,
- polling_interval, reconnect_interval)
+ plugin = OVSQuantumAgent(integ_br, root_helper, polling_interval,
+ reconnect_interval, target_v2_api)
# Start everything.
plugin.daemon_loop(db_connection_url)
]
agent_opts = [
+ cfg.BoolOpt('target_v2_api', default=True),
cfg.IntOpt('polling_interval', default=2),
cfg.StrOpt('root_helper', default='sudo'),
cfg.StrOpt('log_file', default=None),
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright 2011 Nicira Networks, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+# @author: Aaron Rosen, Nicira Networks, Inc.
+
+from sqlalchemy.orm import exc
+
+import quantum.db.api as db
+from quantum.plugins.openvswitch import ovs_models_v2
+
+
+def get_vlans():
+ session = db.get_session()
+ try:
+ bindings = (session.query(ovs_models_v2.VlanBinding).
+ all())
+ except exc.NoResultFound:
+ return []
+ return [(binding.vlan_id, binding.network_id) for binding in bindings]
+
+
+def add_vlan_binding(vlan_id, net_id):
+ session = db.get_session()
+ binding = ovs_models_v2.VlanBinding(vlan_id, net_id)
+ session.add(binding)
+ session.flush()
+ return binding
+
+
+def remove_vlan_binding(net_id):
+ session = db.get_session()
+ try:
+ binding = (session.query(ovs_models_v2.VlanBinding).
+ filter_by(network_id=net_id).
+ one())
+ session.delete(binding)
+ except exc.NoResultFound:
+ pass
+ session.flush()
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright 2011 Nicira Networks, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+# @author: Aaron Rosen, Nicira Networks, Inc.
+
+
+from sqlalchemy import Column, Integer, String
+
+from quantum.db import models_v2
+
+
+class VlanBinding(models_v2.model_base.BASEV2):
+ """Represents a binding of network_id to vlan_id."""
+ __tablename__ = 'vlan_bindings'
+
+ vlan_id = Column(Integer, primary_key=True)
+ network_id = Column(String(255))
+
+ def __init__(self, vlan_id, network_id):
+ self.network_id = network_id
+ self.vlan_id = vlan_id
+
+ def __repr__(self):
+ return "<VlanBinding(%s,%s)>" % (self.vlan_id, self.network_id)
+
+
+class TunnelIP(models_v2.model_base.BASEV2):
+ """Represents a remote IP in tunnel mode."""
+ __tablename__ = 'tunnel_ips'
+
+ ip_address = Column(String(255), primary_key=True)
+
+ def __init__(self, ip_address):
+ self.ip_address = ip_address
+
+ def __repr__(self):
+ return "<TunnelIP(%s)>" % (self.ip_address)
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Dan Wendlandt, Nicira Networks, Inc.
# @author: Dave Lapsley, Nicira Networks, Inc.
+# @author: Aaron Rosen, Nicira Networks, Inc.
import logging
import os
from quantum.api.api_common import OperationalStatus
from quantum.common import exceptions as q_exc
from quantum.common.utils import find_config_file
-import quantum.db.api as db
+from quantum.db import api as db
+from quantum.db import db_base_plugin_v2
+from quantum.db import models_v2
from quantum.plugins.openvswitch.common import config
from quantum.plugins.openvswitch import ovs_db
+from quantum.plugins.openvswitch import ovs_db_v2
from quantum.quantum_plugin_base import QuantumPluginBase
-LOG = logging.getLogger("ovs_quantum_plugin")
-
+LOG = logging.getLogger("ovs_quantum_plugin")
CONF_FILE = find_config_file({"plugin": "openvswitch"},
"ovs_quantum_plugin.ini")
free_vlans = set()
def __init__(self, vlan_min=1, vlan_max=4094):
+ if vlan_min > vlan_max:
+ LOG.warn("Using default VLAN values! vlan_min = %s is larger"
+ " than vlan_max = %s!" % (vlan_min, vlan_max))
+ vlan_min = 1
+ vlan_max = 4094
+
self.vlan_min = vlan_min
self.vlan_max = vlan_max
self.vlans.clear()
else:
LOG.error("No vlan found with network \"%s\"", network_id)
+ def populate_already_used(self, vlans):
+ for vlan_id, network_id in vlans:
+ LOG.debug("Adding already populated vlan %s -> %s" %
+ (vlan_id, network_id))
+ self.already_used(vlan_id, network_id)
+
class OVSQuantumPlugin(QuantumPluginBase):
def __init__(self, configfile=None):
- if configfile is None:
- if os.path.exists(CONF_FILE):
- configfile = CONF_FILE
- else:
- configfile = find_config(os.path.abspath(
- os.path.dirname(__file__)))
- if configfile is None:
- raise Exception("Configuration file \"%s\" doesn't exist" %
- (configfile))
- LOG.debug("Using configuration file: %s" % configfile)
- conf = config.parse(configfile)
+ conf = config.parse(CONF_FILE)
options = {"sql_connection": conf.DATABASE.sql_connection}
reconnect_interval = conf.DATABASE.reconnect_interval
options.update({"reconnect_interval": reconnect_interval})
db.configure_db(options)
- vlan_min = conf.OVS.vlan_min
- vlan_max = conf.OVS.vlan_max
-
- if vlan_min > vlan_max:
- LOG.warn("Using default VLAN values! vlan_min = %s is larger"
- " than vlan_max = %s!" % (vlan_min, vlan_max))
- vlan_min = 1
- vlan_max = 4094
-
- self.vmap = VlanMap(vlan_min, vlan_max)
+ self.vmap = VlanMap(conf.OVS.vlan_min, conf.OVS.vlan_max)
# Populate the map with anything that is already present in the
# database
- vlans = ovs_db.get_vlans()
- for x in vlans:
- vlan_id, network_id = x
- LOG.debug("Adding already populated vlan %s -> %s" %
- (vlan_id, network_id))
- self.vmap.already_used(vlan_id, network_id)
+ self.vmap.populate_already_used(ovs_db.get_vlans())
def get_all_networks(self, tenant_id, **kwargs):
nets = []
def create_network(self, tenant_id, net_name, **kwargs):
net = db.network_create(tenant_id, net_name,
op_status=OperationalStatus.UP)
+ try:
+ vlan_id = self.vmap.acquire(str(net.uuid))
+ except NoFreeVLANException:
+ db.network_destroy(net.uuid)
+ raise
+
LOG.debug("Created network: %s" % net)
- vlan_id = self.vmap.acquire(str(net.uuid))
ovs_db.add_vlan_binding(vlan_id, str(net.uuid))
return self._make_net_dict(str(net.uuid), net.name, [], net.op_status)
db.validate_port_ownership(tenant_id, net_id, port_id)
res = db.port_get(port_id, net_id)
return res.interface_id
+
+
+class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
+ def __init__(self, configfile=None):
+ conf = config.parse(CONF_FILE)
+ options = {"sql_connection": conf.DATABASE.sql_connection}
+ options.update({'base': models_v2.model_base.BASEV2})
+ reconnect_interval = conf.DATABASE.reconnect_interval
+ options.update({"reconnect_interval": reconnect_interval})
+ db.configure_db(options)
+
+ self.vmap = VlanMap(conf.OVS.vlan_min, conf.OVS.vlan_max)
+ self.vmap.populate_already_used(ovs_db_v2.get_vlans())
+
+ def create_network(self, context, network):
+ net = super(OVSQuantumPluginV2, self).create_network(context, network)
+ try:
+ vlan_id = self.vmap.acquire(str(net['id']))
+ except NoFreeVLANException:
+ super(OVSQuantumPluginV2, self).delete_network(context, net['id'])
+ raise
+
+ LOG.debug("Created network: %s" % net['id'])
+ ovs_db_v2.add_vlan_binding(vlan_id, str(net['id']))
+ return net
+
+ def delete_network(self, context, id):
+ ovs_db_v2.remove_vlan_binding(id)
+ self.vmap.release(id)
+ return super(OVSQuantumPluginV2, self).delete_network(context, id)