From 046516109b739a431bc1a64e65803f2f5fdfba14 Mon Sep 17 00:00:00 2001 From: Conner Ferguson Date: Tue, 11 Nov 2014 16:58:04 -0500 Subject: [PATCH] Added Plexxi Driver Change-Id: Iee629b199ce8c4251d7c2aea27877fc88b46990d --- congress/datasources/plexxi_driver.py | 540 ++++++++++++++++++ .../datasources/tests/unit/plexxi_fakes.py | 174 ++++++ .../tests/unit/test_plexxi_driver.py | 158 +++++ etc/datasources.conf.sample | 8 + thirdparty/plexxi_setup.sh | 4 + 5 files changed, 884 insertions(+) create mode 100644 congress/datasources/plexxi_driver.py create mode 100644 congress/datasources/tests/unit/plexxi_fakes.py create mode 100644 congress/datasources/tests/unit/test_plexxi_driver.py create mode 100644 thirdparty/plexxi_setup.sh diff --git a/congress/datasources/plexxi_driver.py b/congress/datasources/plexxi_driver.py new file mode 100644 index 000000000..17a698539 --- /dev/null +++ b/congress/datasources/plexxi_driver.py @@ -0,0 +1,540 @@ +#!/usr/bin/env python +# Copyright (c) 2014 Marist SDN Innovation lab Joint with Plexxi Inc. +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +import json +import requests + +try: + from plexxi.core.api.binding import AffinityGroup + from plexxi.core.api.binding import Job + from plexxi.core.api.binding import PhysicalPort + from plexxi.core.api.binding import PlexxiSwitch + from plexxi.core.api.binding import VirtualizationHost + from plexxi.core.api.binding import VirtualMachine + from plexxi.core.api.binding import VirtualSwitch + from plexxi.core.api.binding import VmwareVirtualMachine + from plexxi.core.api.session import CoreSession +except ImportError: + pass + +from congress.datasources.datasource_driver import DataSourceDriver +from congress.datasources import datasource_utils +from congress.openstack.common import log as logging +from oslo.config import cfg + +LOG = logging.getLogger(__name__) + + +def d6service(name, keys, inbox, datapath, args): + """This method is called by d6cage to create a dataservice instance. + """ + + return PlexxiDriver(name, keys, inbox, datapath, args) + + +class PlexxiDriver(DataSourceDriver): + HOSTS = "hosts" + HOST_MACS = HOSTS + '.macs' + HOST_GUESTS = HOSTS + '.guests' + VMS = "vms" + VM_MACS = VMS + '.macs' + AFFINITIES = "affinities" + VSWITCHES = "vswitches" + PLEXXISWITCHES = "plexxiswitches" + PORTS = "ports" + PORT_LINKS = PORTS + '.links' + NETWORKLINKS = "networklinks" + + def __init__(self, name='', keys='', inbox=None, datapath=None, args=None, + session=None): + args['tenant_name'] = None + super(PlexxiDriver, self).__init__(name, keys, inbox, datapath, args) + self.exchange = session + self.creds = datasource_utils.get_credentials(name, args) + self.raw_state = {} + self.key_index = self.create_index() + self.name_rule = False + self.cooldown = 0 + try: + self.unique_names = self.string_to_bool(args['unique_names']) + except KeyError: + LOG.warning("unique_names has not been configured in " + "datasources.conf, defaulting to False.") + self.unique_names = False + port = str(cfg.CONF.bind_port) + host = str(cfg.CONF.bind_host) + self.api_address = "http://" + host + ":" + port + "/v1" + + def update_from_datasource(self): + """Called when it is time to pull new data from this datasource. + + Pulls lists of objects from PlexxiCore, if the data does not match + the correspondig table in the driver's raw state or has not yet been + added to the state, the driver calls methods to parse this data. + + Once all data has been updated,sets + self.state[tablename] = + for every tablename exported by PlexxiCore. + """ + + # Initialize instance variables that get set during update + self.hosts = [] + self.mac_list = [] + self.guest_list = [] + self.plexxi_switches = [] + self.affinities = [] + self.vswitches = [] + self.vms = [] + self.vm_macs = [] + self.ports = [] + self.network_links = [] + + if self.exchange is None: + self.connect_to_plexxi(self.creds) + + # Get host data from PlexxiCore + hosts = VirtualizationHost.getAll(session=self.exchange) + if (self.HOSTS not in self.state or + hosts != self.raw_state[self.HOSTS]): + self._translate_hosts(hosts) + self.raw_state[self.HOSTS] = hosts + else: + self.hosts = self.state[self.HOSTS] + self.mac_list = self.state[self.HOST_MACS] + self.guest_list = self.state[self.HOST_GUESTS] + + # Get PlexxiSwitch Data from PlexxiCore + plexxiswitches = PlexxiSwitch.getAll(session=self.exchange) + if (self.PLEXXISWITCHES not in self.state or + plexxiswitches != self.raw_state[self.PLEXXISWITCHES]): + self._translate_pswitches(plexxiswitches) + self.raw_state[self.PLEXXISWITCHES] = plexxiswitches + else: + self.plexxi_switches = self.state[self.PLEXXISWITCHES] + + # Get affinity data from PlexxiCore + affinities = AffinityGroup.getAll(session=self.exchange) + if (self.AFFINITIES not in self.state or + affinities != self.raw_state[self.AFFINITIES]): + if AffinityGroup.getCount(session=self.exchange) == 0: + self.state[self.AFFINITIES] = ['No Affinities found'] + else: + self._translate_affinites(affinities) + self.raw_state[self.AFFINITIES] = affinities + else: + self.affinties = self.state[self.AFFINITIES] + + # Get vswitch data from PlexxiCore + vswitches = VirtualSwitch.getAll(session=self.exchange) + if (self.VSWITCHES not in self.state or + vswitches != self.raw_state[self.VSWITCHES]): + self._translate_vswitches(vswitches) + self.raw_state[self.VSWITCHES] = vswitches + else: + self.vswitches = self.state[self.VSWITCHES] + + # Get virtual machine data from PlexxiCore + vms = VirtualMachine.getAll(session=self.exchange) + if (self.VMS not in self.state or + vms != self.raw_state[self.VMS]): + self._translate_vms(vms) + self.raw_state[self.VMS] = set(vms) + else: + self.vms = self.state[self.VMS] + self.vm_macs = self.state[self.VMS_MACS] + # Get port data from PlexxiCore + ports = PhysicalPort.getAll(session=self.exchange) + if(self.PORTS not in self.state or + ports != self.raw_state[self.PORTS]): + self._translate_ports(ports) + self.raw_state[self.PORTS] = set(ports) + else: + self.ports = self.state[self.PORTS] + self.network_links = self.state[self.PORT_LINKS] + + LOG.debug("Setting Plexxi State") + self.state = {} + self.state['key_index'] = self.key_index + self.state[self.HOSTS] = set(self.hosts) + self.state[self.HOST_MACS] = set(self.mac_list) + self.state[self.HOST_GUESTS] = set(self.guest_list) + self.state[self.PLEXXISWITCHES] = set(self.plexxi_switches) + self.state[self.PLEXXISWITCHES + '.macs'] = set(self.ps_macs) + self.state[self.AFFINITIES] = set(self.affinities) + self.state[self.VSWITCHES] = set(self.vswitches) + self.state[self.VMS] = set(self.vms) + self.state[self.VM_MACS] = set(self.vm_macs) + self.state[self.PORTS] = set(self.ports) + self.state[self.PORT_LINKS] = set(self.network_links) + # Create Rules + if self.name_rule is False: + self.create_rule_table() + # Act on Policy + if self.unique_names is True: + if self.cooldown == 0: + self.name_response() + else: + self.cooldown -= 1 + + @classmethod + def get_schema(cls): + """Creates a table schema for incoming data from PlexxiCore. + + Returns a dictionary map of tablenames corresponding to column names + for that table. Both tableNames and columnnames are strings. + """ + + d = {} + d[cls.HOSTS] = ("uuid", "name", "mac_count", "vmcount") + d[cls.VMS] = ("uuid", "name", "host_uuid", "ip", "mac_count") + d[cls.AFFINITIES] = ("uuid", "name") + d[cls.VSWITCHES] = ("uuid", "host_count", "vnic_count") + d[cls.PLEXXISWITCHES] = ("uuid", "ip", "status") + d[cls.PORTS] = ("uuid", "name") + d[cls.NETWORKLINKS] = ("uuid", "name", "port_uuid", "start_uuid", + "start_name", "stop_uuid", "stop_name") + return d + + def _translate_hosts(self, hosts): + """Translates data about Hosts from PlexxiCore for Congress. + + Responsible for the states 'hosts','hosts.macs' and 'hosts.guests' + """ + + row_keys = self.get_column_map(self.HOSTS) + hostlist = [] + maclist = [] + vm_uuids = [] + for host in hosts: + row = ['None'] * (max(row_keys.values()) + 1) + hostID = host.getForeignUuid() + row[row_keys['uuid']] = hostID + row[row_keys['name']] = host.getName() + pnics = host.getPhysicalNetworkInterfaces() + if pnics: + for pnic in pnics: + mac = str(pnic.getMacAddress()) + tuple_mac = (hostID, mac) + maclist.append(tuple_mac) + mac_count = len(maclist) + if (mac_count > 0): + row[row_keys['mac_count']] = mac_count + vmCount = host.getVirtualMachineCount() + row[row_keys['vmcount']] = vmCount + if vmCount != 0: + vms = host.getVirtualMachines() + for vm in vms: + tuple_vmid = (hostID, vm.getForeignUuid()) + vm_uuids.append(tuple_vmid) + hostlist.append(tuple(row)) + self.hosts = hostlist + self.mac_list = maclist + self.guest_list = vm_uuids + + def _translate_pswitches(self, plexxi_switches): + """Translates data on Plexxi Switches from PlexxiCore for Congress. + + Responsible for state 'Plexxi_swtiches' and 'Plexxi_switches.macs' + """ + + row_keys = self.get_column_map(self.PLEXXISWITCHES) + pslist = [] + maclist = [] + for switch in plexxi_switches: + row = ['None'] * (max(row_keys.values()) + 1) + psuuid = str(switch.getUuid()) + row[row_keys['uuid']] = psuuid + psip = str(switch.getIpAddress()) + row[row_keys['ip']] = psip + psstatus = str(switch.getStatus()) + row[row_keys['status']] = psstatus + pnics = switch.getPhysicalNetworkInterfaces() + for pnic in pnics: + mac = str(pnic.getMacAddress()) + macrow = [psuuid, mac] + maclist.append(tuple(macrow)) + pslist.append(tuple(row)) + self.plexxi_switches = pslist + self.ps_macs = maclist + + def _translate_affinites(self, affinites): + """Translates data about affinites from PlexxiCore for Congress. + + Responsible for state 'affinities' + """ + + row_keys = self.get_column_map(self.AFFINITIES) + affinitylist = [] + for affinity in affinites: + row = ['None'] * (max(row_keys.values()) + 1) + uuid = str(affinity.getUuid()) + row[row_keys['uuid']] = uuid + row[row_keys['name']] = affinity.getName() + affinitylist.append(tuple(row)) + self.affinities = affinitylist + + def _translate_vswitches(self, vswitches): + """Translates data about vswitches from PlexxiCore for Congress. + + Responsible for states vswitchlist,vswitch_macs,vswitch_hosts + """ + + # untested + row_keys = self.get_column_map(self.VSWITCHES) + vswitchlist = [] + tuple_macs = [] + vswitch_host_list = [] + for vswitch in vswitches: + row = ['None'] * (max(row_keys.values()) + 1) + vswitchID = vswitch.getForeignUuid() + row[row_keys['uuid']] = vswitchID + vSwitchHosts = vswitch.getVirtualizationHosts() + try: + host_count = len(vSwitchHosts) + except TypeError: + host_count = 0 + row[row_keys['host_count']] = host_count + if host_count != 0: + for host in vSwitchHosts: + hostuuid = host.getForeignUuid() + hostrow = [vswitchID, hostuuid] + vswitch_host_list.append(tuple(hostrow)) + vswitch_vnics = vswitch.getVirtualNetworkInterfaces() + try: + vnic_count = len(vswitch_vnics) + except TypeError: + vnic_count = 0 + row[row_keys['vnic_count']] = vnic_count + if vnic_count != 0: + for vnic in vswitch_vnics: + mac = vnic.getMacAddress() + macrow = [vswitchID, str(mac)] + tuple_macs.append(tuple(macrow)) + vswitchlist.append(tuple(row)) + self.vswitches = vswitchlist + self.vswitch_macs = tuple_macs + self.vswitch_hosts = vswitch_host_list + + def _translate_vms(self, vms): + """Translate data on VMs from PlexxiCore for Congress. + + Responsible for states 'vms' and 'vms.macs' + """ + + row_keys = self.get_column_map(self.VMS) + vmlist = [] + maclist = [] + for vm in vms: + row = ['None'] * (max(row_keys.values()) + 1) + vmID = vm.getForeignUuid() + row[row_keys['uuid']] = vmID + vmName = vm.getName() + row[row_keys['name']] = vmName + try: + vmhost = vm.getVirtualizationHost() + vmhostuuid = vmhost.getForeignUuid() + row[row_keys['host_uuid']] = vmhostuuid + except AttributeError: + LOG.debug("The host for " + vmName + " could not be found") + vmIP = vm.getIpAddress() + if vmIP: + row[row_keys['ip']] = vmIP + vmVnics = vm.getVirtualNetworkInterfaces() + mac_count = 0 + for vnic in vmVnics: + mac = str(vnic.getMacAddress()) + tuple_mac = (vmID, mac) + maclist.append(tuple_mac) + mac_count += 1 + row[row_keys['mac_count']] = mac_count + vmlist.append(tuple(row)) + self.vms = vmlist + self.vm_macs = maclist + + def _translate_ports(self, ports): + """Translate data about ports from PlexxiCore for Congress. + + Responsible for states 'ports' and 'ports.links' + """ + + row_keys = self.get_column_map(self.PORTS) + link_keys = self.get_column_map(self.NETWORKLINKS) + port_list = [] + link_list = [] + for port in ports: + row = ['None'] * (max(row_keys.values()) + 1) + portID = str(port.getUuid()) + row[row_keys['uuid']] = portID + portName = str(port.getName()) + row[row_keys['name']] = portName + links = port.getNetworkLinks() + if links: + link_keys = self.get_column_map(self.NETWORKLINKS) + for link in links: + link_row = self._translate_network_link(link, link_keys, + portID) + link_list.append(tuple(link_row)) + port_list.append(tuple(row)) + self.ports = port_list + self.network_links = link_list + + def _translate_network_link(self, link, row_keys, sourcePortUuid): + """Translates data about network links from PlexxiCore for Congress. + + Subfunction of translate_ports,each handles a set of network links + attached to a port. Directly responsible for the state of + 'ports.links' + """ + + row = ['None'] * (max(row_keys.values()) + 1) + linkID = str(link.getUuid()) + row[row_keys['uuid']] = linkID + row[row_keys['port_uuid']] = sourcePortUuid + linkName = str(link.getName()) + row[row_keys['name']] = linkName + linkStartObj = link.getStartNetworkInterface() + linkStartName = str(linkStartObj.getName()) + row[row_keys['start_name']] = linkStartName + linkStartUuid = str(linkStartObj.getUuid()) + row[row_keys['start_uuid']] = linkStartUuid + linkStopObj = link.getStopNetworkInterface() + linkStopUuid = str(linkStopObj.getUuid()) + row[row_keys['stop_uuid']] = linkStopUuid + linkStopName = str(linkStopObj.getName()) + row[row_keys['stop_name']] = linkStopName + return row + + def string_to_bool(self, string): + """Used for parsing boolean variables stated in datasources.conf. + """ + + string = string.strip() + s = string.lower() + if s in['true', 'yes', 'on']: + return True + else: + return False + + def create_index(self): + """Creates key_index table + + The key_index table is used expose the table schema through the API. + The CONGRESS_INDEX_ID field contains the name of the table the rest + of the data is pertaining to.The other values give the names + columns paired with their position in the table. + """ + + key_index = [] + row_keys = self.get_schema() + for key in row_keys.keys(): + key_map = self.get_column_map(key) + key_map['CONGRESS_INDEX_ID'] = key + key_index.append(key_map) + return key_index + + def connect_to_plexxi(self, creds): + """Create a CoreSession connecting congress to PlexxiCore using + credentials provided in datasources.conf + """ + + if 'auth_url' not in creds: + LOG.error("Plexxi url not supplied. Could not start Plexxi" + + "connection driver") + if 'username' not in creds: + LOG.error("Plexxi username not supplied. Could not start " + + "Plexxi connection driver") + if 'password' not in creds: + LOG.error("Plexxi password not supplied. Could not start " + + "Plexxi connection driver") + try: + self.exchange = CoreSession.connect( + baseUrl=creds['auth_url'], + allowUntrusted=True, + username=creds['username'], + password=creds['password']) + except requests.exceptions.HTTPError as error: + if int(error.response.status_code) == 401 or\ + int(error.response.status_code) == 403: + msg = "Incorrect username/password combination. Passed" + \ + "in username was " + creds['username'] + ", " + \ + "password was " + creds['password'] + + raise Exception(requests.exceptions.HTTPErrror(msg)) + else: + raise Exception(requests.exceptions.HTTPError(error)) + + except requests.exceptions.ConnectionError: + msg = "Cannot connect to PlexxiCore at " + creds['auth_url'] + \ + " with the username " + creds['username'] + " and the " + \ + "password " + creds['password'] + + raise Exception(requests.exceptions.ConnectionError(msg)) + + def create_rule_table(self): + """Creates RepeatedName table for unique names policy. + + The RepeatedName table contains the name and plexxiUuid of + VMs that have the same name in the Plexxi table and the Nova Table. + """ + + repeated_name_rule = '{"rule": "RepeatedName(vname,pvuuid)' +\ + ':- plexxi:vms(pvuuid,vname,phuuid,pvip,pvmaccount,pvaffin),' +\ + 'nova:servers(nvuuid,vname,a,nstatus,b,c,d,num)"}' + try: + requests.post(self.api_address + '/policies/classification/rules', + data=repeated_name_rule) + self.name_rule = False + except Exception: + LOG.exception("Could not create Repeated Name table") + + def name_response(self): + """Checks for any entries in the RepeatedName table. + + For all entries found in the RepeatedName table, the corresponding + VM will be then prefixed with 'conflict-' in PlexxiCore. + """ + + vmname = False + vmuuid = False + json_response = [] + self.cooldown += 1 + try: + plexxivms = VmwareVirtualMachine.getAll(session=self.exchange) + table = requests.get(self.api_address + "/policies/" + + "classification/tables/RepeatedName/rows") + json_response = json.loads(table.text) + + for row in json_response['results']: + vmname = row['data'][0] + vmuuid = row['data'][1] + if vmname and vmuuid: + for plexxivm in plexxivms: + if (plexxivm.getForeignUuid() == vmuuid): + new_vm_name = "Conflict-" + vmname + desc = "Congress has found a VM with the same " +\ + "name on the nova network. This vm " +\ + "will now be renamed to " + new_vm_name + job_name = " Congress Driver:Changing virtual" +\ + "machine, " + vmname + "\'s name" + changenamejob = Job.create(name=job_name, + description=desc + ".", + session=self.exchange) + changenamejob.begin() + plexxivm.setName(new_vm_name) + changenamejob.commit() + LOG.info(desc + " in PlexxiCore.") + except Exception: + LOG.exception("error in name_response") diff --git a/congress/datasources/tests/unit/plexxi_fakes.py b/congress/datasources/tests/unit/plexxi_fakes.py new file mode 100644 index 000000000..fc41447ee --- /dev/null +++ b/congress/datasources/tests/unit/plexxi_fakes.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python +# Copyright (c) 2014 Marist SDN Innovation lab Joint with Plexxi 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. + + +class MockAffinity(object): + def __init__(self, uuid, name): + self.uuid = uuid + self.name = name + + def getUuid(self): + return self.uuid + + def getName(self): + return self.name + + +class MockCoreSession(object): + def __init__(self): + pass + + def disconnect(): + pass + + +class MockHost(object): + def __init__(self, uuid, name, mac_count, pnics): + self.uuid = uuid + self.name = name + self.mac_count = mac_count + self.pnics = pnics + self.vms = [] + + def addvm(self, vm): + self.vms.append(vm) + + def getForeignUuid(self): + return self.uuid + + def getUuid(self): + return self.uuid + + def getName(self): + return self.name + + def getPhysicalNetworkInterfaces(self): + return self.pnics + + def getVirtualMachineCount(self): + return len(self.vms) + + def getVirtualMachines(self): + return self.vms + + +class MockNetworkLink(object): + def __init__(self, uuid, name, stopint, startint): + self.uuid = uuid + self.name = name + self.startint = startint + self.stopint = stopint + + def getUuid(self): + return self.uuid + + def getName(self): + return self.name + + def getStartNetworkInterface(self): + return self.startint + + def getStopNetworkInterface(self): + return self.stopint + + +class MockNIC(object): + def __init__(self, uuid, mac): + self.uuid = uuid + self.mac = mac + + def getMacAddress(self): + return self.mac + + +class MockPort(object): + def __init__(self, uuid, name, networklinks): + self.uuid = uuid + self.name = name + self.networklinks = networklinks + + def getUuid(self): + return self.uuid + + def getName(self): + return self.name + + def getNetworkLinks(self): + return self.networklinks + + +class MockSwitch(object): + def __init__(self, uuid, ip, name, status, pnics): + self.uuid = uuid + self.ip = ip + self.status = status + self.pnics = pnics + self.name = name + + def getUuid(self): + return self.uuid + + def getName(self): + return self.name + + def getIpAddress(self): + return self.ip + + def getStatus(self): + return self.status + + def getPhysicalNetworkInterfaces(self): + return self.pnics + + +class MockVM(object): + def __init__(self, uuid, ip, name, host, vnics): + self.uuid = uuid + self.ip = ip + self.host = host + self.name = name + self.vnics = vnics + + def getForeignUuid(self): + return self.uuid + + def getVirtualizationHost(self): + return self.host + + def getName(self): + return self.name + + def getIpAddress(self): + return self.ip + + def getVirtualNetworkInterfaces(self): + return self.vnics + + +class MockVSwitch(object): + def __init__(self, uuid, hosts, vnics): + self.uuid = uuid + self.hosts = hosts + self.vnics = vnics + + def getForeignUuid(self): + return self.uuid + + def getVirtualizationHosts(self): + return self.hosts + + def getVirtualNetworkInterfaces(self): + return self.vnics diff --git a/congress/datasources/tests/unit/test_plexxi_driver.py b/congress/datasources/tests/unit/test_plexxi_driver.py new file mode 100644 index 000000000..837ce6dc9 --- /dev/null +++ b/congress/datasources/tests/unit/test_plexxi_driver.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from congress.datasources import plexxi_driver +from congress.datasources.tests.unit.plexxi_fakes import MockAffinity +from congress.datasources.tests.unit.plexxi_fakes import MockCoreSession +from congress.datasources.tests.unit.plexxi_fakes import MockHost +from congress.datasources.tests.unit.plexxi_fakes import MockNetworkLink +from congress.datasources.tests.unit.plexxi_fakes import MockNIC +from congress.datasources.tests.unit.plexxi_fakes import MockPort +from congress.datasources.tests.unit.plexxi_fakes import MockSwitch +from congress.datasources.tests.unit.plexxi_fakes import MockVM +from congress.datasources.tests.unit.plexxi_fakes import MockVSwitch +from congress.tests import base +from congress.tests import helper + + +class TestPlexxiDriver(base.TestCase): + def setUp(self): + super(TestPlexxiDriver, self).setUp() + args = helper.datasource_openstack_args() + args['unique_names'] = 'False' + session = MockCoreSession() + self.driver = plexxi_driver.PlexxiDriver(args=args, session=session) + self.driver.exchange = True + vnic1 = MockNIC(uuid='f318ac0a-9255-4af0-8a41-6f3fbc06c8aa', + mac='B8:ED:0A:4D:82:91') + vnic2 = MockNIC(uuid='f318ac0a-9255-4af0-8a41-6f3fbc06c8a2', + mac='B8:ED:0A:4D:82:99') + pnic1 = MockNIC(uuid='f318ac0a-9255-4af0-8a41-6f3fbc06c8ab', + mac='B8:ED:0A:4D:82:92') + pnic2 = MockNIC(uuid='f318ac0a-9255-4af0-8a41-6f3fbc06c8ac', + mac='B8:ED:0A:4D:82:93') + host1 = MockHost('eed4ebfc-25e5-4a65-9f37-b70b8e8219d3', + 'mock1', + 1, + [pnic1]) + vm1 = MockVM('2ca924f6-90aa-4ce8-a986-f62f8f64d14b', + '192.168.90.2', + 'namevm', + host1, + [vnic1]) + host1.addvm(vm1) + switch1 = MockSwitch('12da13e3-ecb2-4c26-98a0-26cb07f9c33d', + '192.168.90.3', + 'switch1', + 'HEALTHY', + [pnic2]) + affinity = MockAffinity('fd487ecf-5279-4d3c-9378-7fb214f5dd5a', + 'Testfinnity') + affinity2 = MockAffinity('fd487ecf-5279-4d3c-9378-7fb214f5dd5b', + 'Testfinnity2') + vswitch = MockVSwitch('fd487ecf-5279-4d3c-9378-7fb214f5dd5c', + [host1], + [vnic2]) + link1 = MockNetworkLink('fd487ecf-5279-4d3c-9378-7fb214f5dd5f', + 'Link1', + host1, + switch1) + port = MockPort('fd487ecf-5279-4d3c-9378-7fb214f5dd5d', + 'Port1', + [link1]) + port2 = MockPort('fd487ecf-5279-4d3c-9378-7fb214f5dd5e', + 'Port2', + None) + + self.hosts = [host1] + self.pswitches = [switch1] + self.affinites = [affinity, affinity2] + self.vswitches = [vswitch] + self.vms = [vm1] + self.ports = [port, port2] + + def test_translate_hosts(self): + self.driver._translate_hosts(self.hosts) + ExpectedHosts = [('eed4ebfc-25e5-4a65-9f37-b70b8e8219d3', + 'mock1', + 1, + 1)] + self.assertEqual(self.driver.hosts, ExpectedHosts) + ExpectedHost_Macs = [('eed4ebfc-25e5-4a65-9f37-b70b8e8219d3', + 'B8:ED:0A:4D:82:92')] + self.assertEqual(self.driver.mac_list, ExpectedHost_Macs) + ExpectedGuests = [('eed4ebfc-25e5-4a65-9f37-b70b8e8219d3', + '2ca924f6-90aa-4ce8-a986-f62f8f64d14b')] + self.assertEqual(self.driver.guest_list, ExpectedGuests) + + def test_translate_pswitches(self): + self.driver._translate_pswitches(self.pswitches) + ExpectedpSwitches = [('12da13e3-ecb2-4c26-98a0-26cb07f9c33d', + '192.168.90.3', + 'HEALTHY')] + self.assertEqual(self.driver.plexxi_switches, ExpectedpSwitches) + ExpectedPSmacs = [('12da13e3-ecb2-4c26-98a0-26cb07f9c33d', + 'B8:ED:0A:4D:82:93')] + self.assertEqual(self.driver.ps_macs, ExpectedPSmacs) + + def test_translate_affinites(self): + self.driver._translate_affinites(self.affinites) + ExpectedAffinities = [('fd487ecf-5279-4d3c-9378-7fb214f5dd5a', + 'Testfinnity'), + ('fd487ecf-5279-4d3c-9378-7fb214f5dd5b', + 'Testfinnity2')] + + self.assertEqual(self.driver.affinities, ExpectedAffinities) + + def test_translate_vswitches(self): + self.driver._translate_vswitches(self.vswitches) + ExpectedvSwitches = [('fd487ecf-5279-4d3c-9378-7fb214f5dd5c', + 1, + 1)] + self.assertEqual(self.driver.vswitches, ExpectedvSwitches) + ExpectedvSwitch_macs = [('fd487ecf-5279-4d3c-9378-7fb214f5dd5c', + 'B8:ED:0A:4D:82:99')] + self.assertEqual(self.driver.vswitch_macs, ExpectedvSwitch_macs) + ExpectedvSwitch_hosts = [('fd487ecf-5279-4d3c-9378-7fb214f5dd5c', + 'eed4ebfc-25e5-4a65-9f37-b70b8e8219d3')] + + self.assertEqual(self.driver.vswitch_hosts, ExpectedvSwitch_hosts) + + def test_translate_vms(self): + self.driver._translate_vms(self.vms) + ExpectedVMs = [('2ca924f6-90aa-4ce8-a986-f62f8f64d14b', + 'namevm', + 'eed4ebfc-25e5-4a65-9f37-b70b8e8219d3', + '192.168.90.2', + 1)] + + self.assertEqual(self.driver.vms, ExpectedVMs) + Expectedvm_macs = [('2ca924f6-90aa-4ce8-a986-f62f8f64d14b', + 'B8:ED:0A:4D:82:91')] + self.assertEqual(self.driver.vm_macs, Expectedvm_macs) + + def test_translate_ports(self): + self.driver._translate_ports(self.ports) + ExpectedPorts = [('fd487ecf-5279-4d3c-9378-7fb214f5dd5d', + 'Port1'), + ('fd487ecf-5279-4d3c-9378-7fb214f5dd5e', + 'Port2')] + self.assertEqual(self.driver.ports, ExpectedPorts) + ExpectedLinks = [('fd487ecf-5279-4d3c-9378-7fb214f5dd5f', + 'Link1', + 'fd487ecf-5279-4d3c-9378-7fb214f5dd5d', + '12da13e3-ecb2-4c26-98a0-26cb07f9c33d', + 'switch1', + 'eed4ebfc-25e5-4a65-9f37-b70b8e8219d3', + 'mock1')] + self.assertEqual(self.driver.network_links, ExpectedLinks) diff --git a/etc/datasources.conf.sample b/etc/datasources.conf.sample index 8e4d93905..014a2d9cc 100644 --- a/etc/datasources.conf.sample +++ b/etc/datasources.conf.sample @@ -46,3 +46,11 @@ username: admin password: password auth_url: http://127.0.0.1:5000/v2.0 tenant_name: admin + +#[plexxi] +#module:datasources/plexxi_driver.py +#username: plexxiCore_username +#password: plexxiCore_password +#auth_url: http://PlexxiCoreURL/PlexxiCore/api +#unique_names: False + diff --git a/thirdparty/plexxi_setup.sh b/thirdparty/plexxi_setup.sh new file mode 100644 index 000000000..abba56a2a --- /dev/null +++ b/thirdparty/plexxi_setup.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# Replace 2.0.2-sp2 with your repective plexxicore verison +# This can be checked at http://PlexxiCoreURL:8080/PlexxiCore/ +pip install -v --index-url http://pypi.plexxi.com plexxi==2.0.2-sp2