# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2011 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. # import inspect import logging as LOG from quantum.common import exceptions as exc from quantum.common import utils from quantum.quantum_plugin_base import QuantumPluginBase from quantum.plugins.cisco import l2network_plugin_configuration as conf from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_exceptions as cexc LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) class L2Network(QuantumPluginBase): _networks = {} _tenants = {} _portprofiles = {} def __init__(self): self._net_counter = 0 self._portprofile_counter = 0 self._port_counter = 0 self._vlan_counter = int(conf.VLAN_START) - 1 self._model = utils.import_object(conf.MODEL_CLASS) """ Core API implementation """ def get_all_networks(self, tenant_id): """ Returns a dictionary containing all for the specified tenant. """ LOG.debug("get_all_networks() called\n") self._invokeDevicePlugins(self._funcName(), [tenant_id]) return self._networks.values() def create_network(self, tenant_id, net_name): """ Creates a new Virtual Network, and assigns it a symbolic name. """ LOG.debug("create_network() called\n") new_net_id = self._get_unique_net_id(tenant_id) vlan_id = self._get_vlan_for_tenant(tenant_id, net_name) vlan_name = self._get_vlan_name(new_net_id, str(vlan_id)) self._invokeDevicePlugins(self._funcName(), [tenant_id, net_name, new_net_id, vlan_name, vlan_id]) new_net_dict = {const.NET_ID: new_net_id, const.NET_NAME: net_name, const.NET_PORTS: {}, const.NET_VLAN_NAME: vlan_name, const.NET_VLAN_ID: vlan_id, const.NET_TENANTS: [tenant_id]} self._networks[new_net_id] = new_net_dict tenant = self._get_tenant(tenant_id) tenant_networks = tenant[const.TENANT_NETWORKS] tenant_networks[new_net_id] = new_net_dict return new_net_dict def delete_network(self, tenant_id, net_id): """ Deletes the network with the specified network identifier belonging to the specified tenant. """ LOG.debug("delete_network() called\n") net = self._networks.get(net_id) if net: if len(net[const.NET_PORTS].values()) > 0: ports_on_net = net[const.NET_PORTS].values() for port in ports_on_net: if port[const.ATTACHMENT]: raise exc.NetworkInUse(net_id=net_id) for port in ports_on_net: self.delete_port(tenant_id, net_id, port[const.PORT_ID]) self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id]) self._networks.pop(net_id) tenant = self._get_tenant(tenant_id) tenant_networks = tenant[const.TENANT_NETWORKS] tenant_networks.pop(net_id) return net # Network not found raise exc.NetworkNotFound(net_id=net_id) def get_network_details(self, tenant_id, net_id): """ Gets the details of a particular network """ LOG.debug("get_network_details() called\n") self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id]) network = self._get_network(tenant_id, net_id) ports_on_net = network[const.NET_PORTS].values() return {const.NET_ID: network[const.NET_ID], const.NET_NAME: network[const.NET_NAME], const.NET_PORTS: ports_on_net} def rename_network(self, tenant_id, net_id, new_name): """ Updates the symbolic name belonging to a particular Virtual Network. """ LOG.debug("rename_network() called\n") self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id, new_name]) network = self._get_network(tenant_id, net_id) network[const.NET_NAME] = new_name return network def get_all_ports(self, tenant_id, net_id): """ Retrieves all port identifiers belonging to the specified Virtual Network. """ LOG.debug("get_all_ports() called\n") self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id]) network = self._get_network(tenant_id, net_id) ports_on_net = network[const.NET_PORTS].values() return ports_on_net def create_port(self, tenant_id, net_id, port_state=None): """ Creates a port on the specified Virtual Network. """ LOG.debug("create_port() called\n") net = self._get_network(tenant_id, net_id) ports = net[const.NET_PORTS] unique_port_id_string = self._get_unique_port_id(tenant_id, net_id) self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id, port_state, unique_port_id_string]) new_port_dict = {const.PORT_ID: unique_port_id_string, const.PORT_STATE: const.PORT_UP, const.ATTACHMENT: None} ports[unique_port_id_string] = new_port_dict return new_port_dict def delete_port(self, tenant_id, net_id, port_id): """ Deletes a port on a specified Virtual Network, if the port contains a remote interface attachment, the remote interface should first be un-plugged and then the port can be deleted. """ LOG.debug("delete_port() called\n") port = self._get_port(tenant_id, net_id, port_id) if port[const.ATTACHMENT]: raise exc.PortInUse(net_id=net_id, port_id=port_id, att_id=port[const.ATTACHMENT]) try: #TODO (Sumit): Before deleting port profile make sure that there # is no VM using this port profile self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id, port_id]) net = self._get_network(tenant_id, net_id) net[const.NET_PORTS].pop(port_id) except KeyError: raise exc.PortNotFound(net_id=net_id, port_id=port_id) def update_port(self, tenant_id, net_id, port_id, port_state): """ Updates the state of a port on the specified Virtual Network. """ LOG.debug("update_port() called\n") self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id, port_id, port_state]) port = self._get_port(tenant_id, net_id, port_id) self._validate_port_state(port_state) port[const.PORT_STATE] = port_state return port def get_port_details(self, tenant_id, net_id, port_id): """ This method allows the user to retrieve a remote interface that is attached to this particular port. """ LOG.debug("get_port_details() called\n") self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id, port_id]) return self._get_port(tenant_id, net_id, port_id) def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id): """ Attaches a remote interface to the specified port on the specified Virtual Network. """ LOG.debug("plug_interface() called\n") self._validate_attachment(tenant_id, net_id, port_id, remote_interface_id) port = self._get_port(tenant_id, net_id, port_id) if port[const.ATTACHMENT]: raise exc.PortInUse(net_id=net_id, port_id=port_id, att_id=port[const.ATTACHMENT]) self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id, port_id, remote_interface_id]) port[const.ATTACHMENT] = remote_interface_id def unplug_interface(self, tenant_id, net_id, port_id): """ Detaches a remote interface from the specified port on the specified Virtual Network. """ LOG.debug("unplug_interface() called\n") port = self._get_port(tenant_id, net_id, port_id) self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id, port_id]) port[const.ATTACHMENT] = None """ Extension API implementation """ def get_all_portprofiles(self, tenant_id): return self._portprofiles.values() def get_portprofile_details(self, tenant_id, profile_id): return self._get_portprofile(tenant_id, profile_id) def create_portprofile(self, tenant_id, profile_name, vlan_id): profile_id = self._get_unique_profile_id(tenant_id) new_port_profile_dict = {const.PROFILE_ID: profile_id, const.PROFILE_NAME: profile_name, const.PROFILE_ASSOCIATIONS: [], const.PROFILE_VLAN_ID: vlan_id, const.PROFILE_QOS: None} self._portprofiles[profile_id] = new_port_profile_dict tenant = self._get_tenant(tenant_id) portprofiles = tenant[const.TENANT_PORTPROFILES] portprofiles[profile_id] = new_port_profile_dict return new_port_profile_dict def delete_portprofile(self, tenant_id, profile_id): portprofile = self._get_portprofile(tenant_id, profile_id) associations = portprofile[const.PROFILE_ASSOCIATIONS] if len(associations) > 0: raise cexc.PortProfileInvalidDelete(tenant_id=tenant_id, profile_id=profile_id) else: self._portprofiles.pop(profile_id) tenant = self._get_tenant(tenant_id) tenant[const.TENANT_PORTPROFILES].pop(profile_id) def rename_portprofile(self, tenant_id, profile_id, new_name): portprofile = self._get_portprofile(tenant_id, profile_id) portprofile[const.PROFILE_NAME] = new_name return portprofile def associate_portprofile(self, tenant_id, net_id, port_id, portprofile_id): portprofile = self._get_portprofile(tenant_id, portprofile_id) associations = portprofile[const.PROFILE_ASSOCIATIONS] associations.append(port_id) def disassociate_portprofile(self, tenant_id, net_id, port_id, portprofile_id): portprofile = self._get_portprofile(tenant_id, portprofile_id) associations = portprofile[const.PROFILE_ASSOCIATIONS] associations.remove(port_id) def create_defaultPProfile(self, tenant_id, network_id, profile_name, vlan_id): pass """ Private functions """ def _invokeDevicePlugins(self, function_name, args): """ All device-specific calls are delegate to the model """ getattr(self._model, function_name)(args) def _get_vlan_for_tenant(self, tenant_id, net_name): # TODO (Sumit): # The VLAN ID for a tenant might need to be obtained from # somewhere (from Donabe/Melange?) # Also need to make sure that the VLAN ID is not being used already # Currently, just a wrap-around counter ranging from VLAN_START to # VLAN_END self._vlan_counter += 1 self._vlan_counter %= int(conf.VLAN_END) if self._vlan_counter < int(conf.VLAN_START): self._vlan_counter = int(conf.VLAN_START) return self._vlan_counter def _get_vlan_name(self, net_id, vlan): vlan_name = conf.VLAN_NAME_PREFIX + net_id + "-" + vlan return vlan_name def _validate_port_state(self, port_state): if port_state.upper() not in (const.PORT_UP, const.PORT_DOWN): raise exc.StateInvalid(port_state=port_state) return True def _validate_attachment(self, tenant_id, network_id, port_id, remote_interface_id): network = self._get_network(tenant_id, network_id) for port in network[const.NET_PORTS].values(): if port[const.ATTACHMENT] == remote_interface_id: raise exc.AlreadyAttached(net_id=network_id, port_id=port_id, att_id=port[const.ATTACHMENT], att_port_id=port[const.PORT_ID]) def _get_network(self, tenant_id, network_id): network = self._networks.get(network_id) if not network: raise exc.NetworkNotFound(net_id=network_id) return network def _get_tenant(self, tenant_id): tenant = self._tenants.get(tenant_id) if not tenant: LOG.debug("Creating new tenant record with tenant id %s\n" % tenant_id) tenant = {const.TENANT_ID: tenant_id, const.TENANT_NAME: tenant_id, const.TENANT_NETWORKS: {}, const.TENANT_PORTPROFILES: {}} self._tenants[tenant_id] = tenant return tenant def _get_port(self, tenant_id, network_id, port_id): net = self._get_network(tenant_id, network_id) port = net[const.NET_PORTS].get(port_id) if not port: raise exc.PortNotFound(net_id=network_id, port_id=port_id) return port def _get_portprofile(self, tenant_id, portprofile_id): portprofile = self._portprofiles.get(portprofile_id) if not portprofile: raise cexc.PortProfileNotFound(tenant_id=tenant_id, portprofile_id=portprofile_id) return portprofile def _get_unique_net_id(self, tenant_id): self._net_counter += 1 self._net_counter %= int(conf.MAX_NETWORKS) id = tenant_id[:3] + \ "-n-" + ("0" * (6 - len(str(self._net_counter)))) + \ str(self._net_counter) # TODO (Sumit): Need to check if the ID has already been allocated # ID will be generated by DB return id def _get_unique_port_id(self, tenant_id, net_id): self._port_counter += 1 self._port_counter %= int(conf.MAX_PORTS) id = net_id + "-p-" + str(self._port_counter) # TODO (Sumit): Need to check if the ID has already been allocated # ID will be generated by DB return id def _get_unique_profile_id(self, tenant_id): self._portprofile_counter += 1 self._portprofile_counter %= int(conf.MAX_PORT_PROFILES) id = tenant_id[:3] + "-pp-" + \ ("0" * (6 - len(str(self._net_counter)))) \ + str(self._portprofile_counter) # TODO (Sumit): Need to check if the ID has already been allocated # ID will be generated by DB return id def _funcName(self, offset=0): return inspect.stack()[1 + offset][3] """ TODO (Sumit): (1) Persistent storage """