diff --git a/etc/quantum.conf b/etc/quantum.conf index ad8e18de2a..86eeb9c69e 100644 --- a/etc/quantum.conf +++ b/etc/quantum.conf @@ -43,7 +43,7 @@ api_paste_config = api-paste.ini # allow_bulk = True # RPC configuration options. Defined in rpc __init__ # The messaging module to use, defaults to kombu. -# rpc_backend = quantum.openstack.common.notifier.rpc.impl_kombu +# rpc_backend = quantum.openstack.common.rpc.impl_kombu # Size of RPC thread pool # rpc_thread_pool_size = 64, # Size of RPC connection pool diff --git a/etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini b/etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini index f494e5b28a..239d1f92fc 100644 --- a/etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini +++ b/etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini @@ -1,6 +1,9 @@ [VLANS] -vlan_start = 1000 -vlan_end = 3000 +# (ListOpt) Comma-separated list of +# :: tuples enumerating ranges +# of VLAN IDs on named physical networks that are available for +# allocation. +# network_vlan_ranges = default:1000:2999 [DATABASE] # This line MUST be changed to actually run the plugin. @@ -16,8 +19,12 @@ sql_connection = sqlite:// reconnect_interval = 2 [LINUX_BRIDGE] -# This is the interface connected to the switch on your Quantum network -physical_interface = eth1 +# (ListOpt) Comma-separated list of +# : tuples mapping physical +# network names to agent's node-specific physical network +# interfaces. Server uses physical network names for validation but +# ignores interfaces. +# physical_interface_mappings = default:eth1 [AGENT] # Agent's polling interval in seconds @@ -25,7 +32,5 @@ polling_interval = 2 # Change to "sudo quantum-rootwrap" to limit commands that can be run # as root. root_helper = sudo -# Use Quantumv2 API -# target_v2_api = False # Use RPC messaging to interface between agent and plugin # rpc = True diff --git a/quantum/api/v2/attributes.py b/quantum/api/v2/attributes.py index 20ddd66c33..7e115ca458 100644 --- a/quantum/api/v2/attributes.py +++ b/quantum/api/v2/attributes.py @@ -27,6 +27,10 @@ from quantum.common import exceptions as q_exc LOG = logging.getLogger(__name__) +def is_attr_set(attribute): + return attribute not in (None, ATTR_NOT_SPECIFIED) + + def _validate_boolean(data, valid_values=None): if data in [True, False]: return @@ -46,6 +50,19 @@ def _validate_values(data, valid_values=None): return msg +def _validate_range(data, valid_values=None): + min_value = valid_values[0] + max_value = valid_values[1] + if data >= min_value and data <= max_value: + return + else: + msg_dict = dict(data=data, min_value=min_value, max_value=max_value) + msg = _("%(data)s is not in range %(min_value)s through " + "%(max_value)s") % msg_dict + LOG.debug("validate_range: %s", msg) + return msg + + def _validate_mac_address(data, valid_values=None): try: netaddr.EUI(data) @@ -120,6 +137,7 @@ MAC_PATTERN = "^%s[aceACE02468](:%s{2}){5}$" % (HEX_ELEM, HEX_ELEM) # Dictionary that maintains a list of validation functions validators = {'type:boolean': _validate_boolean, 'type:values': _validate_values, + 'type:range': _validate_range, 'type:mac_address': _validate_mac_address, 'type:ip_address': _validate_ip_address, 'type:ip_address_or_none': _validate_ip_address_or_none, diff --git a/quantum/common/exceptions.py b/quantum/common/exceptions.py index aeb994b7e9..529af1bb3e 100644 --- a/quantum/common/exceptions.py +++ b/quantum/common/exceptions.py @@ -119,7 +119,8 @@ class IpAddressInUse(InUse): class VlanIdInUse(InUse): message = _("Unable to create the network. " - "The VLAN %(vlan_id)s is in use.") + "The VLAN %(vlan_id)s on physical network " + "%(physical_network)s is in use.") class ResourceExhausted(QuantumException): diff --git a/quantum/db/db_base_plugin_v2.py b/quantum/db/db_base_plugin_v2.py index 1abd81b3c9..85f5c42ec9 100644 --- a/quantum/db/db_base_plugin_v2.py +++ b/quantum/db/db_base_plugin_v2.py @@ -755,7 +755,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2): def update_network(self, context, id, network): n = network['network'] - with context.session.begin(): + with context.session.begin(subtransactions=True): network = self._get_network(context, id) # validate 'shared' parameter if 'shared' in n: @@ -764,7 +764,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2): return self._make_network_dict(network) def delete_network(self, context, id): - with context.session.begin(): + with context.session.begin(subtransactions=True): network = self._get_network(context, id) filter = {'network_id': [id]} @@ -874,7 +874,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2): s = subnet['subnet'] self._validate_subnet(s) - with context.session.begin(): + with context.session.begin(subtransactions=True): if "dns_nameservers" in s: old_dns_list = self._get_dns_by_subnet(context, id) @@ -922,7 +922,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2): return self._make_subnet_dict(subnet) def delete_subnet(self, context, id): - with context.session.begin(): + with context.session.begin(subtransactions=True): subnet = self._get_subnet(context, id) # Check if ports are using this subnet allocated_qry = context.session.query(models_v2.IPAllocation) @@ -999,7 +999,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2): def update_port(self, context, id, port): p = port['port'] - with context.session.begin(): + with context.session.begin(subtransactions=True): port = self._get_port(context, id) # Check if the IPs need to be updated if 'fixed_ips' in p: @@ -1024,7 +1024,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2): return self._make_port_dict(port) def delete_port(self, context, id): - with context.session.begin(): + with context.session.begin(subtransactions=True): port = self._get_port(context, id) allocated_qry = context.session.query(models_v2.IPAllocation) diff --git a/quantum/extensions/providernet.py b/quantum/extensions/providernet.py index fff8a707fc..19b8f5b116 100644 --- a/quantum/extensions/providernet.py +++ b/quantum/extensions/providernet.py @@ -17,9 +17,17 @@ from quantum.api.v2 import attributes EXTENDED_ATTRIBUTES_2_0 = { 'networks': { - # TODO(rkukura): specify validation - 'provider:vlan_id': {'allow_post': True, 'allow_put': False, + 'provider:network_type': {'allow_post': True, 'allow_put': True, + 'validate': {'type:values': ['flat', + 'vlan']}, + 'default': attributes.ATTR_NOT_SPECIFIED, + 'is_visible': True}, + 'provider:physical_network': {'allow_post': True, 'allow_put': True, + 'default': attributes.ATTR_NOT_SPECIFIED, + 'is_visible': True}, + 'provider:vlan_id': {'allow_post': True, 'allow_put': True, 'convert_to': int, + 'validate': {'type:range': (1, 4095)}, 'default': attributes.ATTR_NOT_SPECIFIED, 'is_visible': True}, } diff --git a/quantum/plugins/linuxbridge/README b/quantum/plugins/linuxbridge/README index 1b152f714e..6613087c49 100644 --- a/quantum/plugins/linuxbridge/README +++ b/quantum/plugins/linuxbridge/README @@ -58,9 +58,9 @@ quantum_use_dhcp=true Make the Linux Bridge plugin the current quantum plugin -- edit quantum.conf and change the core_plugin according to the API version -V1: "core_plugin = quantum.plugins.linuxbridge.LinuxBridgePlugin.LinuxBridgePlugin" -V2: "core_plugin = quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2" +- edit quantum.conf and change the core_plugin + +core_plugin = quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2 # -- Database config. @@ -104,10 +104,26 @@ mysql> FLUSH PRIVILEGES; actually running the server set it to mysql. At any given time, only one of these should be active in the conf file (you can comment out the other). -- Remember to change the interface configuration to indicate the correct - ethernet interface on that particular host which is being used to participate - in the Quantum networks. This configuration has to be applied on each host - on which the agent runs. +- On the quantum server, network_vlan_ranges must be configured in + linuxbridge_conf.ini to specify the names of the physical networks + managed by the linuxbridge plugin, along with the ranges of VLAN IDs + available on each physical network for allocation to virtual + networks. An entry of the form + "::" specifies a VLAN range on + the named physical network. An entry of the form + "" specifies a named network without making a + range of VLANs available for allocation. Networks specified using + either form are available for adminstrators to create provider flat + networks and provider VLANs. Multiple VLAN ranges can be specified + for the same physical network. + + The following example linuxbridge_conf.ini entry shows three + physical networks that can be used to create provider networks, with + ranges of VLANs available for allocation on two of them: + + [VLANS] + network_vlan_ranges = physnet1:1000:2999,physnet1:3000:3999,physnet2,physnet3:1:4094 + # -- Agent configuration @@ -122,6 +138,22 @@ mysql> FLUSH PRIVILEGES; Note: debug and logging information should be updated in etc/quantum.conf +- On each compute node, the network_interface_mappings must be + configured in linuxbridge_conf.ini to map each physical network name + to the physical interface connecting the node to that physical + network. Entries are of the form + ":". For example, one compute + node may use the following physical_inteface_mappings entries: + + [LINUX_BRIDGE] + physical_interface_mappings = physnet1:eth1,physnet2:eth2,physnet3:eth3 + + while another might use: + + [LINUX_BRIDGE] + physical_interface_mappings = physnet1:em3,physnet2:em2,physnet3:em1 + + $ Run the following: python linuxbridge_quantum_agent.py --config-file quantum.conf --config-file linuxbridge_conf.ini diff --git a/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py b/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py index 2679a6474a..6a11227eee 100755 --- a/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py +++ b/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py @@ -34,6 +34,7 @@ import eventlet import pyudev from sqlalchemy.ext.sqlsoup import SqlSoup +from quantum.agent.linux import utils from quantum.agent import rpc as agent_rpc from quantum.common import config as logging_config from quantum.common import topics @@ -42,8 +43,7 @@ from quantum.openstack.common import context from quantum.openstack.common import rpc from quantum.openstack.common.rpc import dispatcher from quantum.plugins.linuxbridge.common import config - -from quantum.agent.linux import utils +from quantum.plugins.linuxbridge.common import constants logging.basicConfig() LOG = logging.getLogger(__name__) @@ -67,9 +67,8 @@ DEFAULT_RECONNECT_INTERVAL = 2 class LinuxBridge: - def __init__(self, br_name_prefix, physical_interface, root_helper): - self.br_name_prefix = br_name_prefix - self.physical_interface = physical_interface + def __init__(self, interface_mappings, root_helper): + self.interface_mappings = interface_mappings self.root_helper = root_helper def device_exists(self, device): @@ -92,14 +91,14 @@ class LinuxBridge: if not network_id: LOG.warning("Invalid Network ID, will lead to incorrect bridge" "name") - bridge_name = self.br_name_prefix + network_id[0:11] + bridge_name = BRIDGE_NAME_PREFIX + network_id[0:11] return bridge_name - def get_subinterface_name(self, vlan_id): + def get_subinterface_name(self, physical_interface, vlan_id): if not vlan_id: LOG.warning("Invalid VLAN ID, will lead to incorrect " "subinterface name") - subinterface_name = '%s.%s' % (self.physical_interface, vlan_id) + subinterface_name = '%s.%s' % (physical_interface, vlan_id) return subinterface_name def get_tap_device_name(self, interface_id): @@ -174,21 +173,27 @@ class LinuxBridge: DEVICE_NAME_PLACEHOLDER, device_name) return os.path.exists(bridge_port_path) - def ensure_vlan_bridge(self, network_id, vlan_id): + def ensure_vlan_bridge(self, network_id, physical_interface, vlan_id): """Create a vlan and bridge unless they already exist.""" - interface = self.ensure_vlan(vlan_id) + interface = self.ensure_vlan(physical_interface, vlan_id) bridge_name = self.get_bridge_name(network_id) self.ensure_bridge(bridge_name, interface) return interface - def ensure_vlan(self, vlan_id): + def ensure_flat_bridge(self, network_id, physical_interface): + """Create a non-vlan bridge unless it already exists.""" + bridge_name = self.get_bridge_name(network_id) + self.ensure_bridge(bridge_name, physical_interface) + return physical_interface + + def ensure_vlan(self, physical_interface, vlan_id): """Create a vlan unless it already exists.""" - interface = self.get_subinterface_name(vlan_id) + interface = self.get_subinterface_name(physical_interface, vlan_id) if not self.device_exists(interface): LOG.debug("Creating subinterface %s for VLAN %s on interface %s" % - (interface, vlan_id, self.physical_interface)) + (interface, vlan_id, physical_interface)) if utils.execute(['ip', 'link', 'add', 'link', - self.physical_interface, + physical_interface, 'name', interface, 'type', 'vlan', 'id', vlan_id], root_helper=self.root_helper): return @@ -225,7 +230,8 @@ class LinuxBridge: utils.execute(['brctl', 'addif', bridge_name, interface], root_helper=self.root_helper) - def add_tap_interface(self, network_id, vlan_id, tap_device_name): + def add_tap_interface(self, network_id, physical_interface, vlan_id, + tap_device_name): """ If a VIF has been plugged into a network, this function will add the corresponding tap device to the relevant bridge @@ -249,7 +255,10 @@ class LinuxBridge: tap_device_name], root_helper=self.root_helper): return False - self.ensure_vlan_bridge(network_id, vlan_id) + if int(vlan_id) == constants.FLAT_VLAN_ID: + self.ensure_flat_bridge(network_id, physical_interface) + else: + self.ensure_vlan_bridge(network_id, physical_interface, vlan_id) if utils.execute(['brctl', 'addif', bridge_name, tap_device_name], root_helper=self.root_helper): return False @@ -257,26 +266,38 @@ class LinuxBridge: bridge_name)) return True - def add_interface(self, network_id, vlan_id, interface_id): + def add_interface(self, network_id, physical_network, vlan_id, + interface_id): if not interface_id: """ Since the VIF id is null, no VIF is plugged into this port no more processing is required """ return False + + physical_interface = self.interface_mappings.get(physical_network) + if not physical_interface: + LOG.error("No mapping for physical network %s" % physical_network) + return False + if interface_id.startswith(GATEWAY_INTERFACE_PREFIX): - return self.add_tap_interface(network_id, vlan_id, interface_id) + return self.add_tap_interface(network_id, + physical_interface, vlan_id, + interface_id) else: tap_device_name = self.get_tap_device_name(interface_id) - return self.add_tap_interface(network_id, vlan_id, tap_device_name) + return self.add_tap_interface(network_id, + physical_interface, vlan_id, + tap_device_name) def delete_vlan_bridge(self, bridge_name): if self.device_exists(bridge_name): interfaces_on_bridge = self.get_interfaces_on_bridge(bridge_name) for interface in interfaces_on_bridge: self.remove_interface(bridge_name, interface) - if interface.startswith(self.physical_interface): - self.delete_vlan(interface) + for physical_interface in self.interface_mappings.itervalues(): + if interface.startswith(physical_interface): + self.delete_vlan(interface) LOG.debug("Deleting bridge %s" % bridge_name) if utils.execute(['ip', 'link', 'set', bridge_name, 'down'], @@ -360,23 +381,23 @@ class LinuxBridgeRpcCallbacks(): class LinuxBridgeQuantumAgentDB: - def __init__(self, br_name_prefix, physical_interface, polling_interval, - reconnect_interval, root_helper, target_v2_api, - db_connection_url): + def __init__(self, interface_mappings, polling_interval, + reconnect_interval, root_helper, db_connection_url): self.polling_interval = polling_interval self.root_helper = root_helper - self.setup_linux_bridge(br_name_prefix, physical_interface) - self.target_v2_api = target_v2_api + self.setup_linux_bridge(interface_mappings) self.reconnect_interval = reconnect_interval self.db_connected = False self.db_connection_url = db_connection_url - def setup_linux_bridge(self, br_name_prefix, physical_interface): - self.linux_br = LinuxBridge(br_name_prefix, physical_interface, - self.root_helper) + def setup_linux_bridge(self, interface_mappings): + self.linux_br = LinuxBridge(interface_mappings, self.root_helper) - def process_port_binding(self, network_id, interface_id, vlan_id): - return self.linux_br.add_interface(network_id, vlan_id, interface_id) + def process_port_binding(self, network_id, interface_id, + physical_network, vlan_id): + return self.linux_br.add_interface(network_id, + physical_network, vlan_id, + interface_id) def remove_port_binding(self, network_id, interface_id): bridge_name = self.linux_br.get_bridge_name(network_id) @@ -437,16 +458,18 @@ class LinuxBridgeQuantumAgentDB: old_port_bindings): vlan_bindings = {} try: - vlan_binds = db.vlan_bindings.all() + network_binds = db.network_bindings.all() except Exception as e: - LOG.info("Unable to get vlan bindings! Exception: %s" % e) + LOG.info("Unable to get network 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} + for bind in network_binds: + entry = {'network_id': bind.network_id, + 'physical_network': bind.physical_network, + 'vlan_id': bind.vlan_id} vlan_bindings[bind.network_id] = entry vlans_string = "%s %s" % (vlans_string, entry) @@ -462,19 +485,12 @@ class LinuxBridgeQuantumAgentDB: 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' + 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 if append_entry: port_bindings.append(entry) @@ -484,15 +500,15 @@ class LinuxBridgeQuantumAgentDB: ports_string = "%s %s" % (ports_string, pb) port_id = pb['uuid'] interface_id = pb['interface_id'] + network_id = pb['network_id'] - vlan_id = str(vlan_bindings[pb['network_id']]['vlan_id']) - if self.process_port_binding(pb['network_id'], + physical_network = vlan_bindings[network_id]['physical_network'] + vlan_id = str(vlan_bindings[network_id]['vlan_id']) + if self.process_port_binding(network_id, interface_id, + physical_network, 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 + all_bindings[port_id].status = OP_STATUS_UP plugged_interfaces.append(interface_id) @@ -539,15 +555,16 @@ class LinuxBridgeQuantumAgentDB: class LinuxBridgeQuantumAgentRPC: - def __init__(self, br_name_prefix, physical_interface, polling_interval, + def __init__(self, interface_mappings, polling_interval, root_helper): self.polling_interval = polling_interval self.root_helper = root_helper - self.setup_linux_bridge(br_name_prefix, physical_interface) - self.setup_rpc(physical_interface) + self.setup_linux_bridge(interface_mappings) + self.setup_rpc(interface_mappings.values()) - def setup_rpc(self, physical_interface): - mac = utils.get_interface_mac(physical_interface) + def setup_rpc(self, physical_interfaces): + # REVISIT try until one succeeds? + mac = utils.get_interface_mac(physical_interfaces[0]) self.agent_id = '%s%s' % ('lb', (mac.replace(":", ""))) self.topic = topics.AGENT self.plugin_rpc = agent_rpc.PluginApi(topics.PLUGIN) @@ -569,12 +586,14 @@ class LinuxBridgeQuantumAgentRPC: monitor = pyudev.Monitor.from_netlink(self.udev) monitor.filter_by('net') - def setup_linux_bridge(self, br_name_prefix, physical_interface): - self.linux_br = LinuxBridge(br_name_prefix, physical_interface, - self.root_helper) + def setup_linux_bridge(self, interface_mappings): + self.linux_br = LinuxBridge(interface_mappings, self.root_helper) - def process_port_binding(self, network_id, interface_id, vlan_id): - return self.linux_br.add_interface(network_id, vlan_id, interface_id) + def process_port_binding(self, network_id, interface_id, + physical_network, vlan_id): + return self.linux_br.add_interface(network_id, + physical_network, vlan_id, + interface_id) def remove_port_binding(self, network_id, interface_id): bridge_name = self.linux_br.get_bridge_name(network_id) @@ -633,6 +652,7 @@ class LinuxBridgeQuantumAgentRPC: # create the networking for the port self.process_port_binding(details['network_id'], details['port_id'], + details['physical_network'], details['vlan_id']) else: self.remove_port_binding(details['network_id'], @@ -696,29 +716,32 @@ def main(): # (TODO) gary - swap with common logging logging_config.setup_logging(cfg.CONF) - br_name_prefix = BRIDGE_NAME_PREFIX - physical_interface = cfg.CONF.LINUX_BRIDGE.physical_interface + interface_mappings = {} + for mapping in cfg.CONF.LINUX_BRIDGE.physical_interface_mappings: + try: + physical_network, physical_interface = mapping.split(':') + interface_mappings[physical_network] = physical_interface + LOG.debug("physical network %s mapped to physical interface %s" % + (physical_network, physical_interface)) + except ValueError as ex: + LOG.error("Invalid physical interface mapping: \'%s\' - %s" % + (mapping, ex)) + sys.exit(1) + polling_interval = cfg.CONF.AGENT.polling_interval reconnect_interval = cfg.CONF.DATABASE.reconnect_interval root_helper = cfg.CONF.AGENT.root_helper rpc = cfg.CONF.AGENT.rpc - if not cfg.CONF.AGENT.target_v2_api: - rpc = False - if rpc: - plugin = LinuxBridgeQuantumAgentRPC(br_name_prefix, - physical_interface, + plugin = LinuxBridgeQuantumAgentRPC(interface_mappings, polling_interval, root_helper) else: db_connection_url = cfg.CONF.DATABASE.sql_connection - target_v2_api = cfg.CONF.AGENT.target_v2_api - plugin = LinuxBridgeQuantumAgentDB(br_name_prefix, - physical_interface, + plugin = LinuxBridgeQuantumAgentDB(interface_mappings, polling_interval, reconnect_interval, root_helper, - target_v2_api, db_connection_url) LOG.info("Agent initialized successfully, now running... ") plugin.daemon_loop() diff --git a/quantum/plugins/linuxbridge/common/config.py b/quantum/plugins/linuxbridge/common/config.py index cafe455f24..a73fdca1b1 100644 --- a/quantum/plugins/linuxbridge/common/config.py +++ b/quantum/plugins/linuxbridge/common/config.py @@ -19,10 +19,15 @@ from quantum.openstack.common import cfg +DEFAULT_VLAN_RANGES = ['default:1000:2999'] +DEFAULT_INTERFACE_MAPPINGS = ['default:eth1'] + vlan_opts = [ - cfg.IntOpt('vlan_start', default=1000), - cfg.IntOpt('vlan_end', default=3000), + cfg.ListOpt('network_vlan_ranges', + default=DEFAULT_VLAN_RANGES, + help="List of :: " + "or "), ] database_opts = [ @@ -32,13 +37,14 @@ database_opts = [ ] bridge_opts = [ - cfg.StrOpt('physical_interface', default='eth1'), + cfg.ListOpt('physical_interface_mappings', + default=DEFAULT_INTERFACE_MAPPINGS, + help="List of :"), ] agent_opts = [ cfg.IntOpt('polling_interval', default=2), cfg.StrOpt('root_helper', default='sudo'), - cfg.BoolOpt('target_v2_api', default=False), cfg.BoolOpt('rpc', default=True), ] diff --git a/quantum/plugins/linuxbridge/common/constants.py b/quantum/plugins/linuxbridge/common/constants.py index 7e67247eef..87f908898e 100644 --- a/quantum/plugins/linuxbridge/common/constants.py +++ b/quantum/plugins/linuxbridge/common/constants.py @@ -17,47 +17,11 @@ # @author: Sumit Naiksatam, Cisco Systems, Inc. -PORT_STATE = 'port-state' +FLAT_VLAN_ID = -1 + PORT_UP = "ACTIVE" PORT_DOWN = "DOWN" -UUID = 'uuid' -TENANTID = 'tenant_id' -NETWORKID = 'network_id' -NETWORKNAME = 'name' -NETWORKPORTS = 'ports' -OPSTATUS = 'op_status' -INTERFACEID = 'interface_id' -PORTSTATE = 'state' -PORTID = 'port_id' -PPNAME = 'name' -PPVLANID = 'vlan_id' VLANID = 'vlan_id' -VLANNAME = 'vlan_name' - -ATTACHMENT = 'attachment' PORT_ID = 'port-id' -PORT_OP_STATUS = 'port-op-status' - NET_ID = 'net-id' -NET_NAME = 'net-name' -NET_PORTS = 'net-ports' -NET_OP_STATUS = 'net-op-status' -NET_VLAN_NAME = 'net-vlan-name' -NET_VLAN_ID = 'net-vlan-id' -NET_TENANTS = 'net-tenants' - -USERNAME = 'username' -PASSWORD = 'password' - -DELIMITERS = "[,;:\b\s]" - -UUID_LENGTH = 36 - -UNPLUGGED = '(detached)' - -ASSOCIATION_STATUS = 'association_status' - -ATTACHED = 'attached' - -DETACHED = 'detached' diff --git a/quantum/plugins/linuxbridge/common/exceptions.py b/quantum/plugins/linuxbridge/common/exceptions.py deleted file mode 100644 index 897db0ac87..0000000000 --- a/quantum/plugins/linuxbridge/common/exceptions.py +++ /dev/null @@ -1,58 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# -# Copyright 2012 Cisco Systems, 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: Sumit Naiksatam, Cisco Systems, Inc. -# @author: Rohit Agarwalla, Cisco Systems, Inc. - -""" -Exceptions used by the LinuxBridge plugin -""" - -from quantum.common import exceptions - - -class NetworksLimit(exceptions.QuantumException): - """Total number of network objects limit has been hit""" - message = _("Unable to create new network. Number of networks" - "for the system has exceeded the limit") - - -class NetworkVlanBindingAlreadyExists(exceptions.QuantumException): - """Binding cannot be created, since it already exists""" - message = _("NetworkVlanBinding for %(vlan_id)s and network " - "%(network_id)s already exists") - - -class NetworkVlanBindingNotFound(exceptions.QuantumException): - """Binding could not be found""" - message = _("NetworkVlanBinding for network " - "%(network_id)s does not exist") - - -class VlanIDNotFound(exceptions.QuantumException): - """VLAN ID cannot be found""" - message = _("Vlan ID %(vlan_id)s not found") - - -class VlanIDNotAvailable(exceptions.QuantumException): - """No VLAN ID available""" - message = _("No Vlan ID available") - - -class UnableToChangeVlanRange(exceptions.QuantumException): - """No VLAN ID available""" - message = _("Current VLAN ID range %(range_start)s to %(range_end)s " - "cannot be changed. Please check plugin conf file.") diff --git a/quantum/plugins/linuxbridge/db/l2network_db.py b/quantum/plugins/linuxbridge/db/l2network_db.py deleted file mode 100644 index 498fdcb633..0000000000 --- a/quantum/plugins/linuxbridge/db/l2network_db.py +++ /dev/null @@ -1,311 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012, Cisco Systems, 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. -# @author: Rohit Agarwalla, Cisco Systems, Inc. - -import logging - -from sqlalchemy import func -from sqlalchemy.orm import exc - -from quantum.api import api_common -from quantum.common import exceptions as q_exc -import quantum.db.api as db -from quantum.db import models_v2 -from quantum.openstack.common import cfg -from quantum.plugins.linuxbridge.common import config -from quantum.plugins.linuxbridge.common import exceptions as c_exc -from quantum.plugins.linuxbridge.db import l2network_models_v2 - -LOG = logging.getLogger(__name__) - -# The global variable for the database version model -L2_MODEL = l2network_models_v2 - - -def initialize(base=None): - global L2_MODEL - options = {"sql_connection": "%s" % cfg.CONF.DATABASE.sql_connection} - options.update({"sql_max_retries": cfg.CONF.DATABASE.sql_max_retries}) - options.update({"reconnect_interval": - cfg.CONF.DATABASE.reconnect_interval}) - if base: - options.update({"base": base}) - db.configure_db(options) - create_vlanids() - - -def create_vlanids(): - """Prepopulate the vlan_bindings table""" - LOG.debug("create_vlanids() called") - session = db.get_session() - start = cfg.CONF.VLANS.vlan_start - end = cfg.CONF.VLANS.vlan_end - try: - vlanid = session.query(L2_MODEL.VlanID).one() - except exc.MultipleResultsFound: - """ - TODO (Sumit): Salvatore rightly points out that this will not handle - change in VLAN ID range across server reboots. This is currently not - a supported feature. This logic will need to change if this feature - has to be supported. - Per Dan's suggestion we just throw a server exception for now. - """ - current_start = ( - int(session.query(func.min(L2_MODEL.VlanID.vlan_id)). - one()[0])) - current_end = ( - int(session.query(func.max(L2_MODEL.VlanID.vlan_id)). - one()[0])) - if current_start != start or current_end != end: - LOG.debug("Old VLAN range %s-%s" % (current_start, current_end)) - LOG.debug("New VLAN range %s-%s" % (start, end)) - raise c_exc.UnableToChangeVlanRange(range_start=current_start, - range_end=current_end) - except exc.NoResultFound: - LOG.debug("Setting VLAN range to %s-%s" % (start, end)) - while start <= end: - vlanid = L2_MODEL.VlanID(start) - session.add(vlanid) - start += 1 - session.flush() - return - - -def get_all_vlanids(): - """Get all the vlanids""" - LOG.debug("get_all_vlanids() called") - session = db.get_session() - try: - vlanids = (session.query(L2_MODEL.VlanID). - all()) - return vlanids - except exc.NoResultFound: - return [] - - -def is_vlanid_used(vlan_id): - """Check if a vlanid is in use""" - LOG.debug("is_vlanid_used() called") - session = db.get_session() - try: - vlanid = (session.query(L2_MODEL.VlanID). - filter_by(vlan_id=vlan_id). - one()) - return vlanid["vlan_used"] - except exc.NoResultFound: - raise c_exc.VlanIDNotFound(vlan_id=vlan_id) - - -def release_vlanid(vlan_id): - """Set the vlanid state to be unused, and delete if not in range""" - LOG.debug("release_vlanid() called") - session = db.get_session() - try: - vlanid = (session.query(L2_MODEL.VlanID). - filter_by(vlan_id=vlan_id). - one()) - vlanid["vlan_used"] = False - if (vlan_id >= cfg.CONF.VLANS.vlan_start and - vlan_id <= cfg.CONF.VLANS.vlan_end): - session.merge(vlanid) - else: - session.delete(vlanid) - session.flush() - return vlanid["vlan_used"] - except exc.NoResultFound: - raise c_exc.VlanIDNotFound(vlan_id=vlan_id) - return - - -def delete_vlanid(vlan_id): - """Delete a vlanid entry from db""" - LOG.debug("delete_vlanid() called") - session = db.get_session() - try: - vlanid = (session.query(L2_MODEL.VlanID). - filter_by(vlan_id=vlan_id). - one()) - session.delete(vlanid) - session.flush() - return vlanid - except exc.NoResultFound: - raise c_exc.VlanIDNotFound(vlan_id=vlan_id) - - -def reserve_vlanid(): - """Reserve the first unused vlanid""" - LOG.debug("reserve_vlanid() called") - session = db.get_session() - try: - rvlan = (session.query(L2_MODEL.VlanID). - first()) - if not rvlan: - create_vlanids() - - rvlan = (session.query(L2_MODEL.VlanID). - filter_by(vlan_used=False). - first()) - if not rvlan: - raise c_exc.VlanIDNotAvailable() - - rvlanid = (session.query(L2_MODEL.VlanID). - filter_by(vlan_id=rvlan["vlan_id"]). - one()) - rvlanid["vlan_used"] = True - session.merge(rvlanid) - session.flush() - return rvlan["vlan_id"] - except exc.NoResultFound: - raise c_exc.VlanIDNotAvailable() - - -def reserve_specific_vlanid(vlan_id): - """Reserve a specific vlanid""" - LOG.debug("reserve_specific_vlanid() called") - if vlan_id < 1 or vlan_id > 4094: - msg = _("Specified VLAN %s outside legal range (1-4094)") % vlan_id - raise q_exc.InvalidInput(error_message=msg) - session = db.get_session() - try: - rvlanid = (session.query(l2network_models_v2.VlanID). - filter_by(vlan_id=vlan_id). - one()) - if rvlanid["vlan_used"]: - raise q_exc.VlanIdInUse(vlan_id=vlan_id) - LOG.debug("reserving dynamic vlanid %s" % vlan_id) - rvlanid["vlan_used"] = True - session.merge(rvlanid) - except exc.NoResultFound: - rvlanid = l2network_models_v2.VlanID(vlan_id) - LOG.debug("reserving non-dynamic vlanid %s" % vlan_id) - rvlanid["vlan_used"] = True - session.add(rvlanid) - session.flush() - - -def get_all_vlanids_used(): - """Get all the vlanids used""" - LOG.debug("get_all_vlanids() called") - session = db.get_session() - try: - vlanids = (session.query(L2_MODEL.VlanID). - filter_by(vlan_used=True). - all()) - return vlanids - except exc.NoResultFound: - return [] - - -def get_all_vlan_bindings(): - """List all the vlan to network associations""" - LOG.debug("get_all_vlan_bindings() called") - session = db.get_session() - try: - bindings = (session.query(L2_MODEL.VlanBinding). - all()) - return bindings - except exc.NoResultFound: - return [] - - -def get_vlan_binding(netid): - """List the vlan given a network_id""" - LOG.debug("get_vlan_binding() called") - session = db.get_session() - try: - binding = (session.query(L2_MODEL.VlanBinding). - filter_by(network_id=netid). - one()) - return binding - except exc.NoResultFound: - raise c_exc.NetworkVlanBindingNotFound(network_id=netid) - - -def add_vlan_binding(vlanid, netid): - """Add a vlan to network association""" - LOG.debug("add_vlan_binding() called") - session = db.get_session() - try: - binding = (session.query(L2_MODEL.VlanBinding). - filter_by(vlan_id=vlanid). - one()) - raise c_exc.NetworkVlanBindingAlreadyExists(vlan_id=vlanid, - network_id=netid) - except exc.NoResultFound: - binding = L2_MODEL.VlanBinding(vlanid, netid) - session.add(binding) - session.flush() - return binding - - -def remove_vlan_binding(netid): - """Remove a vlan to network association""" - LOG.debug("remove_vlan_binding() called") - session = db.get_session() - try: - binding = (session.query(L2_MODEL.VlanBinding). - filter_by(network_id=netid). - one()) - session.delete(binding) - session.flush() - return binding - except exc.NoResultFound: - pass - - -def update_vlan_binding(netid, newvlanid=None): - """Update a vlan to network association""" - LOG.debug("update_vlan_binding() called") - session = db.get_session() - try: - binding = (session.query(L2_MODEL.VlanBinding). - filter_by(network_id=netid). - one()) - if newvlanid: - binding["vlan_id"] = newvlanid - session.merge(binding) - session.flush() - return binding - except exc.NoResultFound: - raise q_exc.NetworkNotFound(net_id=netid) - - -def get_port_from_device(device): - """Get port from database""" - LOG.debug("get_port_from_device() called") - session = db.get_session() - ports = session.query(models_v2.Port).all() - if not ports: - return - for port in ports: - if port['id'].startswith(device): - return port - return - - -def set_port_status(port_id, status): - """Set the port status""" - LOG.debug("set_port_status as %s called", status) - session = db.get_session() - try: - port = session.query(models_v2.Port).filter_by(id=port_id).one() - port['status'] = status - if status == api_common.PORT_STATUS_DOWN: - port['device_id'] = '' - session.merge(port) - session.flush() - except exc.NoResultFound: - raise q_exc.PortNotFound(port_id=port_id) diff --git a/quantum/plugins/linuxbridge/db/l2network_db_v2.py b/quantum/plugins/linuxbridge/db/l2network_db_v2.py new file mode 100644 index 0000000000..35bfd0cf52 --- /dev/null +++ b/quantum/plugins/linuxbridge/db/l2network_db_v2.py @@ -0,0 +1,196 @@ +# Copyright (c) 2012 OpenStack, LLC. +# +# 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 sqlalchemy.orm import exc + +from quantum.api import api_common +from quantum.common import exceptions as q_exc +import quantum.db.api as db +from quantum.db import models_v2 +from quantum.openstack.common import cfg +from quantum.plugins.linuxbridge.common import config +from quantum.plugins.linuxbridge.db import l2network_models_v2 + +LOG = logging.getLogger(__name__) + + +def initialize(): + options = {"sql_connection": "%s" % cfg.CONF.DATABASE.sql_connection} + options.update({"sql_max_retries": cfg.CONF.DATABASE.sql_max_retries}) + options.update({"reconnect_interval": + cfg.CONF.DATABASE.reconnect_interval}) + options.update({"base": models_v2.model_base.BASEV2}) + db.configure_db(options) + + +def sync_network_states(network_vlan_ranges): + """Synchronize network_states table with current configured VLAN ranges.""" + + # process vlan ranges for each physical network separately + for physical_network, vlan_ranges in network_vlan_ranges.iteritems(): + + # determine current configured allocatable vlans for this + # physical network + vlan_ids = set() + for vlan_range in vlan_ranges: + vlan_ids |= set(xrange(vlan_range[0], vlan_range[1] + 1)) + + session = db.get_session() + with session.begin(): + # remove from table unallocated vlans not currently allocatable + try: + states = (session.query(l2network_models_v2.NetworkState). + filter_by(physical_network=physical_network). + all()) + for state in states: + try: + # see if vlan is allocatable + vlan_ids.remove(state.vlan_id) + except KeyError: + # it's not allocatable, so check if its allocated + if not state.allocated: + # it's not, so remove it from table + LOG.debug("removing vlan %s on physical network " + "%s from pool" % + (state.vlan_id, physical_network)) + session.delete(state) + except exc.NoResultFound: + pass + + # add missing allocatable vlans to table + for vlan_id in sorted(vlan_ids): + state = l2network_models_v2.NetworkState(physical_network, + vlan_id) + session.add(state) + + +def get_network_state(physical_network, vlan_id): + """Get state of specified network""" + + session = db.get_session() + try: + state = (session.query(l2network_models_v2.NetworkState). + filter_by(physical_network=physical_network, + vlan_id=vlan_id). + one()) + return state + except exc.NoResultFound: + return None + + +def reserve_network(session): + with session.begin(subtransactions=True): + state = (session.query(l2network_models_v2.NetworkState). + filter_by(allocated=False). + first()) + if not state: + raise q_exc.NoNetworkAvailable() + LOG.debug("reserving vlan %s on physical network %s from pool" % + (state.vlan_id, state.physical_network)) + state.allocated = True + return (state.physical_network, state.vlan_id) + + +def reserve_specific_network(session, physical_network, vlan_id): + with session.begin(subtransactions=True): + try: + state = (session.query(l2network_models_v2.NetworkState). + filter_by(physical_network=physical_network, + vlan_id=vlan_id). + one()) + if state.allocated: + raise q_exc.VlanIdInUse(vlan_id=vlan_id, + physical_network=physical_network) + LOG.debug("reserving specific vlan %s on physical network %s " + "from pool" % (vlan_id, physical_network)) + state.allocated = True + except exc.NoResultFound: + LOG.debug("reserving specific vlan %s on physical network %s " + "outside pool" % (vlan_id, physical_network)) + state = l2network_models_v2.NetworkState(physical_network, vlan_id) + state.allocated = True + session.add(state) + + +def release_network(session, physical_network, vlan_id, network_vlan_ranges): + with session.begin(subtransactions=True): + try: + state = (session.query(l2network_models_v2.NetworkState). + filter_by(physical_network=physical_network, + vlan_id=vlan_id). + one()) + state.allocated = False + inside = False + for vlan_range in network_vlan_ranges.get(physical_network, []): + if vlan_id >= vlan_range[0] and vlan_id <= vlan_range[1]: + inside = True + break + if inside: + LOG.debug("releasing vlan %s on physical network %s to pool" % + (vlan_id, physical_network)) + else: + LOG.debug("releasing vlan %s on physical network %s outside " + "pool" % (vlan_id, physical_network)) + session.delete(state) + except exc.NoResultFound: + LOG.warning("vlan_id %s on physical network %s not found" % + (vlan_id, physical_network)) + + +def add_network_binding(session, network_id, physical_network, vlan_id): + with session.begin(subtransactions=True): + binding = l2network_models_v2.NetworkBinding(network_id, + physical_network, vlan_id) + session.add(binding) + + +def get_network_binding(session, network_id): + try: + binding = (session.query(l2network_models_v2.NetworkBinding). + filter_by(network_id=network_id). + one()) + return binding + except exc.NoResultFound: + return + + +def get_port_from_device(device): + """Get port from database""" + LOG.debug("get_port_from_device() called") + session = db.get_session() + ports = session.query(models_v2.Port).all() + if not ports: + return + for port in ports: + if port['id'].startswith(device): + return port + return + + +def set_port_status(port_id, status): + """Set the port status""" + LOG.debug("set_port_status as %s called", status) + session = db.get_session() + try: + port = session.query(models_v2.Port).filter_by(id=port_id).one() + port['status'] = status + if status == api_common.PORT_STATUS_DOWN: + port['device_id'] = '' + session.merge(port) + session.flush() + except exc.NoResultFound: + raise q_exc.PortNotFound(port_id=port_id) diff --git a/quantum/plugins/linuxbridge/db/l2network_models_v2.py b/quantum/plugins/linuxbridge/db/l2network_models_v2.py index 925be3157c..ca840a285f 100644 --- a/quantum/plugins/linuxbridge/db/l2network_models_v2.py +++ b/quantum/plugins/linuxbridge/db/l2network_models_v2.py @@ -14,38 +14,46 @@ # limitations under the License. import sqlalchemy as sa -from sqlalchemy import orm from quantum.db import model_base -class VlanID(model_base.BASEV2): - """Represents a vlan_id usage""" - __tablename__ = 'vlan_ids' +class NetworkState(model_base.BASEV2): + """Represents state of vlan_id on physical network""" + __tablename__ = 'network_states' - vlan_id = sa.Column(sa.Integer, nullable=False, primary_key=True) - vlan_used = sa.Column(sa.Boolean, nullable=False) + physical_network = sa.Column(sa.String(64), nullable=False, + primary_key=True) + vlan_id = sa.Column(sa.Integer, nullable=False, primary_key=True, + autoincrement=False) + allocated = sa.Column(sa.Boolean, nullable=False) - def __init__(self, vlan_id): + def __init__(self, physical_network, vlan_id): + self.physical_network = physical_network self.vlan_id = vlan_id - self.vlan_used = False + self.allocated = False def __repr__(self): - return "" % (self.vlan_id, self.vlan_used) + return "" % (self.physical_network, + self.vlan_id, self.allocated) -class VlanBinding(model_base.BASEV2): - """Represents a binding of vlan_id to network_id""" - __tablename__ = 'vlan_bindings' +class NetworkBinding(model_base.BASEV2): + """Represents binding of virtual network to physical_network and vlan_id""" + __tablename__ = 'network_bindings' - network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id', - ondelete="CASCADE"), + network_id = sa.Column(sa.String(36), + sa.ForeignKey('networks.id', ondelete="CASCADE"), primary_key=True) + physical_network = sa.Column(sa.String(64), nullable=False) vlan_id = sa.Column(sa.Integer, nullable=False) - def __init__(self, vlan_id, network_id): - self.vlan_id = vlan_id + def __init__(self, network_id, physical_network, vlan_id): self.network_id = network_id + self.physical_network = physical_network + self.vlan_id = vlan_id def __repr__(self): - return "" % (self.vlan_id, self.network_id) + return "" % (self.network_id, + self.physical_network, + self.vlan_id) diff --git a/quantum/plugins/linuxbridge/lb_quantum_plugin.py b/quantum/plugins/linuxbridge/lb_quantum_plugin.py index 80f88b9d48..534a1e1c0a 100644 --- a/quantum/plugins/linuxbridge/lb_quantum_plugin.py +++ b/quantum/plugins/linuxbridge/lb_quantum_plugin.py @@ -14,10 +14,13 @@ # limitations under the License. import logging +import sys from quantum.api import api_common from quantum.api.v2 import attributes +from quantum.common import exceptions as q_exc from quantum.common import topics +from quantum.db import api as db_api from quantum.db import db_base_plugin_v2 from quantum.db import models_v2 from quantum.openstack.common import context @@ -25,7 +28,8 @@ from quantum.openstack.common import cfg from quantum.openstack.common import rpc from quantum.openstack.common.rpc import dispatcher from quantum.openstack.common.rpc import proxy -from quantum.plugins.linuxbridge.db import l2network_db as cdb +from quantum.plugins.linuxbridge.common import constants +from quantum.plugins.linuxbridge.db import l2network_db_v2 as db from quantum import policy @@ -39,8 +43,8 @@ class LinuxBridgeRpcCallbacks(): # Device names start with "tap" TAP_PREFIX_LEN = 3 - def __init__(self, context): - self.context = context + def __init__(self, rpc_context): + self.rpc_context = rpc_context def create_rpc_dispatcher(self): '''Get the rpc dispatcher for this manager. @@ -50,38 +54,40 @@ class LinuxBridgeRpcCallbacks(): ''' return dispatcher.RpcDispatcher([self]) - def get_device_details(self, context, **kwargs): + def get_device_details(self, rpc_context, **kwargs): """Agent requests device details""" agent_id = kwargs.get('agent_id') device = kwargs.get('device') LOG.debug("Device %s details requested from %s", device, agent_id) - port = cdb.get_port_from_device(device[self.TAP_PREFIX_LEN:]) + port = db.get_port_from_device(device[self.TAP_PREFIX_LEN:]) if port: - vlan_binding = cdb.get_vlan_binding(port['network_id']) + binding = db.get_network_binding(db_api.get_session(), + port['network_id']) entry = {'device': device, - 'vlan_id': vlan_binding['vlan_id'], + 'physical_network': binding.physical_network, + 'vlan_id': binding.vlan_id, 'network_id': port['network_id'], 'port_id': port['id'], 'admin_state_up': port['admin_state_up']} # Set the port status to UP - cdb.set_port_status(port['id'], api_common.PORT_STATUS_UP) + db.set_port_status(port['id'], api_common.PORT_STATUS_UP) else: entry = {'device': device} LOG.debug("%s can not be found in database", device) return entry - def update_device_down(self, context, **kwargs): + def update_device_down(self, rpc_context, **kwargs): """Device no longer exists on agent""" # (TODO) garyk - live migration and port status agent_id = kwargs.get('agent_id') device = kwargs.get('device') LOG.debug("Device %s no longer exists on %s", device, agent_id) - port = cdb.get_port_from_device(device[self.TAP_PREFIX_LEN:]) + port = db.get_port_from_device(device[self.TAP_PREFIX_LEN:]) if port: entry = {'device': device, 'exists': True} # Set port status to DOWN - cdb.set_port_status(port['id'], api_common.PORT_STATUS_UP) + db.set_port_status(port['id'], api_common.PORT_STATUS_DOWN) else: entry = {'device': device, 'exists': False} @@ -115,10 +121,11 @@ class AgentNotifierApi(proxy.RpcProxy): network_id=network_id), topic=self.topic_network_delete) - def port_update(self, context, port, vlan_id): + def port_update(self, context, port, physical_network, vlan_id): self.fanout_cast(context, self.make_msg('port_update', port=port, + physical_network=physical_network, vlan_id=vlan_id), topic=self.topic_port_update) @@ -137,24 +144,29 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2): be updated to take advantage of it. """ + # This attribute specifies whether the plugin supports or not + # bulk operations. Name mangling is used in order to ensure it + # is qualified by class + __native_bulk_support = True + supported_extension_aliases = ["provider"] def __init__(self): - cdb.initialize(base=models_v2.model_base.BASEV2) + db.initialize() + self._parse_network_vlan_ranges() + db.sync_network_states(self.network_vlan_ranges) self.rpc = cfg.CONF.AGENT.rpc - if cfg.CONF.AGENT.rpc and cfg.CONF.AGENT.target_v2_api: - self.setup_rpc() - if not cfg.CONF.AGENT.target_v2_api: - self.rpc = False + if self.rpc: + self._setup_rpc() LOG.debug("Linux Bridge Plugin initialization complete") - def setup_rpc(self): + def _setup_rpc(self): # RPC support self.topic = topics.PLUGIN - self.context = context.RequestContext('quantum', 'quantum', - is_admin=False) + self.rpc_context = context.RequestContext('quantum', 'quantum', + is_admin=False) self.conn = rpc.create_connection(new=True) - self.callbacks = LinuxBridgeRpcCallbacks(self.context) + self.callbacks = LinuxBridgeRpcCallbacks(self.rpc_context) self.dispatcher = self.callbacks.create_rpc_dispatcher() self.conn.create_consumer(self.topic, self.dispatcher, fanout=False) @@ -162,7 +174,32 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2): self.conn.consume_in_thread() self.notifier = AgentNotifierApi(topics.AGENT) - # TODO(rkukura) Use core mechanism for attribute authorization + def _parse_network_vlan_ranges(self): + self.network_vlan_ranges = {} + for entry in cfg.CONF.VLANS.network_vlan_ranges: + if ':' in entry: + try: + physical_network, vlan_min, vlan_max = entry.split(':') + self._add_network_vlan_range(physical_network, + int(vlan_min), + int(vlan_max)) + except ValueError as ex: + LOG.error("Invalid network VLAN range: \'%s\' - %s" % + (entry, ex)) + sys.exit(1) + else: + self._add_network(entry) + LOG.debug("network VLAN ranges: %s" % self.network_vlan_ranges) + + def _add_network_vlan_range(self, physical_network, vlan_min, vlan_max): + self._add_network(physical_network) + self.network_vlan_ranges[physical_network].append((vlan_min, vlan_max)) + + def _add_network(self, physical_network): + if physical_network not in self.network_vlan_ranges: + self.network_vlan_ranges[physical_network] = [] + + # REVISIT(rkukura) Use core mechanism for attribute authorization # when available. def _check_provider_view_auth(self, context, network): @@ -177,40 +214,119 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2): def _extend_network_dict(self, context, network): if self._check_provider_view_auth(context, network): - vlan_binding = cdb.get_vlan_binding(network['id']) - network['provider:vlan_id'] = vlan_binding['vlan_id'] + binding = db.get_network_binding(context.session, network['id']) + network['provider:physical_network'] = binding.physical_network + if binding.vlan_id == constants.FLAT_VLAN_ID: + network['provider:network_type'] = 'flat' + network['provider:vlan_id'] = None + else: + network['provider:network_type'] = 'vlan' + network['provider:vlan_id'] = binding.vlan_id + + def _process_provider_create(self, context, attrs): + network_type = attrs.get('provider:network_type') + physical_network = attrs.get('provider:physical_network') + vlan_id = attrs.get('provider:vlan_id') + + network_type_set = attributes.is_attr_set(network_type) + physical_network_set = attributes.is_attr_set(physical_network) + vlan_id_set = attributes.is_attr_set(vlan_id) + + if not (network_type_set or physical_network_set or vlan_id_set): + return (None, None, None) + + # Authorize before exposing plugin details to client + self._enforce_provider_set_auth(context, attrs) + + if not network_type_set: + msg = _("provider:network_type required") + raise q_exc.InvalidInput(error_message=msg) + elif network_type == 'flat': + if vlan_id_set: + msg = _("provider:vlan_id specified for flat network") + raise q_exc.InvalidInput(error_message=msg) + else: + vlan_id = constants.FLAT_VLAN_ID + elif network_type == 'vlan': + if not vlan_id_set: + msg = _("provider:vlan_id required") + raise q_exc.InvalidInput(error_message=msg) + else: + msg = _("invalid provider:network_type %s" % network_type) + raise q_exc.InvalidInput(error_message=msg) + + if physical_network_set: + if physical_network not in self.network_vlan_ranges: + msg = _("unknown provider:physical_network %s" % + physical_network) + raise q_exc.InvalidInput(error_message=msg) + elif 'default' in self.network_vlan_ranges: + physical_network = 'default' + else: + msg = _("provider:physical_network required") + raise q_exc.InvalidInput(error_message=msg) + + return (network_type, physical_network, vlan_id) + + def _check_provider_update(self, context, attrs): + network_type = attrs.get('provider:network_type') + physical_network = attrs.get('provider:physical_network') + vlan_id = attrs.get('provider:vlan_id') + + network_type_set = attributes.is_attr_set(network_type) + physical_network_set = attributes.is_attr_set(physical_network) + vlan_id_set = attributes.is_attr_set(vlan_id) + + if not (network_type_set or physical_network_set or vlan_id_set): + return + + # Authorize before exposing plugin details to client + self._enforce_provider_set_auth(context, attrs) + + msg = _("plugin does not support updating provider attributes") + raise q_exc.InvalidInput(error_message=msg) def create_network(self, context, network): - net = super(LinuxBridgePluginV2, self).create_network(context, - network) - try: - vlan_id = network['network'].get('provider:vlan_id') - if vlan_id not in (None, attributes.ATTR_NOT_SPECIFIED): - self._enforce_provider_set_auth(context, net) - cdb.reserve_specific_vlanid(int(vlan_id)) + (network_type, physical_network, + vlan_id) = self._process_provider_create(context, + network['network']) + + session = context.session + with session.begin(subtransactions=True): + if not network_type: + physical_network, vlan_id = db.reserve_network(session) else: - vlan_id = cdb.reserve_vlanid() - cdb.add_vlan_binding(vlan_id, net['id']) + db.reserve_specific_network(session, physical_network, vlan_id) + net = super(LinuxBridgePluginV2, self).create_network(context, + network) + db.add_network_binding(session, net['id'], + physical_network, vlan_id) self._extend_network_dict(context, net) - except: - super(LinuxBridgePluginV2, self).delete_network(context, - net['id']) - raise + # note - exception will rollback entire transaction return net def update_network(self, context, id, network): - net = super(LinuxBridgePluginV2, self).update_network(context, id, - network) - self._extend_network_dict(context, net) + self._check_provider_update(context, network['network']) + + session = context.session + with session.begin(subtransactions=True): + net = super(LinuxBridgePluginV2, self).update_network(context, id, + network) + self._extend_network_dict(context, net) return net def delete_network(self, context, id): - vlan_binding = cdb.get_vlan_binding(id) - result = super(LinuxBridgePluginV2, self).delete_network(context, id) - cdb.release_vlanid(vlan_binding['vlan_id']) + session = context.session + with session.begin(subtransactions=True): + binding = db.get_network_binding(session, id) + result = super(LinuxBridgePluginV2, self).delete_network(context, + id) + db.release_network(session, binding.physical_network, + binding.vlan_id, self.network_vlan_ranges) + # the network_binding record is deleted via cascade from + # the network record, so explicit removal is not necessary if self.rpc: - self.notifier.network_delete(self.context, id) - return result + self.notifier.network_delete(self.rpc_context, id) def get_network(self, context, id, fields=None, verbose=None): net = super(LinuxBridgePluginV2, self).get_network(context, id, @@ -233,7 +349,9 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2): port = super(LinuxBridgePluginV2, self).update_port(context, id, port) if self.rpc: if original_port['admin_state_up'] != port['admin_state_up']: - vlan_binding = cdb.get_vlan_binding(port['network_id']) - self.notifier.port_update(self.context, port, - vlan_binding['vlan_id']) + binding = db.get_network_binding(context.session, + port['network_id']) + self.notifier.port_update(self.rpc_context, port, + binding.physical_network, + binding.vlan_id) return port diff --git a/quantum/plugins/linuxbridge/tests/unit/test_defaults.py b/quantum/plugins/linuxbridge/tests/unit/test_defaults.py new file mode 100644 index 0000000000..f3361e72a3 --- /dev/null +++ b/quantum/plugins/linuxbridge/tests/unit/test_defaults.py @@ -0,0 +1,42 @@ +# Copyright (c) 2012 OpenStack, LLC. +# +# 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 unittest2 as unittest + +from quantum.openstack.common import cfg +from quantum.plugins.linuxbridge.common import config + + +class ConfigurationTest(unittest.TestCase): + + def test_defaults(self): + self.assertEqual('sqlite://', + cfg.CONF.DATABASE.sql_connection) + self.assertEqual(-1, + cfg.CONF.DATABASE.sql_max_retries) + self.assertEqual(2, + cfg.CONF.DATABASE.reconnect_interval) + self.assertEqual(2, + cfg.CONF.AGENT.polling_interval) + self.assertEqual('sudo', + cfg.CONF.AGENT.root_helper) + + ranges = cfg.CONF.VLANS.network_vlan_ranges + self.assertEqual(1, len(ranges)) + self.assertEqual('default:1000:2999', ranges[0]) + + mappings = cfg.CONF.LINUX_BRIDGE.physical_interface_mappings + self.assertEqual(1, len(mappings)) + self.assertEqual('default:eth1', mappings[0]) diff --git a/quantum/plugins/linuxbridge/tests/unit/test_lb_db.py b/quantum/plugins/linuxbridge/tests/unit/test_lb_db.py new file mode 100644 index 0000000000..6579b4c8df --- /dev/null +++ b/quantum/plugins/linuxbridge/tests/unit/test_lb_db.py @@ -0,0 +1,125 @@ +# Copyright (c) 2012 OpenStack, LLC. +# +# 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 unittest2 + +from quantum.common import exceptions as q_exc +from quantum.db import api as db +from quantum.plugins.linuxbridge.db import l2network_db_v2 as lb_db + +PHYS_NET = 'physnet1' +VLAN_MIN = 10 +VLAN_MAX = 19 +VLAN_RANGES = {PHYS_NET: [(VLAN_MIN, VLAN_MAX)]} +UPDATED_VLAN_RANGES = {PHYS_NET: [(VLAN_MIN + 5, VLAN_MAX + 5)]} +TEST_NETWORK_ID = 'abcdefghijklmnopqrstuvwxyz' + + +class NetworkStatesTest(unittest2.TestCase): + def setUp(self): + lb_db.initialize() + lb_db.sync_network_states(VLAN_RANGES) + self.session = db.get_session() + + def tearDown(self): + db.clear_db() + + def test_sync_network_states(self): + self.assertIsNone(lb_db.get_network_state(PHYS_NET, + VLAN_MIN - 1)) + self.assertFalse(lb_db.get_network_state(PHYS_NET, + VLAN_MIN).allocated) + self.assertFalse(lb_db.get_network_state(PHYS_NET, + VLAN_MIN + 1).allocated) + self.assertFalse(lb_db.get_network_state(PHYS_NET, + VLAN_MAX).allocated) + self.assertIsNone(lb_db.get_network_state(PHYS_NET, + VLAN_MAX + 1)) + + lb_db.sync_network_states(UPDATED_VLAN_RANGES) + + self.assertIsNone(lb_db.get_network_state(PHYS_NET, + VLAN_MIN + 5 - 1)) + self.assertFalse(lb_db.get_network_state(PHYS_NET, + VLAN_MIN + 5).allocated) + self.assertFalse(lb_db.get_network_state(PHYS_NET, + VLAN_MIN + 5 + 1).allocated) + self.assertFalse(lb_db.get_network_state(PHYS_NET, + VLAN_MAX + 5).allocated) + self.assertIsNone(lb_db.get_network_state(PHYS_NET, + VLAN_MAX + 5 + 1)) + + def test_network_pool(self): + vlan_ids = set() + for x in xrange(VLAN_MIN, VLAN_MAX + 1): + physical_network, vlan_id = lb_db.reserve_network(self.session) + self.assertEqual(physical_network, PHYS_NET) + self.assertGreaterEqual(vlan_id, VLAN_MIN) + self.assertLessEqual(vlan_id, VLAN_MAX) + vlan_ids.add(vlan_id) + + with self.assertRaises(q_exc.NoNetworkAvailable): + physical_network, vlan_id = lb_db.reserve_network(self.session) + + for vlan_id in vlan_ids: + lb_db.release_network(self.session, PHYS_NET, vlan_id, VLAN_RANGES) + + def test_specific_network_inside_pool(self): + vlan_id = VLAN_MIN + 5 + self.assertFalse(lb_db.get_network_state(PHYS_NET, + vlan_id).allocated) + lb_db.reserve_specific_network(self.session, PHYS_NET, vlan_id) + self.assertTrue(lb_db.get_network_state(PHYS_NET, + vlan_id).allocated) + + with self.assertRaises(q_exc.VlanIdInUse): + lb_db.reserve_specific_network(self.session, PHYS_NET, vlan_id) + + lb_db.release_network(self.session, PHYS_NET, vlan_id, VLAN_RANGES) + self.assertFalse(lb_db.get_network_state(PHYS_NET, + vlan_id).allocated) + + def test_specific_network_outside_pool(self): + vlan_id = VLAN_MAX + 5 + self.assertIsNone(lb_db.get_network_state(PHYS_NET, vlan_id)) + lb_db.reserve_specific_network(self.session, PHYS_NET, vlan_id) + self.assertTrue(lb_db.get_network_state(PHYS_NET, + vlan_id).allocated) + + with self.assertRaises(q_exc.VlanIdInUse): + lb_db.reserve_specific_network(self.session, PHYS_NET, vlan_id) + + lb_db.release_network(self.session, PHYS_NET, vlan_id, VLAN_RANGES) + self.assertIsNone(lb_db.get_network_state(PHYS_NET, vlan_id)) + + +class NetworkBindingsTest(unittest2.TestCase): + def setUp(self): + lb_db.initialize() + self.session = db.get_session() + + def tearDown(self): + db.clear_db() + + def test_add_network_binding(self): + self.assertIsNone(lb_db.get_network_binding(self.session, + TEST_NETWORK_ID)) + lb_db.add_network_binding(self.session, TEST_NETWORK_ID, PHYS_NET, + 1234) + binding = lb_db.get_network_binding(self.session, TEST_NETWORK_ID) + self.assertIsNotNone(binding) + self.assertEqual(binding.network_id, TEST_NETWORK_ID) + self.assertEqual(binding.physical_network, PHYS_NET) + self.assertEqual(binding.vlan_id, 1234) diff --git a/quantum/plugins/linuxbridge/tests/unit/test_rpcapi.py b/quantum/plugins/linuxbridge/tests/unit/test_rpcapi.py index 1fc3f6fd6a..04ed559f02 100644 --- a/quantum/plugins/linuxbridge/tests/unit/test_rpcapi.py +++ b/quantum/plugins/linuxbridge/tests/unit/test_rpcapi.py @@ -74,7 +74,9 @@ class rpcApiTestCase(unittest2.TestCase): topics.PORT, topics.UPDATE), 'port_update', rpc_method='fanout_cast', - port='fake_port', vlan_id='fake_vlan_id') + port='fake_port', + physical_network='fake_net', + vlan_id='fake_vlan_id') def test_device_details(self): rpcapi = agent_rpc.PluginApi(topics.PLUGIN) diff --git a/quantum/plugins/openvswitch/ovs_db_v2.py b/quantum/plugins/openvswitch/ovs_db_v2.py index d7c29c1bb4..06f3716d18 100644 --- a/quantum/plugins/openvswitch/ovs_db_v2.py +++ b/quantum/plugins/openvswitch/ovs_db_v2.py @@ -140,7 +140,9 @@ def reserve_specific_vlan_id(vlan_id, session): filter_by(vlan_id=vlan_id). one()) if record.vlan_used: - raise q_exc.VlanIdInUse(vlan_id=vlan_id) + # REVISIT(rkukura) pass phyiscal_network + raise q_exc.VlanIdInUse(vlan_id=vlan_id, + physical_network='default') LOG.debug("reserving specific vlan %s from pool" % vlan_id) record.vlan_used = True except exc.NoResultFound: diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.py b/quantum/plugins/openvswitch/ovs_quantum_plugin.py index 6792cd54f5..9bc15886ba 100644 --- a/quantum/plugins/openvswitch/ovs_quantum_plugin.py +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.py @@ -234,15 +234,86 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2): network['provider:vlan_id'] = ovs_db_v2.get_vlan( network['id'], context.session) + def _process_provider_create(self, context, attrs): + network_type = attrs.get('provider:network_type') + physical_network = attrs.get('provider:physical_network') + vlan_id = attrs.get('provider:vlan_id') + + network_type_set = attributes.is_attr_set(network_type) + physical_network_set = attributes.is_attr_set(physical_network) + vlan_id_set = attributes.is_attr_set(vlan_id) + + if not (network_type_set or physical_network_set or vlan_id_set): + return (None, None, None) + + # Authorize before exposing plugin details to client + self._enforce_provider_set_auth(context, attrs) + + if not network_type_set: + msg = _("provider:network_type required") + raise q_exc.InvalidInput(error_message=msg) + elif network_type == 'flat': + msg = _("plugin does not support flat networks") + raise q_exc.InvalidInput(error_message=msg) + # REVISIT(rkukura) to be enabled in phase 3 + # if vlan_id_set: + # msg = _("provider:vlan_id specified for flat network") + # raise q_exc.InvalidInput(error_message=msg) + # else: + # vlan_id = db.FLAT_VLAN_ID + elif network_type == 'vlan': + if not vlan_id_set: + msg = _("provider:vlan_id required") + raise q_exc.InvalidInput(error_message=msg) + else: + msg = _("invalid provider:network_type %s" % network_type) + raise q_exc.InvalidInput(error_message=msg) + + if physical_network_set: + msg = _("plugin does not support specifying physical_network") + raise q_exc.InvalidInput(error_message=msg) + # REVISIT(rkukura) to be enabled in phase 3 + # if physical_network not in self.physical_networks: + # msg = _("unknown provider:physical_network %s" % + # physical_network) + # raise q_exc.InvalidInput(error_message=msg) + #elif 'default' in self.physical_networks: + # physical_network = 'default' + #else: + # msg = _("provider:physical_network required") + # raise q_exc.InvalidInput(error_message=msg) + + return (network_type, physical_network, vlan_id) + + def _check_provider_update(self, context, attrs): + network_type = attrs.get('provider:network_type') + physical_network = attrs.get('provider:physical_network') + vlan_id = attrs.get('provider:vlan_id') + + network_type_set = attributes.is_attr_set(network_type) + physical_network_set = attributes.is_attr_set(physical_network) + vlan_id_set = attributes.is_attr_set(vlan_id) + + if not (network_type_set or physical_network_set or vlan_id_set): + return + + # Authorize before exposing plugin details to client + self._enforce_provider_set_auth(context, attrs) + + msg = _("plugin does not support updating provider attributes") + raise q_exc.InvalidInput(error_message=msg) + def create_network(self, context, network): + (network_type, physical_network, + vlan_id) = self._process_provider_create(context, + network['network']) + net = super(OVSQuantumPluginV2, self).create_network(context, network) try: - vlan_id = network['network'].get('provider:vlan_id') - if vlan_id not in (None, attributes.ATTR_NOT_SPECIFIED): - self._enforce_provider_set_auth(context, net) - ovs_db_v2.reserve_specific_vlan_id(vlan_id, context.session) - else: + if not network_type: vlan_id = ovs_db_v2.reserve_vlan_id(context.session) + else: + ovs_db_v2.reserve_specific_vlan_id(vlan_id, context.session) except Exception: super(OVSQuantumPluginV2, self).delete_network(context, net['id']) raise @@ -253,6 +324,8 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2): return net def update_network(self, context, id, network): + self._check_provider_update(context, network['network']) + net = super(OVSQuantumPluginV2, self).update_network(context, id, network) self._extend_network_dict(context, net) diff --git a/quantum/plugins/openvswitch/tests/unit/test_defaults.py b/quantum/plugins/openvswitch/tests/unit/test_defaults.py index 1987d8ea63..f8e7f1ec7c 100644 --- a/quantum/plugins/openvswitch/tests/unit/test_defaults.py +++ b/quantum/plugins/openvswitch/tests/unit/test_defaults.py @@ -16,6 +16,7 @@ import unittest from quantum.openstack.common import cfg +from quantum.plugins.openvswitch.common import config class ConfigurationTest(unittest.TestCase): diff --git a/quantum/tests/etc/quantum.conf.test b/quantum/tests/etc/quantum.conf.test index 0dbb4a2a27..961af50420 100644 --- a/quantum/tests/etc/quantum.conf.test +++ b/quantum/tests/etc/quantum.conf.test @@ -16,3 +16,6 @@ api_extensions_path = unit/extensions # Paste configuration file api_paste_config = api-paste.ini.test + +# The messaging module to use, defaults to kombu. +rpc_backend = quantum.openstack.common.rpc.impl_fake