Merge "Initial implemention of MetaPlugin"
This commit is contained in:
commit
6856f04d34
26
etc/quantum/plugins/metaplugin/metaplugin.ini
Normal file
26
etc/quantum/plugins/metaplugin/metaplugin.ini
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[DATABASE]
|
||||||
|
# This line MUST be changed to actually run the plugin.
|
||||||
|
# Example:
|
||||||
|
# sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum
|
||||||
|
# Replace 127.0.0.1 above with the IP address of the database used by the
|
||||||
|
# main quantum server. (Leave it as is if the database runs on this host.)
|
||||||
|
sql_connection = mysql://root:password@localhost/quantum_metaplugin?charset=utf8
|
||||||
|
|
||||||
|
# Database reconnection retry times - in event connectivity is lost
|
||||||
|
# set to -1 implgies an infinite retry count
|
||||||
|
# sql_max_retries = 10
|
||||||
|
# Database reconnection interval in seconds - in event connectivity is lost
|
||||||
|
reconnect_interval = 2
|
||||||
|
|
||||||
|
[META]
|
||||||
|
## This is list of flavor:quantum_plugins
|
||||||
|
# extension method is used in the order of this list
|
||||||
|
plugin_list= 'openvswitch:quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPluginV2,linuxbridge:quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2'
|
||||||
|
|
||||||
|
# Default value of flavor
|
||||||
|
default_flavor = 'openvswitch'
|
||||||
|
|
||||||
|
# supported extentions
|
||||||
|
supported_extension_aliases = 'providernet'
|
||||||
|
# specific method map for each flavor to extensions
|
||||||
|
extension_map = 'get_port_stats:nvp'
|
@ -23,8 +23,8 @@ import netaddr
|
|||||||
from quantum.agent.linux import ip_lib
|
from quantum.agent.linux import ip_lib
|
||||||
from quantum.agent.linux import ovs_lib
|
from quantum.agent.linux import ovs_lib
|
||||||
from quantum.agent.linux import utils
|
from quantum.agent.linux import utils
|
||||||
from quantum.common import exceptions
|
|
||||||
from quantum.openstack.common import cfg
|
from quantum.openstack.common import cfg
|
||||||
|
from quantum.openstack.common import importutils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -36,7 +36,9 @@ OPTS = [
|
|||||||
help='MTU setting for device.'),
|
help='MTU setting for device.'),
|
||||||
cfg.StrOpt('ryu_api_host',
|
cfg.StrOpt('ryu_api_host',
|
||||||
default='127.0.0.1:8080',
|
default='127.0.0.1:8080',
|
||||||
help='Openflow Ryu REST API host:port')
|
help='Openflow Ryu REST API host:port'),
|
||||||
|
cfg.StrOpt('meta_flavor_driver_mappings',
|
||||||
|
help='Mapping between flavor and LinuxInterfaceDriver')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -213,3 +215,62 @@ class RyuInterfaceDriver(OVSInterfaceDriver):
|
|||||||
datapath_id = ovs_br.get_datapath_id()
|
datapath_id = ovs_br.get_datapath_id()
|
||||||
port_no = ovs_br.get_port_ofport(device_name)
|
port_no = ovs_br.get_port_ofport(device_name)
|
||||||
self.ryu_client.create_port(network_id, datapath_id, port_no)
|
self.ryu_client.create_port(network_id, datapath_id, port_no)
|
||||||
|
|
||||||
|
|
||||||
|
class MetaInterfaceDriver(LinuxInterfaceDriver):
|
||||||
|
def __init__(self, conf):
|
||||||
|
super(MetaInterfaceDriver, self).__init__(conf)
|
||||||
|
from quantumclient.v2_0 import client
|
||||||
|
self.quantum = client.Client(
|
||||||
|
username=self.conf.admin_user,
|
||||||
|
password=self.conf.admin_password,
|
||||||
|
tenant_name=self.conf.admin_tenant_name,
|
||||||
|
auth_url=self.conf.auth_url,
|
||||||
|
auth_strategy=self.conf.auth_strategy,
|
||||||
|
auth_region=self.conf.auth_region
|
||||||
|
)
|
||||||
|
self.flavor_driver_map = {}
|
||||||
|
for flavor, driver_name in [
|
||||||
|
driver_set.split(':')
|
||||||
|
for driver_set in
|
||||||
|
self.conf.meta_flavor_driver_mappings.split(',')]:
|
||||||
|
self.flavor_driver_map[flavor] =\
|
||||||
|
self._load_driver(driver_name)
|
||||||
|
|
||||||
|
def _get_driver_by_network_id(self, network_id):
|
||||||
|
network = self.quantum.show_network(network_id)
|
||||||
|
flavor = network['network']['flavor:id']
|
||||||
|
return self.flavor_driver_map[flavor]
|
||||||
|
|
||||||
|
def _get_driver_by_device_name(self, device_name):
|
||||||
|
device = ip_lib.IPDevice(device_name, self.conf.root_helper)
|
||||||
|
mac_address = device.link.address
|
||||||
|
ports = self.quantum.list_ports(mac_address=mac_address)
|
||||||
|
if not 'ports' in ports or len(ports['ports']) < 1:
|
||||||
|
raise Exception('No port for this device %s' % device_name)
|
||||||
|
return self._get_driver_by_network_id(ports['ports'][0]['network_id'])
|
||||||
|
|
||||||
|
def get_device_name(self, port):
|
||||||
|
driver = self._get_driver_by_network_id(port.network_id)
|
||||||
|
return driver.get_device_name(port)
|
||||||
|
|
||||||
|
def plug(self, network_id, port_id, device_name, mac_address):
|
||||||
|
driver = self._get_driver_by_network_id(network_id)
|
||||||
|
return driver.plug(network_id, port_id, device_name, mac_address)
|
||||||
|
|
||||||
|
def unplug(self, device_name):
|
||||||
|
driver = self._get_driver_by_device_name(device_name)
|
||||||
|
return driver.unplug(device_name)
|
||||||
|
|
||||||
|
def _load_driver(self, driver_provider):
|
||||||
|
LOG.debug("Driver location:%s", driver_provider)
|
||||||
|
# If the plugin can't be found let them know gracefully
|
||||||
|
try:
|
||||||
|
LOG.info("Loading Driver: %s" % driver_provider)
|
||||||
|
plugin_klass = importutils.import_class(driver_provider)
|
||||||
|
except ClassNotFound:
|
||||||
|
LOG.exception("Error loading driver")
|
||||||
|
raise Exception("driver_provider not found. You can install a "
|
||||||
|
"Driver with: pip install <plugin-name>\n"
|
||||||
|
"Example: pip install quantum-sample-driver")
|
||||||
|
return plugin_klass(self.conf)
|
||||||
|
59
quantum/extensions/flavor.py
Normal file
59
quantum/extensions/flavor.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012 Nachi Ueno, NTT MCL, 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from quantum.api.v2 import attributes
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
FLAVOR_ATTRIBUTE = {
|
||||||
|
'networks': {
|
||||||
|
'flavor:id': {'allow_post': True,
|
||||||
|
'allow_put': False,
|
||||||
|
'is_visible': True,
|
||||||
|
'default': attributes.ATTR_NOT_SPECIFIED}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Flavor(object):
|
||||||
|
@classmethod
|
||||||
|
def get_name(cls):
|
||||||
|
return "Flavor for each network"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_alias(cls):
|
||||||
|
return "flavor"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_description(cls):
|
||||||
|
return "Flavor"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_namespace(cls):
|
||||||
|
return "http://docs.openstack.org/ext/flavor/api/v1.0"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_updated(cls):
|
||||||
|
return "2012-07-20T10:00:00-00:00"
|
||||||
|
|
||||||
|
def get_extended_attributes(self, version):
|
||||||
|
if version == "2.0":
|
||||||
|
return FLAVOR_ATTRIBUTE
|
||||||
|
else:
|
||||||
|
return {}
|
81
quantum/plugins/metaplugin/README
Normal file
81
quantum/plugins/metaplugin/README
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# -- Background
|
||||||
|
|
||||||
|
This plugin support multiple plugin at same time. This plugin is for L3 connectivility
|
||||||
|
between networks which are realized by different plugins.This plugin add new attribute 'flavor:id'.
|
||||||
|
flavor:id correspond to specific plugin ( flavor-plugin mapping could be configureable by plugin_list config.
|
||||||
|
This plugin also support extensions. We can map extension to plugin by using extension_map config.
|
||||||
|
|
||||||
|
[DATABASE]
|
||||||
|
# This line MUST be changed to actually run the plugin.
|
||||||
|
# Example:
|
||||||
|
# sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum
|
||||||
|
# Replace 127.0.0.1 above with the IP address of the database used by the
|
||||||
|
# main quantum server. (Leave it as is if the database runs on this host.)
|
||||||
|
sql_connection = mysql://root:password@localhost/quantum_metaplugin?charset=utf8
|
||||||
|
|
||||||
|
# Database reconnection retry times - in event connectivity is lost
|
||||||
|
# set to -1 implgies an infinite retry count
|
||||||
|
# sql_max_retries = 10
|
||||||
|
# Database reconnection interval in seconds - in event connectivity is lost
|
||||||
|
reconnect_interval = 2
|
||||||
|
|
||||||
|
[META]
|
||||||
|
## This is list of flavor:quantum_plugins
|
||||||
|
# extension method is used in the order of this list
|
||||||
|
plugin_list= 'openvswitch:quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPluginV2,linuxbridge:quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2'
|
||||||
|
|
||||||
|
# Default value of flavor
|
||||||
|
default_flavor = 'openvswitch'
|
||||||
|
|
||||||
|
# supported extentions
|
||||||
|
supported_extension_aliases = 'providernet'
|
||||||
|
# specific method map for each flavor to extensions
|
||||||
|
extension_map = 'get_port_stats:nvp'
|
||||||
|
|
||||||
|
# -- BridgeDriver Configration
|
||||||
|
# In order to use metaplugin, you should use MetaDriver. Following configation is needed.
|
||||||
|
|
||||||
|
[DEFAULT]
|
||||||
|
# Meta Plugin
|
||||||
|
# Mapping between flavor and driver
|
||||||
|
meta_flavor_driver_mappings = openvswitch:quantum.agent.linux.interface.OVSInterfaceDriver, linuxbridge:quantum.agent.linux.interface.BridgeInterfaceDriver
|
||||||
|
# interface driver for MetaPlugin
|
||||||
|
interface_driver = quantum.agent.linux.interface.MetaInterfaceDriver
|
||||||
|
|
||||||
|
# -- Agent
|
||||||
|
Agents for Metaplugin are in quantum/plugins/metaplugin/agent
|
||||||
|
linuxbridge_quantum_agent and ovs_quantum_agent is available.
|
||||||
|
|
||||||
|
# -- Extensions
|
||||||
|
|
||||||
|
- flavor
|
||||||
|
MetaPlugin supports flavor and provider net extension.
|
||||||
|
Metaplugin select plugin_list using flavor.
|
||||||
|
One plugin may use multiple flavor value. If the plugin support flavor, it may provide
|
||||||
|
multiple flavor of network.
|
||||||
|
|
||||||
|
- Attribute extension
|
||||||
|
Each plugin can use attribute extension such as provider_net, if you specify that in supported_extension_aliases.
|
||||||
|
|
||||||
|
- providernet
|
||||||
|
Vlan ID range of each plugin should be different, since Metaplugin dose not manage that.
|
||||||
|
|
||||||
|
#- limitations
|
||||||
|
|
||||||
|
Basically, All plugin should inherit QuantumDBPluginV2.
|
||||||
|
Metaplugin assumes all plugin share same Database expecially for IPAM part in QuantumV2 API.
|
||||||
|
You can use another plugin if you use ProxyPluginV2, which proxies request to the another quantum server.
|
||||||
|
|
||||||
|
Example flavor configration for ProxyPluginV2
|
||||||
|
|
||||||
|
meta_flavor_driver_mappings = "openvswitch:quantum.agent.linux.interface.OVSInterfaceDriver,proxy:quantum.plugins.metaplugin.proxy_quantum_plugin.ProxyPluginV2"
|
||||||
|
|
||||||
|
[Proxy]
|
||||||
|
auth_url = http://10.0.0.1:35357/v2.0
|
||||||
|
auth_region = RegionOne
|
||||||
|
admin_tenant_name = service
|
||||||
|
admin_user = quantum
|
||||||
|
admin_password = password
|
||||||
|
|
||||||
|
|
||||||
|
|
16
quantum/plugins/metaplugin/__init__.py
Normal file
16
quantum/plugins/metaplugin/__init__.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, 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.
|
176
quantum/plugins/metaplugin/agent/linuxbridge_quantum_agent.py
Executable file
176
quantum/plugins/metaplugin/agent/linuxbridge_quantum_agent.py
Executable file
@ -0,0 +1,176 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012 Cisco Systems, Inc.
|
||||||
|
# Copyright 2012 NTT MCL, 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.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Performs per host Linux Bridge configuration for Quantum.
|
||||||
|
# Based on the structure of the OpenVSwitch agent in the
|
||||||
|
# Quantum OpenVSwitch Plugin.
|
||||||
|
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
from sqlalchemy.ext.sqlsoup import SqlSoup
|
||||||
|
|
||||||
|
from quantum.openstack.common import cfg
|
||||||
|
from quantum.common import config as logging_config
|
||||||
|
from quantum.plugins.linuxbridge.common import config
|
||||||
|
import quantum.plugins.linuxbridge.agent.linuxbridge_quantum_agent as lb
|
||||||
|
|
||||||
|
from quantum.agent.linux import utils
|
||||||
|
|
||||||
|
logging.basicConfig()
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
BRIDGE_NAME_PREFIX = "brq"
|
||||||
|
VLAN_BINDINGS = "vlan_bindings"
|
||||||
|
PORT_BINDINGS = "port_bindings"
|
||||||
|
OP_STATUS_UP = "UP"
|
||||||
|
OP_STATUS_DOWN = "DOWN"
|
||||||
|
# Default inteval values
|
||||||
|
DEFAULT_POLLING_INTERVAL = 2
|
||||||
|
DEFAULT_RECONNECT_INTERVAL = 2
|
||||||
|
|
||||||
|
|
||||||
|
class MetaLinuxBridgeQuantumAgent(lb.LinuxBridgeQuantumAgent):
|
||||||
|
|
||||||
|
def manage_networks_on_host(self, db,
|
||||||
|
old_vlan_bindings,
|
||||||
|
old_port_bindings):
|
||||||
|
vlan_bindings = {}
|
||||||
|
try:
|
||||||
|
flavor_key = db.flavors.network_id
|
||||||
|
vlan_key = db.vlan_bindings.network_id
|
||||||
|
query = db.session.query(db.vlan_bindings)
|
||||||
|
joined = query.join((db.flavors,
|
||||||
|
flavor_key == vlan_key))
|
||||||
|
where = db.flavors.flavor == 'linuxbridge'
|
||||||
|
vlan_binds = joined.filter(where).all()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.info("Unable to get vlan bindings! Exception: %s" % e)
|
||||||
|
self.db_connected = False
|
||||||
|
return {VLAN_BINDINGS: {},
|
||||||
|
PORT_BINDINGS: []}
|
||||||
|
|
||||||
|
vlans_string = ""
|
||||||
|
for bind in vlan_binds:
|
||||||
|
entry = {'network_id': bind.network_id, 'vlan_id': bind.vlan_id}
|
||||||
|
vlan_bindings[bind.network_id] = entry
|
||||||
|
vlans_string = "%s %s" % (vlans_string, entry)
|
||||||
|
|
||||||
|
port_bindings = []
|
||||||
|
try:
|
||||||
|
flavor_key = db.flavors.network_id
|
||||||
|
port_key = db.ports.network_id
|
||||||
|
query = db.session.query(db.ports)
|
||||||
|
joined = query.join((db.flavors,
|
||||||
|
flavor_key == port_key))
|
||||||
|
where = db.flavors.flavor == 'linuxbridge'
|
||||||
|
port_binds = joined.filter(where).all()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.info("Unable to get port bindings! Exception: %s" % e)
|
||||||
|
self.db_connected = False
|
||||||
|
return {VLAN_BINDINGS: {},
|
||||||
|
PORT_BINDINGS: []}
|
||||||
|
|
||||||
|
all_bindings = {}
|
||||||
|
for bind in port_binds:
|
||||||
|
append_entry = False
|
||||||
|
if self.target_v2_api:
|
||||||
|
all_bindings[bind.id] = bind
|
||||||
|
entry = {'network_id': bind.network_id,
|
||||||
|
'uuid': bind.id,
|
||||||
|
'status': bind.status,
|
||||||
|
'interface_id': bind.id}
|
||||||
|
append_entry = bind.admin_state_up
|
||||||
|
else:
|
||||||
|
all_bindings[bind.uuid] = bind
|
||||||
|
entry = {'network_id': bind.network_id, 'state': bind.state,
|
||||||
|
'op_status': bind.op_status, 'uuid': bind.uuid,
|
||||||
|
'interface_id': bind.interface_id}
|
||||||
|
append_entry = bind.state == 'ACTIVE'
|
||||||
|
if append_entry:
|
||||||
|
port_bindings.append(entry)
|
||||||
|
|
||||||
|
plugged_interfaces = []
|
||||||
|
ports_string = ""
|
||||||
|
for pb in port_bindings:
|
||||||
|
ports_string = "%s %s" % (ports_string, pb)
|
||||||
|
port_id = pb['uuid']
|
||||||
|
interface_id = pb['interface_id']
|
||||||
|
|
||||||
|
vlan_id = str(vlan_bindings[pb['network_id']]['vlan_id'])
|
||||||
|
if self.process_port_binding(port_id,
|
||||||
|
pb['network_id'],
|
||||||
|
interface_id,
|
||||||
|
vlan_id):
|
||||||
|
if self.target_v2_api:
|
||||||
|
all_bindings[port_id].status = OP_STATUS_UP
|
||||||
|
else:
|
||||||
|
all_bindings[port_id].op_status = OP_STATUS_UP
|
||||||
|
|
||||||
|
plugged_interfaces.append(interface_id)
|
||||||
|
|
||||||
|
if old_port_bindings != port_bindings:
|
||||||
|
LOG.debug("Port-bindings: %s" % ports_string)
|
||||||
|
|
||||||
|
self.process_unplugged_interfaces(plugged_interfaces)
|
||||||
|
|
||||||
|
if old_vlan_bindings != vlan_bindings:
|
||||||
|
LOG.debug("VLAN-bindings: %s" % vlans_string)
|
||||||
|
|
||||||
|
self.process_deleted_networks(vlan_bindings)
|
||||||
|
|
||||||
|
try:
|
||||||
|
db.commit()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.info("Unable to update database! Exception: %s" % e)
|
||||||
|
db.rollback()
|
||||||
|
vlan_bindings = {}
|
||||||
|
port_bindings = []
|
||||||
|
|
||||||
|
return {VLAN_BINDINGS: vlan_bindings,
|
||||||
|
PORT_BINDINGS: port_bindings}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
cfg.CONF(args=sys.argv, project='quantum')
|
||||||
|
|
||||||
|
# (TODO) - swap with common logging
|
||||||
|
logging_config.setup_logging(cfg.CONF)
|
||||||
|
|
||||||
|
br_name_prefix = BRIDGE_NAME_PREFIX
|
||||||
|
physical_interface = cfg.CONF.LINUX_BRIDGE.physical_interface
|
||||||
|
polling_interval = cfg.CONF.AGENT.polling_interval
|
||||||
|
reconnect_interval = cfg.CONF.DATABASE.reconnect_interval
|
||||||
|
root_helper = cfg.CONF.AGENT.root_helper
|
||||||
|
'Establish database connection and load models'
|
||||||
|
db_connection_url = cfg.CONF.DATABASE.sql_connection
|
||||||
|
plugin = MetaLinuxBridgeQuantumAgent(br_name_prefix, physical_interface,
|
||||||
|
polling_interval, reconnect_interval,
|
||||||
|
root_helper,
|
||||||
|
cfg.CONF.AGENT.target_v2_api)
|
||||||
|
LOG.info("Agent initialized successfully, now running... ")
|
||||||
|
plugin.daemon_loop(db_connection_url)
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
194
quantum/plugins/metaplugin/agent/ovs_quantum_agent.py
Executable file
194
quantum/plugins/metaplugin/agent/ovs_quantum_agent.py
Executable file
@ -0,0 +1,194 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# 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: Somik Behera, Nicira Networks, Inc.
|
||||||
|
# @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 sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
from sqlalchemy.ext import sqlsoup
|
||||||
|
|
||||||
|
from quantum.agent.linux import ovs_lib
|
||||||
|
from quantum.common import config as logging_config
|
||||||
|
from quantum.openstack.common import cfg
|
||||||
|
from quantum.plugins.openvswitch.common import config
|
||||||
|
from quantum.plugins.openvswitch.agent.ovs_quantum_agent import OVSQuantumAgent
|
||||||
|
|
||||||
|
logging.basicConfig()
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Global constants.
|
||||||
|
OP_STATUS_UP = "UP"
|
||||||
|
OP_STATUS_DOWN = "DOWN"
|
||||||
|
|
||||||
|
# A placeholder for dead vlans.
|
||||||
|
DEAD_VLAN_TAG = "4095"
|
||||||
|
|
||||||
|
# Default interval values
|
||||||
|
DEFAULT_POLLING_INTERVAL = 2
|
||||||
|
DEFAULT_RECONNECT_INTERVAL = 2
|
||||||
|
|
||||||
|
|
||||||
|
class MetaOVSQuantumAgent(OVSQuantumAgent):
|
||||||
|
|
||||||
|
def daemon_loop(self, db_connection_url):
|
||||||
|
'''Main processing loop for Non-Tunneling Agent.
|
||||||
|
|
||||||
|
:param options: database information - in the event need to reconnect
|
||||||
|
'''
|
||||||
|
self.local_vlan_map = {}
|
||||||
|
old_local_bindings = {}
|
||||||
|
old_vif_ports = {}
|
||||||
|
db_connected = False
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if not db_connected:
|
||||||
|
time.sleep(self.reconnect_interval)
|
||||||
|
db = sqlsoup.SqlSoup(db_connection_url)
|
||||||
|
db_connected = True
|
||||||
|
LOG.info("Connecting to database \"%s\" on %s" %
|
||||||
|
(db.engine.url.database, db.engine.url.host))
|
||||||
|
|
||||||
|
all_bindings = {}
|
||||||
|
try:
|
||||||
|
flavor_key = db.flavors.network_id
|
||||||
|
port_key = db.ports.network_id
|
||||||
|
query = db.session.query(db.ports)
|
||||||
|
joined = query.join((db.flavors,
|
||||||
|
flavor_key == port_key))
|
||||||
|
where = db.flavors.flavor == 'openvswitch'
|
||||||
|
ports = joined.filter(where).all()
|
||||||
|
except Exception, e:
|
||||||
|
LOG.info("Unable to get port bindings! Exception: %s" % e)
|
||||||
|
db_connected = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
for port in ports:
|
||||||
|
if self.target_v2_api:
|
||||||
|
all_bindings[port.id] = port
|
||||||
|
else:
|
||||||
|
all_bindings[port.interface_id] = port
|
||||||
|
|
||||||
|
vlan_bindings = {}
|
||||||
|
try:
|
||||||
|
flavor_key = db.flavors.network_id
|
||||||
|
vlan_key = db.vlan_bindings.network_id
|
||||||
|
query = db.session.query(db.vlan_bindings)
|
||||||
|
joined = query.join((db.flavors,
|
||||||
|
flavor_key == vlan_key))
|
||||||
|
where = db.flavors.flavor == 'openvswitch'
|
||||||
|
vlan_binds = joined.filter(where).all()
|
||||||
|
except Exception, e:
|
||||||
|
LOG.info("Unable to get vlan bindings! Exception: %s" % e)
|
||||||
|
db_connected = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
for bind in vlan_binds:
|
||||||
|
vlan_bindings[bind.network_id] = bind.vlan_id
|
||||||
|
|
||||||
|
new_vif_ports = {}
|
||||||
|
new_local_bindings = {}
|
||||||
|
vif_ports = self.int_br.get_vif_ports()
|
||||||
|
for p in vif_ports:
|
||||||
|
new_vif_ports[p.vif_id] = p
|
||||||
|
if p.vif_id in all_bindings:
|
||||||
|
net_id = all_bindings[p.vif_id].network_id
|
||||||
|
new_local_bindings[p.vif_id] = net_id
|
||||||
|
else:
|
||||||
|
# no binding, put him on the 'dead vlan'
|
||||||
|
self.int_br.set_db_attribute("Port", p.port_name, "tag",
|
||||||
|
DEAD_VLAN_TAG)
|
||||||
|
self.int_br.add_flow(priority=2,
|
||||||
|
in_port=p.ofport,
|
||||||
|
actions="drop")
|
||||||
|
|
||||||
|
old_b = old_local_bindings.get(p.vif_id, None)
|
||||||
|
new_b = new_local_bindings.get(p.vif_id, None)
|
||||||
|
|
||||||
|
if old_b != new_b:
|
||||||
|
if old_b is not None:
|
||||||
|
LOG.info("Removing binding to net-id = %s for %s"
|
||||||
|
% (old_b, str(p)))
|
||||||
|
self.port_unbound(p, True)
|
||||||
|
if p.vif_id in all_bindings:
|
||||||
|
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
|
||||||
|
net_id = all_bindings[p.vif_id].network_id
|
||||||
|
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].status = OP_STATUS_UP
|
||||||
|
LOG.info(("Adding binding to net-id = %s "
|
||||||
|
"for %s on vlan %s") %
|
||||||
|
(new_b, str(p), vlan_id))
|
||||||
|
|
||||||
|
for vif_id in old_vif_ports:
|
||||||
|
if vif_id not in new_vif_ports:
|
||||||
|
LOG.info("Port Disappeared: %s" % vif_id)
|
||||||
|
if vif_id in old_local_bindings:
|
||||||
|
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].status = OP_STATUS_DOWN
|
||||||
|
|
||||||
|
old_vif_ports = new_vif_ports
|
||||||
|
old_local_bindings = new_local_bindings
|
||||||
|
try:
|
||||||
|
db.commit()
|
||||||
|
except Exception, e:
|
||||||
|
LOG.info("Unable to commit to database! Exception: %s" % e)
|
||||||
|
db.rollback()
|
||||||
|
old_local_bindings = {}
|
||||||
|
old_vif_ports = {}
|
||||||
|
|
||||||
|
time.sleep(self.polling_interval)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
cfg.CONF(args=sys.argv, project='quantum')
|
||||||
|
|
||||||
|
# (TODO) gary - swap with common logging
|
||||||
|
logging_config.setup_logging(cfg.CONF)
|
||||||
|
|
||||||
|
# Determine which agent type to use.
|
||||||
|
enable_tunneling = cfg.CONF.OVS.enable_tunneling
|
||||||
|
integ_br = cfg.CONF.OVS.integration_bridge
|
||||||
|
db_connection_url = cfg.CONF.DATABASE.sql_connection
|
||||||
|
polling_interval = cfg.CONF.AGENT.polling_interval
|
||||||
|
reconnect_interval = cfg.CONF.DATABASE.reconnect_interval
|
||||||
|
root_helper = cfg.CONF.AGENT.root_helper
|
||||||
|
|
||||||
|
# Determine API Version to use
|
||||||
|
target_v2_api = cfg.CONF.AGENT.target_v2_api
|
||||||
|
|
||||||
|
# Get parameters for OVSQuantumAgent.
|
||||||
|
plugin = MetaOVSQuantumAgent(integ_br, root_helper, polling_interval,
|
||||||
|
reconnect_interval, target_v2_api)
|
||||||
|
|
||||||
|
# Start everything.
|
||||||
|
plugin.daemon_loop(db_connection_url)
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
16
quantum/plugins/metaplugin/common/__init__.py
Normal file
16
quantum/plugins/metaplugin/common/__init__.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, 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.
|
45
quantum/plugins/metaplugin/common/config.py
Normal file
45
quantum/plugins/metaplugin/common/config.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, 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.
|
||||||
|
|
||||||
|
from quantum.openstack.common import cfg
|
||||||
|
|
||||||
|
|
||||||
|
database_opts = [
|
||||||
|
cfg.StrOpt('sql_connection', default='sqlite://'),
|
||||||
|
cfg.IntOpt('sql_max_retries', default=-1),
|
||||||
|
cfg.IntOpt('reconnect_interval', default=2),
|
||||||
|
]
|
||||||
|
|
||||||
|
meta_plugin_opts = [
|
||||||
|
cfg.StrOpt('plugin_list', default=''),
|
||||||
|
cfg.StrOpt('default_flavor', default=''),
|
||||||
|
cfg.StrOpt('supported_extension_aliases', default=''),
|
||||||
|
cfg.StrOpt('extension_map', default='')
|
||||||
|
]
|
||||||
|
|
||||||
|
proxy_plugin_opts = [
|
||||||
|
cfg.StrOpt('admin_user'),
|
||||||
|
cfg.StrOpt('admin_password'),
|
||||||
|
cfg.StrOpt('admin_tenant_name'),
|
||||||
|
cfg.StrOpt('auth_url'),
|
||||||
|
cfg.StrOpt('auth_strategy', default='keystone'),
|
||||||
|
cfg.StrOpt('auth_region'),
|
||||||
|
]
|
||||||
|
|
||||||
|
cfg.CONF.register_opts(database_opts, "DATABASE")
|
||||||
|
cfg.CONF.register_opts(meta_plugin_opts, "META")
|
||||||
|
cfg.CONF.register_opts(proxy_plugin_opts, "PROXY")
|
40
quantum/plugins/metaplugin/meta_db_v2.py
Normal file
40
quantum/plugins/metaplugin/meta_db_v2.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, 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.
|
||||||
|
|
||||||
|
from sqlalchemy.orm import exc
|
||||||
|
|
||||||
|
import quantum.db.api as db
|
||||||
|
from quantum.plugins.metaplugin import meta_models_v2
|
||||||
|
|
||||||
|
|
||||||
|
def get_flavor_by_network(net_id):
|
||||||
|
session = db.get_session()
|
||||||
|
try:
|
||||||
|
binding = (session.query(meta_models_v2.Flavor).
|
||||||
|
filter_by(network_id=net_id).
|
||||||
|
one())
|
||||||
|
except exc.NoResultFound:
|
||||||
|
return None
|
||||||
|
return binding.flavor
|
||||||
|
|
||||||
|
|
||||||
|
def add_flavor_binding(flavor, net_id):
|
||||||
|
session = db.get_session()
|
||||||
|
binding = meta_models_v2.Flavor(flavor=flavor, network_id=net_id)
|
||||||
|
session.add(binding)
|
||||||
|
session.flush()
|
||||||
|
return binding
|
32
quantum/plugins/metaplugin/meta_models_v2.py
Normal file
32
quantum/plugins/metaplugin/meta_models_v2.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, 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.
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import Column, String
|
||||||
|
|
||||||
|
from quantum.db import models_v2
|
||||||
|
|
||||||
|
|
||||||
|
class Flavor(models_v2.model_base.BASEV2):
|
||||||
|
"""Represents a binding of network_id to flavor."""
|
||||||
|
flavor = Column(String(255))
|
||||||
|
network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id',
|
||||||
|
ondelete="CASCADE"),
|
||||||
|
primary_key=True)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Flavor(%s,%s)>" % (self.flavor, self.network_id)
|
216
quantum/plugins/metaplugin/meta_quantum_plugin.py
Normal file
216
quantum/plugins/metaplugin/meta_quantum_plugin.py
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, 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.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from quantum.common import exceptions as exc
|
||||||
|
|
||||||
|
from quantum.api.v2 import attributes
|
||||||
|
from quantum.common.utils import find_config_file
|
||||||
|
from quantum.db import api as db
|
||||||
|
from quantum.db import db_base_plugin_v2
|
||||||
|
from quantum.db import models_v2
|
||||||
|
from quantum.openstack.common import cfg
|
||||||
|
from quantum.openstack.common import importutils
|
||||||
|
from quantum.plugins.metaplugin.common import config
|
||||||
|
from quantum.plugins.metaplugin import meta_db_v2
|
||||||
|
from quantum.plugins.metaplugin.meta_models_v2 import Flavor
|
||||||
|
from quantum import policy
|
||||||
|
|
||||||
|
LOG = logging.getLogger("metaplugin")
|
||||||
|
|
||||||
|
|
||||||
|
class MetaPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
|
||||||
|
def __init__(self, configfile=None):
|
||||||
|
LOG.debug("Start initializing metaplugin")
|
||||||
|
options = {"sql_connection": cfg.CONF.DATABASE.sql_connection}
|
||||||
|
options.update({'base': models_v2.model_base.BASEV2})
|
||||||
|
sql_max_retries = cfg.CONF.DATABASE.sql_max_retries
|
||||||
|
options.update({"sql_max_retries": sql_max_retries})
|
||||||
|
reconnect_interval = cfg.CONF.DATABASE.reconnect_interval
|
||||||
|
options.update({"reconnect_interval": reconnect_interval})
|
||||||
|
self.supported_extension_aliases = \
|
||||||
|
cfg.CONF.META.supported_extension_aliases.split(',')
|
||||||
|
self.supported_extension_aliases.append('flavor')
|
||||||
|
|
||||||
|
# Ignore config option overapping
|
||||||
|
def _is_opt_registered(opts, opt):
|
||||||
|
if opt.dest in opts:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
cfg._is_opt_registered = _is_opt_registered
|
||||||
|
|
||||||
|
# Keep existing tables if multiple plugin use same table name.
|
||||||
|
db.model_base.QuantumBase.__table_args__ = {'keep_existing': True}
|
||||||
|
|
||||||
|
self.plugins = {}
|
||||||
|
|
||||||
|
plugin_list = [plugin_set.split(':')
|
||||||
|
for plugin_set
|
||||||
|
in cfg.CONF.META.plugin_list.split(',')]
|
||||||
|
for flavor, plugin_provider in plugin_list:
|
||||||
|
self.plugins[flavor] = self._load_plugin(plugin_provider)
|
||||||
|
|
||||||
|
self.extension_map = {}
|
||||||
|
if not cfg.CONF.META.extension_map == '':
|
||||||
|
extension_list = [method_set.split(':')
|
||||||
|
for method_set
|
||||||
|
in cfg.CONF.META.extension_map.split(',')]
|
||||||
|
for method_name, flavor in extension_list:
|
||||||
|
self.extension_map[method_name] = flavor
|
||||||
|
|
||||||
|
self.default_flavor = cfg.CONF.META.default_flavor
|
||||||
|
|
||||||
|
if not self.default_flavor in self.plugins:
|
||||||
|
raise exc.Invalid('default_flavor %s is not plugin list' %
|
||||||
|
self.default_flavor)
|
||||||
|
|
||||||
|
def _load_plugin(self, plugin_provider):
|
||||||
|
LOG.debug("Plugin location:%s", plugin_provider)
|
||||||
|
# If the plugin can't be found let them know gracefully
|
||||||
|
try:
|
||||||
|
LOG.info("Loading Plugin: %s" % plugin_provider)
|
||||||
|
plugin_klass = importutils.import_class(plugin_provider)
|
||||||
|
except exc.ClassNotFound:
|
||||||
|
LOG.exception("Error loading plugin")
|
||||||
|
raise Exception("Plugin not found. You can install a "
|
||||||
|
"plugin with: pip install <plugin-name>\n"
|
||||||
|
"Example: pip install quantum-sample-plugin")
|
||||||
|
return plugin_klass()
|
||||||
|
|
||||||
|
def _get_plugin(self, flavor):
|
||||||
|
if not flavor in self.plugins:
|
||||||
|
raise Exception("Plugin for flavor %s not found." % flavor)
|
||||||
|
return self.plugins[flavor]
|
||||||
|
|
||||||
|
def __getattr__(self, key):
|
||||||
|
# At first, try to pickup extension command from extension_map
|
||||||
|
|
||||||
|
if key in self.extension_map:
|
||||||
|
flavor = self.extension_map[key]
|
||||||
|
plugin = self._get_plugin(flavor)
|
||||||
|
if plugin and hasattr(plugin, key):
|
||||||
|
return getattr(plugin, key)
|
||||||
|
|
||||||
|
# Second, try to match extension method in order of pluign list
|
||||||
|
|
||||||
|
for flavor, plugin in self.plugins.items():
|
||||||
|
if hasattr(plugin, key):
|
||||||
|
return getattr(plugin, key)
|
||||||
|
|
||||||
|
# if no plugin support the method, then raise
|
||||||
|
raise AttributeError
|
||||||
|
|
||||||
|
def _extend_network_dict(self, context, network):
|
||||||
|
network['flavor:id'] = self._get_flavor_by_network_id(network['id'])
|
||||||
|
|
||||||
|
def create_network(self, context, network):
|
||||||
|
n = network['network']
|
||||||
|
flavor = n.get('flavor:id')
|
||||||
|
if not str(flavor) in self.plugins:
|
||||||
|
flavor = self.default_flavor
|
||||||
|
plugin = self._get_plugin(flavor)
|
||||||
|
net = plugin.create_network(context, network)
|
||||||
|
LOG.debug("Created network: %s with flavor %s " % (net['id'], flavor))
|
||||||
|
try:
|
||||||
|
meta_db_v2.add_flavor_binding(flavor, str(net['id']))
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error('failed to add flavor bindings')
|
||||||
|
plugin.delete_network(context, net['id'])
|
||||||
|
raise Exception('Failed to create network')
|
||||||
|
|
||||||
|
LOG.debug("Created network: %s" % net['id'])
|
||||||
|
self._extend_network_dict(context, net)
|
||||||
|
return net
|
||||||
|
|
||||||
|
def delete_network(self, context, id):
|
||||||
|
flavor = meta_db_v2.get_flavor_by_network(id)
|
||||||
|
plugin = self._get_plugin(flavor)
|
||||||
|
return plugin.delete_network(context, id)
|
||||||
|
|
||||||
|
def get_network(self, context, id, fields=None, verbose=None):
|
||||||
|
flavor = meta_db_v2.get_flavor_by_network(id)
|
||||||
|
plugin = self._get_plugin(flavor)
|
||||||
|
net = plugin.get_network(context, id, fields, verbose)
|
||||||
|
if not fields or 'flavor:id' in fields:
|
||||||
|
self._extend_network_dict(context, net)
|
||||||
|
return net
|
||||||
|
|
||||||
|
def get_networks_with_flavor(self, context, filters=None,
|
||||||
|
fields=None, verbose=None):
|
||||||
|
collection = self._model_query(context, models_v2.Network)
|
||||||
|
collection = collection.join(Flavor,
|
||||||
|
models_v2.Network.id == Flavor.network_id)
|
||||||
|
if filters:
|
||||||
|
for key, value in filters.iteritems():
|
||||||
|
if key == 'flavor:id':
|
||||||
|
column = Flavor.flavor
|
||||||
|
else:
|
||||||
|
column = getattr(models_v2.Network, key, None)
|
||||||
|
if column:
|
||||||
|
collection = collection.filter(column.in_(value))
|
||||||
|
return [self._make_network_dict(c, fields) for c in collection.all()]
|
||||||
|
|
||||||
|
def get_networks(self, context, filters=None, fields=None, verbose=None):
|
||||||
|
nets = self.get_networks_with_flavor(context, filters,
|
||||||
|
None, verbose)
|
||||||
|
return [self.get_network(context, net['id'],
|
||||||
|
fields, verbose)
|
||||||
|
for net in nets]
|
||||||
|
|
||||||
|
def _get_flavor_by_network_id(self, network_id):
|
||||||
|
return meta_db_v2.get_flavor_by_network(network_id)
|
||||||
|
|
||||||
|
def _get_plugin_by_network_id(self, network_id):
|
||||||
|
flavor = self._get_flavor_by_network_id(network_id)
|
||||||
|
return self._get_plugin(flavor)
|
||||||
|
|
||||||
|
def create_port(self, context, port):
|
||||||
|
p = port['port']
|
||||||
|
if not 'network_id' in p:
|
||||||
|
raise exc.NotFound
|
||||||
|
plugin = self._get_plugin_by_network_id(p['network_id'])
|
||||||
|
return plugin.create_port(context, port)
|
||||||
|
|
||||||
|
def update_port(self, context, id, port):
|
||||||
|
port_in_db = self.get_port(context, id)
|
||||||
|
plugin = self._get_plugin_by_network_id(port_in_db['network_id'])
|
||||||
|
return plugin.update_port(context, id, port)
|
||||||
|
|
||||||
|
def delete_port(self, context, id):
|
||||||
|
port_in_db = self.get_port(context, id)
|
||||||
|
plugin = self._get_plugin_by_network_id(port_in_db['network_id'])
|
||||||
|
return plugin.delete_port(context, id)
|
||||||
|
|
||||||
|
def create_subnet(self, context, subnet):
|
||||||
|
s = subnet['subnet']
|
||||||
|
if not 'network_id' in s:
|
||||||
|
raise exc.NotFound
|
||||||
|
plugin = self._get_plugin_by_network_id(s['network_id'])
|
||||||
|
return plugin.create_subnet(context, subnet)
|
||||||
|
|
||||||
|
def update_subnet(self, context, id, subnet):
|
||||||
|
s = self.get_subnet(context, id)
|
||||||
|
plugin = self._get_plugin_by_network_id(s['network_id'])
|
||||||
|
return plugin.update_subnet(context, id, subnet)
|
||||||
|
|
||||||
|
def delete_subnet(self, context, id):
|
||||||
|
s = self.get_subnet(context, id)
|
||||||
|
plugin = self._get_plugin_by_network_id(s['network_id'])
|
||||||
|
return plugin.delete_subnet(context, id)
|
132
quantum/plugins/metaplugin/proxy_quantum_plugin.py
Normal file
132
quantum/plugins/metaplugin/proxy_quantum_plugin.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, 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.
|
||||||
|
|
||||||
|
from quantum.db import api as db
|
||||||
|
from quantum.db import db_base_plugin_v2
|
||||||
|
from quantum.db import models_v2
|
||||||
|
from quantum.openstack.common import cfg
|
||||||
|
from quantumclient.common import exceptions
|
||||||
|
from quantumclient.v2_0 import client
|
||||||
|
|
||||||
|
|
||||||
|
class ProxyPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
|
||||||
|
def __init__(self, configfile=None):
|
||||||
|
options = {"sql_connection": cfg.CONF.DATABASE.sql_connection}
|
||||||
|
options.update({'base': models_v2.model_base.BASEV2})
|
||||||
|
sql_max_retries = cfg.CONF.DATABASE.sql_max_retries
|
||||||
|
options.update({"sql_max_retries": sql_max_retries})
|
||||||
|
reconnect_interval = cfg.CONF.DATABASE.reconnect_interval
|
||||||
|
options.update({"reconnect_interval": reconnect_interval})
|
||||||
|
db.configure_db(options)
|
||||||
|
self.quantum = client.Client(
|
||||||
|
username=cfg.CONF.PROXY.admin_user,
|
||||||
|
password=cfg.CONF.PROXY.admin_password,
|
||||||
|
tenant_name=cfg.CONF.PROXY.admin_tenant_name,
|
||||||
|
auth_url=cfg.CONF.PROXY.auth_url,
|
||||||
|
auth_strategy=cfg.CONF.PROXY.auth_strategy,
|
||||||
|
auth_region=cfg.CONF.PROXY.auth_region
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_client(self):
|
||||||
|
return self.quantum
|
||||||
|
|
||||||
|
def create_subnet(self, context, subnet):
|
||||||
|
subnet_remote = self._get_client().create_subnet(subnet)
|
||||||
|
subnet['subnet']['id'] = subnet_remote['id']
|
||||||
|
tenant_id = self._get_tenant_id_for_create(context, subnet['subnet'])
|
||||||
|
subnet['subnet']['tenant_id'] = tenant_id
|
||||||
|
try:
|
||||||
|
subnet_in_db = super(ProxyPluginV2, self).create_subnet(
|
||||||
|
context, subnet)
|
||||||
|
except:
|
||||||
|
self._get_client().delete_subnet(subnet_remote['id'])
|
||||||
|
return subnet_in_db
|
||||||
|
|
||||||
|
def update_subnet(self, context, id, subnet):
|
||||||
|
subnet_in_db = super(ProxyPluginV2, self).update_subnet(
|
||||||
|
context, id, subnet)
|
||||||
|
try:
|
||||||
|
self._get_client().update_subnet(id, subnet)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("update subnet failed: %e" % e)
|
||||||
|
return subnet_in_db
|
||||||
|
|
||||||
|
def delete_subnet(self, context, id):
|
||||||
|
try:
|
||||||
|
self._get_client().delete_subnet(id)
|
||||||
|
except exceptions.NotFound:
|
||||||
|
LOG.warn("subnet in remote have already deleted")
|
||||||
|
pass
|
||||||
|
return super(ProxyPluginV2, self).delete_subnet(context, id)
|
||||||
|
|
||||||
|
def create_network(self, context, network):
|
||||||
|
network_remote = self._get_client().create_network(network)
|
||||||
|
network['network']['id'] = network_remote['id']
|
||||||
|
tenant_id = self._get_tenant_id_for_create(context, network['network'])
|
||||||
|
network['network']['tenant_id'] = tenant_id
|
||||||
|
try:
|
||||||
|
network_in_db = super(ProxyPluginV2, self).create_network(
|
||||||
|
context, network)
|
||||||
|
except:
|
||||||
|
self._get_client().delete_network(network_remote['id'])
|
||||||
|
return network_in_db
|
||||||
|
|
||||||
|
def update_network(self, context, id, network):
|
||||||
|
network_in_db = super(ProxyPluginV2, self).update_network(
|
||||||
|
context, id, network)
|
||||||
|
try:
|
||||||
|
self._get_client().update_network(id, network)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("update network failed: %e" % e)
|
||||||
|
return network_in_db
|
||||||
|
|
||||||
|
def delete_network(self, context, id):
|
||||||
|
try:
|
||||||
|
self._get_client().delete_network(id)
|
||||||
|
except exceptions.NetworkNotFound:
|
||||||
|
LOG.warn("network in remote have already deleted")
|
||||||
|
pass
|
||||||
|
return super(ProxyPluginV2, self).delete_network(context, id)
|
||||||
|
|
||||||
|
def create_port(self, context, port):
|
||||||
|
port_remote = self._get_client().create_port(port)
|
||||||
|
port['port']['id'] = port_remote['id']
|
||||||
|
tenant_id = self._get_tenant_id_for_create(context, port['port'])
|
||||||
|
port['port']['tenant_id'] = tenant_id
|
||||||
|
try:
|
||||||
|
port_in_db = super(ProxyPluginV2, self).create_port(
|
||||||
|
context, port)
|
||||||
|
except:
|
||||||
|
self._get_client().delete_port(port_remote['id'])
|
||||||
|
return port_in_db
|
||||||
|
|
||||||
|
def update_port(self, context, id, port):
|
||||||
|
port_in_db = super(ProxyPluginV2, self).update_port(
|
||||||
|
context, id, port)
|
||||||
|
try:
|
||||||
|
self._get_client().update_port(id, port)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("update port failed: %e" % e)
|
||||||
|
return port_in_db
|
||||||
|
|
||||||
|
def delete_port(self, context, id):
|
||||||
|
try:
|
||||||
|
self._get_client().delete_port(id)
|
||||||
|
except exceptions.portNotFound:
|
||||||
|
LOG.warn("port in remote have already deleted")
|
||||||
|
pass
|
||||||
|
return super(ProxyPluginV2, self).delete_port(context, id)
|
57
quantum/plugins/metaplugin/run_tests.py
Executable file
57
quantum/plugins/metaplugin/run_tests.py
Executable file
@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2010 OpenStack, LLC
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
"""Unittest runner for quantum Meta plugin
|
||||||
|
|
||||||
|
This file should be run from the top dir in the quantum directory
|
||||||
|
|
||||||
|
To run all tests::
|
||||||
|
PLUGIN_DIR=quantum/plugins/metaplugin ./run_tests.sh
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from nose import config
|
||||||
|
from nose import core
|
||||||
|
|
||||||
|
sys.path.append(os.getcwd())
|
||||||
|
sys.path.append(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
from quantum.common.test_lib import run_tests, test_config
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
exit_status = False
|
||||||
|
|
||||||
|
# if a single test case was specified,
|
||||||
|
# we should only invoked the tests once
|
||||||
|
invoke_once = len(sys.argv) > 1
|
||||||
|
|
||||||
|
test_config['plugin_name'] = "meta_quantum_plugin.MetaPluginV2"
|
||||||
|
|
||||||
|
cwd = os.getcwd()
|
||||||
|
|
||||||
|
working_dir = os.path.abspath("quantum/plugins/metaplugin")
|
||||||
|
c = config.Config(stream=sys.stdout,
|
||||||
|
env=os.environ,
|
||||||
|
verbosity=3,
|
||||||
|
workingDir=working_dir)
|
||||||
|
exit_status = exit_status or run_tests(c)
|
||||||
|
|
||||||
|
sys.exit(exit_status)
|
16
quantum/plugins/metaplugin/tests/__init__.py
Normal file
16
quantum/plugins/metaplugin/tests/__init__.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, 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.
|
16
quantum/plugins/metaplugin/tests/unit/__init__.py
Normal file
16
quantum/plugins/metaplugin/tests/unit/__init__.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, 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.
|
44
quantum/plugins/metaplugin/tests/unit/basetest.py
Normal file
44
quantum/plugins/metaplugin/tests/unit/basetest.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, 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.
|
||||||
|
|
||||||
|
import mox
|
||||||
|
import stubout
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import quantum.db.api as db
|
||||||
|
from quantum.db import models_v2
|
||||||
|
from quantum.plugins.metaplugin.tests.unit import utils
|
||||||
|
|
||||||
|
|
||||||
|
class BaseMetaTest(unittest.TestCase):
|
||||||
|
"""base test class for MetaPlugin unit tests"""
|
||||||
|
def setUp(self):
|
||||||
|
config = utils.get_config()
|
||||||
|
options = {"sql_connection": config.get("DATABASE", "sql_connection")}
|
||||||
|
options.update({'base': models_v2.model_base.BASEV2})
|
||||||
|
db.configure_db(options)
|
||||||
|
|
||||||
|
self.config = config
|
||||||
|
self.mox = mox.Mox()
|
||||||
|
self.stubs = stubout.StubOutForTesting()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.mox.UnsetStubs()
|
||||||
|
self.stubs.UnsetAll()
|
||||||
|
self.stubs.SmartUnsetAll()
|
||||||
|
self.mox.VerifyAll()
|
||||||
|
db.clear_db()
|
57
quantum/plugins/metaplugin/tests/unit/fake_plugin.py
Normal file
57
quantum/plugins/metaplugin/tests/unit/fake_plugin.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from quantum.common import exceptions as q_exc
|
||||||
|
from quantum.common.utils import find_config_file
|
||||||
|
from quantum.db import api as db
|
||||||
|
from quantum.db import db_base_plugin_v2
|
||||||
|
from quantum.db import models_v2
|
||||||
|
|
||||||
|
|
||||||
|
class Fake1(db_base_plugin_v2.QuantumDbPluginV2):
|
||||||
|
def fake_func(self):
|
||||||
|
return 'fake1'
|
||||||
|
|
||||||
|
def create_network(self, context, network):
|
||||||
|
net = super(Fake1, self).create_network(context, network)
|
||||||
|
return net
|
||||||
|
|
||||||
|
def delete_network(self, context, id):
|
||||||
|
return super(Fake1, self).delete_network(context, id)
|
||||||
|
|
||||||
|
def create_port(self, context, port):
|
||||||
|
port['port']['device_id'] = self.fake_func()
|
||||||
|
port = super(Fake1, self).create_port(context, port)
|
||||||
|
return port
|
||||||
|
|
||||||
|
def create_subnet(self, context, subnet):
|
||||||
|
subnet = super(Fake1, self).create_subnet(context, subnet)
|
||||||
|
return subnet
|
||||||
|
|
||||||
|
def update_port(self, context, id, port):
|
||||||
|
port = super(Fake1, self).update_port(context, id, port)
|
||||||
|
return port
|
||||||
|
|
||||||
|
def delete_port(self, context, id):
|
||||||
|
return super(Fake1, self).delete_port(context, id)
|
||||||
|
|
||||||
|
|
||||||
|
class Fake2(Fake1):
|
||||||
|
def fake_func(self):
|
||||||
|
return 'fake2'
|
||||||
|
|
||||||
|
def fake_func2(self):
|
||||||
|
return 'fake2'
|
268
quantum/plugins/metaplugin/tests/unit/test_plugin_base.py
Normal file
268
quantum/plugins/metaplugin/tests/unit/test_plugin_base.py
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright 2012, Nachi Ueno, NTT MCL, 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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import mox
|
||||||
|
import mock
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from quantum.common import config
|
||||||
|
from quantum.common.exceptions import NotImplementedError
|
||||||
|
from quantum.db import api as db
|
||||||
|
from quantum.openstack.common import cfg
|
||||||
|
from quantum.plugins.metaplugin.meta_quantum_plugin import MetaPluginV2
|
||||||
|
from quantum.plugins.metaplugin.proxy_quantum_plugin import ProxyPluginV2
|
||||||
|
from quantum.plugins.metaplugin.tests.unit.basetest import BaseMetaTest
|
||||||
|
from quantum.plugins.metaplugin.tests.unit import fake_plugin
|
||||||
|
from quantum import context
|
||||||
|
|
||||||
|
CONF_FILE = ""
|
||||||
|
ROOTDIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
ETCDIR = os.path.join(ROOTDIR, 'etc')
|
||||||
|
META_PATH = "quantum.plugins.metaplugin"
|
||||||
|
FAKE_PATH = "%s.tests.unit" % META_PATH
|
||||||
|
PROXY_PATH = "%s.proxy_quantum_plugin.ProxyPluginV2" % META_PATH
|
||||||
|
PLUGIN_LIST = \
|
||||||
|
'fake1:%s.fake_plugin.Fake1,fake2:%s.fake_plugin.Fake2,proxy:%s' % \
|
||||||
|
(FAKE_PATH, FAKE_PATH, PROXY_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
def etcdir(*p):
|
||||||
|
return os.path.join(ETCDIR, *p)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginBaseTest(BaseMetaTest):
|
||||||
|
"""Class conisting of MetaQuantumPluginV2 unit tests"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(PluginBaseTest, self).setUp()
|
||||||
|
db._ENGINE = None
|
||||||
|
db._MAKER = None
|
||||||
|
self.fake_tenant_id = str(uuid.uuid4())
|
||||||
|
self.context = context.get_admin_context()
|
||||||
|
|
||||||
|
args = ['--config-file', etcdir('quantum.conf.test')]
|
||||||
|
#config.parse(args=args)
|
||||||
|
# Update the plugin
|
||||||
|
cfg.CONF.set_override('auth_url', 'http://localhost:35357/v2.0',
|
||||||
|
'PROXY')
|
||||||
|
cfg.CONF.set_override('auth_region', 'RegionOne', 'PROXY')
|
||||||
|
cfg.CONF.set_override('admin_user', 'quantum', 'PROXY')
|
||||||
|
cfg.CONF.set_override('admin_password', 'password', 'PROXY')
|
||||||
|
cfg.CONF.set_override('admin_tenant_name', 'service', 'PROXY')
|
||||||
|
cfg.CONF.set_override('plugin_list', PLUGIN_LIST, 'META')
|
||||||
|
cfg.CONF.set_override('default_flavor', 'fake2', 'META')
|
||||||
|
cfg.CONF.set_override('base_mac', "12:34:56:78:90:ab")
|
||||||
|
|
||||||
|
self.client_cls_p = mock.patch('quantumclient.v2_0.client.Client')
|
||||||
|
client_cls = self.client_cls_p.start()
|
||||||
|
self.client_inst = mock.Mock()
|
||||||
|
client_cls.return_value = self.client_inst
|
||||||
|
self.client_inst.create_network.return_value = \
|
||||||
|
{'id': 'fake_id'}
|
||||||
|
self.client_inst.create_port.return_value = \
|
||||||
|
{'id': 'fake_id'}
|
||||||
|
self.client_inst.create_subnet.return_value = \
|
||||||
|
{'id': 'fake_id'}
|
||||||
|
self.client_inst.update_network.return_value = \
|
||||||
|
{'id': 'fake_id'}
|
||||||
|
self.client_inst.update_port.return_value = \
|
||||||
|
{'id': 'fake_id'}
|
||||||
|
self.client_inst.update_subnet.return_value = \
|
||||||
|
{'id': 'fake_id'}
|
||||||
|
self.client_inst.delete_network.return_value = True
|
||||||
|
self.client_inst.delete_port.return_value = True
|
||||||
|
self.client_inst.delete_subnet.return_value = True
|
||||||
|
self.plugin = MetaPluginV2(configfile=None)
|
||||||
|
|
||||||
|
def _fake_network(self, flavor):
|
||||||
|
data = {'network': {'name': flavor,
|
||||||
|
'admin_state_up': True,
|
||||||
|
'tenant_id': self.fake_tenant_id,
|
||||||
|
'flavor:id': flavor}}
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _fake_port(self, net_id):
|
||||||
|
return {'port': {'name': net_id,
|
||||||
|
'network_id': net_id,
|
||||||
|
'admin_state_up': True,
|
||||||
|
'device_id': 'bad_device_id',
|
||||||
|
'admin_state_up': True,
|
||||||
|
'fixed_ips': [],
|
||||||
|
'mac_address':
|
||||||
|
self.plugin._generate_mac(self.context, net_id),
|
||||||
|
'tenant_id': self.fake_tenant_id}}
|
||||||
|
|
||||||
|
def _fake_subnet(self, net_id):
|
||||||
|
allocation_pools = [{'start': '10.0.0.2',
|
||||||
|
'end': '10.0.0.254'}]
|
||||||
|
return {'subnet': {'name': net_id,
|
||||||
|
'network_id': net_id,
|
||||||
|
'gateway_ip': '10.0.0.1',
|
||||||
|
'cidr': '10.0.0.0/24',
|
||||||
|
'allocation_pools': allocation_pools,
|
||||||
|
'enable_dhcp': True,
|
||||||
|
'ip_version': 4}}
|
||||||
|
|
||||||
|
def test_create_delete_network(self):
|
||||||
|
network1 = self._fake_network('fake1')
|
||||||
|
ret1 = self.plugin.create_network(self.context, network1)
|
||||||
|
self.assertEqual('fake1', ret1['flavor:id'])
|
||||||
|
|
||||||
|
network2 = self._fake_network('fake2')
|
||||||
|
ret2 = self.plugin.create_network(self.context, network2)
|
||||||
|
self.assertEqual('fake2', ret2['flavor:id'])
|
||||||
|
|
||||||
|
network3 = self._fake_network('proxy')
|
||||||
|
ret3 = self.plugin.create_network(self.context, network3)
|
||||||
|
self.assertEqual('proxy', ret3['flavor:id'])
|
||||||
|
|
||||||
|
db_ret1 = self.plugin.get_network(self.context, ret1['id'])
|
||||||
|
self.assertEqual('fake1', db_ret1['name'])
|
||||||
|
|
||||||
|
db_ret2 = self.plugin.get_network(self.context, ret2['id'])
|
||||||
|
self.assertEqual('fake2', db_ret2['name'])
|
||||||
|
|
||||||
|
db_ret3 = self.plugin.get_network(self.context, ret3['id'])
|
||||||
|
self.assertEqual('proxy', db_ret3['name'])
|
||||||
|
|
||||||
|
db_ret4 = self.plugin.get_networks(self.context)
|
||||||
|
self.assertEqual(3, len(db_ret4))
|
||||||
|
|
||||||
|
db_ret5 = self.plugin.get_networks(self.context,
|
||||||
|
{'flavor:id': ['fake1']})
|
||||||
|
self.assertEqual(1, len(db_ret5))
|
||||||
|
self.assertEqual('fake1', db_ret5[0]['name'])
|
||||||
|
self.plugin.delete_network(self.context, ret1['id'])
|
||||||
|
self.plugin.delete_network(self.context, ret2['id'])
|
||||||
|
self.plugin.delete_network(self.context, ret3['id'])
|
||||||
|
|
||||||
|
def test_create_delete_port(self):
|
||||||
|
network1 = self._fake_network('fake1')
|
||||||
|
network_ret1 = self.plugin.create_network(self.context, network1)
|
||||||
|
network2 = self._fake_network('fake2')
|
||||||
|
network_ret2 = self.plugin.create_network(self.context, network2)
|
||||||
|
network3 = self._fake_network('proxy')
|
||||||
|
network_ret3 = self.plugin.create_network(self.context, network3)
|
||||||
|
|
||||||
|
port1 = self._fake_port(network_ret1['id'])
|
||||||
|
port2 = self._fake_port(network_ret2['id'])
|
||||||
|
port3 = self._fake_port(network_ret3['id'])
|
||||||
|
|
||||||
|
port1_ret = self.plugin.create_port(self.context, port1)
|
||||||
|
port2_ret = self.plugin.create_port(self.context, port2)
|
||||||
|
port3_ret = self.plugin.create_port(self.context, port3)
|
||||||
|
|
||||||
|
self.assertEqual('fake1', port1_ret['device_id'])
|
||||||
|
self.assertEqual('fake2', port2_ret['device_id'])
|
||||||
|
self.assertEqual('bad_device_id', port3_ret['device_id'])
|
||||||
|
|
||||||
|
port_in_db1 = self.plugin.get_port(self.context, port1_ret['id'])
|
||||||
|
port_in_db2 = self.plugin.get_port(self.context, port2_ret['id'])
|
||||||
|
port_in_db3 = self.plugin.get_port(self.context, port3_ret['id'])
|
||||||
|
|
||||||
|
self.assertEqual('fake1', port_in_db1['device_id'])
|
||||||
|
self.assertEqual('fake2', port_in_db2['device_id'])
|
||||||
|
self.assertEqual('bad_device_id', port_in_db3['device_id'])
|
||||||
|
|
||||||
|
port1['port']['admin_state_up'] = False
|
||||||
|
port2['port']['admin_state_up'] = False
|
||||||
|
port3['port']['admin_state_up'] = False
|
||||||
|
self.plugin.update_port(self.context, port1_ret['id'], port1)
|
||||||
|
self.plugin.update_port(self.context, port2_ret['id'], port2)
|
||||||
|
self.plugin.update_port(self.context, port3_ret['id'], port3)
|
||||||
|
port_in_db1 = self.plugin.get_port(self.context, port1_ret['id'])
|
||||||
|
port_in_db2 = self.plugin.get_port(self.context, port2_ret['id'])
|
||||||
|
port_in_db3 = self.plugin.get_port(self.context, port3_ret['id'])
|
||||||
|
self.assertEqual(False, port_in_db1['admin_state_up'])
|
||||||
|
self.assertEqual(False, port_in_db2['admin_state_up'])
|
||||||
|
self.assertEqual(False, port_in_db3['admin_state_up'])
|
||||||
|
|
||||||
|
self.plugin.delete_port(self.context, port1_ret['id'])
|
||||||
|
self.plugin.delete_port(self.context, port2_ret['id'])
|
||||||
|
self.plugin.delete_port(self.context, port3_ret['id'])
|
||||||
|
|
||||||
|
self.plugin.delete_network(self.context, network_ret1['id'])
|
||||||
|
self.plugin.delete_network(self.context, network_ret2['id'])
|
||||||
|
self.plugin.delete_network(self.context, network_ret3['id'])
|
||||||
|
|
||||||
|
def test_create_delete_subnet(self):
|
||||||
|
network1 = self._fake_network('fake1')
|
||||||
|
network_ret1 = self.plugin.create_network(self.context, network1)
|
||||||
|
network2 = self._fake_network('fake2')
|
||||||
|
network_ret2 = self.plugin.create_network(self.context, network2)
|
||||||
|
network3 = self._fake_network('proxy')
|
||||||
|
network_ret3 = self.plugin.create_network(self.context, network3)
|
||||||
|
|
||||||
|
subnet1 = self._fake_subnet(network_ret1['id'])
|
||||||
|
subnet2 = self._fake_subnet(network_ret2['id'])
|
||||||
|
subnet3 = self._fake_subnet(network_ret3['id'])
|
||||||
|
|
||||||
|
subnet1_ret = self.plugin.create_subnet(self.context, subnet1)
|
||||||
|
subnet2_ret = self.plugin.create_subnet(self.context, subnet2)
|
||||||
|
subnet3_ret = self.plugin.create_subnet(self.context, subnet3)
|
||||||
|
self.assertEqual(network_ret1['id'], subnet1_ret['network_id'])
|
||||||
|
self.assertEqual(network_ret2['id'], subnet2_ret['network_id'])
|
||||||
|
self.assertEqual(network_ret3['id'], subnet3_ret['network_id'])
|
||||||
|
|
||||||
|
subnet_in_db1 = self.plugin.get_subnet(self.context, subnet1_ret['id'])
|
||||||
|
subnet_in_db2 = self.plugin.get_subnet(self.context, subnet2_ret['id'])
|
||||||
|
subnet_in_db3 = self.plugin.get_subnet(self.context, subnet3_ret['id'])
|
||||||
|
|
||||||
|
subnet1['subnet']['ip_version'] = 6
|
||||||
|
subnet1['subnet']['allocation_pools'].pop()
|
||||||
|
subnet2['subnet']['ip_version'] = 6
|
||||||
|
subnet2['subnet']['allocation_pools'].pop()
|
||||||
|
subnet3['subnet']['ip_version'] = 6
|
||||||
|
subnet3['subnet']['allocation_pools'].pop()
|
||||||
|
|
||||||
|
self.plugin.update_subnet(self.context,
|
||||||
|
subnet1_ret['id'], subnet1)
|
||||||
|
self.plugin.update_subnet(self.context,
|
||||||
|
subnet2_ret['id'], subnet2)
|
||||||
|
self.plugin.update_subnet(self.context,
|
||||||
|
subnet3_ret['id'], subnet3)
|
||||||
|
subnet_in_db1 = self.plugin.get_subnet(self.context, subnet1_ret['id'])
|
||||||
|
subnet_in_db2 = self.plugin.get_subnet(self.context, subnet2_ret['id'])
|
||||||
|
subnet_in_db3 = self.plugin.get_subnet(self.context, subnet3_ret['id'])
|
||||||
|
|
||||||
|
self.assertEqual(6, subnet_in_db1['ip_version'])
|
||||||
|
self.assertEqual(6, subnet_in_db2['ip_version'])
|
||||||
|
self.assertEqual(6, subnet_in_db3['ip_version'])
|
||||||
|
|
||||||
|
self.plugin.delete_subnet(self.context, subnet1_ret['id'])
|
||||||
|
self.plugin.delete_subnet(self.context, subnet2_ret['id'])
|
||||||
|
self.plugin.delete_subnet(self.context, subnet3_ret['id'])
|
||||||
|
|
||||||
|
self.plugin.delete_network(self.context, network_ret1['id'])
|
||||||
|
self.plugin.delete_network(self.context, network_ret2['id'])
|
||||||
|
self.plugin.delete_network(self.context, network_ret3['id'])
|
||||||
|
|
||||||
|
def test_extension_method(self):
|
||||||
|
self.assertEqual('fake1', self.plugin.fake_func())
|
||||||
|
self.assertEqual('fake2', self.plugin.fake_func2())
|
||||||
|
|
||||||
|
def test_extension_not_implemented_method(self):
|
||||||
|
try:
|
||||||
|
self.plugin.not_implemented()
|
||||||
|
except AttributeError:
|
||||||
|
return
|
||||||
|
except:
|
||||||
|
self.fail("AttributeError Error is not raised")
|
||||||
|
|
||||||
|
self.fail("No Error is not raised")
|
@ -24,6 +24,7 @@ from quantum.agent.linux import interface
|
|||||||
from quantum.agent.linux import ip_lib
|
from quantum.agent.linux import ip_lib
|
||||||
from quantum.agent.linux import utils
|
from quantum.agent.linux import utils
|
||||||
from quantum.openstack.common import cfg
|
from quantum.openstack.common import cfg
|
||||||
|
from quantum.agent.dhcp_agent import DeviceManager
|
||||||
|
|
||||||
|
|
||||||
class BaseChild(interface.LinuxInterfaceDriver):
|
class BaseChild(interface.LinuxInterfaceDriver):
|
||||||
@ -332,3 +333,55 @@ class TestRyuInterfaceDriver(TestBase):
|
|||||||
expected.extend([mock.call().device().link.set_up()])
|
expected.extend([mock.call().device().link.set_up()])
|
||||||
|
|
||||||
self.ip.assert_has_calls(expected)
|
self.ip.assert_has_calls(expected)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMetaInterfaceDriver(TestBase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestMetaInterfaceDriver, self).setUp()
|
||||||
|
self.conf.register_opts(DeviceManager.OPTS)
|
||||||
|
self.client_cls_p = mock.patch('quantumclient.v2_0.client.Client')
|
||||||
|
client_cls = self.client_cls_p.start()
|
||||||
|
self.client_inst = mock.Mock()
|
||||||
|
client_cls.return_value = self.client_inst
|
||||||
|
|
||||||
|
fake_network = {'network': {'flavor:id': 'fake1'}}
|
||||||
|
fake_port = {'ports':
|
||||||
|
[{'mac_address':
|
||||||
|
'aa:bb:cc:dd:ee:ffa', 'network_id': 'test'}]}
|
||||||
|
|
||||||
|
self.client_inst.list_ports.return_value = fake_port
|
||||||
|
self.client_inst.show_network.return_value = fake_network
|
||||||
|
|
||||||
|
self.conf.set_override('auth_url', 'http://localhost:35357/v2.0')
|
||||||
|
self.conf.set_override('auth_region', 'RegionOne')
|
||||||
|
self.conf.set_override('admin_user', 'quantum')
|
||||||
|
self.conf.set_override('admin_password', 'password')
|
||||||
|
self.conf.set_override('admin_tenant_name', 'service')
|
||||||
|
self.conf.set_override(
|
||||||
|
'meta_flavor_driver_mappings',
|
||||||
|
'fake1:quantum.agent.linux.interface.OVSInterfaceDriver,'
|
||||||
|
'fake2:quantum.agent.linux.interface.BridgeInterfaceDriver')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.client_cls_p.stop()
|
||||||
|
super(TestMetaInterfaceDriver, self).tearDown()
|
||||||
|
|
||||||
|
def test_get_driver_by_network_id(self):
|
||||||
|
meta_interface = interface.MetaInterfaceDriver(self.conf)
|
||||||
|
driver = meta_interface._get_driver_by_network_id('test')
|
||||||
|
self.assertTrue(isinstance(
|
||||||
|
driver,
|
||||||
|
interface.OVSInterfaceDriver))
|
||||||
|
|
||||||
|
def test_get_driver_by_device_name(self):
|
||||||
|
device_address_p = mock.patch(
|
||||||
|
'quantum.agent.linux.ip_lib.IpLinkCommand.address')
|
||||||
|
device_address = device_address_p.start()
|
||||||
|
device_address.return_value = 'aa:bb:cc:dd:ee:ffa'
|
||||||
|
|
||||||
|
meta_interface = interface.MetaInterfaceDriver(self.conf)
|
||||||
|
driver = meta_interface._get_driver_by_device_name('test')
|
||||||
|
self.assertTrue(isinstance(
|
||||||
|
driver,
|
||||||
|
interface.OVSInterfaceDriver))
|
||||||
|
device_address_p.stop()
|
||||||
|
3
setup.py
3
setup.py
@ -48,6 +48,7 @@ cisco_plugin_config_path = 'etc/quantum/plugins/cisco'
|
|||||||
linuxbridge_plugin_config_path = 'etc/quantum/plugins/linuxbridge'
|
linuxbridge_plugin_config_path = 'etc/quantum/plugins/linuxbridge'
|
||||||
nvp_plugin_config_path = 'etc/quantum/plugins/nicira'
|
nvp_plugin_config_path = 'etc/quantum/plugins/nicira'
|
||||||
ryu_plugin_config_path = 'etc/quantum/plugins/ryu'
|
ryu_plugin_config_path = 'etc/quantum/plugins/ryu'
|
||||||
|
meta_plugin_config_path = 'etc/quantum/plugins/metaplugin'
|
||||||
|
|
||||||
DataFiles = [
|
DataFiles = [
|
||||||
(config_path,
|
(config_path,
|
||||||
@ -70,6 +71,8 @@ DataFiles = [
|
|||||||
(nvp_plugin_config_path,
|
(nvp_plugin_config_path,
|
||||||
['etc/quantum/plugins/nicira/nvp.ini']),
|
['etc/quantum/plugins/nicira/nvp.ini']),
|
||||||
(ryu_plugin_config_path, ['etc/quantum/plugins/ryu/ryu.ini']),
|
(ryu_plugin_config_path, ['etc/quantum/plugins/ryu/ryu.ini']),
|
||||||
|
(meta_plugin_config_path,
|
||||||
|
['etc/quantum/plugins/metaplugin/metaplugin.ini'])
|
||||||
]
|
]
|
||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
|
Loading…
Reference in New Issue
Block a user