diff --git a/etc/quantum.conf b/etc/quantum.conf index 05856094e5..85d3fd6f46 100644 --- a/etc/quantum.conf +++ b/etc/quantum.conf @@ -17,13 +17,13 @@ api_extensions_path = extensions [composite:quantum] use = egg:Paste#urlmap /: quantumversions -/v0.1: quantumapi +/v1.0: quantumapi [pipeline:quantumapi] # To enable keystone integration uncomment the following line and # comment the next one -#pipeline = authN authZ extensions quantumapiapp -pipeline = extensions quantumapiapp +pipeline = authN authZ extensions quantumapiapp +#pipeline = extensions quantumapiapp [filter:authN] @@ -46,4 +46,4 @@ paste.filter_factory = quantum.common.extensions:plugin_aware_extension_middlewa paste.app_factory = quantum.api.versions:Versions.factory [app:quantumapiapp] -paste.app_factory = quantum.api:APIRouterV01.factory +paste.app_factory = quantum.api:APIRouterV1.factory diff --git a/extensions/novatenant.py b/extensions/novatenant.py index f6ef130986..5309ce29ee 100644 --- a/extensions/novatenant.py +++ b/extensions/novatenant.py @@ -63,8 +63,8 @@ class Novatenant(object): """ Returns Ext Resource """ parent_resource = dict(member_name="tenant", collection_name="extensions/csco/tenants") - member_actions = {'get_host': "PUT", - 'get_instance_port': "PUT"} + member_actions = {'schedule_host': "PUT", + 'associate_port': "PUT"} controller = NovatenantsController(QuantumManager.get_plugin()) return [extensions.ResourceExtension('novatenants', controller, parent=parent_resource, @@ -79,7 +79,7 @@ class NovatenantsController(common.QuantumController): 'param-name': 'novatenant_name', 'required': True}] - _get_host_ops_param_list = [{ + _schedule_host_ops_param_list = [{ 'param-name': 'instance_id', 'required': True}, { 'param-name': 'instance_desc', @@ -99,40 +99,56 @@ class NovatenantsController(common.QuantumController): #added for cisco's extension # pylint: disable-msg=E1101,W0613 - def get_host(self, request, tenant_id, id): + def show(self, request, tenant_id, id): + """ Returns novatenant details for the given novatenant id """ + return "novatenant is a dummy resource" + + def create(self, request, tenant_id): + """ Creates a new novatenant for a given tenant """ + return "novatenant is a dummy resource" + + def update(self, request, tenant_id, id): + """ Updates the name for the novatenant with the given id """ + return "novatenant is a dummy resource" + + def delete(self, request, tenant_id, id): + """ Destroys the Novatenant with the given id """ + return "novatenant is a dummy resource" + + #added for cisco's extension + def schedule_host(self, request, tenant_id, id): content_type = request.best_match_content_type() - print "Content type:%s" % content_type try: req_params = \ self._parse_request_params(request, - self._get_host_ops_param_list) + self._schedule_host_ops_param_list) except exc.HTTPError as exp: return faults.Fault(exp) instance_id = req_params['instance_id'] instance_desc = req_params['instance_desc'] try: - host = self._plugin.get_host(tenant_id, instance_id, instance_desc) + host = self._plugin.\ + schedule_host(tenant_id, instance_id, instance_desc) builder = novatenant_view.get_view_builder(request) result = builder.build_host(host) return result except qexception.PortNotFound as exp: return faults.Fault(faults.PortNotFound(exp)) - def get_instance_port(self, request, tenant_id, id): + def associate_port(self, request, tenant_id, id): content_type = request.best_match_content_type() - print "Content type:%s" % content_type try: req_params = \ self._parse_request_params(request, - self._get_host_ops_param_list) + self._schedule_host_ops_param_list) except exc.HTTPError as exp: return faults.Fault(exp) instance_id = req_params['instance_id'] instance_desc = req_params['instance_desc'] try: vif = self._plugin. \ - get_instance_port(tenant_id, instance_id, instance_desc) + associate_port(tenant_id, instance_id, instance_desc) builder = novatenant_view.get_view_builder(request) result = builder.build_vif(vif) return result diff --git a/extensions/portprofile.py b/extensions/portprofile.py index 8ecf65061a..4c0eda9296 100644 --- a/extensions/portprofile.py +++ b/extensions/portprofile.py @@ -175,7 +175,6 @@ class PortprofilesController(common.QuantumController): def associate_portprofile(self, request, tenant_id, id): """ associate a portprofile to the port """ content_type = request.best_match_content_type() - print "Content type:%s" % content_type try: req_params = \ @@ -198,7 +197,6 @@ class PortprofilesController(common.QuantumController): def disassociate_portprofile(self, request, tenant_id, id): """ Disassociate a portprofile from a port """ content_type = request.best_match_content_type() - print "Content type:%s" % content_type try: req_params = \ self._parse_request_params(request, diff --git a/quantum/api/__init__.py b/quantum/api/__init__.py index 0ff7ea2349..ea02d26779 100644 --- a/quantum/api/__init__.py +++ b/quantum/api/__init__.py @@ -37,7 +37,7 @@ LOG = logging.getLogger('quantum.api') FLAGS = flags.FLAGS -class APIRouterV01(wsgi.Router): +class APIRouterV1(wsgi.Router): """ Routes requests on the Quantum API to the appropriate controller """ @@ -45,7 +45,7 @@ class APIRouterV01(wsgi.Router): def __init__(self, options=None): mapper = routes.Mapper() self._setup_routes(mapper, options) - super(APIRouterV01, self).__init__(mapper) + super(APIRouterV1, self).__init__(mapper) def _setup_routes(self, mapper, options): # Loads the quantum plugin @@ -64,9 +64,7 @@ class APIRouterV01(wsgi.Router): parent_resource=dict(member_name='network', collection_name=uri_prefix +\ 'networks')) - attachments_ctrl = attachments.Controller(plugin) - mapper.connect("get_resource", uri_prefix + 'networks/{network_id}/' \ 'ports/{id}/attachment{.format}', diff --git a/quantum/common/authentication.py b/quantum/common/authentication.py index 280db36113..8b08eb7e47 100755 --- a/quantum/common/authentication.py +++ b/quantum/common/authentication.py @@ -229,7 +229,7 @@ class AuthProtocol(object): """Client sent bad claims""" return HTTPUnauthorized()(self.env, self.start_response) - def _validate_claims(self, claims): + def _validate_claims(self, claims, retry=False): """Validate claims, and provide identity information if applicable """ # Step 1: We need to auth with the keystone service, so get an @@ -257,16 +257,27 @@ class AuthProtocol(object): conn = http_connect(self.auth_host, self.auth_port, 'GET', self._build_token_uri(claims), headers=headers) resp = conn.getresponse() - # data = resp.read() conn.close() if not str(resp.status).startswith('20'): # Keystone rejected claim + # In case a 404 error it might just be that the token has expired + # Therefore try and get a new token + # of course assuming admin credentials have been specified + # Note(salvatore-orlando): the 404 here is not really + # what should be returned + if self.admin_user and self.admin_password and \ + not retry and str(resp.status) == '404': + LOG.warn("Unable to validate token." + + "Admin token possibly expired.") + self.admin_token = None + return self._validate_claims(claims, True) return False else: #TODO(Ziad): there is an optimization we can do here. We have just #received data from Keystone that we can use instead of making #another call in _expound_claims + LOG.info("Claims successfully validated") return True def _expound_claims(self): diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index 79680b2254..cfe6bc786a 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -338,7 +338,7 @@ def plugin_aware_extension_middleware_factory(global_config, **local_config): def _factory(app): extensions_path = global_config.get('api_extensions_path', '') ext_mgr = PluginAwareExtensionManager(extensions_path, - QuantumManager().get_plugin()) + QuantumManager.get_plugin()) return ExtensionMiddleware(app, global_config, ext_mgr=ext_mgr) return _factory diff --git a/quantum/manager.py b/quantum/manager.py index 727a2c8104..782b71cee7 100644 --- a/quantum/manager.py +++ b/quantum/manager.py @@ -26,7 +26,7 @@ The caller should make sure that QuantumManager is a singleton. import gettext import logging import os -import logging + gettext.install('quantum', unicode=1) from common import utils diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index 6b853d34b0..ef8a49afb3 100755 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -2,7 +2,8 @@ README: A Quantum Plugin Framework for Supporting L2 Networks Spannning Multiple Switches ========================================================================================= -:Author: Sumit Naiksatam, Ram Durairaj, Mark Voelker, Edgar Magana, Shweta Padubidri, Rohit Agarwalla, Ying Liu, Debo Dutta +:Author: Sumit Naiksatam, Ram Durairaj, Mark Voelker, Edgar Magana, Shweta Padubidri, + Rohit Agarwalla, Ying Liu, Debo Dutta :Contact: netstack@lists.launchpad.net :Web site: https://launchpad.net/~cisco-openstack :Copyright: 2011 Cisco Systems, Inc. @@ -86,7 +87,11 @@ Module Structure: /common - Modules common to the entire plugin /conf - All configuration files /db - Persistence framework + /models - Class(es) which tie the logical abstractions + to the physical topology /nexus - Nexus-specific modules + /segmentation - Implementation of segmentation manager, + e.g. VLAN Manager /tests - Tests specific to this plugin /ucs - UCS-specific modules @@ -100,9 +105,16 @@ Plugin Installation Instructions provider = quantum.plugins.cisco.l2network_plugin.L2Network -3. If you are not running Quantum on the same host as the OpenStack Cloud - Controller, you will need to change the db_ip_address configuration - in nova.ini. +3. Configure your OpenStack installation to use the 802.1qbh VIF driver and + Quantum-aware scheduler by editing the /etc/nova/nova.conf file with the + following entries: + +--scheduler_driver=quantum.plugins.cisco.nova.quantum_aware_scheduler.QuantumScheduler +--quantum_host=127.0.0.1 +--quantum_port=9696 +--libvirt_vif_driver=quantum.plugins.cisco.nova.vifdirect.Libvirt802dot1QbhDriver +--libvirt_vif_type=802.1Qbh + 4. If you want to turn on support for Cisco Nexus switches: 4a. Uncomment the nexus_plugin property in @@ -173,7 +185,39 @@ password=mySecretPasswordForNova username=admin password=mySecretPasswordForNexus -7. Start the Quantum service. If something doesn't work, verify that +7. Configure the UCS systems' information in your deployment by editing the + quantum/plugins/cisco/conf/ucs_inventory.ini file. You can configure multiple + UCSMs per deployment, multiple chasses per UCSM, and multiple blades per + chassis. Chassis ID and blade ID can be obtained from the UCSM (they will + typically numbers like 1, 2, 3, etc. + +[ucsm-1] +ip_address = +[[chassis-1]] +chassis_id = +[[[blade-1]]] +blade_id = +host_name = +[[[blade-2]]] +blade_id = +host_name = +[[[blade-3]]] +blade_id = +host_name = + +[ucsm-2] +ip_address = +[[chassis-1]] +chassis_id = +[[[blade-1]]] +blade_id = +host_name = +[[[blade-2]]] +blade_id = +host_name = + + +8. Start the Quantum service. If something doesn't work, verify that your configuration of each of the above files hasn't gone a little kaka. Once you've put right what once went wrong, leap on. @@ -241,54 +285,9 @@ result the quantum/plugins/cisco/run_tests.py script. python run_tests.py quantum.plugins.cisco.tests.unit.test_cisco_extension -Additional installation required on Nova Compute ------------------------------------------------- -1. Create a table in the "nova" database for ports. This can be - accomplished with the following SQL statement: - -CREATE TABLE ports ( - port_id VARCHAR(255) PRIMARY KEY, - profile_name VARCHAR(255), - dynamic_vnic VARCHAR(255), - host VARCHAR(255), - instance_name VARCHAR(255), - instance_nic_name VARCHAR(255), - used TINYINT(1) -); - - Assuming you're using MySQL, you can run the following command from a - shell prompt on the Cloud Controller node to create the table: - -mysql -uroot -p nova -e 'create table ports (port_id VARCHAR(255) primary key, profile_name VARCHAR(255), dynamic_vnic VARCHAR(255), host VARCHAR(255), instance_name VARCHAR(255), instance_nic_name VARCHAR(255), used tinyint(1));' - -You'll be prompted for a password. - -2. A patch is available for the Cactus release in this branch: - https://code.launchpad.net/~snaiksat/quantum/cactus-ucs-support - replace the following file in your installation: - /usr/lib/python2.6/site-packages/nova/virt/libvirt_conn.py - with the file from the branch: - nova/virt/libvirt_conn.py - -3. Add the following file from the Cisco Nova branch: - nova/virt/cisco_ucs.py - to: - /usr/lib/python2.6/site-packages/nova/virt/cisco_ucs.py - -4. Add the 802.1Qbh specific libvirt template file, from: - nova/virt/libvirt-qbh.xml.template - to: - /usr/share/nova/libvirt-qbh.xml.template - -5. Edit /etc/nova.conf to set the libvirt XML template to the above template: - --libvirt_xml_template=/usr/share/nova/libvirt-qbh.xml.template - -6. Restart the nova-compute service. - - (Note that the requirement for the above patch is temporary and will go away - with the integration with OpenStack Diablo. A 802.1Qbh-specific VIF driver - will be made available as per the specification here: - http://wiki.openstack.org/network-refactoring#VIF_driver) + To run specific tests + python run_tests.py + quantum.plugins.cisco.tests.unit.test_cisco_extension:. Bingo bango bongo! That's it! Thanks for taking the leap into Quantum. diff --git a/quantum/plugins/cisco/common/cisco_constants.py b/quantum/plugins/cisco/common/cisco_constants.py index 67ba05d581..fbcc9b94cc 100644 --- a/quantum/plugins/cisco/common/cisco_constants.py +++ b/quantum/plugins/cisco/common/cisco_constants.py @@ -20,6 +20,7 @@ """ PLUGINS = 'PLUGINS' +INVENTORY = 'INVENTORY' PORT_STATE = 'port-state' PORT_UP = "ACTIVE" @@ -39,6 +40,9 @@ PPQOS = 'qos' PPID = 'portprofile_id' PPDEFAULT = 'default' VLANID = 'vlan_id' +VLANNAME = 'vlan_name' +PORTPROFILENAME = 'portprofile_name' +QOS = 'qos' ATTACHMENT = 'attachment' PORT_ID = 'port-id' @@ -113,11 +117,13 @@ RHEL_DEVICE_NAME_REPFIX = "eth" UCS_PLUGIN = 'ucs_plugin' NEXUS_PLUGIN = 'nexus_plugin' +UCS_INVENTORY = 'ucs_inventory' +NEXUS_INVENTORY = 'nexus_inventory' PLUGIN_OBJ_REF = 'plugin-obj-ref' PARAM_LIST = 'param-list' -DEVICE_IP = 'device-ip' +DEVICE_IP = 'device_ip' NO_VLAN_ID = 0 @@ -128,5 +134,18 @@ VIF_DESC = 'vif_desc' DEVICENAME = 'device' UCSPROFILE = 'portprofile' -MAX_CREDENTIALS = 65568 -MAX_QOS_LEVELS = 1024 +IP_ADDRESS = 'ip_address' +CHASSIS_ID = 'chassis_id' +BLADE_ID = 'blade_id' +HOST_NAME = 'host_name' + +INSTANCE_ID = 'instance_id' +VIF_ID = 'vif_id' +PROJECT_ID = 'project_id' + +UCS_INVENTORY = 'ucs_inventory' +LEAST_RSVD_BLADE_DICT = 'least_rsvd_blade_dict' + +UCSM_IP = 'ucsm_ip_address' + +NETWORK_ADMIN = 'network_admin' diff --git a/quantum/plugins/cisco/common/cisco_credentials.py b/quantum/plugins/cisco/common/cisco_credentials.py index 299c5d8f02..02eab54690 100644 --- a/quantum/plugins/cisco/common/cisco_credentials.py +++ b/quantum/plugins/cisco/common/cisco_credentials.py @@ -24,10 +24,14 @@ import os from quantum.plugins.cisco.common import cisco_configparser as confp from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_exceptions as cexc +from quantum.plugins.cisco.db import l2network_db as cdb LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) +TENANT = const.NETWORK_ADMIN + CREDENTIALS_FILE = "../conf/credentials.ini" cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \ @@ -39,32 +43,43 @@ class Store(object): """Credential Store""" @staticmethod - def putCredential(id, username, password): + def initialize(): + for id in _creds_dictionary.keys(): + try: + cdb.add_credential(TENANT, id, + _creds_dictionary[id][const.USERNAME], + _creds_dictionary[id][const.PASSWORD]) + except cexc.CredentialAlreadyExists: + # We are quietly ignoring this, since it only happens + # if this class module is loaded more than once, in which + # case, the credentials are already populated + pass + + @staticmethod + def putCredential(cred_name, username, password): """Set the username and password""" - _creds_dictionary[id] = {const.USERNAME: username, - const.PASSWORD: password} + credential = cdb.add_credential(TENANT, cred_name, username, password) @staticmethod - def getUsername(id): + def getUsername(cred_name): """Get the username""" - return _creds_dictionary[id][const.USERNAME] + credential = cdb.get_credential_name(TENANT, cred_name) + return credential[const.CREDENTIAL_USERNAME] @staticmethod - def getPassword(id): + def getPassword(cred_name): """Get the password""" - return _creds_dictionary[id][const.PASSWORD] + credential = cdb.get_credential_name(TENANT, cred_name) + return credential[const.CREDENTIAL_PASSWORD] @staticmethod - def getCredential(id): + def getCredential(cred_name): """Get the username and password""" - return _creds_dictionary[id] + credential = cdb.get_credential_name(TENANT, cred_name) + return {const.USERNAME: const.CREDENTIAL_USERNAME, + const.PASSWORD: const.CREDENTIAL_PASSWORD} @staticmethod - def getCredentials(): - """Get all usernames and passwords""" - return _creds_dictionary - - @staticmethod - def deleteCredential(id): + def deleteCredential(cred_name): """Delete a credential""" - return _creds_dictionary.pop(id) + cdb.remove_credential(TENANT, cred_name) diff --git a/quantum/plugins/cisco/common/cisco_exceptions.py b/quantum/plugins/cisco/common/cisco_exceptions.py index 3d4a819b28..f8440a61b3 100644 --- a/quantum/plugins/cisco/common/cisco_exceptions.py +++ b/quantum/plugins/cisco/common/cisco_exceptions.py @@ -101,17 +101,75 @@ class QoSLevelInvalidDelete(exceptions.QuantumException): "for tenant %(tenant_id)s since association exists") +class QosNameAlreadyExists(exceptions.QuantumException): + """QoS Name already exists""" + message = _("QoS level with name %(qos_name)s already exists " \ + "for tenant %(tenant_id)s") + + class CredentialNotFound(exceptions.QuantumException): """Credential with this ID cannot be found""" message = _("Credential %(credential_id)s could not be found " \ "for tenant %(tenant_id)s") +class CredentialNameNotFound(exceptions.QuantumException): + """Credential Name could not be found""" + message = _("Credential %(credential_name)s could not be found " \ + "for tenant %(tenant_id)s") + + +class CredentialAlreadyExists(exceptions.QuantumException): + """Credential ID already exists""" + message = _("Credential %(credential_id)s already exists " \ + "for tenant %(tenant_id)s") + + class NexusPortBindingNotFound(exceptions.QuantumException): """NexusPort Binding is not present""" message = _("Nexus Port Binding %(port_id) is not present") +class UcsmBindingNotFound(exceptions.QuantumException): + """Ucsm Binding is not present""" + message = _("Ucsm Binding with ip %(ucsm_ip) is not present") + + +class UcsmBindingAlreadyExists(exceptions.QuantumException): + """Ucsm Binding already exists""" + message = _("Ucsm Binding with ip %(ucsm_ip) already exists") + + +class DynamicVnicNotFound(exceptions.QuantumException): + """Ucsm Binding is not present""" + message = _("Dyanmic Vnic %(vnic_id) is not present") + + +class DynamicVnicAlreadyExists(exceptions.QuantumException): + """Ucsm Binding already exists""" + message = _("Dynamic Vnic with name %(device_name) already exists") + + +class BladeNotFound(exceptions.QuantumException): + """Blade is not present""" + message = _("Blade %(blade_id) is not present") + + +class BladeAlreadyExists(exceptions.QuantumException): + """Blade already exists""" + message = _("Blade with mgmt_ip %(mgmt_ip) already exists") + + +class PortVnicBindingAlreadyExists(exceptions.QuantumException): + """PortVnic Binding already exists""" + message = _("PortVnic Binding %(port_id) already exists") + + +class PortVnicNotFound(exceptions.QuantumException): + """PortVnic Binding is not present""" + message = _("PortVnic Binding %(port_id) is not present") + + try: _("test") except NameError: diff --git a/quantum/plugins/cisco/common/cisco_utils.py b/quantum/plugins/cisco/common/cisco_utils.py index 58b62bd543..b5e70623b6 100644 --- a/quantum/plugins/cisco/common/cisco_utils.py +++ b/quantum/plugins/cisco/common/cisco_utils.py @@ -26,6 +26,8 @@ import traceback from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_nova_configuration as conf +from quantum.plugins.cisco.db import api as db +from quantum.plugins.cisco.db import l2network_db as cdb LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) @@ -39,6 +41,44 @@ def get16ByteUUID(uuid): return hashlib.md5(uuid).hexdigest()[:16] +def make_net_dict(net_id, net_name, ports): + """Helper funciton""" + res = {const.NET_ID: net_id, const.NET_NAME: net_name} + res[const.NET_PORTS] = ports + return res + + +def make_port_dict(port_id, port_state, net_id, attachment): + """Helper funciton""" + res = {const.PORT_ID: port_id, const.PORT_STATE: port_state} + res[const.NET_ID] = net_id + res[const.ATTACHMENT] = attachment + return res + + +def make_portprofile_dict(tenant_id, profile_id, profile_name, + qos): + """Helper funciton""" + profile_associations = make_portprofile_assc_list(tenant_id, + profile_id) + res = {const.PROFILE_ID: str(profile_id), + const.PROFILE_NAME: profile_name, + const.PROFILE_ASSOCIATIONS: profile_associations, + const.PROFILE_VLAN_ID: None, + const.PROFILE_QOS: qos} + return res + + +def make_portprofile_assc_list(tenant_id, profile_id): + """Helper function to create port profile association list""" + plist = cdb.get_pp_binding(tenant_id, profile_id) + assc_list = [] + for port in plist: + assc_list.append(port[const.PORTID]) + + return assc_list + + class DBUtils(object): """Utilities to use connect to MySQL DB and execute queries""" diff --git a/quantum/plugins/cisco/conf/l2network_plugin.ini b/quantum/plugins/cisco/conf/l2network_plugin.ini index 3a740a9713..421d301acd 100644 --- a/quantum/plugins/cisco/conf/l2network_plugin.ini +++ b/quantum/plugins/cisco/conf/l2network_plugin.ini @@ -1,6 +1,6 @@ [VLANS] -vlan_start= -vlan_end= +vlan_start=100 +vlan_end=3000 vlan_name_prefix=q- [PORTS] @@ -13,4 +13,7 @@ max_port_profiles=65568 max_networks=65568 [MODEL] -model_class=quantum.plugins.cisco.l2network_model.L2NetworkModel +model_class=quantum.plugins.cisco.models.l2network_multi_blade.L2NetworkMultiBlade + +[SEGMENTATION] +manager_class=quantum.plugins.cisco.segmentation.l2network_vlan_mgr.L2NetworkVLANMgr diff --git a/quantum/plugins/cisco/conf/plugins.ini b/quantum/plugins/cisco/conf/plugins.ini index 8b4b476a0a..de98ccb18e 100644 --- a/quantum/plugins/cisco/conf/plugins.ini +++ b/quantum/plugins/cisco/conf/plugins.ini @@ -1,3 +1,7 @@ [PLUGINS] ucs_plugin=quantum.plugins.cisco.ucs.cisco_ucs_plugin.UCSVICPlugin #nexus_plugin=quantum.plugins.cisco.nexus.cisco_nexus_plugin.NexusPlugin + +[INVENTORY] +ucs_plugin=quantum.plugins.cisco.ucs.cisco_ucs_inventory.UCSInventory +#nexus_plugin=quantum.plugins.cisco.nexus.cisco_nexus_inventory.NexusInventory diff --git a/quantum/plugins/cisco/conf/ucs_inventory.ini b/quantum/plugins/cisco/conf/ucs_inventory.ini new file mode 100644 index 0000000000..889609adc8 --- /dev/null +++ b/quantum/plugins/cisco/conf/ucs_inventory.ini @@ -0,0 +1,24 @@ +[ucsm-1] +ip_address = +[[chassis-1]] +chassis_id = +[[[blade-1]]] +blade_id = +host_name = +[[[blade-2]]] +blade_id = +host_name = +[[[blade-3]]] +blade_id = +host_name = + +[ucsm-2] +ip_address = +[[chassis-1]] +chassis_id = +[[[blade-1]]] +blade_id = +host_name = +[[[blade-2]]] +blade_id = +host_name = diff --git a/quantum/plugins/cisco/db/api.py b/quantum/plugins/cisco/db/api.py index db4e631a28..55bce235fb 100644 --- a/quantum/plugins/cisco/db/api.py +++ b/quantum/plugins/cisco/db/api.py @@ -252,3 +252,46 @@ def port_destroy(net_id, port_id): return port except exc.NoResultFound: raise q_exc.PortNotFound(port_id=port_id) + + +#methods using just port_id +def port_get_by_id(port_id): + session = get_session() + try: + return session.query(models.Port).\ + filter_by(uuid=port_id).one() + except exc.NoResultFound: + raise q_exc.PortNotFound(port_id=port_id) + + +def port_set_attachment_by_id(port_id, new_interface_id): + session = get_session() + port = port_get_by_id(port_id) + + if new_interface_id != "": + if port['interface_id']: + raise q_exc.PortInUse(port_id=port_id, + att_id=port['interface_id']) + + try: + port = session.query(models.Port).\ + filter_by(interface_id=new_interface_id).\ + one() + raise q_exc.AlreadyAttached(port_id=port_id, + att_id=new_interface_id, + att_port_id=port['uuid']) + except exc.NoResultFound: + pass + port.interface_id = new_interface_id + session.merge(port) + session.flush() + return port + + +def port_unset_attachment_by_id(port_id): + session = get_session() + port = port_get_by_id(port_id) + port.interface_id = None + session.merge(port) + session.flush() + return port diff --git a/quantum/plugins/cisco/db/l2network_db.py b/quantum/plugins/cisco/db/l2network_db.py index b19a94335e..30a1f5d36e 100644 --- a/quantum/plugins/cisco/db/l2network_db.py +++ b/quantum/plugins/cisco/db/l2network_db.py @@ -24,6 +24,8 @@ from quantum.plugins.cisco.db import l2network_models import logging as LOG import quantum.plugins.cisco.db.api as db +import quantum.plugins.cisco.db.nexus_db as ndb +import quantum.plugins.cisco.db.ucs_db as udb def initialize(): @@ -129,6 +131,19 @@ def reserve_vlanid(): raise c_exc.VlanIDNotAvailable() +def get_all_vlanids_used(): + """Gets all the vlanids used""" + LOG.debug("get_all_vlanids() called") + session = db.get_session() + try: + vlanids = session.query(l2network_models.VlanID).\ + filter_by(vlan_used=True).\ + all() + return vlanids + except exc.NoResultFound: + return [] + + def get_all_vlan_bindings(): """Lists all the vlan to network associations""" LOG.debug("get_all_vlan_bindings() called") @@ -366,3 +381,176 @@ def update_pp_binding(tenantid, ppid, newtenantid=None, newportid=None, except exc.NoResultFound: raise c_exc.PortProfileNotFound(tenant_id=tenantid, portprofile_id=ppid) + + +def get_all_qoss(tenant_id): + """Lists all the qos to tenant associations""" + LOG.debug("get_all_qoss() called") + session = db.get_session() + try: + qoss = session.query(l2network_models.QoS).\ + filter_by(tenant_id=tenant_id).\ + all() + return qoss + except exc.NoResultFound: + return [] + + +def get_qos(tenant_id, qos_id): + """Lists the qos given a tenant_id and qos_id""" + LOG.debug("get_qos() called") + session = db.get_session() + try: + qos = session.query(l2network_models.QoS).\ + filter_by(tenant_id=tenant_id).\ + filter_by(qos_id=qos_id).\ + one() + return qos + except exc.NoResultFound: + raise c_exc.QosNotFound(qos_id=qos_id, + tenant_id=tenant_id) + + +def add_qos(tenant_id, qos_name, qos_desc): + """Adds a qos to tenant association""" + LOG.debug("add_qos() called") + session = db.get_session() + try: + qos = session.query(l2network_models.QoS).\ + filter_by(tenant_id=tenant_id).\ + filter_by(qos_name=qos_name).\ + one() + raise c_exc.QosNameAlreadyExists(qos_name=qos_name, + tenant_id=tenant_id) + except exc.NoResultFound: + qos = l2network_models.QoS(tenant_id, qos_name, qos_desc) + session.add(qos) + session.flush() + return qos + + +def remove_qos(tenant_id, qos_id): + """Removes a qos to tenant association""" + session = db.get_session() + try: + qos = session.query(l2network_models.QoS).\ + filter_by(tenant_id=tenant_id).\ + filter_by(qos_id=qos_id).\ + one() + session.delete(qos) + session.flush() + return qos + except exc.NoResultFound: + pass + + +def update_qos(tenant_id, qos_id, new_qos_name=None): + """Updates a qos to tenant association""" + session = db.get_session() + try: + qos = session.query(l2network_models.QoS).\ + filter_by(tenant_id=tenant_id).\ + filter_by(qos_id=qos_id).\ + one() + if new_qos_name: + qos["qos_name"] = new_qos_name + session.merge(qos) + session.flush() + return qos + except exc.NoResultFound: + raise c_exc.QosNotFound(qos_id=qos_id, + tenant_id=tenant_id) + + +def get_all_credentials(tenant_id): + """Lists all the creds for a tenant""" + session = db.get_session() + try: + creds = session.query(l2network_models.Credential).\ + filter_by(tenant_id=tenant_id).\ + all() + return creds + except exc.NoResultFound: + return [] + + +def get_credential(tenant_id, credential_id): + """Lists the creds for given a cred_id and tenant_id""" + session = db.get_session() + try: + cred = session.query(l2network_models.Credential).\ + filter_by(tenant_id=tenant_id).\ + filter_by(credential_id=credential_id).\ + one() + return cred + except exc.NoResultFound: + raise c_exc.CredentialNotFound(credential_id=credential_id, + tenant_id=tenant_id) + + +def get_credential_name(tenant_id, credential_name): + """Lists the creds for given a cred_name and tenant_id""" + session = db.get_session() + try: + cred = session.query(l2network_models.Credential).\ + filter_by(tenant_id=tenant_id).\ + filter_by(credential_name=credential_name).\ + one() + return cred + except exc.NoResultFound: + raise c_exc.CredentialNameNotFound(credential_name=credential_name, + tenant_id=tenant_id) + + +def add_credential(tenant_id, credential_name, user_name, password): + """Adds a qos to tenant association""" + session = db.get_session() + try: + cred = session.query(l2network_models.Credential).\ + filter_by(tenant_id=tenant_id).\ + filter_by(credential_name=credential_name).\ + one() + raise c_exc.CredentialAlreadyExists(credential_name=credential_name, + tenant_id=tenant_id) + except exc.NoResultFound: + cred = l2network_models.Credential(tenant_id, + credential_name, user_name, password) + session.add(cred) + session.flush() + return cred + + +def remove_credential(tenant_id, credential_id): + """Removes a credential from a tenant""" + session = db.get_session() + try: + cred = session.query(l2network_models.Credential).\ + filter_by(tenant_id=tenant_id).\ + filter_by(credential_id=credential_id).\ + one() + session.delete(cred) + session.flush() + return cred + except exc.NoResultFound: + pass + + +def update_credential(tenant_id, credential_id, + new_user_name=None, new_password=None): + """Updates a credential for a tenant""" + session = db.get_session() + try: + cred = session.query(l2network_models.Credential).\ + filter_by(tenant_id=tenant_id).\ + filter_by(credential_id=credential_id).\ + one() + if new_user_name: + cred["user_name"] = new_user_name + if new_password: + cred["password"] = new_password + session.merge(cred) + session.flush() + return cred + except exc.NoResultFound: + raise c_exc.CredentialNotFound(credential_id=credential_id, + tenant_id=tenant_id) diff --git a/quantum/plugins/cisco/db/l2network_models.py b/quantum/plugins/cisco/db/l2network_models.py index 8bc29d3984..63caf92fde 100644 --- a/quantum/plugins/cisco/db/l2network_models.py +++ b/quantum/plugins/cisco/db/l2network_models.py @@ -77,7 +77,7 @@ class VlanID(BASE, L2NetworkBase): self.vlan_used = False def __repr__(self): - return "" % \ + return "" % \ (self.vlan_id, self.vlan_used) @@ -87,7 +87,7 @@ class VlanBinding(BASE, L2NetworkBase): vlan_id = Column(Integer, primary_key=True) vlan_name = Column(String(255)) - network_id = Column(String(255), ForeignKey("networks.uuid"), \ + network_id = Column(String(255), ForeignKey("networks.uuid"), nullable=False) network = relation(models.Network, uselist=False) @@ -128,9 +128,9 @@ class PortProfileBinding(BASE, L2NetworkBase): id = Column(Integer, primary_key=True, autoincrement=True) tenant_id = Column(String(255)) - port_id = Column(String(255), ForeignKey("ports.uuid"), \ + port_id = Column(String(255), ForeignKey("ports.uuid"), nullable=False) - portprofile_id = Column(String(255), ForeignKey("portprofiles.uuid"), \ + portprofile_id = Column(String(255), ForeignKey("portprofiles.uuid"), nullable=False) default = Column(Boolean) ports = relation(models.Port) @@ -145,3 +145,46 @@ class PortProfileBinding(BASE, L2NetworkBase): def __repr__(self): return "" % \ (self.tenant_id, self.port_id, self.portprofile_id, self.default) + + +class QoS(BASE, L2NetworkBase): + """Represents QoS for a tenant""" + __tablename__ = 'qoss' + + qos_id = Column(String(255)) + tenant_id = Column(String(255), primary_key=True) + qos_name = Column(String(255), primary_key=True) + qos_desc = Column(String(255)) + + def __init__(self, tenant_id, qos_name, qos_desc): + self.qos_id = str(uuid.uuid4()) + self.tenant_id = tenant_id + self.qos_name = qos_name + self.qos_desc = qos_desc + + def __repr__(self): + return "" % \ + (self.qos_id, self.tenant_id, self.qos_name, self.qos_desc) + + +class Credential(BASE, L2NetworkBase): + """Represents credentials for a tenant""" + __tablename__ = 'credentials' + + credential_id = Column(String(255)) + tenant_id = Column(String(255), primary_key=True) + credential_name = Column(String(255), primary_key=True) + user_name = Column(String(255)) + password = Column(String(255)) + + def __init__(self, tenant_id, credential_name, user_name, password): + self.credential_id = str(uuid.uuid4()) + self.tenant_id = tenant_id + self.credential_name = credential_name + self.user_name = user_name + self.password = password + + def __repr__(self): + return "" % \ + (self.credential_id, self.tenant_id, self.credential_name, + self.user_name, self.password) diff --git a/quantum/plugins/cisco/db/ucs_db.py b/quantum/plugins/cisco/db/ucs_db.py new file mode 100644 index 0000000000..cfe2df27ec --- /dev/null +++ b/quantum/plugins/cisco/db/ucs_db.py @@ -0,0 +1,130 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011, 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 as LOG + +from sqlalchemy.orm import exc + +import quantum.plugins.cisco.db.api as db + +from quantum.plugins.cisco.common import cisco_exceptions as c_exc +from quantum.plugins.cisco.db import ucs_models + + +def get_all_portbindings(): + """Lists all the port bindings""" + LOG.debug("db get_all_portbindings() called") + session = db.get_session() + try: + port_bindings = session.query(ucs_models.PortBinding).\ + all() + return port_bindings + except exc.NoResultFound: + return [] + + +def get_portbinding(port_id): + """Lists a port binding""" + LOG.debug("get_portbinding() called") + session = db.get_session() + try: + port_binding = session.query(ucs_models.PortBinding).\ + filter_by(port_id=port_id).\ + one() + return port_binding + except exc.NoResultFound: + raise c_exc.PortVnicNotFound(port_id=port_id) + + +def add_portbinding(port_id, blade_intf_dn, portprofile_name, + vlan_name, vlan_id, qos): + """Adds a port binding""" + LOG.debug("add_portbinding() called") + session = db.get_session() + try: + port_binding = session.query(ucs_models.PortBinding).\ + filter_by(port_id=port_id).\ + one() + raise c_exc.PortVnicBindingAlreadyExists(port_id=port_id) + except exc.NoResultFound: + port_binding = ucs_models.PortBinding(port_id, blade_intf_dn, \ + portprofile_name, vlan_name, vlan_id, qos) + session.add(port_binding) + session.flush() + return port_binding + + +def remove_portbinding(port_id): + """Removes a port binding""" + LOG.debug("db remove_portbinding() called") + session = db.get_session() + try: + port_binding = session.query(ucs_models.PortBinding).\ + filter_by(port_id=port_id).\ + one() + session.delete(port_binding) + session.flush() + return port_binding + except exc.NoResultFound: + pass + + +def update_portbinding(port_id, blade_intf_dn=None, portprofile_name=None, + vlan_name=None, vlan_id=None, qos=None, + tenant_id=None, instance_id=None, + vif_id=None): + """Updates port binding""" + LOG.debug("db update_portbinding() called") + session = db.get_session() + try: + port_binding = session.query(ucs_models.PortBinding).\ + filter_by(port_id=port_id).\ + one() + if blade_intf_dn: + port_binding.blade_intf_dn = blade_intf_dn + if portprofile_name: + port_binding.portprofile_name = portprofile_name + if vlan_name: + port_binding.vlan_name = vlan_name + if vlan_name: + port_binding.vlan_id = vlan_id + if qos: + port_binding.qos = qos + if tenant_id: + port_binding.tenant_id = tenant_id + if instance_id: + port_binding.instance_id = instance_id + if vif_id: + port_binding.vif_id = vif_id + session.merge(port_binding) + session.flush() + return port_binding + except exc.NoResultFound: + raise c_exc.PortVnicNotFound(port_id=port_id) + + +def get_portbinding_dn(blade_intf_dn): + """Lists a port binding""" + LOG.debug("get_portbinding_dn() called") + session = db.get_session() + try: + port_binding = session.query(ucs_models.PortBinding).\ + filter_by(blade_intf_dn=blade_intf_dn).\ + one() + return port_binding + except exc.NoResultFound: + return [] diff --git a/quantum/plugins/cisco/db/ucs_models.py b/quantum/plugins/cisco/db/ucs_models.py new file mode 100644 index 0000000000..b799bb244f --- /dev/null +++ b/quantum/plugins/cisco/db/ucs_models.py @@ -0,0 +1,55 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011, 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. + +from sqlalchemy import Column, Integer, String, ForeignKey +from sqlalchemy.orm import relation + +from quantum.plugins.cisco.db import models +from quantum.plugins.cisco.db.l2network_models import L2NetworkBase +from quantum.plugins.cisco.db.models import BASE + + +class PortBinding(BASE, L2NetworkBase): + """Represents Port binding to device interface""" + __tablename__ = 'port_bindings' + + id = Column(Integer, primary_key=True, autoincrement=True) + port_id = Column(String(255), ForeignKey("ports.uuid"), + nullable=False) + blade_intf_dn = Column(String(255), nullable=False) + portprofile_name = Column(String(255)) + vlan_name = Column(String(255)) + vlan_id = Column(Integer) + qos = Column(String(255)) + tenant_id = Column(String(255)) + instance_id = Column(String(255)) + vif_id = Column(String(255)) + ports = relation(models.Port, uselist=False) + + def __init__(self, port_id, blade_intf_dn, portprofile_name, + vlan_name, vlan_id, qos): + self.port_id = port_id + self.blade_intf_dn = blade_intf_dn + self.portprofile_name = portprofile_name + self.vlan_name = vlan_name + self.vlan_id = vlan_id + self.qos = qos + + def __repr__(self): + return "" % \ + (self.port_id, self.blade_intf_dn, self.portprofile_name, + self.vlan_name, self.vlan_id, self.qos) diff --git a/quantum/plugins/cisco/l2device_inventory_base.py b/quantum/plugins/cisco/l2device_inventory_base.py new file mode 100644 index 0000000000..9ecbed3feb --- /dev/null +++ b/quantum/plugins/cisco/l2device_inventory_base.py @@ -0,0 +1,343 @@ +""" +# 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 +from abc import ABCMeta, abstractmethod + + +class L2NetworkDeviceInventoryBase(object): + """ + Base class for L2 Network Device Inventory + This is used by the L2Nework Model to get information about + the actual devices of a particular type in a given deployment. + For instance, an implementation in the context of UCS will + know what UCSMs, chasses, blades, and dynamic vnics are + present in a particular deployment. + Similarly, an implementation in the context of Nexus switches + will know which switches are present in the system, and how they + are interconnected to other switches/devices. + """ + + __metaclass__ = ABCMeta + + @abstractmethod + def get_all_networks(self, args): + """ + Returns a dictionary containing the first element as a device + IP address list. The model then invokes the device-specific plugin + for each device IP in that list. This is followed by zero or more + key-value pairs (specific to each operation, device type, and + deployment. + The model implementation may or may not process the returned + values, but needs to pass them to the device-specific plugin. + Since the device-specific plugin and this inventory implementation + are assumed to be implemented by the same entity, the + device-sepcific knows how to process this dictionary. + :returns: a dictionary with the following signature: + {'device_ip': [] + 'key-1': "value 1", + ... + 'key-n': "value n" + } + :raises: + """ + pass + + @abstractmethod + def create_network(self, args): + """ + Returns a dictionary containing the first element as a device + IP address list. The model then invokes the device-specific plugin + for each device IP in that list. This is followed by zero or more + key-value pairs (specific to each operation, device type, and + deployment. + The model implementation may or may not process the returned + values, but needs to pass them to the device-specific plugin. + Since the device-specific plugin and this inventory implementation + are assumed to be implemented by the same entity, the + device-sepcific knows how to process this dictionary. + :returns: a dictionary with the following signature: + {'device_ip': [] + 'key-1': "value 1", + ... + 'key-n': "value n" + } + :raises: + """ + pass + + @abstractmethod + def delete_network(self, args): + """ + Returns a dictionary containing the first element as a device + IP address list. The model then invokes the device-specific plugin + for each device IP in that list. This is followed by zero or more + key-value pairs (specific to each operation, device type, and + deployment. + The model implementation may or may not process the returned + values, but needs to pass them to the device-specific plugin. + Since the device-specific plugin and this inventory implementation + are assumed to be implemented by the same entity, the + device-sepcific knows how to process this dictionary. + :returns: a dictionary with the following signature: + {'device_ip': [] + 'key-1': "value 1", + ... + 'key-n': "value n" + } + :raises: + """ + pass + + @abstractmethod + def get_network_details(self, args): + """ + Returns a dictionary containing the first element as a device + IP address list. The model then invokes the device-specific plugin + for each device IP in that list. This is followed by zero or more + key-value pairs (specific to each operation, device type, and + deployment. + The model implementation may or may not process the returned + values, but needs to pass them to the device-specific plugin. + Since the device-specific plugin and this inventory implementation + are assumed to be implemented by the same entity, the + device-sepcific knows how to process this dictionary. + :returns: a dictionary with the following signature: + {'device_ip': [] + 'key-1': "value 1", + ... + 'key-n': "value n" + } + :raises: + """ + pass + + @abstractmethod + def rename_network(self, args): + """ + Returns a dictionary containing the first element as a device + IP address list. The model then invokes the device-specific plugin + for each device IP in that list. This is followed by zero or more + key-value pairs (specific to each operation, device type, and + deployment. + The model implementation may or may not process the returned + values, but needs to pass them to the device-specific plugin. + Since the device-specific plugin and this inventory implementation + are assumed to be implemented by the same entity, the + device-sepcific knows how to process this dictionary. + :returns: a dictionary with the following signature: + {'device_ip': [] + 'key-1': "value 1", + ... + 'key-n': "value n" + } + :raises: + """ + pass + + @abstractmethod + def get_all_ports(self, args): + """ + Returns a dictionary containing the first element as a device + IP address list. The model then invokes the device-specific plugin + for each device IP in that list. This is followed by zero or more + key-value pairs (specific to each operation, device type, and + deployment. + The model implementation may or may not process the returned + values, but needs to pass them to the device-specific plugin. + Since the device-specific plugin and this inventory implementation + are assumed to be implemented by the same entity, the + device-sepcific knows how to process this dictionary. + :returns: a dictionary with the following signature: + {'device_ip': [] + 'key-1': "value 1", + ... + 'key-n': "value n" + } + :raises: + """ + pass + + @abstractmethod + def create_port(self, args): + """ + Returns a dictionary containing the first element as a device + IP address list. The model then invokes the device-specific plugin + for each device IP in that list. This is followed by zero or more + key-value pairs (specific to each operation, device type, and + deployment. + The model implementation may or may not process the returned + values, but needs to pass them to the device-specific plugin. + Since the device-specific plugin and this inventory implementation + are assumed to be implemented by the same entity, the + device-sepcific knows how to process this dictionary. + :returns: a dictionary with the following signature: + {'device_ip': [] + 'key-1': "value 1", + ... + 'key-n': "value n" + } + :raises: + """ + pass + + @abstractmethod + def delete_port(self, args): + """ + Returns a dictionary containing the first element as a device + IP address list. The model then invokes the device-specific plugin + for each device IP in that list. This is followed by zero or more + key-value pairs (specific to each operation, device type, and + deployment. + The model implementation may or may not process the returned + values, but needs to pass them to the device-specific plugin. + Since the device-specific plugin and this inventory implementation + are assumed to be implemented by the same entity, the + device-sepcific knows how to process this dictionary. + :returns: a dictionary with the following signature: + {'device_ip': [] + 'key-1': "value 1", + ... + 'key-n': "value n" + } + :raises: + """ + pass + + @abstractmethod + def update_port(self, args): + """ + Returns a dictionary containing the first element as a device + IP address list. The model then invokes the device-specific plugin + for each device IP in that list. This is followed by zero or more + key-value pairs (specific to each operation, device type, and + deployment. + The model implementation may or may not process the returned + values, but needs to pass them to the device-specific plugin. + Since the device-specific plugin and this inventory implementation + are assumed to be implemented by the same entity, the + device-sepcific knows how to process this dictionary. + :returns: a dictionary with the following signature: + {'device_ip': [] + 'key-1': "value 1", + ... + 'key-n': "value n" + } + :raises: + """ + pass + + @abstractmethod + def get_port_details(self, args): + """ + Returns a dictionary containing the first element as a device + IP address list. The model then invokes the device-specific plugin + for each device IP in that list. This is followed by zero or more + key-value pairs (specific to each operation, device type, and + deployment. + The model implementation may or may not process the returned + values, but needs to pass them to the device-specific plugin. + Since the device-specific plugin and this inventory implementation + are assumed to be implemented by the same entity, the + device-sepcific knows how to process this dictionary. + :returns: a dictionary with the following signature: + {'device_ip': [] + 'key-1': "value 1", + ... + 'key-n': "value n" + } + :raises: + """ + pass + + @abstractmethod + def plug_interface(self, args): + """ + Returns a dictionary containing the first element as a device + IP address list. The model then invokes the device-specific plugin + for each device IP in that list. This is followed by zero or more + key-value pairs (specific to each operation, device type, and + deployment. + The model implementation may or may not process the returned + values, but needs to pass them to the device-specific plugin. + Since the device-specific plugin and this inventory implementation + are assumed to be implemented by the same entity, the + device-sepcific knows how to process this dictionary. + :returns: a dictionary with the following signature: + {'device_ip': [] + 'key-1': "value 1", + ... + 'key-n': "value n" + } + :raises: + """ + pass + + @abstractmethod + def unplug_interface(self, args): + """ + Returns a dictionary containing the first element as a device + IP address list. The model then invokes the device-specific plugin + for each device IP in that list. This is followed by zero or more + key-value pairs (specific to each operation, device type, and + deployment. + The model implementation may or may not process the returned + values, but needs to pass them to the device-specific plugin. + Since the device-specific plugin and this inventory implementation + are assumed to be implemented by the same entity, the + device-sepcific knows how to process this dictionary. + :returns: a dictionary with the following signature: + {'device_ip': [] + 'key-1': "value 1", + ... + 'key-n': "value n" + } + :raises: + """ + pass + + @classmethod + def __subclasshook__(cls, klass): + """ + The __subclasshook__ method is a class method + that will be called everytime a class is tested + using issubclass(klass, Plugin). + In that case, it will check that every method + marked with the abstractmethod decorator is + provided by the plugin class. + """ + if cls is L2NetworkDeviceInventoryBase: + for method in cls.__abstractmethods__: + method_ok = False + for base in klass.__mro__: + if method in base.__dict__: + fn_obj = base.__dict__[method] + if inspect.isfunction(fn_obj): + abstract_fn_obj = cls.__dict__[method] + arg_count = fn_obj.func_code.co_argcount + expected_arg_count = \ + abstract_fn_obj.func_code.co_argcount + method_ok = arg_count == expected_arg_count + if method_ok: + continue + return NotImplemented + return True + return NotImplemented diff --git a/quantum/plugins/cisco/l2network_model.py b/quantum/plugins/cisco/l2network_model.py deleted file mode 100644 index 1ed4b56bd5..0000000000 --- a/quantum/plugins/cisco/l2network_model.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -# 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 utils -from quantum.plugins.cisco import l2network_plugin_configuration as conf -from quantum.plugins.cisco.common import cisco_constants as const -from quantum.plugins.cisco.l2network_model_base import L2NetworkModelBase - -LOG.basicConfig(level=LOG.WARN) -LOG.getLogger(const.LOGGER_COMPONENT_NAME) - - -class L2NetworkModel(L2NetworkModelBase): - """ - Implements the L2NetworkModelBase - This implementation works with UCS and Nexus plugin, - with one UCS blade, and one Nexus switch. - """ - _plugins = {} - - def __init__(self): - for key in conf.PLUGINS[const.PLUGINS].keys(): - self._plugins[key] = utils.import_object( - conf.PLUGINS[const.PLUGINS][key]) - LOG.debug("Loaded device plugin %s" % \ - conf.PLUGINS[const.PLUGINS][key]) - - def _func_name(self, offset=0): - """Get the name of the calling function""" - return inspect.stack()[1 + offset][3] - - def _invoke_all_device_plugins(self, function_name, args, kwargs): - """Invoke all device plugins for this model implementation""" - for plugin_obj_ref in self._plugins.values(): - getattr(plugin_obj_ref, function_name)(*args, **kwargs) - - def _invoke_ucs_plugin(self, function_name, args, kwargs): - """Invoke only the UCS plugin""" - if const.UCS_PLUGIN in self._plugins.keys(): - getattr(self._plugins[const.UCS_PLUGIN], - function_name)(*args, **kwargs) - - def _invoke_nexus_plugin(self, function_name, args, kwargs): - """Invoke only the Nexus plugin""" - if const.NEXUS_PLUGIN in self._plugins.keys(): - getattr(self._plugins[const.NEXUS_PLUGIN], - function_name)(*args, **kwargs) - - def get_all_networks(self, args): - """Not implemented for this model""" - pass - - def create_network(self, args): - """Support for the Quantum core API call""" - device_params = {const.DEVICE_IP: ""} - self._invoke_all_device_plugins(self._func_name(), args, device_params) - - def delete_network(self, args): - """Support for the Quantum core API call""" - device_params = {const.DEVICE_IP: ""} - self._invoke_all_device_plugins(self._func_name(), args, device_params) - - def get_network_details(self, args): - """Not implemented for this model""" - pass - - def rename_network(self, args): - """Support for the Quantum core API call""" - device_params = {const.DEVICE_IP: ""} - self._invoke_all_device_plugins(self._func_name(), args, device_params) - - def get_all_ports(self, args): - """Not implemented for this model""" - pass - - def create_port(self, args): - """Support for the Quantum core API call""" - device_params = {const.DEVICE_IP: ""} - self._invoke_ucs_plugin(self._func_name(), args, device_params) - - def delete_port(self, args): - """Support for the Quantum core API call""" - device_params = {const.DEVICE_IP: ""} - self._invoke_ucs_plugin(self._func_name(), args, device_params) - - def update_port(self, args): - """Not implemented for this model""" - pass - - def get_port_details(self, args): - """Not implemented for this model""" - pass - - def plug_interface(self, args): - """Support for the Quantum core API call""" - device_params = {const.DEVICE_IP: ""} - self._invoke_ucs_plugin(self._func_name(), args, device_params) - - def unplug_interface(self, args): - """Support for the Quantum core API call""" - device_params = {const.DEVICE_IP: ""} - self._invoke_ucs_plugin(self._func_name(), args, device_params) diff --git a/quantum/plugins/cisco/l2network_plugin.py b/quantum/plugins/cisco/l2network_plugin.py index c85c46786c..8eb908657d 100644 --- a/quantum/plugins/cisco/l2network_plugin.py +++ b/quantum/plugins/cisco/l2network_plugin.py @@ -26,10 +26,12 @@ import platform 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_exceptions as cexc from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_credentials as cred +from quantum.plugins.cisco.common import cisco_exceptions as cexc +from quantum.plugins.cisco.common import cisco_utils as cutil from quantum.plugins.cisco.db import api as db from quantum.plugins.cisco.db import l2network_db as cdb @@ -41,17 +43,13 @@ class L2Network(QuantumPluginBase): """ L2 Network Framework Plugin """ supported_extension_aliases = ["Cisco Credential", "Cisco Port Profile", "Cisco qos", "Cisco Nova Tenant"] - _qos_levels = {} - _credentials = {} def __init__(self): - self._vlan_counter = int(conf.VLAN_START) - 1 - self._model = utils.import_object(conf.MODEL_CLASS) cdb.initialize() - # TODO (Sumit): The following should move to the segmentation module - cdb.create_vlanids() - self._qoslevels_counter = 0 - self._credentials_counter = 0 + cred.Store.initialize() + self._model = utils.import_object(conf.MODEL_CLASS) + self._vlan_mgr = utils.import_object(conf.MANAGER_CLASS) + LOG.debug("L2Network plugin initialization done successfully\n") """ Core API implementation @@ -67,7 +65,7 @@ class L2Network(QuantumPluginBase): networks_list = db.network_list(tenant_id) new_networks_list = [] for network in networks_list: - new_network_dict = self._make_net_dict(network[const.UUID], + new_network_dict = cutil.make_net_dict(network[const.UUID], network[const.NETWORKNAME], []) new_networks_list.append(new_network_dict) @@ -107,10 +105,10 @@ class L2Network(QuantumPluginBase): if port[const.INTERFACEID]: raise exc.NetworkInUse(net_id=net_id) for port in ports_on_net: - self.delete_port(tenant_id, net_id, port[const.PORTID]) + self.delete_port(tenant_id, net_id, port[const.UUID]) self._invoke_device_plugins(self._func_name(), [tenant_id, net_id]) - net_dict = self._make_net_dict(net[const.UUID], + net_dict = cutil.make_net_dict(net[const.UUID], net[const.NETWORKNAME], []) self._release_vlan_for_tenant(tenant_id, net_id) @@ -125,18 +123,18 @@ class L2Network(QuantumPluginBase): Gets the details of a particular network """ LOG.debug("get_network_details() called\n") - self._invoke_device_plugins(self._func_name(), [tenant_id, net_id]) network = db.network_get(net_id) + self._invoke_device_plugins(self._func_name(), [tenant_id, net_id]) ports_list = network[const.NETWORKPORTS] ports_on_net = [] for port in ports_list: - new_port = self._make_port_dict(port[const.UUID], + new_port = cutil.make_port_dict(port[const.UUID], port[const.PORTSTATE], port[const.NETWORKID], port[const.INTERFACEID]) ports_on_net.append(new_port) - new_network = self._make_net_dict(network[const.UUID], + new_network = cutil.make_net_dict(network[const.UUID], network[const.NETWORKNAME], ports_on_net) @@ -148,10 +146,10 @@ class L2Network(QuantumPluginBase): Virtual Network. """ LOG.debug("rename_network() called\n") + network = db.network_rename(tenant_id, net_id, new_name) self._invoke_device_plugins(self._func_name(), [tenant_id, net_id, new_name]) - network = db.network_rename(tenant_id, net_id, new_name) - net_dict = self._make_net_dict(network[const.UUID], + net_dict = cutil.make_net_dict(network[const.UUID], network[const.NETWORKNAME], []) return net_dict @@ -162,12 +160,12 @@ class L2Network(QuantumPluginBase): specified Virtual Network. """ LOG.debug("get_all_ports() called\n") - self._invoke_device_plugins(self._func_name(), [tenant_id, net_id]) network = db.network_get(net_id) + self._invoke_device_plugins(self._func_name(), [tenant_id, net_id]) ports_list = network[const.NETWORKPORTS] ports_on_net = [] for port in ports_list: - new_port = self._make_port_dict(port[const.UUID], + new_port = cutil.make_port_dict(port[const.UUID], port[const.PORTSTATE], port[const.NETWORKID], port[const.INTERFACEID]) @@ -185,7 +183,7 @@ class L2Network(QuantumPluginBase): self._invoke_device_plugins(self._func_name(), [tenant_id, net_id, port_state, unique_port_id_string]) - new_port_dict = self._make_port_dict(port[const.UUID], + new_port_dict = cutil.make_port_dict(port[const.UUID], port[const.PORTSTATE], port[const.NETWORKID], port[const.INTERFACEID]) @@ -199,22 +197,31 @@ class L2Network(QuantumPluginBase): then the port can be deleted. """ LOG.debug("delete_port() called\n") - self._invoke_device_plugins(self._func_name(), [tenant_id, net_id, - port_id]) - db.port_destroy(net_id, port_id) - new_port_dict = self._make_port_dict(port_id, None, None, None) - return new_port_dict + network = db.network_get(net_id) + port = db.port_get(net_id, port_id) + attachment_id = port[const.INTERFACEID] + if not attachment_id: + self._invoke_device_plugins(self._func_name(), [tenant_id, + net_id, + port_id]) + db.port_destroy(net_id, port_id) + new_port_dict = cutil.make_port_dict(port_id, None, None, None) + return new_port_dict + else: + raise exc.PortInUse(port_id=port_id, net_id=net_id, + att_id=attachment_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") + network = db.network_get(net_id) self._invoke_device_plugins(self._func_name(), [tenant_id, net_id, port_id, port_state]) self._validate_port_state(port_state) db.port_set_state(net_id, port_id, port_state) - new_port_dict = self._make_port_dict(port_id, port_state, net_id, + new_port_dict = cutil.make_port_dict(port_id, port_state, net_id, None) return new_port_dict @@ -224,10 +231,11 @@ class L2Network(QuantumPluginBase): that is attached to this particular port. """ LOG.debug("get_port_details() called\n") + network = db.network_get(net_id) self._invoke_device_plugins(self._func_name(), [tenant_id, net_id, port_id]) port = db.port_get(net_id, port_id) - new_port_dict = self._make_port_dict(port[const.UUID], + new_port_dict = cutil.make_port_dict(port[const.UUID], port[const.PORTSTATE], port[const.NETWORKID], port[const.INTERFACEID]) @@ -240,6 +248,7 @@ class L2Network(QuantumPluginBase): specified Virtual Network. """ LOG.debug("plug_interface() called\n") + network = db.network_get(net_id) self._invoke_device_plugins(self._func_name(), [tenant_id, net_id, port_id, remote_interface_id]) @@ -251,6 +260,7 @@ class L2Network(QuantumPluginBase): specified Virtual Network. """ LOG.debug("unplug_interface() called\n") + network = db.network_get(net_id) self._invoke_device_plugins(self._func_name(), [tenant_id, net_id, port_id]) db.port_unset_attachment(net_id, port_id) @@ -264,7 +274,7 @@ class L2Network(QuantumPluginBase): pplist = cdb.get_all_portprofiles() new_pplist = [] for portprofile in pplist: - new_pp = self._make_portprofile_dict(tenant_id, + new_pp = cutil.make_portprofile_dict(tenant_id, portprofile[const.UUID], portprofile[const.PPNAME], portprofile[const.PPQOS]) @@ -281,7 +291,7 @@ class L2Network(QuantumPluginBase): raise cexc.PortProfileNotFound(tenant_id=tenant_id, portprofile_id=profile_id) - new_pp = self._make_portprofile_dict(tenant_id, + new_pp = cutil.make_portprofile_dict(tenant_id, portprofile[const.UUID], portprofile[const.PPNAME], portprofile[const.PPQOS]) @@ -292,7 +302,7 @@ class L2Network(QuantumPluginBase): LOG.debug("create_portprofile() called\n") portprofile = cdb.add_portprofile(tenant_id, profile_name, const.NO_VLAN_ID, qos) - new_pp = self._make_portprofile_dict(tenant_id, + new_pp = cutil.make_portprofile_dict(tenant_id, portprofile[const.UUID], portprofile[const.PPNAME], portprofile[const.PPQOS]) @@ -323,7 +333,7 @@ class L2Network(QuantumPluginBase): raise cexc.PortProfileNotFound(tenant_id=tenant_id, portprofile_id=profile_id) portprofile = cdb.update_portprofile(tenant_id, profile_id, new_name) - new_pp = self._make_portprofile_dict(tenant_id, + new_pp = cutil.make_portprofile_dict(tenant_id, portprofile[const.UUID], portprofile[const.PPNAME], portprofile[const.PPQOS]) @@ -356,66 +366,57 @@ class L2Network(QuantumPluginBase): def get_all_qoss(self, tenant_id): """Get all QoS levels""" LOG.debug("get_all_qoss() called\n") - return self._qos_levels.values() + qoslist = cdb.get_all_qoss(tenant_id) + return qoslist def get_qos_details(self, tenant_id, qos_id): """Get QoS Details""" LOG.debug("get_qos_details() called\n") try: - qos_level = self._get_qos_level(tenant_id, qos_id) + qos_level = cdb.get_qos(tenant_id, qos_id) except Exception, excp: raise cexc.QosNotFound(tenant_id=tenant_id, - qos_id=qos_id) + qos_id=qos_id) return qos_level def create_qos(self, tenant_id, qos_name, qos_desc): """Create a QoS level""" LOG.debug("create_qos() called\n") - qos_id = self._get_unique_qos_id(tenant_id) - new_qos_level_dict = {const.QOS_LEVEL_ID: qos_id, - const.QOS_LEVEL_NAME: qos_name, - const.QOS_LEVEL_ASSOCIATIONS: [], - const.QOS_LEVEL_DESCRIPTION: qos_desc} - self._qos_levels[qos_id] = new_qos_level_dict - return new_qos_level_dict + qos = cdb.add_qos(tenant_id, qos_name, str(qos_desc)) + return qos def delete_qos(self, tenant_id, qos_id): """Delete a QoS level""" LOG.debug("delete_qos() called\n") try: - qos_level = self._get_qos_level(tenant_id, qos_id) + qos_level = cdb.get_qos(tenant_id, qos_id) except Exception, excp: raise cexc.QosNotFound(tenant_id=tenant_id, - qos_id=qos_id) - associations = qos_level[const.QOS_LEVEL_ASSOCIATIONS] - if len(associations) > 0: - raise cexc.QoSLevelInvalidDelete(tenant_id=tenant_id, - qos_id=qos_id) - else: - self._qos_levels.pop(qos_id) + qos_id=qos_id) + return cdb.remove_qos(tenant_id, qos_id) def rename_qos(self, tenant_id, qos_id, new_name): """Rename QoS level""" LOG.debug("rename_qos() called\n") - qos_level = self._get_qos_level(tenant_id, qos_id) try: - qos_level = self._get_qos_level(tenant_id, qos_id) + qos_level = cdb.get_qos(tenant_id, qos_id) except Exception, excp: raise cexc.QosNotFound(tenant_id=tenant_id, - qos_id=qos_id) - qos_level[const.QOS_LEVEL_NAME] = new_name - return qos_level + qos_id=qos_id) + qos = cdb.update_qos(tenant_id, qos_id, new_name) + return qos def get_all_credentials(self, tenant_id): """Get all credentials""" LOG.debug("get_all_credentials() called\n") - return self._credentials.values() + credential_list = cdb.get_all_credentials(tenant_id) + return credential_list def get_credential_details(self, tenant_id, credential_id): """Get a particular credential""" LOG.debug("get_credential_details() called\n") try: - credential = self._get_credential(tenant_id, credential_id) + credential = cdb.get_credential(tenant_id, credential_id) except Exception, excp: raise cexc.CredentialNotFound(tenant_id=tenant_id, credential_id=credential_id) @@ -425,77 +426,65 @@ class L2Network(QuantumPluginBase): password): """Create a new credential""" LOG.debug("create_credential() called\n") - credential_id = self._get_unique_credential_id(tenant_id) - masked_password = const.MASKED_PASSWORD - new_credential_dict = {const.CREDENTIAL_ID: credential_id, - const.CREDENTIAL_NAME: credential_name, - const.CREDENTIAL_USERNAME: user_name, - const.CREDENTIAL_PASSWORD: masked_password} - self._credentials[credential_id] = new_credential_dict - cred.Store.putCredential(credential_id, user_name, password) - return new_credential_dict + credential = cdb.add_credential(tenant_id, credential_name, + user_name, password) + return credential def delete_credential(self, tenant_id, credential_id): """Delete a credential""" LOG.debug("delete_credential() called\n") try: - credential = self._get_credential(tenant_id, credential_id) + credential = cdb.get_credential(tenant_id, credential_id) except Exception, excp: raise cexc.CredentialNotFound(tenant_id=tenant_id, credential_id=credential_id) - self._credentials.pop(credential_id) - cred.Store.deleteCredential(credential_id) + credential = cdb.remove_credential(tenant_id, credential_id) + return credential def rename_credential(self, tenant_id, credential_id, new_name): """Rename the particular credential resource""" LOG.debug("rename_credential() called\n") try: - credential = self._get_credential(tenant_id, credential_id) + credential = cdb.get_credential(tenant_id, credential_id) except Exception, excp: raise cexc.CredentialNotFound(tenant_id=tenant_id, credential_id=credential_id) - - credential[const.CREDENTIAL_NAME] = new_name + credential = cdb.update_credential(tenant_id, credential_id, new_name) return credential - def get_host(self, tenant_id, instance_id, instance_desc): + def schedule_host(self, tenant_id, instance_id, instance_desc): """Provides the hostname on which a dynamic vnic is reserved""" - LOG.debug("get_host() called\n") - host_list = {const.HOST_LIST: {const.HOST_1: platform.node()}} + LOG.debug("schedule_host() called\n") + host_list = self._invoke_device_plugins(self._func_name(), [tenant_id, + instance_id, + instance_desc]) return host_list - def get_instance_port(self, tenant_id, instance_id, instance_desc): + def associate_port(self, tenant_id, instance_id, instance_desc): """ Get the portprofile name and the device namei for the dynamic vnic """ - LOG.debug("get_instance_port() called\n") - vif_desc = {const.VIF_DESC: - {const.DEVICENAME: "eth2", const.UCSPROFILE: "default"}} - return vif_desc + LOG.debug("associate_port() called\n") + return self._invoke_device_plugins(self._func_name(), [tenant_id, + instance_id, + instance_desc]) """ Private functions """ def _invoke_device_plugins(self, function_name, args): """ - All device-specific calls are delegate to the model + All device-specific calls are delegated to the model """ - getattr(self._model, function_name)(args) + return getattr(self._model, function_name)(args) def _get_vlan_for_tenant(self, tenant_id, net_name): """Get vlan ID""" - # 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 - return cdb.reserve_vlanid() + return self._vlan_mgr.reserve_segmentation_id(tenant_id, net_name) def _release_vlan_for_tenant(self, tenant_id, net_id): """Relase VLAN""" - vlan_binding = cdb.get_vlan_binding(net_id) - return cdb.release_vlanid(vlan_binding[const.VLANID]) + return self._vlan_mgr.release_segmentation_id(tenant_id, net_id) def _get_vlan_name(self, net_id, vlan): """Getting the vlan name from the tenant and vlan""" @@ -511,69 +500,3 @@ class L2Network(QuantumPluginBase): def _func_name(self, offset=0): """Getting the name of the calling funciton""" return inspect.stack()[1 + offset][3] - - def _make_net_dict(self, net_id, net_name, ports): - """Helper funciton to create network resource dictionary""" - res = {const.NET_ID: net_id, const.NET_NAME: net_name} - res[const.NET_PORTS] = ports - return res - - def _make_port_dict(self, port_id, port_state, net_id, attachment): - """Helper function to create port resource dictionary""" - res = {const.PORT_ID: port_id, const.PORT_STATE: port_state} - res[const.NET_ID] = net_id - res[const.ATTACHMENT] = attachment - return res - - def _make_portprofile_dict(self, tenant_id, profile_id, profile_name, - qos): - """Helper funciton to create port-profile resource dictionary""" - profile_associations = self._make_portprofile_assc_list(tenant_id, - profile_id) - res = {const.PROFILE_ID: str(profile_id), - const.PROFILE_NAME: profile_name, - const.PROFILE_ASSOCIATIONS: profile_associations, - const.PROFILE_VLAN_ID: None, - const.PROFILE_QOS: qos} - return res - - def _make_portprofile_assc_list(self, tenant_id, profile_id): - """Helper function to create port profile association list""" - plist = cdb.get_pp_binding(tenant_id, profile_id) - assc_list = [] - for port in plist: - assc_list.append(port[const.PORTID]) - - return assc_list - - def _get_qos_level(self, tenant_id, qos_id): - """Return a QoS level based on the ID""" - qos_level = self._qos_levels.get(qos_id) - if not qos_level: - raise cexc.QosNotFound(tenant_id=tenant_id, - qos_id=qos_id) - return qos_level - - def _get_credential(self, tenant_id, credential_id): - """Return a credential based on the ID""" - credential = self._credentials.get(credential_id) - if not credential: - raise cexc.CredentialNotFound(tenant_id=tenant_id, - credetial_id=credential_id) - return credential - - def _get_unique_qos_id(self, tenant_id): - """Get a unique QoS ID""" - self._qoslevels_counter += 1 - self._qoslevels_counter %= int(const.MAX_QOS_LEVELS) - qos_id = tenant_id[16:] + "-qos-" + str(self._qoslevels_counter) - # TODO (Sumit): Need to check if the ID has already been allocated - return qos_id - - def _get_unique_credential_id(self, tenant_id): - """Get a unique credential ID""" - self._credentials_counter += 1 - self._credentials_counter %= int(const.MAX_CREDENTIALS) - cred_id = tenant_id[16:] + "-crd-" + str(self._credentials_counter) - # TODO (Sumit): Need to check if the ID has already been allocated - return cred_id diff --git a/quantum/plugins/cisco/l2network_plugin_configuration.py b/quantum/plugins/cisco/l2network_plugin_configuration.py index 3fffd1d720..ec20fcceb0 100644 --- a/quantum/plugins/cisco/l2network_plugin_configuration.py +++ b/quantum/plugins/cisco/l2network_plugin_configuration.py @@ -49,6 +49,9 @@ MAX_NETWORKS = SECTION_CONF['max_networks'] SECTION_CONF = CONF_PARSER_OBJ['MODEL'] MODEL_CLASS = SECTION_CONF['model_class'] +SECTION_CONF = CONF_PARSER_OBJ['SEGMENTATION'] +MANAGER_CLASS = SECTION_CONF['manager_class'] + CONF_FILE = "conf/plugins.ini" CONF_PARSER_OBJ = confp.\ diff --git a/quantum/plugins/cisco/l2network_segmentation_base.py b/quantum/plugins/cisco/l2network_segmentation_base.py new file mode 100644 index 0000000000..081d50fdfb --- /dev/null +++ b/quantum/plugins/cisco/l2network_segmentation_base.py @@ -0,0 +1,75 @@ +""" +# 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 +from abc import ABCMeta, abstractmethod + + +class L2NetworkSegmentationMgrBase(object): + """ + Base class for L2 Network Segmentation Manager + """ + + __metaclass__ = ABCMeta + + @abstractmethod + def reserve_segmentation_id(self, tenant_id, net_name, **kwargs): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def release_segmentation_id(self, tenant_id, net_id, **kwargs): + """ + :returns: + :raises: + """ + pass + + @classmethod + def __subclasshook__(cls, klass): + """ + The __subclasshook__ method is a class method + that will be called everytime a class is tested + using issubclass(klass, Plugin). + In that case, it will check that every method + marked with the abstractmethod decorator is + provided by the plugin class. + """ + if cls is L2NetworkSegmentationMgrBase: + for method in cls.__abstractmethods__: + method_ok = False + for base in klass.__mro__: + if method in base.__dict__: + fn_obj = base.__dict__[method] + if inspect.isfunction(fn_obj): + abstract_fn_obj = cls.__dict__[method] + arg_count = fn_obj.func_code.co_argcount + expected_arg_count = \ + abstract_fn_obj.func_code.co_argcount + method_ok = arg_count == expected_arg_count + if method_ok: + continue + return NotImplemented + return True + return NotImplemented diff --git a/quantum/plugins/cisco/models/__init__.py b/quantum/plugins/cisco/models/__init__.py new file mode 100644 index 0000000000..09b3fab896 --- /dev/null +++ b/quantum/plugins/cisco/models/__init__.py @@ -0,0 +1,20 @@ +""" +# 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. +# +""" diff --git a/quantum/plugins/cisco/models/l2network_multi_blade.py b/quantum/plugins/cisco/models/l2network_multi_blade.py new file mode 100644 index 0000000000..acd24d5ee6 --- /dev/null +++ b/quantum/plugins/cisco/models/l2network_multi_blade.py @@ -0,0 +1,173 @@ +""" +# 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. +# +""" + +from copy import deepcopy +import inspect +import logging as LOG +import platform + +from quantum.common import exceptions as exc +from quantum.common import utils +from quantum.plugins.cisco.l2network_model_base import L2NetworkModelBase +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(__name__) + + +class L2NetworkMultiBlade(L2NetworkModelBase): + """ + Implements the L2NetworkModelBase + This implementation works with UCS and Nexus plugin for the + following topology: + One or more UCSM (each with one or more chasses connected) + All UCSM connected to a single Nexus Switch + """ + _plugins = {} + _inventory = {} + + def __init__(self): + for key in conf.PLUGINS[const.PLUGINS].keys(): + self._plugins[key] = utils.import_object( + conf.PLUGINS[const.PLUGINS][key]) + LOG.debug("Loaded device plugin %s\n" % \ + conf.PLUGINS[const.PLUGINS][key]) + if key in conf.PLUGINS[const.INVENTORY].keys(): + self._inventory[key] = utils.import_object( + conf.PLUGINS[const.INVENTORY][key]) + LOG.debug("Loaded device inventory %s\n" % \ + conf.PLUGINS[const.INVENTORY][key]) + + def _func_name(self, offset=0): + """Get the name of the calling function""" + return inspect.stack()[1 + offset][3] + + def _invoke_plugin_per_device(self, plugin_key, function_name, args): + """Invoke only device plugin for all the devices in the system""" + if not plugin_key in self._plugins.keys(): + LOG.info("No %s Plugin loaded" % plugin_key) + LOG.info("%s: %s with args %s ignored" \ + % (plugin_key, function_name, args)) + return + device_params = self._invoke_inventory(plugin_key, function_name, + args) + device_ips = device_params[const.DEVICE_IP] + if not device_ips: + self._invoke_plugin(plugin_key, function_name, args, + device_params) + else: + for device_ip in device_ips: + new_device_params = deepcopy(device_params) + new_device_params[const.DEVICE_IP] = device_ip + self._invoke_plugin(plugin_key, function_name, args, + new_device_params) + + def _invoke_inventory(self, plugin_key, function_name, args): + """Invoke only the inventory implementation""" + if not plugin_key in self._inventory.keys(): + LOG.warn("No %s inventory loaded" % plugin_key) + LOG.warn("%s: %s with args %s ignored" \ + % (plugin_key, function_name, args)) + return {const.DEVICE_IP: []} + else: + return getattr(self._inventory[plugin_key], function_name)(args) + + def _invoke_plugin(self, plugin_key, function_name, args, kwargs): + """Invoke only the device plugin""" + return getattr(self._plugins[plugin_key], function_name)(*args, + **kwargs) + + def get_all_networks(self, args): + """Not implemented for this model""" + pass + + def create_network(self, args): + """Support for the Quantum core API call""" + self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(), + args) + self._invoke_plugin_per_device(const.NEXUS_PLUGIN, + self._func_name(), args) + + def delete_network(self, args): + """Support for the Quantum core API call""" + self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(), + args) + self._invoke_plugin_per_device(const.NEXUS_PLUGIN, + self._func_name(), args) + + def get_network_details(self, args): + """Not implemented for this model""" + pass + + def rename_network(self, args): + """Support for the Quantum core API call""" + self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(), + args) + self._invoke_plugin_per_device(const.NEXUS_PLUGIN, + self._func_name(), args) + + def get_all_ports(self, args): + """Not implemented for this model""" + pass + + def create_port(self, args): + """Support for the Quantum core API call""" + self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(), + args) + + def delete_port(self, args): + """Support for the Quantum core API call""" + self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(), + args) + + def update_port(self, args): + """Not implemented for this model""" + pass + + def get_port_details(self, args): + """Not implemented for this model""" + pass + + def plug_interface(self, args): + """Support for the Quantum core API call""" + self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(), + args) + + def unplug_interface(self, args): + """Support for the Quantum core API call""" + self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(), + args) + + def schedule_host(self, args): + """Provides the hostname on which a dynamic vnic is reserved""" + LOG.debug("schedule_host() called\n") + return self._invoke_inventory(const.UCS_PLUGIN, self._func_name(), + args) + + def associate_port(self, args): + """ + Get the portprofile name and the device namei for the dynamic vnic + """ + LOG.debug("associate_port() called\n") + return self._invoke_inventory(const.UCS_PLUGIN, self._func_name(), + args) diff --git a/quantum/plugins/cisco/models/l2network_single_blade.py b/quantum/plugins/cisco/models/l2network_single_blade.py new file mode 100644 index 0000000000..617d1a55d6 --- /dev/null +++ b/quantum/plugins/cisco/models/l2network_single_blade.py @@ -0,0 +1,164 @@ +""" +# 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. +# +""" + +from copy import deepcopy +import inspect +import logging as LOG +import platform + +from quantum.common import exceptions as exc +from quantum.common import utils +from quantum.plugins.cisco.l2network_model_base import L2NetworkModelBase +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(__name__) + + +class L2NetworkSingleBlade(L2NetworkModelBase): + """ + Implements the L2NetworkModelBase + This implementation works with a single UCS blade + """ + _plugins = {} + _inventory = {} + + def __init__(self): + for key in conf.PLUGINS[const.PLUGINS].keys(): + self._plugins[key] = utils.import_object( + conf.PLUGINS[const.PLUGINS][key]) + LOG.debug("Loaded device plugin %s\n" % \ + conf.PLUGINS[const.PLUGINS][key]) + if key in conf.PLUGINS[const.INVENTORY].keys(): + self._inventory[key] = utils.import_object( + conf.PLUGINS[const.INVENTORY][key]) + LOG.debug("Loaded device inventory %s\n" % \ + conf.PLUGINS[const.INVENTORY][key]) + + def _func_name(self, offset=0): + """Get the name of the calling function""" + return inspect.stack()[1 + offset][3] + + def _invoke_plugin_per_device(self, plugin_key, function_name, args): + """Invoke only device plugin for all the devices in the system""" + if not plugin_key in self._plugins.keys(): + LOG.info("No %s Plugin loaded" % plugin_key) + LOG.info("%s: %s with args %s ignored" \ + % (plugin_key, function_name, args)) + return + device_params = self._invoke_inventory(plugin_key, function_name, + args) + device_ips = device_params[const.DEVICE_IP] + if not device_ips: + self._invoke_plugin(plugin_key, function_name, args, + device_params) + else: + for device_ip in device_ips: + new_device_params = deepcopy(device_params) + new_device_params[const.DEVICE_IP] = device_ip + self._invoke_plugin(plugin_key, function_name, args, + new_device_params) + + def _invoke_inventory(self, plugin_key, function_name, args): + """Invoke only the inventory implementation""" + if not plugin_key in self._inventory.keys(): + LOG.warn("No %s inventory loaded" % plugin_key) + LOG.warn("%s: %s with args %s ignored" \ + % (plugin_key, function_name, args)) + return {const.DEVICE_IP: []} + else: + return getattr(self._inventory[plugin_key], function_name)(args) + + def _invoke_plugin(self, plugin_key, function_name, args, kwargs): + """Invoke only the device plugin""" + return getattr(self._plugins[plugin_key], function_name)(*args, + **kwargs) + + def get_all_networks(self, args): + """Not implemented for this model""" + pass + + def create_network(self, args): + """Support for the Quantum core API call""" + self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(), + args) + + def delete_network(self, args): + """Support for the Quantum core API call""" + self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(), + args) + + def get_network_details(self, args): + """Not implemented for this model""" + pass + + def rename_network(self, args): + """Support for the Quantum core API call""" + self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(), + args) + + def get_all_ports(self, args): + """Not implemented for this model""" + pass + + def create_port(self, args): + """Support for the Quantum core API call""" + self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(), + args) + + def delete_port(self, args): + """Support for the Quantum core API call""" + self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(), + args) + + def update_port(self, args): + """Not implemented for this model""" + pass + + def get_port_details(self, args): + """Not implemented for this model""" + pass + + def plug_interface(self, args): + """Support for the Quantum core API call""" + self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(), + args) + + def unplug_interface(self, args): + """Support for the Quantum core API call""" + self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(), + args) + + def schedule_host(self, args): + """Provides the hostname on which a dynamic vnic is reserved""" + LOG.debug("schedule_host() called\n") + return self._invoke_inventory(const.UCS_PLUGIN, self._func_name(), + args) + + def associate_port(self, args): + """ + Get the portprofile name and the device namei for the dynamic vnic + """ + LOG.debug("associate_port() called\n") + return self._invoke_inventory(const.UCS_PLUGIN, self._func_name(), + args) diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py index 7fe7338372..a760fefad3 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py @@ -24,6 +24,7 @@ Implements a Nexus-OS NETCONF over SSHv2 API Client import logging as LOG from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.db import l2network_db as cdb from quantum.plugins.cisco.nexus import cisco_nexus_snippets as snipp from ncclient import manager @@ -119,8 +120,12 @@ class CiscoNEXUSDriver(): with self.nxos_connect(nexus_host, int(nexus_ssh_port), nexus_user, nexus_password) as man: self.enable_vlan(man, vlan_id, vlan_name) - self.enable_vlan_on_trunk_int(man, nexus_first_interface, vlan_id) - self.enable_vlan_on_trunk_int(man, nexus_second_interface, vlan_id) + vlan_ids = self.build_vlans_cmd() + LOG.debug("NexusDriver VLAN IDs: %s" % vlan_ids) + self.enable_vlan_on_trunk_int(man, nexus_first_interface, + vlan_ids) + self.enable_vlan_on_trunk_int(man, nexus_second_interface, + vlan_ids) def delete_vlan(self, vlan_id, nexus_host, nexus_user, nexus_password, nexus_first_interface, nexus_second_interface, @@ -132,5 +137,19 @@ class CiscoNEXUSDriver(): with self.nxos_connect(nexus_host, int(nexus_ssh_port), nexus_user, nexus_password) as man: self.disable_vlan(man, vlan_id) - self.disable_switch_port(man, nexus_first_interface) - self.disable_switch_port(man, nexus_second_interface) + self.disable_vlan_on_trunk_int(man, nexus_first_interface, + vlan_id) + self.disable_vlan_on_trunk_int(man, nexus_second_interface, + vlan_id) + + def build_vlans_cmd(self): + """ + Builds a string with all the VLANs on the same Switch + """ + assigned_vlan = cdb.get_all_vlanids_used() + vlans = '' + for vlanid in assigned_vlan: + vlans = str(vlanid["vlan_id"]) + ',' + vlans + if vlans == '': + vlans = 'none' + return vlans.strip(',') diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py b/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py index 62f3806f32..dd93cf10e3 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py @@ -26,9 +26,9 @@ from quantum.common import exceptions as exc from quantum.common import utils from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_credentials as cred +from quantum.plugins.cisco.db import nexus_db as nxos_db from quantum.plugins.cisco.l2device_plugin_base import L2DevicePluginBase from quantum.plugins.cisco.nexus import cisco_nexus_configuration as conf -from quantum.plugins.cisco.db import nexus_db as nxos_db LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) diff --git a/quantum/plugins/cisco/nova/quantum_aware_scheduler.py b/quantum/plugins/cisco/nova/quantum_aware_scheduler.py index 0f73618ee0..4394980dfa 100644 --- a/quantum/plugins/cisco/nova/quantum_aware_scheduler.py +++ b/quantum/plugins/cisco/nova/quantum_aware_scheduler.py @@ -35,11 +35,12 @@ flags.DEFINE_integer('quantum_port', 9696, HOST = FLAGS.quantum_host PORT = FLAGS.quantum_port USE_SSL = False -ACTION_PREFIX_EXT = '/v0.1' +ACTION_PREFIX_EXT = '/v1.0' ACTION_PREFIX_CSCO = ACTION_PREFIX_EXT + \ '/extensions/csco/tenants/{tenant_id}' TENANT_ID = 'nova' CSCO_EXT_NAME = 'Cisco Nova Tenant' +ACTION = '/schedule_host' class QuantumScheduler(driver.Scheduler): @@ -83,7 +84,7 @@ class QuantumScheduler(driver.Scheduler): client = Client(HOST, PORT, USE_SSL, format='json', tenant=TENANT_ID, action_prefix=ACTION_PREFIX_CSCO) - request_url = "/novatenants/" + project_id + "/get_host" + request_url = "/novatenants/" + project_id + ACTION data = client.do_request('PUT', request_url, body=instance_data_dict) hostname = data["host_list"]["host_1"] diff --git a/quantum/plugins/cisco/nova/vifdirect.py b/quantum/plugins/cisco/nova/vifdirect.py index c1fd09af46..015c9acebf 100644 --- a/quantum/plugins/cisco/nova/vifdirect.py +++ b/quantum/plugins/cisco/nova/vifdirect.py @@ -40,11 +40,12 @@ HOST = FLAGS.quantum_host PORT = FLAGS.quantum_port USE_SSL = False TENANT_ID = 'nova' -ACTION_PREFIX_EXT = '/v0.1' +ACTION_PREFIX_EXT = '/v1.0' ACTION_PREFIX_CSCO = ACTION_PREFIX_EXT + \ '/extensions/csco/tenants/{tenant_id}' TENANT_ID = 'nova' CSCO_EXT_NAME = 'Cisco Nova Tenant' +ACTION = '/associate_port' class Libvirt802dot1QbhDriver(VIFDriver): @@ -87,7 +88,7 @@ class Libvirt802dot1QbhDriver(VIFDriver): client = Client(HOST, PORT, USE_SSL, format='json', tenant=TENANT_ID, action_prefix=ACTION_PREFIX_CSCO) - request_url = "/novatenants/" + project_id + "/get_instance_port" + request_url = "/novatenants/" + project_id + ACTION data = client.do_request('PUT', request_url, body=instance_data_dict) device = data['vif_desc']['device'] diff --git a/quantum/plugins/cisco/segmentation/__init__.py b/quantum/plugins/cisco/segmentation/__init__.py new file mode 100644 index 0000000000..09b3fab896 --- /dev/null +++ b/quantum/plugins/cisco/segmentation/__init__.py @@ -0,0 +1,20 @@ +""" +# 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. +# +""" diff --git a/quantum/plugins/cisco/segmentation/l2network_vlan_mgr.py b/quantum/plugins/cisco/segmentation/l2network_vlan_mgr.py new file mode 100644 index 0000000000..f361980a4d --- /dev/null +++ b/quantum/plugins/cisco/segmentation/l2network_vlan_mgr.py @@ -0,0 +1,47 @@ +""" +# 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 logging as LOG + +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.db import l2network_db as cdb +from quantum.plugins.cisco.l2network_segmentation_base \ + import L2NetworkSegmentationMgrBase + +LOG.basicConfig(level=LOG.WARN) +LOG.getLogger(const.LOGGER_COMPONENT_NAME) + + +class L2NetworkVLANMgr(L2NetworkSegmentationMgrBase): + """ + VLAN Manager which gets VLAN ID from DB + """ + def __init__(self): + cdb.create_vlanids() + + def reserve_segmentation_id(self, tenant_id, net_name, **kwargs): + """Get an available VLAN ID""" + return cdb.reserve_vlanid() + + def release_segmentation_id(self, tenant_id, net_id, **kwargs): + """Release the ID""" + vlan_binding = cdb.get_vlan_binding(net_id) + return cdb.release_vlanid(vlan_binding[const.VLANID]) diff --git a/quantum/plugins/cisco/tests/unit/test_cisco_extension.py b/quantum/plugins/cisco/tests/unit/test_cisco_extension.py index e1f2391bf4..ac7d9bfe26 100644 --- a/quantum/plugins/cisco/tests/unit/test_cisco_extension.py +++ b/quantum/plugins/cisco/tests/unit/test_cisco_extension.py @@ -80,14 +80,14 @@ class PortprofileExtensionTest(unittest.TestCase): self.profile_path = '/extensions/csco/tenants/tt/portprofiles' self.portprofile_path = '/extensions/csco/tenants/tt/portprofiles/' self.test_port_profile = {'portprofile': - {'portprofile_name': 'cisco_test_portprofile', - 'qos_name': 'test-qos1'}} + {'portprofile_name': 'cisco_test_portprofile', + 'qos_name': 'test-qos1'}} self.tenant_id = "test_tenant" self.network_name = "test_network" options = {} options['plugin_provider'] = 'quantum.plugins.cisco.l2network_plugin'\ '.L2Network' - self.api = server.APIRouterV01(options) + self.api = server.APIRouterV1(options) self._l2network_plugin = l2network_plugin.L2Network() def test_list_portprofile(self): @@ -107,9 +107,10 @@ class PortprofileExtensionTest(unittest.TestCase): content_type=self.contenttype) index_response = self.test_app.get(self.profile_path) + index_resp_body = wsgi.Serializer().deserialize(index_response.body, + self.contenttype) self.assertEqual(200, index_response.status_int) - # Clean Up - Delete the Port Profiles resp_body1 = wsgi.Serializer().deserialize(create_response1.body, self.contenttype) portprofile_path1_temp = self.portprofile_path +\ @@ -117,9 +118,17 @@ class PortprofileExtensionTest(unittest.TestCase): portprofile_path1 = str(portprofile_path1_temp) resp_body2 = wsgi.Serializer().deserialize(create_response2.body, self.contenttype) + list_all_portprofiles = [resp_body1['portprofiles']['portprofile'], + resp_body2['portprofiles']['portprofile']] + self.assertTrue(index_resp_body['portprofiles'][0] in + list_all_portprofiles) + self.assertTrue(index_resp_body['portprofiles'][1] in + list_all_portprofiles) portprofile_path2_temp = self.portprofile_path +\ resp_body2['portprofiles']['portprofile']['id'] portprofile_path2 = str(portprofile_path2_temp) + + # Clean Up - Delete the Port Profiles self.tear_down_profile(portprofile_path1) self.tear_down_profile(portprofile_path2) LOG.debug("test_list_portprofile - END") @@ -168,6 +177,14 @@ class PortprofileExtensionTest(unittest.TestCase): resp_body['portprofiles']['portprofile']['id'] show_port_path = str(show_path_temp) show_response = self.test_app.get(show_port_path) + show_resp_dict = wsgi.Serializer().deserialize(show_response.body, + self.contenttype) + self.assertEqual( + show_resp_dict['portprofiles']['portprofile']['qos_name'], + self.test_port_profile['portprofile']['qos_name']) + self.assertEqual( + show_resp_dict['portprofiles']['portprofile']['name'], + self.test_port_profile['portprofile']['portprofile_name']) self.assertEqual(200, show_response.status_int) # Clean Up - Delete the Port Profile @@ -204,6 +221,14 @@ class PortprofileExtensionTest(unittest.TestCase): resp_body['portprofiles']['portprofile']['id'] rename_path = str(rename_path_temp) rename_response = self.test_app.put(rename_path, rename_req_body) + rename_resp_dict = wsgi.Serializer().deserialize(rename_response.body, + self.contenttype) + self.assertEqual( + rename_resp_dict['portprofiles']['portprofile']['qos_name'], + self.test_port_profile['portprofile']['qos_name']) + self.assertEqual( + rename_resp_dict['portprofiles']['portprofile']['name'], + rename_port_profile['portprofile']['portprofile_name']) self.assertEqual(200, rename_response.status_int) # Clean Up - Delete the Port Profile @@ -288,8 +313,8 @@ class PortprofileExtensionTest(unittest.TestCase): req.headers = {} req.headers['Accept'] = content_type req.body = body - return req LOG.debug("test_create_request - END") + return req def _create_network(self, name=None): @@ -301,15 +326,15 @@ class PortprofileExtensionTest(unittest.TestCase): else: net_name = self.network_name net_path = "/tenants/tt/networks" - net_data = {'network': {'net-name': '%s' % net_name}} + net_data = {'network': {'name': '%s' % net_name}} req_body = wsgi.Serializer().serialize(net_data, self.contenttype) network_req = self.create_request(net_path, req_body, self.contenttype, 'POST') network_res = network_req.get_response(self.api) network_data = wsgi.Serializer().deserialize(network_res.body, self.contenttype) - return network_data['networks']['network']['id'] LOG.debug("Creating network - END") + return network_data['network']['id'] def _create_port(self, network_id, port_state): @@ -317,7 +342,7 @@ class PortprofileExtensionTest(unittest.TestCase): LOG.debug("Creating port for network %s - START", network_id) port_path = "/tenants/tt/networks/%s/ports" % network_id - port_req_data = {'port': {'port-state': '%s' % port_state}} + port_req_data = {'port': {'state': '%s' % port_state}} req_body = wsgi.Serializer().serialize(port_req_data, self.contenttype) port_req = self.create_request(port_path, req_body, @@ -325,8 +350,27 @@ class PortprofileExtensionTest(unittest.TestCase): port_res = port_req.get_response(self.api) port_data = wsgi.Serializer().deserialize(port_res.body, self.contenttype) - return port_data['ports']['port']['id'] LOG.debug("Creating port for network - END") + return port_data['port']['id'] + + def _delete_port(self, network_id, port_id): + """ Delete port """ + LOG.debug("Deleting port for network %s - START", network_id) + port_path = "/tenants/tt/networks/%(network_id)s/ports/"\ + "%(port_id)s" % locals() + port_req = self.create_request(port_path, None, + self.contenttype, 'DELETE') + port_req.get_response(self.api) + LOG.debug("Deleting port for network - END") + + def _delete_network(self, network_id): + """ Delete network """ + LOG.debug("Deleting network %s - START", network_id) + network_path = "/tenants/tt/networks/%s" % network_id + network_req = self.create_request(network_path, None, + self.contenttype, 'DELETE') + network_req.get_response(self.api) + LOG.debug("Deleting network - END") def test_associate_portprofile(self): @@ -363,6 +407,7 @@ class PortprofileExtensionTest(unittest.TestCase): delete_path = str(delete_path_temp) self.tear_down_associate_profile(delete_path, disassociate_path, req_assign_body) + self.tear_down_port_network(net_id, port_id) LOG.debug("test_associate_portprofile - END") def test_associate_portprofileDNE(self, portprofile_id='100'): @@ -420,8 +465,15 @@ class PortprofileExtensionTest(unittest.TestCase): resp_body['portprofiles']['portprofile']['id'] delete_path = str(delete_path_temp) self.tear_down_profile(delete_path) + self.tear_down_port_network(net_id, port_id) LOG.debug("test_disassociate_portprofile - END") + def tear_down_port_network(self, net_id, port_id): + """ Tear down port and network """ + + self._delete_port(net_id, port_id) + self._delete_network(net_id) + def tear_down_profile(self, delete_profile_path): """ Tear down profile""" @@ -452,8 +504,8 @@ class NovatenantExtensionTest(unittest.TestCase): parent_resource = dict(member_name="tenant", collection_name="extensions/csco/tenants") - member_actions = {'get_host': "PUT", - 'get_instance_port': "PUT"} + member_actions = {'schedule_host': "PUT", + 'associate_port': "PUT"} controller = novatenant.NovatenantsController( QuantumManager.get_plugin()) res_ext = extensions.ResourceExtension('novatenants', controller, @@ -463,47 +515,50 @@ class NovatenantExtensionTest(unittest.TestCase): SimpleExtensionManager(res_ext)) self.contenttype = 'application/json' self.novatenants_path = '/extensions/csco/tenants/tt/novatenants/' - self.test_instance_data = {'novatenant': {'instance_id': 1, - 'instance_desc': {'key1': '1', - 'key2': '2'}}} - - def test_get_host(self): + self.test_associate_port_data = {'novatenant': {'instance_id': 1, + 'instance_desc': {'project_id': 'demo', + 'user_id': 'root', 'vif_id': '23432423'}}} + self.test_associate_data = {'novatenant': {'instance_id': 1, + 'instance_desc': {'project_id': 'demo', + 'user_id': 'root'}}} + self._l2network_plugin = l2network_plugin.L2Network() + def test_schedule_host(self): """ Test get host""" - - LOG.debug("test_get_host - START") - req_body = json.dumps(self.test_instance_data) - host_path = self.novatenants_path + "001/get_host" + LOG.debug("test_schedule_host - START") + req_body = json.dumps(self.test_associate_data) + host_path = self.novatenants_path + "001/schedule_host" host_response = self.test_app.put( - host_path, req_body, - content_type=self.contenttype) + host_path, req_body, + content_type=self.contenttype) self.assertEqual(200, host_response.status_int) - LOG.debug("test_get_host - END") - - def test_get_hostBADRequest(self): + LOG.debug("test_schedule_host - END") + def test_schedule_hostBADRequest(self): """ Test get host bad request""" - - LOG.debug("test_get_hostBADRequest - START") - host_path = self.novatenants_path + "001/get_host" + LOG.debug("test_schedule_hostBADRequest - START") + host_path = self.novatenants_path + "001/schedule_host" host_response = self.test_app.put( host_path, 'BAD_REQUEST', content_type=self.contenttype, status='*') self.assertEqual(400, host_response.status_int) - LOG.debug("test_get_hostBADRequest - END") + LOG.debug("test_schedule_hostBADRequest - END") - def test_instance_port(self): - - """ Test get instance port """ - - LOG.debug("test_instance_port - START") - req_body = json.dumps(self.test_instance_data) - instance_port_path = self.novatenants_path + "001/get_instance_port" - instance_port_response = self.test_app.put( - instance_port_path, req_body, + def test_associate_port(self): + """ Test get associate port """ + LOG.debug("test_associate_port - START") + req_body = json.dumps(self.test_associate_port_data) + associate_port_path = self.novatenants_path + "001/associate_port" + associate_port_response = self.test_app.put( + associate_port_path, req_body, content_type=self.contenttype) - self.assertEqual(200, instance_port_response.status_int) - LOG.debug("test_instance_port - END") + self.assertEqual(200, associate_port_response.status_int) + LOG.debug("test_associate_port - END") + + def tearDown(self): + + """ Tear down """ + db.clear_db() class QosExtensionTest(unittest.TestCase): @@ -525,6 +580,7 @@ class QosExtensionTest(unittest.TestCase): self.qos_second_path = '/extensions/csco/tenants/tt/qos/' self.test_qos_data = {'qos': {'qos_name': 'cisco_test_qos', 'qos_desc': {'PPS': 50, 'TTL': 5}}} + self._l2network_plugin = l2network_plugin.L2Network() def test_create_qos(self): @@ -571,6 +627,8 @@ class QosExtensionTest(unittest.TestCase): create_resp2 = self.test_app.post(self.qos_path, req_body2, content_type=self.contenttype) index_response = self.test_app.get(self.qos_path) + index_resp_body = wsgi.Serializer().deserialize(index_response.body, + self.contenttype) self.assertEqual(200, index_response.status_int) # Clean Up - Delete the qos's @@ -581,6 +639,9 @@ class QosExtensionTest(unittest.TestCase): qos_path1 = str(qos_path1_temp) resp_body2 = wsgi.Serializer().deserialize(create_resp2.body, self.contenttype) + list_all_qos = [resp_body1['qoss']['qos'], resp_body2['qoss']['qos']] + self.assertTrue(index_resp_body['qoss'][0] in list_all_qos) + self.assertTrue(index_resp_body['qoss'][1] in list_all_qos) qos_path2_temp = self.qos_second_path +\ resp_body2['qoss']['qos']['id'] qos_path2 = str(qos_path2_temp) @@ -602,6 +663,12 @@ class QosExtensionTest(unittest.TestCase): resp_body['qoss']['qos']['id'] show_qos_path = str(show_path_temp) show_response = self.test_app.get(show_qos_path) + show_resp_dict = wsgi.Serializer().deserialize(show_response.body, + self.contenttype) + self.assertEqual( + show_resp_dict['qoss']['qos']['name'], + self.test_qos_data['qos']['qos_name']) + self.assertEqual(200, show_response.status_int) # Clean Up - Delete the qos @@ -636,6 +703,11 @@ class QosExtensionTest(unittest.TestCase): rename_path = str(rename_path_temp) rename_response = self.test_app.put(rename_path, rename_req_body) self.assertEqual(200, rename_response.status_int) + rename_resp_dict = wsgi.Serializer().deserialize(rename_response.body, + self.contenttype) + self.assertEqual( + rename_resp_dict['qoss']['qos']['name'], + 'cisco_rename_qos') self.tearDownQos(rename_path) LOG.debug("test_update_qos - END") @@ -709,6 +781,9 @@ class QosExtensionTest(unittest.TestCase): self.test_app.delete(delete_profile_path) + def tearDown(self): + db.clear_db() + class CredentialExtensionTest(unittest.TestCase): @@ -731,6 +806,7 @@ class CredentialExtensionTest(unittest.TestCase): {'credential_name': 'cred8', 'user_name': 'newUser2', 'password': 'newPasswd1'}} + self._l2network_plugin = l2network_plugin.L2Network() def test_list_credentials(self): @@ -751,6 +827,8 @@ class CredentialExtensionTest(unittest.TestCase): content_type=self.contenttype) index_response = self.test_app.get( self.credential_path) + index_resp_body = wsgi.Serializer().deserialize(index_response.body, + self.contenttype) self.assertEqual(200, index_response.status_int) #CLean Up - Deletion of the Credentials resp_body1 = wsgi.Serializer().deserialize( @@ -760,6 +838,12 @@ class CredentialExtensionTest(unittest.TestCase): delete_path1 = str(delete_path1_temp) resp_body2 = wsgi.Serializer().deserialize( create_response2.body, self.contenttype) + list_all_credential = [resp_body1['credentials']['credential'], + resp_body2['credentials']['credential']] + self.assertTrue(index_resp_body['credentials'][0] in + list_all_credential) + self.assertTrue(index_resp_body['credentials'][1] in + list_all_credential) delete_path2_temp = self.cred_second_path +\ resp_body2['credentials']['credential']['id'] delete_path2 = str(delete_path2_temp) @@ -812,6 +896,14 @@ class CredentialExtensionTest(unittest.TestCase): resp_body['credentials']['credential']['id'] show_cred_path = str(show_path_temp) show_response = self.test_app.get(show_cred_path) + show_resp_dict = wsgi.Serializer().deserialize(show_response.body, + self.contenttype) + self.assertEqual( + show_resp_dict['credentials']['credential']['name'], + self.test_credential_data['credential']['user_name']) + self.assertEqual( + show_resp_dict['credentials']['credential']['password'], + self.test_credential_data['credential']['password']) self.assertEqual(200, show_response.status_int) LOG.debug("test_show_credential - END") @@ -846,6 +938,14 @@ class CredentialExtensionTest(unittest.TestCase): resp_body['credentials']['credential']['id'] rename_path = str(rename_path_temp) rename_response = self.test_app.put(rename_path, rename_req_body) + rename_resp_dict = wsgi.Serializer().deserialize(rename_response.body, + self.contenttype) + self.assertEqual( + rename_resp_dict['credentials']['credential']['name'], + 'cred3') + self.assertEqual( + rename_resp_dict['credentials']['credential']['password'], + self.test_credential_data['credential']['password']) self.assertEqual(200, rename_response.status_int) # Clean Up - Delete the Credentials self.tearDownCredential(rename_path) @@ -918,6 +1018,9 @@ class CredentialExtensionTest(unittest.TestCase): def tearDownCredential(self, delete_path): self.test_app.delete(delete_path) + def tearDown(self): + db.clear_db() + def app_factory(global_conf, **local_conf): conf = global_conf.copy() diff --git a/quantum/plugins/cisco/tests/unit/test_database.py b/quantum/plugins/cisco/tests/unit/test_database.py index 2ae7f0271f..4a5ffc9d66 100644 --- a/quantum/plugins/cisco/tests/unit/test_database.py +++ b/quantum/plugins/cisco/tests/unit/test_database.py @@ -27,11 +27,99 @@ from quantum.plugins.cisco.common import cisco_constants as const import quantum.plugins.cisco.db.api as db import quantum.plugins.cisco.db.l2network_db as l2network_db import quantum.plugins.cisco.db.nexus_db as nexus_db +import quantum.plugins.cisco.db.ucs_db as ucs_db LOG.getLogger(const.LOGGER_COMPONENT_NAME) +class UcsDB(object): + """Class consisting of methods to call ucs db methods""" + def get_all_port_bindings(self): + """get all port binding""" + port_bindings = [] + try: + for bind in ucs_db.get_all_portbindings(): + LOG.debug("Getting port binding for port: %s" % bind.port_id) + port_bind_dict = {} + port_bind_dict["port-id"] = bind.port_id + port_bind_dict["blade-intf-dn"] = str(bind.blade_intf_dn) + port_bind_dict["portprofile-name"] = bind.portprofile_name + port_bind_dict["vlan-name"] = bind.vlan_name + port_bind_dict["vlan-id"] = str(bind.vlan_id) + port_bind_dict["qos"] = bind.qos + port_bindings.append(port_bind_dict) + except Exception, exc: + LOG.error("Failed to get all port bindings: %s" % str(exc)) + return port_bindings + + def get_port_binding(self, port_id): + """get port binding""" + port_binding = [] + try: + for bind in ucs_db.get_portbinding(port_id): + LOG.debug("Getting port binding for port: %s" % bind.port_id) + port_bind_dict = {} + port_bind_dict["port-id"] = bind.port_id + port_bind_dict["blade-intf-dn"] = str(bind.blade_intf_dn) + port_bind_dict["portprofile-name"] = bind.portprofile_name + port_bind_dict["vlan-name"] = bind.vlan_name + port_bind_dict["vlan-id"] = str(bind.vlan_id) + port_bind_dict["qos"] = bind.qos + port_binding.append(port_bind_dict) + except Exception, exc: + LOG.error("Failed to get port binding: %s" % str(exc)) + return port_binding + + def create_port_binding(self, port_id, blade_intf_dn, portprofile_name, \ + vlan_name, vlan_id, qos): + """create port binding""" + port_bind_dict = {} + try: + res = ucs_db.add_portbinding(port_id, blade_intf_dn, \ + portprofile_name, vlan_name, vlan_id, qos) + LOG.debug("Created port binding: %s" % res.port_id) + port_bind_dict["port-id"] = res.port_id + port_bind_dict["blade-intf-dn"] = str(res.blade_intf_dn) + port_bind_dict["portprofile-name"] = res.portprofile_name + port_bind_dict["vlan-name"] = res.vlan_name + port_bind_dict["vlan-id"] = str(res.vlan_id) + port_bind_dict["qos"] = res.qos + return port_bind_dict + except Exception, exc: + LOG.error("Failed to create port binding: %s" % str(exc)) + + def delete_port_binding(self, port_id): + """delete port binding""" + try: + res = ucs_db.remove_portbinding(port_id) + LOG.debug("Deleted port binding : %s" % res.port_id) + port_bind_dict = {} + port_bind_dict["port-id"] = res.port_id + return port_bind_dict + except Exception, exc: + raise Exception("Failed to delete port profile: %s" % str(exc)) + + def update_port_binding(self, port_id, blade_intf_dn, \ + portprofile_name, vlan_name, vlan_id, qos): + """update port binding""" + try: + res = ucs_db.update_portbinding(port_id, blade_intf_dn, \ + portprofile_name, vlan_name, vlan_id, qos) + LOG.debug("Updating port binding: %s" % res.port_id) + port_bind_dict = {} + port_bind_dict["port-id"] = res.port_id + port_bind_dict["dynamic-vnic-id"] = str(res.blade_intf_dn) + port_bind_dict["portprofile-name"] = res.portprofile_name + port_bind_dict["vlan-name"] = res.vlan_name + port_bind_dict["vlan-id"] = str(res.vlan_id) + port_bind_dict["qos"] = res.qos + return port_bind_dict + except Exception, exc: + raise Exception("Failed to update portprofile binding:%s" + % str(exc)) + + class NexusDB(object): """Class consisting of methods to call nexus db methods""" def get_all_nexusportbindings(self): @@ -110,7 +198,7 @@ class L2networkDB(object): vlans = [] try: for vlan_bind in l2network_db.get_all_vlan_bindings(): - LOG.debug("Getting vlan bindings for vlan: %s" % \ + LOG.debug("Getting vlan bindings for vlan: %s" % vlan_bind.vlan_id) vlan_dict = {} vlan_dict["vlan-id"] = str(vlan_bind.vlan_id) @@ -126,7 +214,7 @@ class L2networkDB(object): vlan = [] try: for vlan_bind in l2network_db.get_vlan_binding(network_id): - LOG.debug("Getting vlan binding for vlan: %s" \ + LOG.debug("Getting vlan binding for vlan: %s" % vlan_bind.vlan_id) vlan_dict = {} vlan_dict["vlan-id"] = str(vlan_bind.vlan_id) @@ -164,7 +252,7 @@ class L2networkDB(object): def update_vlan_binding(self, network_id, vlan_id, vlan_name): """Update a vlan binding""" try: - res = l2network_db.update_vlan_binding(network_id, vlan_id, \ + res = l2network_db.update_vlan_binding(network_id, vlan_id, vlan_name) LOG.debug("Updating vlan binding for vlan: %s" % res.vlan_id) vlan_dict = {} @@ -252,7 +340,7 @@ class L2networkDB(object): pp_bindings = [] try: for pp_bind in l2network_db.get_all_pp_bindings(): - LOG.debug("Getting port profile binding: %s" % \ + LOG.debug("Getting port profile binding: %s" % pp_bind.portprofile_id) ppbinding_dict = {} ppbinding_dict["portprofile-id"] = str(pp_bind.portprofile_id) @@ -269,7 +357,7 @@ class L2networkDB(object): pp_binding = [] try: for pp_bind in l2network_db.get_pp_binding(tenant_id, pp_id): - LOG.debug("Getting port profile binding: %s" % \ + LOG.debug("Getting port profile binding: %s" % pp_bind.portprofile_id) ppbinding_dict = {} ppbinding_dict["portprofile-id"] = str(pp_bind.portprofile_id) @@ -285,7 +373,7 @@ class L2networkDB(object): """Add a portprofile binding""" ppbinding_dict = {} try: - res = l2network_db.add_pp_binding(tenant_id, port_id, pp_id, \ + res = l2network_db.add_pp_binding(tenant_id, port_id, pp_id, default) LOG.debug("Created port profile binding: %s" % res.portprofile_id) ppbinding_dict["portprofile-id"] = str(res.portprofile_id) @@ -307,7 +395,7 @@ class L2networkDB(object): except Exception, exc: raise Exception("Failed to delete port profile: %s" % str(exc)) - def update_pp_binding(self, tenant_id, pp_id, newtenant_id, \ + def update_pp_binding(self, tenant_id, pp_id, newtenant_id, port_id, default): """Update portprofile binding""" try: @@ -321,7 +409,7 @@ class L2networkDB(object): ppbinding_dict["default"] = res.default return ppbinding_dict except Exception, exc: - raise Exception("Failed to update portprofile binding:%s" \ + raise Exception("Failed to update portprofile binding:%s" % str(exc)) @@ -494,6 +582,101 @@ class QuantumDB(object): raise Exception("Failed to unplug interface: %s" % str(exc)) +class UcsDBTest(unittest.TestCase): + """Class conisting of ucs DB unit tests""" + def setUp(self): + """Setup for ucs db tests""" + l2network_db.initialize() + self.quantum = QuantumDB() + self.dbtest = UcsDB() + LOG.debug("Setup") + + def tearDown(self): + """Tear Down""" + db.clear_db() + + def testm_create_portbinding(self): + """create port binding""" + net1 = self.quantum.create_network("t1", "netid1") + port1 = self.quantum.create_port(net1["net-id"]) + port_bind1 = self.dbtest.create_port_binding(port1["port-id"], + "vnic1", "pp1", "vlan1", 10, "qos1") + self.assertTrue(port_bind1["port-id"] == port1["port-id"]) + self.teardown_portbinding() + self.teardown_network_port() + + def testn_getall_portbindings(self): + """get all port binding""" + net1 = self.quantum.create_network("t1", "netid1") + port1 = self.quantum.create_port(net1["net-id"]) + port2 = self.quantum.create_port(net1["net-id"]) + port_bind1 = self.dbtest.create_port_binding(port1["port-id"], + "vnic1", "pp1", "vlan1", 10, "qos1") + port_bind2 = self.dbtest.create_port_binding(port2["port-id"], + "vnic2", "pp2", "vlan2", 20, "qos2") + port_bindings = self.dbtest.get_all_port_bindings() + count = 0 + for pbind in port_bindings: + if "vlan" in pbind["vlan-name"]: + count += 1 + self.assertTrue(count == 2) + self.teardown_portbinding() + self.teardown_network_port() + + def testo_delete_portbinding(self): + """delete port binding""" + net1 = self.quantum.create_network("t1", "netid1") + port1 = self.quantum.create_port(net1["net-id"]) + port_bind1 = self.dbtest.create_port_binding(port1["port-id"], + "vnic1", "pp1", "vlan1", 10, "qos1") + self.dbtest.delete_port_binding(port1["port-id"]) + port_bindings = self.dbtest.get_all_port_bindings() + count = 0 + for pbind in port_bindings: + if "vlan " in pbind["vlan-name"]: + count += 1 + self.assertTrue(count == 0) + self.teardown_portbinding() + self.teardown_network_port() + + def testp_update_portbinding(self): + """update port binding""" + net1 = self.quantum.create_network("t1", "netid1") + port1 = self.quantum.create_port(net1["net-id"]) + port_bind1 = self.dbtest.create_port_binding(port1["port-id"], + "vnic1", "pp1", "vlan1", 10, "qos1") + port_bind1 = self.dbtest.update_port_binding(port1["port-id"], + "vnic1", "newpp1", "newvlan1", 11, "newqos1") + port_bindings = self.dbtest.get_all_port_bindings() + count = 0 + for pbind in port_bindings: + if "new" in pbind["vlan-name"]: + count += 1 + self.assertTrue(count == 1) + self.teardown_portbinding() + self.teardown_network_port() + + def teardown_portbinding(self): + """tear down port binding""" + LOG.debug("Tearing Down Port Binding") + port_bindings = self.dbtest.get_all_port_bindings() + for port_binding in port_bindings: + portid = port_binding["port-id"] + self.dbtest.delete_port_binding(portid) + + def teardown_network_port(self): + """tearDown for Network and Port table""" + networks = self.quantum.get_all_networks("t1") + for net in networks: + netid = net["net-id"] + name = net["net-name"] + if "net" in name: + ports = self.quantum.get_all_ports(netid) + for por in ports: + self.quantum.delete_port(netid, por["port-id"]) + self.quantum.delete_network(netid) + + class NexusDBTest(unittest.TestCase): """Class conisting of nexus DB unit tests""" def setUp(self): @@ -763,7 +946,7 @@ class L2networkDBTest(unittest.TestCase): self.assertTrue(used == True) used = l2network_db.release_vlanid(vlanid) self.assertTrue(used == False) - self.teardown_vlanid() + #counting on default teardown here to clear db def teardown_network(self): """tearDown Network table""" @@ -809,14 +992,6 @@ class L2networkDBTest(unittest.TestCase): portid = pp_binding["port-id"] self.dbtest.delete_pp_binding("t1", portid, ppid) - def teardown_vlanid(self): - """tearDown VlanID table""" - LOG.debug("Tearing Down Vlan IDs") - vlanids = l2network_db.get_all_vlanids() - for vlanid in vlanids: - vlan_id = vlanid["vlan_id"] - l2network_db.delete_vlanid(vlan_id) - class QuantumDBTest(unittest.TestCase): """Class conisting of Quantum DB unit tests""" diff --git a/quantum/plugins/cisco/tests/unit/test_nexus_plugin.py b/quantum/plugins/cisco/tests/unit/test_nexus_plugin.py index 454f5b703f..65711e7c0e 100644 --- a/quantum/plugins/cisco/tests/unit/test_nexus_plugin.py +++ b/quantum/plugins/cisco/tests/unit/test_nexus_plugin.py @@ -14,12 +14,13 @@ # # @author: Shweta Padubidri, Peter Strunk, Cisco Systems, Inc. # -import unittest import logging +import unittest from quantum.common import exceptions as exc from quantum.plugins.cisco.common import cisco_constants as const -from quantum.plugins.cisco.nexus import cisco_nexus_plugin from quantum.plugins.cisco.db import l2network_db as cdb +from quantum.plugins.cisco.db import api as db +from quantum.plugins.cisco.nexus import cisco_nexus_plugin LOG = logging.getLogger('quantum.tests.test_nexus') @@ -37,8 +38,8 @@ class TestNexusPlugin(unittest.TestCase): self.vlan_name = "q-" + str(self.net_id) + "vlan" self.vlan_id = 267 self.port_id = "9" - self._cisco_nexus_plugin = cisco_nexus_plugin.NexusPlugin() cdb.initialize() + self._cisco_nexus_plugin = cisco_nexus_plugin.NexusPlugin() def test_create_network(self, net_tenant_id=None, network_name=None, network_id=None, net_vlan_name=None, @@ -69,6 +70,8 @@ class TestNexusPlugin(unittest.TestCase): else: vlan_id = self.vlan_id + network_created = self.create_network(tenant_id, net_id) + cdb.add_vlan_binding(vlan_id, vlan_name, network_created["net-id"]) new_net_dict = self._cisco_nexus_plugin.create_network( tenant_id, net_name, net_id, vlan_name, vlan_id) self.assertEqual(new_net_dict[const.NET_ID], self.net_id) @@ -263,6 +266,19 @@ class TestNexusPlugin(unittest.TestCase): self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) LOG.debug("test_get_vlan_id_for_network - END") + def create_network(self, tenant_id, net_name): + """Create a network""" + net_dict = {} + try: + res = db.network_create(tenant_id, net_name) + LOG.debug("Created network: %s" % res.uuid) + net_dict["tenant-id"] = res.tenant_id + net_dict["net-id"] = str(res.uuid) + net_dict["net-name"] = res.name + return net_dict + except Exception, exc: + LOG.error("Failed to create network: %s" % str(exc)) + def tearDownNetwork(self, tenant_id, network_dict_id): """ Clean up functions after the tests diff --git a/quantum/plugins/cisco/tests/unit/test_ucs_driver.py b/quantum/plugins/cisco/tests/unit/test_ucs_driver.py index 9da92e5b06..c7adcc57d9 100644 --- a/quantum/plugins/cisco/tests/unit/test_ucs_driver.py +++ b/quantum/plugins/cisco/tests/unit/test_ucs_driver.py @@ -155,13 +155,3 @@ class TestUCSDriver(unittest.TestCase): self.profile_name, self.profile_client_name) self.assertEqual(profile_details, expected_output) LOG.debug("test_create_profile_post - END") - - def test_get_next_dynamic_nic(self): - """ - Tests get next dynamic nic - """ - - LOG.debug("test_get_next_dynamic_nic - START") - dynamic_nic_id = self.ucsm_driver._get_next_dynamic_nic() - self.assertTrue(len(dynamic_nic_id) > 0) - LOG.debug("test_get_next_dynamic_nic - END") diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_configuration.py b/quantum/plugins/cisco/ucs/cisco_ucs_configuration.py index 8c33481542..a4fbec42b1 100644 --- a/quantum/plugins/cisco/ucs/cisco_ucs_configuration.py +++ b/quantum/plugins/cisco/ucs/cisco_ucs_configuration.py @@ -37,3 +37,10 @@ PROFILE_NAME_PREFIX = SECTION['profile_name_prefix'] SECTION = CP['DRIVER'] UCSM_DRIVER = SECTION['name'] + +CONF_FILE = "../conf/ucs_inventory.ini" + +CP = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \ + + "/" + CONF_FILE) + +INVENTORY = CP.walk(CP.dummy) diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_inventory.py b/quantum/plugins/cisco/ucs/cisco_ucs_inventory.py new file mode 100644 index 0000000000..1ed5e347d9 --- /dev/null +++ b/quantum/plugins/cisco/ucs/cisco_ucs_inventory.py @@ -0,0 +1,682 @@ +""" +# 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. +# +""" +from copy import deepcopy +import logging as LOG + +from quantum.common import exceptions as exc +from quantum.plugins.cisco.l2device_inventory_base \ + import L2NetworkDeviceInventoryBase +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_credentials as cred +from quantum.plugins.cisco.common import cisco_exceptions as cexc +from quantum.plugins.cisco.common import cisco_utils as cutil +from quantum.plugins.cisco.db import api as db +from quantum.plugins.cisco.db import ucs_db as udb +from quantum.plugins.cisco.ucs \ + import cisco_ucs_inventory_configuration as conf +from quantum.plugins.cisco.ucs import cisco_ucs_network_driver + +LOG.basicConfig(level=LOG.WARN) +LOG.getLogger(__name__) + +""" +The _inventory data strcuture contains a nested disctioary: + {"UCSM_IP: {"Chassis-ID": [Balde-ID, Blade-ID], + "Chassis-ID": [Blade-ID, Blade-ID, Blade-ID]]}, + "UCSM_IP: {"Chassis-ID": [Balde-ID]} + } +""" +""" +_inventory_state data structure is organized as below: +{ucsm_ip: + {chassis_id: + {blade_id: + {'blade-data': + {blade-dn-1: {blade-intf-data}, + blade-dn-2: {blade-intf-data} + } + } + } + } +} +'blade-data': Blade Data dictionary has the following keys: +=========================================================== +const.BLADE_INTF_DATA: This is a dictionary, with the key as the + dn of the interface, and the value as the + Blade Interface Dictionary described next +const.BLADE_UNRESERVED_INTF_COUNT: Number of unreserved interfaces + on this blade + +'blade-intf-data': Blade Interface dictionary has the following keys: +===================================================================== +const.BLADE_INTF_DN +const.BLADE_INTF_ORDER +const.BLADE_INTF_LINK_STATE +const.BLADE_INTF_OPER_STATE +const.BLADE_INTF_INST_TYPE +const.BLADE_INTF_RHEL_DEVICE_NAME +const.BLADE_INTF_RESERVATION +const.TENANTID +const.PORTID +const.PROFILE_ID +const.INSTANCE_ID +const.VIF_ID +""" + + +class UCSInventory(L2NetworkDeviceInventoryBase): + """ + Manages the state of all the UCS chasses, and blades in + the system + """ + + _inventory = {} + _host_names = {} + _inventory_state = {} + + def __init__(self): + self._client = cisco_ucs_network_driver.CiscoUCSMDriver() + self._load_inventory() + + def _load_inventory(self): + """Load the inventory from a config file""" + inventory = deepcopy(conf.INVENTORY) + LOG.info("Loaded UCS inventory: %s\n" % inventory) + LOG.info("Building UCS inventory state (this may take a while)...") + + for ucsm in inventory.keys(): + ucsm_ip = inventory[ucsm][const.IP_ADDRESS] + inventory[ucsm].pop(const.IP_ADDRESS) + chassis_dict = {} + for chassis in inventory[ucsm].keys(): + chassis_id = inventory[ucsm][chassis][const.CHASSIS_ID] + inventory[ucsm][chassis].pop(const.CHASSIS_ID) + blade_list = [] + for blade in inventory[ucsm][chassis].keys(): + blade_id = \ + inventory[ucsm][chassis][blade][const.BLADE_ID] + host_name = \ + inventory[ucsm][chassis][blade][const.HOST_NAME] + host_key = ucsm_ip + "-" + chassis_id + "-" + blade_id + self._host_names[host_key] = host_name + blade_list.append(blade_id) + chassis_dict[chassis_id] = blade_list + self._inventory[ucsm_ip] = chassis_dict + + self.build_inventory_state() + + def _get_host_name(self, ucsm_ip, chassis_id, blade_id): + """Get the hostname based on the blade info""" + host_key = ucsm_ip + "-" + chassis_id + "-" + blade_id + return self._host_names[host_key] + + def _get_initial_blade_state(self, chassis_id, blade_id, ucsm_ip, + ucsm_username, ucsm_password): + """Get the initial blade state""" + blade_intf_data = self._client.get_blade_data(chassis_id, blade_id, + ucsm_ip, ucsm_username, + ucsm_password) + + unreserved_counter = 0 + + for blade_intf in blade_intf_data.keys(): + dist_name = blade_intf_data[blade_intf][const.BLADE_INTF_DN] + # We first make a pass through the state in UCSM + # If a particular interface is showing as being allocated in + # UCSM then it is definitely being used and so should be + # marked as reserved, else we temporarily mark it as unreserved + # based on the UCSM state, but may later change it if a port + # association is found in the DB + if (blade_intf_data[blade_intf][const.BLADE_INTF_LINK_STATE] == \ + const.BLADE_INTF_STATE_UNALLOCATED or \ + blade_intf_data[blade_intf][const.BLADE_INTF_LINK_STATE] == \ + const.BLADE_INTF_STATE_UNKNOWN) and \ + blade_intf_data[blade_intf][const.BLADE_INTF_OPER_STATE] == \ + const.BLADE_INTF_STATE_UNKNOWN: + blade_intf_data[blade_intf][const.BLADE_INTF_RESERVATION] = \ + const.BLADE_INTF_UNRESERVED + unreserved_counter += 1 + blade_intf_data[blade_intf][const.TENANTID] = None + blade_intf_data[blade_intf][const.PORTID] = None + blade_intf_data[blade_intf][const.PROFILE_ID] = None + blade_intf_data[blade_intf][const.INSTANCE_ID] = None + blade_intf_data[blade_intf][const.VIF_ID] = None + else: + blade_intf_data[blade_intf][const.BLADE_INTF_RESERVATION] = \ + const.BLADE_INTF_RESERVED + + port_binding = udb.get_portbinding_dn(dist_name) + if port_binding: + # We have found a port binding for this interface in the DB, + # so we have earlier marked this interface as unreserved, we + # need to change it, and also load the state from the DB for + # other associations + if blade_intf_data[blade_intf]\ + [const.BLADE_INTF_RESERVATION] == \ + const.BLADE_INTF_UNRESERVED: + unreserved_counter -= 1 + blade_intf_data[blade_intf]\ + [const.BLADE_INTF_RESERVATION] = \ + const.BLADE_INTF_RESERVED + blade_intf_data[blade_intf][const.TENANTID] = \ + port_binding[const.TENANTID] + blade_intf_data[blade_intf][const.PORTID] = \ + port_binding[const.PORTID] + blade_intf_data[blade_intf][const.PROFILE_ID] = \ + port_binding[const.PORTPROFILENAME] + blade_intf_data[blade_intf][const.INSTANCE_ID] = \ + port_binding[const.INSTANCE_ID] + blade_intf_data[blade_intf][const.VIF_ID] = \ + port_binding[const.VIF_ID] + + blade_data = {const.BLADE_INTF_DATA: blade_intf_data, + const.BLADE_UNRESERVED_INTF_COUNT: unreserved_counter} + return blade_data + + def _get_blade_state(self, chassis_id, blade_id, ucsm_ip, + ucsm_username, ucsm_password): + """Get the blade state""" + blade_intf_data = self._client.get_blade_data(chassis_id, blade_id, + ucsm_ip, ucsm_username, + ucsm_password) + unreserved_counter = 0 + + for blade_intf in blade_intf_data.keys(): + if (blade_intf_data[blade_intf][const.BLADE_INTF_LINK_STATE] == \ + const.BLADE_INTF_STATE_UNALLOCATED or \ + blade_intf_data[blade_intf][const.BLADE_INTF_LINK_STATE] == \ + const.BLADE_INTF_STATE_UNKNOWN) and \ + blade_intf_data[blade_intf][const.BLADE_INTF_OPER_STATE] == \ + const.BLADE_INTF_STATE_UNKNOWN: + blade_intf_data[blade_intf][const.BLADE_INTF_RESERVATION] = \ + const.BLADE_INTF_UNRESERVED + unreserved_counter += 1 + else: + blade_intf_data[blade_intf][const.BLADE_INTF_RESERVATION] = \ + const.BLADE_INTF_RESERVED + + blade_data = {const.BLADE_INTF_DATA: blade_intf_data, + const.BLADE_UNRESERVED_INTF_COUNT: unreserved_counter} + return blade_data + + def _get_all_ucsms(self): + """Return a list of the IPs of all the UCSMs in the system""" + return {const.DEVICE_IP: self._inventory.keys()} + + def _get_blade_for_port(self, args): + """ + Return the a dict with IP address of the blade + on which a dynamic vnic was reserved for this port + """ + tenant_id = args[0] + net_id = args[1] + port_id = args[2] + rsvd_info = self.get_rsvd_blade_intf_by_port(tenant_id, port_id) + if not rsvd_info: + raise exc.PortNotFound(net_id=net_id, port_id=port_id) + device_params = {const.DEVICE_IP: [rsvd_info[const.UCSM_IP]]} + return device_params + + def _get_host_name_for_rsvd_intf(self, tenant_id, instance_id): + """ + Return the hostname of the blade with a reserved instance + for this tenant + """ + for ucsm_ip in self._inventory_state.keys(): + ucsm = self._inventory_state[ucsm_ip] + for chassis_id in ucsm.keys(): + for blade_id in ucsm[chassis_id]: + blade_data = ucsm[chassis_id][blade_id] + blade_intf_data = blade_data[const.BLADE_INTF_DATA] + for blade_intf in blade_intf_data.keys(): + tmp = deepcopy(blade_intf_data[blade_intf]) + if blade_intf_data[blade_intf]\ + [const.BLADE_INTF_RESERVATION] == \ + const.BLADE_INTF_RESERVED and \ + blade_intf_data[blade_intf]\ + [const.TENANTID] == tenant_id and \ + blade_intf_data[blade_intf]\ + [const.INSTANCE_ID] == None: + blade_intf_data[blade_intf]\ + [const.INSTANCE_ID] = instance_id + host_name = self._get_host_name(ucsm_ip, + chassis_id, + blade_id) + port_binding = udb.get_portbinding_dn(blade_intf) + port_id = port_binding[const.PORTID] + udb.update_portbinding(port_id, + instance_id=instance_id) + return host_name + LOG.warn("Could not find a reserved dynamic nic for tenant: %s" % + tenant_id) + return None + + def _get_instance_port(self, tenant_id, instance_id, vif_id): + """ + Return the device name for a reserved interface + """ + found_blade_intf_data = None + for ucsm_ip in self._inventory_state.keys(): + ucsm = self._inventory_state[ucsm_ip] + for chassis_id in ucsm.keys(): + for blade_id in ucsm[chassis_id]: + blade_data = ucsm[chassis_id][blade_id] + blade_intf_data = blade_data[const.BLADE_INTF_DATA] + for blade_intf in blade_intf_data.keys(): + if blade_intf_data[blade_intf]\ + [const.BLADE_INTF_RESERVATION] == \ + const.BLADE_INTF_RESERVED and \ + blade_intf_data[blade_intf]\ + [const.TENANTID] == tenant_id and \ + blade_intf_data[blade_intf]\ + [const.INSTANCE_ID] == instance_id: + found_blade_intf_data = blade_intf_data + LOG.debug("Found blade %s associated with this" \ + " instance: %s" % \ + (blade_id, + instance_id)) + break + + if found_blade_intf_data: + blade_intf_data = found_blade_intf_data + for blade_intf in blade_intf_data.keys(): + if blade_intf_data[blade_intf]\ + [const.BLADE_INTF_RESERVATION] == \ + const.BLADE_INTF_RESERVED and \ + blade_intf_data[blade_intf]\ + [const.TENANTID] == tenant_id and \ + (not blade_intf_data[blade_intf][const.VIF_ID]): + blade_intf_data[blade_intf][const.VIF_ID] = \ + vif_id + blade_intf_data[blade_intf]\ + [const.INSTANCE_ID] = instance_id + port_binding = udb.get_portbinding_dn(blade_intf) + port_id = port_binding[const.PORTID] + udb.update_portbinding(port_id, instance_id=instance_id, + vif_id=vif_id) + db.port_set_attachment_by_id(port_id, vif_id) + device_name = blade_intf_data[blade_intf]\ + [const.BLADE_INTF_RHEL_DEVICE_NAME] + profile_name = port_binding[const.PORTPROFILENAME] + dynamicnic_details = \ + {const.DEVICENAME: device_name, + const.UCSPROFILE: profile_name} + LOG.debug("Found reserved dynamic nic: %s" \ + "associated with port %s" % + (blade_intf_data[blade_intf], port_id)) + LOG.debug("Returning dynamic nic details: %s" % + dynamicnic_details) + return dynamicnic_details + + LOG.warn("Could not find a reserved dynamic nic for tenant: %s" % + tenant_id) + return None + + def _disassociate_vifid_from_port(self, tenant_id, port_id): + """ + Return the device name for a reserved interface + """ + for ucsm_ip in self._inventory_state.keys(): + ucsm = self._inventory_state[ucsm_ip] + for chassis_id in ucsm.keys(): + for blade_id in ucsm[chassis_id]: + blade_data = ucsm[chassis_id][blade_id] + blade_intf_data = blade_data[const.BLADE_INTF_DATA] + for blade_intf in blade_intf_data.keys(): + if blade_intf_data[blade_intf]\ + [const.BLADE_INTF_RESERVATION] == \ + const.BLADE_INTF_RESERVED and \ + blade_intf_data[blade_intf]\ + [const.TENANTID] == tenant_id and \ + blade_intf_data[blade_intf][const.PORTID] == \ + port_id: + vif_id = blade_intf_data[blade_intf][const.VIF_ID] + blade_intf_data[blade_intf][const.VIF_ID] = \ + None + blade_intf_data[blade_intf][const.INSTANCE_ID] = \ + None + udb.update_portbinding(port_id, instance_id=None, + vif_id=None) + LOG.debug("Disassociated VIF-ID: %s " \ + "from port: %s" \ + "in UCS inventory state for blade: %s" % + (vif_id, port_id, + blade_intf_data[blade_intf])) + return + LOG.warn("Disassociating VIF-ID %s in UCS inventory failed. " \ + "Could not find a reserved dynamic nic for tenant: %s" % + (vif_id, tenant_id)) + return None + + def reload_inventory(self): + """Reload the inventory from a conf file""" + self._load_inventory() + + def build_inventory_state(self): + """Populate the state of all the blades""" + for ucsm_ip in self._inventory.keys(): + self._inventory_state[ucsm_ip] = {ucsm_ip: {}} + ucsm_username = cred.Store.getUsername(ucsm_ip) + ucsm_password = cred.Store.getPassword(ucsm_ip) + chasses_state = {} + self._inventory_state[ucsm_ip] = chasses_state + ucsm = self._inventory[ucsm_ip] + for chassis_id in ucsm.keys(): + blades_dict = {} + chasses_state[chassis_id] = blades_dict + for blade_id in ucsm[chassis_id]: + blade_data = self._get_initial_blade_state(chassis_id, + blade_id, + ucsm_ip, + ucsm_username, + ucsm_password) + blades_dict[blade_id] = blade_data + + LOG.debug("UCS Inventory state is: %s\n" % self._inventory_state) + return True + + def get_least_reserved_blade(self): + """Return the blade with least number of dynamic nics reserved""" + unreserved_interface_count = 0 + least_reserved_blade_ucsm = None + least_reserved_blade_chassis = None + least_reserved_blade_id = None + least_reserved_blade_data = None + + for ucsm_ip in self._inventory_state.keys(): + ucsm = self._inventory_state[ucsm_ip] + for chassis_id in ucsm.keys(): + for blade_id in ucsm[chassis_id]: + blade_data = ucsm[chassis_id][blade_id] + if blade_data[const.BLADE_UNRESERVED_INTF_COUNT] > \ + unreserved_interface_count: + unreserved_interface_count = \ + blade_data[const.BLADE_UNRESERVED_INTF_COUNT] + least_reserved_blade_ucsm = ucsm_ip + least_reserved_blade_chassis = chassis_id + least_reserved_blade_id = blade_id + least_reserved_blade_data = blade_data + + if unreserved_interface_count == 0: + LOG.warn("No more dynamic nics available for reservation") + return False + + least_reserved_blade_dict = \ + {const.LEAST_RSVD_BLADE_UCSM: least_reserved_blade_ucsm, + const.LEAST_RSVD_BLADE_CHASSIS: least_reserved_blade_chassis, + const.LEAST_RSVD_BLADE_ID: least_reserved_blade_id, + const.LEAST_RSVD_BLADE_DATA: least_reserved_blade_data} + LOG.debug("Found dynamic nic %s available for reservation", + least_reserved_blade_dict) + return least_reserved_blade_dict + + def reserve_blade_interface(self, ucsm_ip, chassis_id, blade_id, + blade_data_dict, tenant_id, port_id, + portprofile_name): + """Reserve an interface on a blade""" + ucsm_username = cred.Store.getUsername(ucsm_ip) + ucsm_password = cred.Store.getPassword(ucsm_ip) + """ + We are first getting the updated blade interface state + """ + blade_data = self._get_blade_state(chassis_id, blade_id, ucsm_ip, + ucsm_username, ucsm_password) + blade_intf_data = blade_data[const.BLADE_INTF_DATA] + old_blade_intf_data = blade_data_dict[const.BLADE_INTF_DATA] + + """ + We will now copy the older blade interface state + """ + for blade_intf in blade_intf_data.keys(): + blade_intf_data[blade_intf][const.BLADE_INTF_RESERVATION] = \ + old_blade_intf_data[blade_intf]\ + [const.BLADE_INTF_RESERVATION] + blade_intf_data[blade_intf][const.TENANTID] = \ + old_blade_intf_data[blade_intf][const.TENANTID] + blade_intf_data[blade_intf][const.PORTID] = \ + old_blade_intf_data[blade_intf][const.PORTID] + blade_intf_data[blade_intf][const.PROFILE_ID] = \ + old_blade_intf_data[blade_intf][const.PROFILE_ID] + blade_intf_data[blade_intf][const.INSTANCE_ID] = \ + old_blade_intf_data[blade_intf][const.INSTANCE_ID] + blade_intf_data[blade_intf][const.VIF_ID] = \ + old_blade_intf_data[blade_intf][const.VIF_ID] + + blade_data[const.BLADE_UNRESERVED_INTF_COUNT] = \ + blade_data_dict[const.BLADE_UNRESERVED_INTF_COUNT] + """ + Now we will reserve an interface if its available + """ + for blade_intf in blade_intf_data.keys(): + if blade_intf_data[blade_intf][const.BLADE_INTF_RESERVATION] == \ + const.BLADE_INTF_UNRESERVED: + blade_intf_data[blade_intf][const.BLADE_INTF_RESERVATION] = \ + const.BLADE_INTF_RESERVED + blade_intf_data[blade_intf][const.TENANTID] = tenant_id + blade_intf_data[blade_intf][const.PORTID] = port_id + #blade_intf_data[blade_intf][const.PROFILE_ID] = \ + # portprofile_name + blade_intf_data[blade_intf][const.INSTANCE_ID] = None + dev_eth_name = blade_intf_data[blade_intf] \ + [const.BLADE_INTF_RHEL_DEVICE_NAME] + """ + We are replacing the older blade interface state with new + """ + self._inventory_state[ucsm_ip][chassis_id][blade_id] \ + [const.BLADE_INTF_DATA] = blade_intf_data + self._inventory_state[ucsm_ip][chassis_id][blade_id] \ + [const.BLADE_UNRESERVED_INTF_COUNT] -= 1 + host_name = self._get_host_name(ucsm_ip, chassis_id, + blade_id) + reserved_nic_dict = {const.RESERVED_NIC_HOSTNAME: host_name, + const.RESERVED_NIC_NAME: dev_eth_name, + const.BLADE_INTF_DN: blade_intf} + port_binding = udb.add_portbinding(port_id, blade_intf, None, + None, None, None) + udb.update_portbinding(port_id, + tenant_id=blade_intf_data[blade_intf]\ + [const.TENANTID]) + LOG.debug("Reserved blade interface: %s\n" % reserved_nic_dict) + return reserved_nic_dict + + LOG.warn("Dynamic nic %s could not be reserved for port-id: %s" % + (blade_data_dict, port_id)) + return False + + def unreserve_blade_interface(self, ucsm_ip, chassis_id, blade_id, + interface_dn): + """Unreserve a previously reserved interface on a blade""" + ucsm_username = cred.Store.getUsername(ucsm_ip) + ucsm_password = cred.Store.getPassword(ucsm_ip) + self._inventory_state[ucsm_ip][chassis_id][blade_id] \ + [const.BLADE_UNRESERVED_INTF_COUNT] += 1 + blade_intf = self._inventory_state[ucsm_ip][chassis_id]\ + [blade_id][const.BLADE_INTF_DATA][interface_dn] + blade_intf[const.BLADE_INTF_RESERVATION] = const.BLADE_INTF_UNRESERVED + blade_intf[const.TENANTID] = None + blade_intf[const.PORTID] = None + blade_intf[const.PROFILE_ID] = None + blade_intf[const.INSTANCE_ID] = None + blade_intf[const.VIF_ID] = None + LOG.debug("Unreserved blade interface %s\n" % interface_dn) + + def get_rsvd_blade_intf_by_port(self, tenant_id, port_id): + """ + Lookup a reserved blade interface based on tenant_id and port_id + and return the blade interface info + """ + for ucsm_ip in self._inventory_state.keys(): + ucsm = self._inventory_state[ucsm_ip] + for chassis_id in ucsm.keys(): + for blade_id in ucsm[chassis_id]: + blade_data = ucsm[chassis_id][blade_id] + blade_intf_data = blade_data[const.BLADE_INTF_DATA] + for blade_intf in blade_intf_data.keys(): + if not blade_intf_data[blade_intf][const.PORTID] or \ + not blade_intf_data[blade_intf][const.TENANTID]: + continue + if blade_intf_data[blade_intf]\ + [const.BLADE_INTF_RESERVATION] == \ + const.BLADE_INTF_RESERVED and \ + blade_intf_data[blade_intf]\ + [const.TENANTID] == tenant_id and \ + blade_intf_data[blade_intf]\ + [const.PORTID] == port_id: + interface_dn = blade_intf_data[blade_intf]\ + [const.BLADE_INTF_DN] + blade_intf_info = {const.UCSM_IP: ucsm_ip, + const.CHASSIS_ID: chassis_id, + const.BLADE_ID: blade_id, + const.BLADE_INTF_DN: + interface_dn} + return blade_intf_info + LOG.warn("Could not find a reserved nic for tenant: %s port: %s" % + (tenant_id, port_id)) + return None + + def add_blade(self, ucsm_ip, chassis_id, blade_id): + """Add a blade to the inventory""" + # TODO (Sumit) + pass + + def get_all_networks(self, args): + """Return all UCSM IPs""" + LOG.debug("get_all_networks() called\n") + return self._get_all_ucsms() + + def create_network(self, args): + """Return all UCSM IPs""" + LOG.debug("create_network() called\n") + return self._get_all_ucsms() + + def delete_network(self, args): + """Return all UCSM IPs""" + LOG.debug("delete_network() called\n") + return self._get_all_ucsms() + + def get_network_details(self, args): + """Return all UCSM IPs""" + LOG.debug("get_network_details() called\n") + return self._get_all_ucsms() + + def rename_network(self, args): + """Return all UCSM IPs""" + LOG.debug("rename_network() called\n") + return self._get_all_ucsms() + + def get_all_ports(self, args): + """Return all UCSM IPs""" + LOG.debug("get_all_ports() called\n") + return self._get_all_ucsms() + + def create_port(self, args): + """ + Return the a dict with information of the blade + on which a dynamic vnic is available + """ + LOG.debug("create_port() called\n") + least_reserved_blade_dict = self.get_least_reserved_blade() + if not least_reserved_blade_dict: + raise cexc.NoMoreNics() + ucsm_ip = least_reserved_blade_dict[const.LEAST_RSVD_BLADE_UCSM] + device_params = {const.DEVICE_IP: [ucsm_ip], + const.UCS_INVENTORY: self, + const.LEAST_RSVD_BLADE_DICT:\ + least_reserved_blade_dict} + return device_params + + def delete_port(self, args): + """ + Return the a dict with information of the blade + on which a dynamic vnic was reserved for this port + """ + LOG.debug("delete_port() called\n") + tenant_id = args[0] + net_id = args[1] + port_id = args[2] + rsvd_info = self.get_rsvd_blade_intf_by_port(tenant_id, port_id) + if not rsvd_info: + raise exc.PortNotFound(net_id=net_id, port_id=port_id) + device_params = \ + {const.DEVICE_IP: [rsvd_info[const.UCSM_IP]], + const.UCS_INVENTORY: self, + const.CHASSIS_ID: rsvd_info[const.CHASSIS_ID], + const.BLADE_ID: rsvd_info[const.BLADE_ID], + const.BLADE_INTF_DN: rsvd_info[const.BLADE_INTF_DN]} + return device_params + + def update_port(self, args): + """ + Return the a dict with IP address of the blade + on which a dynamic vnic was reserved for this port + """ + LOG.debug("update_port() called\n") + return self._get_blade_for_port(args) + + def get_port_details(self, args): + """ + Return the a dict with IP address of the blade + on which a dynamic vnic was reserved for this port + """ + LOG.debug("get_port_details() called\n") + return self._get_blade_for_port(args) + + def plug_interface(self, args): + """ + Return the a dict with IP address of the blade + on which a dynamic vnic was reserved for this port + """ + LOG.debug("plug_interface() called\n") + return self._get_blade_for_port(args) + + def unplug_interface(self, args): + """ + Return the a dict with IP address of the blade + on which a dynamic vnic was reserved for this port + """ + LOG.debug("unplug_interface() called\n") + tenant_id = args[0] + port_id = args[2] + self._disassociate_vifid_from_port(tenant_id, port_id) + return self._get_blade_for_port(args) + + def schedule_host(self, args): + """Provides the hostname on which a dynamic vnic is reserved""" + LOG.debug("schedule_host() called\n") + instance_id = args[1] + tenant_id = args[2][const.PROJECT_ID] + host_name = self._get_host_name_for_rsvd_intf(tenant_id, instance_id) + host_list = {const.HOST_LIST: {const.HOST_1: host_name}} + LOG.debug("host_list is: %s" % host_list) + return host_list + + def associate_port(self, args): + """ + Get the portprofile name and the device name for the dynamic vnic + """ + LOG.debug("associate_port() called\n") + instance_id = args[1] + tenant_id = args[2][const.PROJECT_ID] + vif_id = args[2][const.VIF_ID] + vif_info = self._get_instance_port(tenant_id, instance_id, vif_id) + vif_desc = {const.VIF_DESC: vif_info} + LOG.debug("vif_desc is: %s" % vif_desc) + return vif_desc diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_inventory_configuration.py b/quantum/plugins/cisco/ucs/cisco_ucs_inventory_configuration.py new file mode 100644 index 0000000000..3183658c36 --- /dev/null +++ b/quantum/plugins/cisco/ucs/cisco_ucs_inventory_configuration.py @@ -0,0 +1,31 @@ +""" +# 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 os + +from quantum.plugins.cisco.common import cisco_configparser as confp + +CONF_FILE = "../conf/ucs_inventory.ini" + +CP = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \ + + "/" + CONF_FILE) + +INVENTORY = CP.walk(CP.dummy) diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py b/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py index 439d447581..6a4f691f54 100644 --- a/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py +++ b/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py @@ -41,6 +41,9 @@ PROFILE_CLIENT = "profileclient_placeholder" VLAN_NAME = "vlanname_placeholder" VLAN_ID = "vlanid_placeholder" OLD_VLAN_NAME = "old_vlanname_placeholder" +BLADE_VALUE = "blade_number_placeholder" +BLADE_DN_VALUE = "blade_dn_placeholder" +CHASSIS_VALUE = "chassis_number_placeholder" DYNAMIC_NIC_PREFIX = "eth" # The following are standard strings, messages used to communicate with UCSM, @@ -112,6 +115,27 @@ DELETE_PROFILE = " " \ " " +GET_BLADE_INTERFACE_STATE = " " + \ + " " + +GET_BLADE_INTERFACE = "" + \ + " " + \ + " " + +# TODO (Sumit): Assumes "adaptor-1", check if this has to be discovered too +GET_BLADE_INTERFACES = " " + \ + " " + class CiscoUCSMDriver(): """UCSM Driver""" @@ -127,29 +151,31 @@ class CiscoUCSMDriver(): conn.request(METHOD, URL, login_data, HEADERS) response = conn.getresponse() response_data = response.read() - LOG.debug(response.status) - LOG.debug(response.reason) - LOG.debug(response_data) + #LOG.debug(response.status) + #LOG.debug(response.reason) + #LOG.debug(response_data) # TODO (Sumit): If login is not successful, throw exception xml_tree = et.XML(response_data) cookie = xml_tree.attrib["outCookie"] data = data.replace(COOKIE_VALUE, cookie) - LOG.debug("POST: %s" % data) + #LOG.debug("POST: %s" % data) conn.request(METHOD, URL, data, HEADERS) response = conn.getresponse() response_data = response.read() - LOG.debug(response.status) - LOG.debug(response.reason) - LOG.debug("UCSM Response: %s" % response_data) + #LOG.debug(response.status) + #LOG.debug(response.reason) + #LOG.debug("UCSM Response: %s" % response_data) + post_data_response = response_data logout_data = "" conn.request(METHOD, URL, logout_data, HEADERS) response = conn.getresponse() response_data = response.read() - LOG.debug(response.status) - LOG.debug(response.reason) - LOG.debug(response_data) + #LOG.debug(response.status) + #LOG.debug(response.reason) + #LOG.debug(response_data) + return post_data_response def _create_vlan_post_data(self, vlan_name, vlan_id): """Create command""" @@ -188,13 +214,61 @@ class CiscoUCSMDriver(): data = DELETE_PROFILE.replace(PROFILE_NAME, profile_name) return data - def _get_next_dynamic_nic(self): - """Get an avaialble dynamic nic on the host""" - dynamic_nic_id = gvif.get_next_dynic() - if len(dynamic_nic_id) > 0: - return dynamic_nic_id - else: - raise cexc.NoMoreNics() + def _get_blade_interfaces_post_data(self, chassis_number, blade_number): + """Create command""" + data = GET_BLADE_INTERFACES.replace(CHASSIS_VALUE, chassis_number) + data = data.replace(BLADE_VALUE, blade_number) + return data + + def _get_blade_intf_st_post_data(self, blade_dn): + """Create command""" + data = GET_BLADE_INTERFACE_STATE.replace(BLADE_DN_VALUE, blade_dn) + return data + + def _get_blade_interfaces(self, chassis_number, blade_number, ucsm_ip, + ucsm_username, ucsm_password): + """Create command""" + data = self._get_blade_interfaces_post_data(chassis_number, + blade_number) + response = self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) + elements = \ + et.XML(response).find("outConfigs").findall("adaptorHostEthIf") + blade_interfaces = {} + for element in elements: + dist_name = element.get("dn", default=None) + if dist_name: + order = element.get("order", default=None) + blade_interface = {const.BLADE_INTF_DN: dist_name, + const.BLADE_INTF_ORDER: order, + const.BLADE_INTF_LINK_STATE: None, + const.BLADE_INTF_OPER_STATE: None, + const.BLADE_INTF_INST_TYPE: None, + const.BLADE_INTF_RHEL_DEVICE_NAME: + self._get_rhel_device_name(order)} + blade_interfaces[dist_name] = blade_interface + + return blade_interfaces + + def _get_blade_interface_state(self, blade_intf, ucsm_ip, + ucsm_username, ucsm_password): + """Create command""" + data = \ + self._get_blade_intf_st_post_data(blade_intf[const.BLADE_INTF_DN]) + response = self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) + elements = \ + et.XML(response).find("outConfigs").findall("dcxVIf") + for element in elements: + blade_intf[const.BLADE_INTF_LINK_STATE] = element.get("linkState", + default=None) + blade_intf[const.BLADE_INTF_OPER_STATE] = element.get("operState", + default=None) + blade_intf[const.BLADE_INTF_INST_TYPE] = element.get("instType", + default=None) + + def _get_rhel_device_name(self, order): + """Get the device name as on the RHEL host""" + device_name = const.RHEL_DEVICE_NAME_REPFIX + str(int(order) - 1) + return device_name def create_vlan(self, vlan_name, vlan_id, ucsm_ip, ucsm_username, ucsm_password): @@ -220,19 +294,26 @@ class CiscoUCSMDriver(): new_vlan_name) self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) - def get_dynamic_nic(self, host): - """Get an avaialble dynamic nic on the host""" - # TODO (Sumit): Check availability per host - # TODO (Sumit): If not available raise exception - # TODO (Sumit): This simple logic assumes that create-port and - # spawn-VM happens in lock-step - # But we should support multiple create-port calls, - # followed by spawn-VM calls - # That would require managing a pool of available - # dynamic vnics per host - dynamic_nic_name = self._get_next_dynamic_nic() - LOG.debug("Reserving dynamic nic %s" % dynamic_nic_name) - return dynamic_nic_name + def get_blade_data(self, chassis_number, blade_number, + ucsm_ip, ucsm_username, + ucsm_password): + """ + Returns only the dynamic interfaces on the blade + """ + blade_interfaces = self._get_blade_interfaces(chassis_number, + blade_number, + ucsm_ip, + ucsm_username, + ucsm_password) + for blade_intf in blade_interfaces.keys(): + self._get_blade_interface_state(blade_interfaces[blade_intf], + ucsm_ip, ucsm_username, + ucsm_password) + if blade_interfaces[blade_intf][const.BLADE_INTF_INST_TYPE] != \ + const.BLADE_INTF_DYNAMIC: + blade_interfaces.pop(blade_intf) + + return blade_interfaces def delete_vlan(self, vlan_name, ucsm_ip, ucsm_username, ucsm_password): """Create request for UCSM""" @@ -244,8 +325,3 @@ class CiscoUCSMDriver(): """Create request for UCSM""" data = self._delete_profile_post_data(profile_name) self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) - - def release_dynamic_nic(self, host): - """Release a reserved dynamic nic on the host""" - # TODO (Sumit): Release on a specific host - pass diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_plugin.py b/quantum/plugins/cisco/ucs/cisco_ucs_plugin.py index a867304ab4..6fe99f39be 100644 --- a/quantum/plugins/cisco/ucs/cisco_ucs_plugin.py +++ b/quantum/plugins/cisco/ucs/cisco_ucs_plugin.py @@ -27,6 +27,9 @@ from quantum.plugins.cisco.common import cisco_exceptions as cexc from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_credentials as cred from quantum.plugins.cisco.common import cisco_utils as cutil +from quantum.plugins.cisco.db import api as db +from quantum.plugins.cisco.db import l2network_db as cdb +from quantum.plugins.cisco.db import ucs_db as udb from quantum.plugins.cisco.l2device_plugin_base import L2DevicePluginBase from quantum.plugins.cisco.ucs import cisco_ucs_configuration as conf @@ -36,16 +39,10 @@ LOG.getLogger(const.LOGGER_COMPONENT_NAME) class UCSVICPlugin(L2DevicePluginBase): """UCS Device Plugin""" - _networks = {} def __init__(self): - self._client = utils.import_object(conf.UCSM_DRIVER) + self._driver = utils.import_object(conf.UCSM_DRIVER) LOG.debug("Loaded driver %s\n" % conf.UCSM_DRIVER) - self._utils = cutil.DBUtils() - # TODO (Sumit) This is for now, when using only one chassis - self._ucsm_ip = conf.UCSM_IP_ADDRESS - self._ucsm_username = cred.Store.getUsername(conf.UCSM_IP_ADDRESS) - self._ucsm_password = cred.Store.getPassword(conf.UCSM_IP_ADDRESS) # TODO (Sumit) Make the counter per UCSM self._port_profile_counter = 0 @@ -56,7 +53,16 @@ class UCSVICPlugin(L2DevicePluginBase): the specified tenant. """ LOG.debug("UCSVICPlugin:get_all_networks() called\n") - return self._networks.values() + self._set_ucsm(kwargs[const.DEVICE_IP]) + networks_list = db.network_list(tenant_id) + new_networks_list = [] + for network in networks_list: + new_network_dict = cutil.make_net_dict(network[const.UUID], + network[const.NETWORKNAME], + []) + new_networks_list.append(new_network_dict) + + return new_networks_list def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id, **kwargs): @@ -65,15 +71,15 @@ class UCSVICPlugin(L2DevicePluginBase): a symbolic name. """ LOG.debug("UCSVICPlugin:create_network() called\n") - self._client.create_vlan(vlan_name, str(vlan_id), self._ucsm_ip, + self._set_ucsm(kwargs[const.DEVICE_IP]) + self._driver.create_vlan(vlan_name, str(vlan_id), self._ucsm_ip, self._ucsm_username, self._ucsm_password) - new_net_dict = {const.NET_ID: net_id, - const.NET_NAME: net_name, - const.NET_PORTS: {}, - const.NET_VLAN_NAME: vlan_name, - const.NET_VLAN_ID: vlan_id} - self._networks[net_id] = new_net_dict - return new_net_dict + network = db.network_get(net_id) + ports_on_net = [] + new_network_dict = cutil.make_net_dict(network[const.UUID], + network[const.NETWORKNAME], + ports_on_net) + return new_network_dict def delete_network(self, tenant_id, net_id, **kwargs): """ @@ -81,17 +87,16 @@ class UCSVICPlugin(L2DevicePluginBase): belonging to the specified tenant. """ LOG.debug("UCSVICPlugin:delete_network() called\n") - net = self._networks.get(net_id) - # TODO (Sumit) : Verify that no attachments are plugged into the - # network - if net: - # TODO (Sumit) : Before deleting the network, make sure all the - # ports associated with this network are also deleted - self._client.delete_vlan(net[const.NET_VLAN_NAME], self._ucsm_ip, - self._ucsm_username, self._ucsm_password) - self._networks.pop(net_id) - return net - raise exc.NetworkNotFound(net_id=net_id) + self._set_ucsm(kwargs[const.DEVICE_IP]) + net = db.network_get(net_id) + vlan_binding = cdb.get_vlan_binding(net[const.UUID]) + vlan_name = vlan_binding[const.VLANNAME] + self._driver.delete_vlan(vlan_name, self._ucsm_ip, + self._ucsm_username, self._ucsm_password) + net_dict = cutil.make_net_dict(net[const.UUID], + net[const.NETWORKNAME], + []) + return net_dict def get_network_details(self, tenant_id, net_id, **kwargs): """ @@ -99,8 +104,22 @@ class UCSVICPlugin(L2DevicePluginBase): spec """ LOG.debug("UCSVICPlugin:get_network_details() called\n") - network = self._get_network(tenant_id, net_id) - return network + self._set_ucsm(kwargs[const.DEVICE_IP]) + network = db.network_get(net_id) + ports_list = network[const.NETWORKPORTS] + ports_on_net = [] + for port in ports_list: + new_port = cutil.make_port_dict(port[const.UUID], + port[const.PORTSTATE], + port[const.NETWORKID], + port[const.INTERFACEID]) + ports_on_net.append(new_port) + + new_network = cutil.make_net_dict(network[const.UUID], + network[const.NETWORKNAME], + ports_on_net) + + return new_network def rename_network(self, tenant_id, net_id, new_name, **kwargs): """ @@ -108,9 +127,12 @@ class UCSVICPlugin(L2DevicePluginBase): Virtual Network. """ LOG.debug("UCSVICPlugin:rename_network() called\n") - network = self._get_network(tenant_id, net_id) - network[const.NET_NAME] = new_name - return network + self._set_ucsm(kwargs[const.DEVICE_IP]) + network = db.network_get(net_id) + net_dict = cutil.make_net_dict(network[const.UUID], + network[const.NETWORKNAME], + []) + return net_dict def get_all_ports(self, tenant_id, net_id, **kwargs): """ @@ -118,8 +140,14 @@ class UCSVICPlugin(L2DevicePluginBase): specified Virtual Network. """ LOG.debug("UCSVICPlugin:get_all_ports() called\n") - network = self._get_network(tenant_id, net_id) - ports_on_net = network[const.NET_PORTS].values() + self._set_ucsm(kwargs[const.DEVICE_IP]) + network = db.network_get(net_id) + ports_list = network[const.NETWORKPORTS] + ports_on_net = [] + for port in ports_list: + port_binding = udb.get_portbinding(port[const.UUID]) + ports_on_net.append(port_binding) + return ports_on_net def create_port(self, tenant_id, net_id, port_state, port_id, **kwargs): @@ -127,28 +155,29 @@ class UCSVICPlugin(L2DevicePluginBase): Creates a port on the specified Virtual Network. """ LOG.debug("UCSVICPlugin:create_port() called\n") - net = self._get_network(tenant_id, net_id) - ports = net[const.NET_PORTS] - # TODO (Sumit): This works on a single host deployment, - # in multi-host environment, dummy needs to be replaced with the - # hostname - dynamic_nic_name = self._client.get_dynamic_nic("dummy") + self._set_ucsm(kwargs[const.DEVICE_IP]) + qos = None + ucs_inventory = kwargs[const.UCS_INVENTORY] + least_rsvd_blade_dict = kwargs[const.LEAST_RSVD_BLADE_DICT] + chassis_id = least_rsvd_blade_dict[const.LEAST_RSVD_BLADE_CHASSIS] + blade_id = least_rsvd_blade_dict[const.LEAST_RSVD_BLADE_ID] + blade_data_dict = least_rsvd_blade_dict[const.LEAST_RSVD_BLADE_DATA] new_port_profile = self._create_port_profile(tenant_id, net_id, port_id, conf.DEFAULT_VLAN_NAME, conf.DEFAULT_VLAN_ID) profile_name = new_port_profile[const.PROFILE_NAME] - sql_query = "INSERT INTO ports (port_id, profile_name, dynamic_vnic," \ - "host, instance_name, instance_nic_name, used) VALUES" \ - "('%s', '%s', '%s', 'dummy', NULL, NULL, 0)" % \ - (port_id, profile_name, dynamic_nic_name) - self._utils.execute_db_query(sql_query) - new_port_dict = {const.PORT_ID: port_id, - const.PORT_STATE: const.PORT_UP, - const.ATTACHMENT: None, - const.PORT_PROFILE: new_port_profile} - ports[port_id] = new_port_dict - return new_port_dict + rsvd_nic_dict = ucs_inventory.\ + reserve_blade_interface(self._ucsm_ip, chassis_id, + blade_id, blade_data_dict, + tenant_id, port_id, + profile_name) + port_binding = udb.update_portbinding(port_id, + portprofile_name=profile_name, + vlan_name=conf.DEFAULT_VLAN_NAME, + vlan_id=conf.DEFAULT_VLAN_ID, + qos=qos) + return port_binding def delete_port(self, tenant_id, net_id, port_id, **kwargs): """ @@ -158,34 +187,25 @@ class UCSVICPlugin(L2DevicePluginBase): then the port can be deleted. """ LOG.debug("UCSVICPlugin: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._client.release_dynamic_nic("dummy") - port_profile = port[const.PORT_PROFILE] - self._delete_port_profile(port_id, - port_profile[const.PROFILE_NAME]) - sql_query = "delete from ports where port_id = \"%s\"" % \ - (port[const.PORT_ID]) - self._utils.execute_db_query(sql_query) - 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) + self._set_ucsm(kwargs[const.DEVICE_IP]) + ucs_inventory = kwargs[const.UCS_INVENTORY] + chassis_id = kwargs[const.CHASSIS_ID] + blade_id = kwargs[const.BLADE_ID] + interface_dn = kwargs[const.BLADE_INTF_DN] + port_binding = udb.get_portbinding(port_id) + profile_name = port_binding[const.PORTPROFILENAME] + self._delete_port_profile(port_id, profile_name) + ucs_inventory.unreserve_blade_interface(self._ucsm_ip, chassis_id, + blade_id, interface_dn) + return udb.remove_portbinding(port_id) def update_port(self, tenant_id, net_id, port_id, port_state, **kwargs): """ Updates the state of a port on the specified Virtual Network. """ LOG.debug("UCSVICPlugin:update_port() called\n") - port = self._get_port(tenant_id, net_id, port_id) - self._validate_port_state(port_state) - port[const.PORT_STATE] = port_state - return port + self._set_ucsm(kwargs[const.DEVICE_IP]) + pass def get_port_details(self, tenant_id, net_id, port_id, **kwargs): """ @@ -193,7 +213,9 @@ class UCSVICPlugin(L2DevicePluginBase): that is attached to this particular port. """ LOG.debug("UCSVICPlugin:get_port_details() called\n") - return self._get_port(tenant_id, net_id, port_id) + self._set_ucsm(kwargs[const.DEVICE_IP]) + port_binding = udb.get_portbinding(port_id) + return port_binding def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id, **kwargs): @@ -202,24 +224,18 @@ class UCSVICPlugin(L2DevicePluginBase): specified Virtual Network. """ LOG.debug("UCSVICPlugin: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]) - port[const.ATTACHMENT] = remote_interface_id - port_profile = port[const.PORT_PROFILE] - profile_name = port_profile[const.PROFILE_NAME] - old_vlan_name = port_profile[const.PROFILE_VLAN_NAME] + self._set_ucsm(kwargs[const.DEVICE_IP]) + port_binding = udb.get_portbinding(port_id) + profile_name = port_binding[const.PORTPROFILENAME] + old_vlan_name = port_binding[const.VLANNAME] new_vlan_name = self._get_vlan_name_for_network(tenant_id, net_id) new_vlan_id = self._get_vlan_id_for_network(tenant_id, net_id) - self._client.change_vlan_in_profile(profile_name, old_vlan_name, + self._driver.change_vlan_in_profile(profile_name, old_vlan_name, new_vlan_name, self._ucsm_ip, self._ucsm_username, self._ucsm_password) - port_profile[const.PROFILE_VLAN_NAME] = new_vlan_name - port_profile[const.PROFILE_VLAN_ID] = new_vlan_id + return udb.update_portbinding(port_id, vlan_name=new_vlan_name, + vlan_id=new_vlan_id) def unplug_interface(self, tenant_id, net_id, port_id, **kwargs): """ @@ -227,18 +243,17 @@ class UCSVICPlugin(L2DevicePluginBase): specified Virtual Network. """ LOG.debug("UCSVICPlugin:unplug_interface() called\n") - port = self._get_port(tenant_id, net_id, port_id) - port[const.ATTACHMENT] = None - port_profile = port[const.PORT_PROFILE] - profile_name = port_profile[const.PROFILE_NAME] - old_vlan_name = port_profile[const.PROFILE_VLAN_NAME] + self._set_ucsm(kwargs[const.DEVICE_IP]) + port_binding = udb.get_portbinding(port_id) + profile_name = port_binding[const.PORTPROFILENAME] + old_vlan_name = port_binding[const.VLANNAME] new_vlan_name = conf.DEFAULT_VLAN_NAME - self._client.change_vlan_in_profile(profile_name, old_vlan_name, + self._driver.change_vlan_in_profile(profile_name, old_vlan_name, new_vlan_name, self._ucsm_ip, self._ucsm_username, self._ucsm_password) - port_profile[const.PROFILE_VLAN_NAME] = conf.DEFAULT_VLAN_NAME - port_profile[const.PROFILE_VLAN_ID] = conf.DEFAULT_VLAN_ID + return udb.update_portbinding(port_id, vlan_name=new_vlan_name, + vlan_id=conf.DEFAULT_VLAN_ID) def _get_profile_name(self, port_id): """Returns the port profile name based on the port UUID""" @@ -246,48 +261,15 @@ class UCSVICPlugin(L2DevicePluginBase): + cutil.get16ByteUUID(port_id) return profile_name - def _validate_port_state(self, port_state): - """Check the 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): - """Check if the VIF can be attached""" - 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.PortInUse(net_id=network_id, - port_id=port_id, - att_id=port[const.ATTACHMENT]) - - def _get_network(self, tenant_id, network_id): - """Get the network object ref""" - network = self._networks.get(network_id) - if not network: - raise exc.NetworkNotFound(net_id=network_id) - return network - def _get_vlan_name_for_network(self, tenant_id, network_id): """Return the VLAN name as set by the L2 network plugin""" - net = self._get_network(tenant_id, network_id) - vlan_name = net[const.NET_VLAN_NAME] - return vlan_name + vlan_binding = cdb.get_vlan_binding(network_id) + return vlan_binding[const.VLANNAME] def _get_vlan_id_for_network(self, tenant_id, network_id): """Return the VLAN id as set by the L2 network plugin""" - net = self._get_network(tenant_id, network_id) - vlan_id = net[const.NET_VLAN_ID] - return vlan_id - - def _get_port(self, tenant_id, network_id, port_id): - """Get the port object ref""" - 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 + vlan_binding = cdb.get_vlan_binding(network_id) + return vlan_binding[const.VLANID] def _create_port_profile(self, tenant_id, net_id, port_id, vlan_name, vlan_id): @@ -295,7 +277,7 @@ class UCSVICPlugin(L2DevicePluginBase): if self._port_profile_counter >= int(conf.MAX_UCSM_PORT_PROFILES): raise cexc.UCSMPortProfileLimit(net_id=net_id, port_id=port_id) profile_name = self._get_profile_name(port_id) - self._client.create_profile(profile_name, vlan_name, self._ucsm_ip, + self._driver.create_profile(profile_name, vlan_name, self._ucsm_ip, self._ucsm_username, self._ucsm_password) self._port_profile_counter += 1 new_port_profile = {const.PROFILE_NAME: profile_name, @@ -305,6 +287,12 @@ class UCSVICPlugin(L2DevicePluginBase): def _delete_port_profile(self, port_id, profile_name): """Delete port profile in UCSM""" - self._client.delete_profile(profile_name, self._ucsm_ip, + self._driver.delete_profile(profile_name, self._ucsm_ip, self._ucsm_username, self._ucsm_password) self._port_profile_counter -= 1 + + def _set_ucsm(self, ucsm_ip): + """Set the UCSM IP, username, and password""" + self._ucsm_ip = ucsm_ip + self._ucsm_username = cred.Store.getUsername(conf.UCSM_IP_ADDRESS) + self._ucsm_password = cred.Store.getPassword(conf.UCSM_IP_ADDRESS) diff --git a/tests/unit/client_tools/stubs.py b/tests/unit/client_tools/stubs.py index 081436bd1a..7b66cac592 100644 --- a/tests/unit/client_tools/stubs.py +++ b/tests/unit/client_tools/stubs.py @@ -42,7 +42,7 @@ class FakeHTTPConnection: self._req = None options = \ dict(plugin_provider='quantum.plugins.SamplePlugin.FakePlugin') - self._api = server.APIRouterV01(options) + self._api = server.APIRouterV1(options) def request(self, method, action, body, headers): # TODO: remove version prefix from action! diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py index 2fe27974a1..70af451d31 100644 --- a/tests/unit/test_api.py +++ b/tests/unit/test_api.py @@ -218,8 +218,8 @@ class APITest(unittest.TestCase): LOG.debug("_test_delete_network - format:%s - START", format) content_type = "application/%s" % format network_id = self._create_network(format) - LOG.debug("Deleting network %(network_id)s"\ - " of tenant %(tenant_id)s", locals()) + LOG.debug("Deleting network %s"\ + " of tenant %s" % (network_id, self.tenant_id)) delete_network_req = testlib.network_delete_request(self.tenant_id, network_id, format) @@ -240,8 +240,8 @@ class APITest(unittest.TestCase): port_state = "ACTIVE" attachment_id = "test_attachment" network_id = self._create_network(format) - LOG.debug("Deleting network %(network_id)s"\ - " of tenant %(tenant_id)s", locals()) + LOG.debug("Deleting network %s"\ + " of tenant %s" % (network_id, self.tenant_id)) port_id = self._create_port(network_id, port_state, format) #plug an attachment into the port LOG.debug("Putting attachment into port %s", port_id) @@ -252,8 +252,8 @@ class APITest(unittest.TestCase): attachment_res = attachment_req.get_response(self.api) self.assertEquals(attachment_res.status_int, 204) - LOG.debug("Deleting network %(network_id)s"\ - " of tenant %(tenant_id)s", locals()) + LOG.debug("Deleting network %s"\ + " of tenant %s" % (network_id, self.tenant_id)) delete_network_req = testlib.network_delete_request(self.tenant_id, network_id, format) @@ -267,12 +267,12 @@ class APITest(unittest.TestCase): content_type = "application/%s" % format port_state = "ACTIVE" network_id = self._create_network(format) - LOG.debug("Deleting network %(network_id)s"\ - " of tenant %(tenant_id)s", locals()) + LOG.debug("Deleting network %s"\ + " of tenant %s" % (network_id, self.tenant_id)) port_id = self._create_port(network_id, port_state, format) - LOG.debug("Deleting network %(network_id)s"\ - " of tenant %(tenant_id)s", locals()) + LOG.debug("Deleting network %s"\ + " of tenant %s" % (network_id, self.tenant_id)) delete_network_req = testlib.network_delete_request(self.tenant_id, network_id, format) @@ -453,8 +453,9 @@ class APITest(unittest.TestCase): port_state = "ACTIVE" network_id = self._create_network(format) port_id = self._create_port(network_id, port_state, format) - LOG.debug("Deleting port %(port_id)s for network %(network_id)s"\ - " of tenant %(tenant_id)s", locals()) + LOG.debug("Deleting port %s for network %s"\ + " of tenant %s" % (port_id, network_id, + self.tenant_id)) delete_port_req = testlib.port_delete_request(self.tenant_id, network_id, port_id, format) @@ -484,8 +485,9 @@ class APITest(unittest.TestCase): attachment_id) attachment_res = attachment_req.get_response(self.api) self.assertEquals(attachment_res.status_int, 204) - LOG.debug("Deleting port %(port_id)s for network %(network_id)s"\ - " of tenant %(tenant_id)s", locals()) + LOG.debug("Deleting port %s for network %s"\ + " of tenant %s" % (port_id, network_id, + self.tenant_id)) delete_port_req = testlib.port_delete_request(self.tenant_id, network_id, port_id, format) @@ -783,7 +785,7 @@ class APITest(unittest.TestCase): def setUp(self): options = {} options['plugin_provider'] = test_config['plugin_name'] - self.api = server.APIRouterV01(options) + self.api = server.APIRouterV1(options) self.tenant_id = "test_tenant" self.network_name = "test_network" self._net_serializer = \ diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index 8fe388c6e5..4e98391cae 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -42,7 +42,7 @@ class CLITest(unittest.TestCase): """Prepare the test environment""" options = {} options['plugin_provider'] = 'quantum.plugins.SamplePlugin.FakePlugin' - self.api = server.APIRouterV01(options) + self.api = server.APIRouterV1(options) self.tenant_id = "test_tenant" self.network_name_1 = "test_network_1"