From fa912dfcd91ea7a7e4a014d7179e7232357dd40c Mon Sep 17 00:00:00 2001 From: Edgar Magana Date: Mon, 12 Dec 2011 23:41:59 -0800 Subject: [PATCH] Implementation of the BP services-insertion-wrapper inside the Cisco Plugin This utility offers a simplify way to insert and remove network services in the path of the traffic to the server VMs, by splitting the network into two, and having the service bridge between the two networks, in the process applying the service. This model is called In-Path (Bump in the Wire) Change-Id: If7e9ad2dcb8124e7d82ac837c5b62c0d09b00bcd --- quantum/plugins/cisco/db/api.py | 11 + quantum/plugins/cisco/db/l2network_db.py | 1 + quantum/plugins/cisco/db/services_db.py | 78 +++++ quantum/plugins/cisco/db/services_models.py | 42 +++ quantum/plugins/cisco/services/README | 70 ++++ quantum/plugins/cisco/services/__init__.py | 19 ++ .../cisco/services/service_insertion.py | 318 ++++++++++++++++++ .../cisco/services/services_constants.py | 35 ++ .../cisco/services/services_logistics.py | 122 +++++++ .../plugins/cisco/tests/unit/test_database.py | 120 +++++++ 10 files changed, 816 insertions(+) create mode 100644 quantum/plugins/cisco/db/services_db.py create mode 100644 quantum/plugins/cisco/db/services_models.py create mode 100644 quantum/plugins/cisco/services/README create mode 100644 quantum/plugins/cisco/services/__init__.py create mode 100644 quantum/plugins/cisco/services/service_insertion.py create mode 100644 quantum/plugins/cisco/services/services_constants.py create mode 100644 quantum/plugins/cisco/services/services_logistics.py diff --git a/quantum/plugins/cisco/db/api.py b/quantum/plugins/cisco/db/api.py index 3e647ffc3..ed42f044a 100644 --- a/quantum/plugins/cisco/db/api.py +++ b/quantum/plugins/cisco/db/api.py @@ -94,6 +94,17 @@ def network_list(tenant_id): all() +def network_id(net_name): + session = get_session() + try: + return session.query(models.Network).\ + options(joinedload(models.Network.ports)). \ + filter_by(name=net_name).\ + all() + except exc.NoResultFound, e: + raise q_exc.NetworkNotFound(net_name=net_name) + + def network_get(net_id): session = get_session() try: diff --git a/quantum/plugins/cisco/db/l2network_db.py b/quantum/plugins/cisco/db/l2network_db.py index f9b981948..c6962f7dd 100644 --- a/quantum/plugins/cisco/db/l2network_db.py +++ b/quantum/plugins/cisco/db/l2network_db.py @@ -26,6 +26,7 @@ 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 +import quantum.plugins.cisco.db.services_db as sdb def initialize(): diff --git a/quantum/plugins/cisco/db/services_db.py b/quantum/plugins/cisco/db/services_db.py new file mode 100644 index 000000000..7235cfb41 --- /dev/null +++ b/quantum/plugins/cisco/db/services_db.py @@ -0,0 +1,78 @@ +# 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: Edgar Magana, 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 services_models + + +def get_all_services_bindings(): + """Lists all the services bindings""" + LOG.debug("get_all_services_bindings() called") + session = db.get_session() + try: + bindings = session.query(services_models.ServicesBinding).\ + all() + return bindings + except exc.NoResultFound: + return [] + + +def get_service_bindings(service_id): + """Lists services bindings for a service_id""" + LOG.debug("get_service_bindings() called") + session = db.get_session() + try: + bindings = session.query(services_models.ServicesBinding).\ + filter_by(service_id=service_id).\ + one() + return bindings + except exc.NoResultFound: + return [] + + +def add_services_binding(service_id, mngnet_id, nbnet_id, sbnet_id): + """Adds a services binding""" + LOG.debug("add_services_binding() called") + session = db.get_session() + binding = services_models.ServicesBinding(service_id, mngnet_id, \ + nbnet_id, sbnet_id) + session.add(binding) + session.flush() + return binding + + +def remove_services_binding(service_id): + """Removes a services binding""" + LOG.debug("remove_services_binding() called") + session = db.get_session() + try: + binding = session.query(services_models.ServicesBinding).\ + filter_by(service_id=service_id).\ + all() + for bind in binding: + session.delete(bind) + session.flush() + return binding + except exc.NoResultFound: + pass diff --git a/quantum/plugins/cisco/db/services_models.py b/quantum/plugins/cisco/db/services_models.py new file mode 100644 index 000000000..8a3b927b1 --- /dev/null +++ b/quantum/plugins/cisco/db/services_models.py @@ -0,0 +1,42 @@ +# 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: Edgar Magana, Cisco Systems, Inc. + +from sqlalchemy import Column, Integer, String + +from quantum.plugins.cisco.db.l2network_models import L2NetworkBase +from quantum.plugins.cisco.db.models import BASE + + +class ServicesBinding(BASE, L2NetworkBase): + """Represents a binding of L2 services to networks""" + __tablename__ = 'services_bindings' + + id = Column(Integer, primary_key=True, autoincrement=True) + service_id = Column(String(255)) + mngnet_id = Column(String(255)) + nbnet_id = Column(String(255)) + sbnet_id = Column(String(255)) + + def __init__(self, service_id, mngnet_id, nbnet_id, sbnet_id): + self.service_id = service_id + self.mngnet_id = mngnet_id + self.nbnet_id = nbnet_id + self.sbnet_id = sbnet_id + + def __repr__(self): + return "" % \ + (self.service_id, self.mngnet_id, self.nbnet_id, self.sbnet_id) diff --git a/quantum/plugins/cisco/services/README b/quantum/plugins/cisco/services/README new file mode 100644 index 000000000..3936ca36c --- /dev/null +++ b/quantum/plugins/cisco/services/README @@ -0,0 +1,70 @@ +========================================================================================= +README: L2 Network Services Insertion Utility +========================================================================================= +:Authors: Edgar Magana, Mani Ramasamy, Ram Durairaj +:Collaborators: Deepak Khanorkar, Sumit Naiksatam, Nat Chidambaram, Dan Wendlandt +:Contact: netstack@lists.launchpad.net +:Web site: https://blueprints.launchpad.net/quantum/+spec/services-insertion-wrapper + +Introduction +------------ +This utility offers a simplify way to insert and remove network services +in the path of the traffic to the server VMs, by splitting the network into two, +and having the service bridge between the two, in the process applying the service. +This model is called In-Path (Bump in the Wire) + +Pre-requisites +-------------- +This integration uses Quantum APIs offered on diablo realease and Nova compute +functionality, basically to create new service instances. + +Instructions +------------------------------------------------------ +This utility offer four functionalities: + +1. insert_inpath_service + +Creates two networks and insert a service vm between them bridging the traffic +path. It also creates a management network to access the service configuration. + +2. delete_service +Deletes the service prevopusly inserted as well as the network dependencies. + +connect_vm +Instanciate a VM which is connected to the southbound network created by +insert_inpath_service. Facilitates the connections of server vms into the +tenant's network. + +4. disconnect_vm +Disconnect from the southbound network and terminates the server vm. + +Example +------------------------------------------------------ +Let's insert a Firewall service between northbound and southbound networks, +the management network will be called mng_net: + +#PYTHONPATH=. python quantum/services/service_insertion.py insert_inpath_service +naas ami-00000029 mng_net northnet southnet + +"ami-00000029" is the reference id provided by Glance for the Firewall image +service instance id: i-00000091 + +Now, we can connect a fresh web server in to the southbound network with: +#PYTHONPATH=. python quantum/services/service_insertion.py connect_vm +naas ami-0000002b i-00000091 + +"ami-0000002b" is the reference id provided by Glace for the Web Server image +and "i-00000091" is the instance id provided by Nova for the FW service instance +previously created. +server instance id: i-00000092 + +If we want to disconnect and shutdown the vm instance server: +#PYTHONPATH=. python quantum/plugins/cisco/services/service_insertion.py disconnect_vm i-00000092 + +We can delete the service instance and the network configuration with: +#PYTHONPATH=. python quantum/plugins/cisco/services/service_insertion.py delete_service naas i-00000091 + +Caveats +------------------------------------------------------ +nova-compute service should be running in the same server that Quantum. +Nova API calls will be implemented in the next release (essex-3) diff --git a/quantum/plugins/cisco/services/__init__.py b/quantum/plugins/cisco/services/__init__.py new file mode 100644 index 000000000..d6e7beb5d --- /dev/null +++ b/quantum/plugins/cisco/services/__init__.py @@ -0,0 +1,19 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2011 OpenStack LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Edgar Magana, Cisco Systems +""" +L2 Network Services Insertion Utility +""" diff --git a/quantum/plugins/cisco/services/service_insertion.py b/quantum/plugins/cisco/services/service_insertion.py new file mode 100644 index 000000000..a393cd166 --- /dev/null +++ b/quantum/plugins/cisco/services/service_insertion.py @@ -0,0 +1,318 @@ +# 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: Edgar Magana, Cisco Systems +# +""" +Network Library to insert services using Quantum APIs +Currently has four functionalities: +1. insert_inpath_service + +2. delete_service +3. connect_vm +4. disconnect_vm +""" + + +import logging +import logging.handlers +import os +import subprocess +import re +import sys + +from optparse import OptionParser +from quantum.client import Client +from quantum.plugins.cisco.db import api as db +from quantum.plugins.cisco.db import l2network_db as l2db +from quantum.plugins.cisco.db import services_db as sdb +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.services import services_constants as servconts +from quantum.plugins.cisco.services import services_logistics as servlogcs + + +LOG = logging.getLogger(__name__) + + +def insert_inpath_service(tenant_id, service_image_id, + management_net_name, northbound_net_name, + southbound_net_name, *args): + """Inserting a network service between two networks""" + print ("Creating Network for Services and Servers") + service_logic = servlogcs.ServicesLogistics() + net_list = {} + multiport_net_list = [] + networks_name_list = [management_net_name, northbound_net_name, \ + southbound_net_name] + client = Client(HOST, PORT, USE_SSL, format='json', tenant=tenant_id) + for net in networks_name_list: + data = {servconts.NETWORK: {servconts.NAME: net}} + net_list[net] = client.create_network(data) + net_list[net][servconts.PORTS] = [] + LOG.debug("Network %s Created with ID: %s " % (net, \ + net_list[net][servconts.NETWORK][servconts.ID])) + print "Completed" + print ("Creating Ports on Services and Server Networks") + LOG.debug("Operation 'create_port' executed.") + if not service_logic.verify_plugin(const.UCS_PLUGIN): + for net in networks_name_list: + net_list[net][servconts.PORTS].append + (client.create_port + (net_list[net][servconts.NETWORK][servconts.ID])) + LOG.debug("Operation 'create_port' executed.") + else: + for net in networks_name_list: + nets = net_list[net][servconts.NETWORK][servconts.ID] + multiport_net_list.append(nets) + data = create_multiport(tenant_id, multiport_net_list) + net_idx = 0 + for net in networks_name_list: + port_id = data[servconts.PORTS][net_idx][servconts.ID] + net_list[net][servconts.PORTS].append(port_id) + LOG.debug("Port UUID: %s on network: %s" % \ + (data[servconts.PORTS][net_idx][servconts.ID], net)) + net_idx = net_idx + 1 + print "Completed" + try: + create_vm_args = [] + create_vm_args.append(servconts.CREATE_VM_CMD) + create_vm_args.append(service_image_id) + print ("Creating VM with image: %s" % (service_image_id)) + process = subprocess.Popen(create_vm_args, stdout=subprocess.PIPE) + result = process.stdout.readlines() + tokens = re.search("i-[a-f0-9]*", str(result[1])) + service_vm_name = tokens.group(0) + print ("Image: %s instantiated successfully" % (service_vm_name)) + + except Exception as exc: + print exc + + service_logic.image_status(service_vm_name) + print "Completed" + print "Attaching Ports To VM Service interfaces" + try: + idx = 0 + for net in networks_name_list: + network_id = net_list[net][servconts.NETWORK][servconts.ID] + port_id = net_list[net][servconts.PORTS][idx] + attachment = client.show_port_attachment(network_id, port_id) + attachment = attachment[servconts.ATTACHMENT][servconts.ID][:36] + LOG.debug("Plugging virtual interface: %s of VM %s \ + into port: %s on network: %s" % + (attachment, service_vm_name, port_id, net)) + attach_data = {servconts.ATTACHMENT: {servconts.ID: '%s' % + attachment}} + client.attach_resource(network_id, port_id, attach_data) + except Exception as exc: + print exc + print "Completed" + try: + LOG.debug("Registering Service in DB") + l2db.initialize() + for uuid_net in db.network_id(networks_name_list[0]): + mngnet_id = str(uuid_net.uuid) + for uuid_net in db.network_id(networks_name_list[1]): + nbnet_id = str(uuid_net.uuid) + for uuid_net in db.network_id(networks_name_list[2]): + sbnet_id = str(uuid_net.uuid) + sdb.add_services_binding(service_vm_name, mngnet_id, nbnet_id, + sbnet_id) + except Exception as exc: + print exc + + +def delete_service(tenant_id, service_instance_id, *args): + """ + Removes a service and all the network configuration + """ + l2db.initialize() + print ("Terminating Service VM") + service_logic = servlogcs.ServicesLogistics() + vms_list = [] + vms_list.append(servconts.DELETE_VM_CMD) + vms_list.append(service_instance_id) + + if not service_logic.image_exist(service_instance_id): + print ("Service VM does not exist") + sys.exit() + + result = subprocess.call(vms_list) + service_logic.image_shutdown_verification(service_instance_id) + + client = Client(HOST, PORT, USE_SSL, format='json', tenant=tenant_id) + service_nets = sdb.get_service_bindings(service_instance_id) + print ("Terminating Ports and Networks") + network_name = db.network_get(service_nets.mngnet_id) + port_id_net = db.port_list(service_nets.mngnet_id) + for ports_uuid in port_id_net: + client.delete_port(service_nets.mngnet_id, ports_uuid.uuid) + client.delete_network(service_nets.mngnet_id) + network_name = db.network_get(service_nets.nbnet_id) + port_id_net = db.port_list(service_nets.nbnet_id) + for ports_uuid in port_id_net: + client.delete_port(service_nets.nbnet_id, ports_uuid.uuid) + client.delete_network(service_nets.nbnet_id) + network_name = db.network_get(service_nets.sbnet_id) + port_id_net = db.port_list(service_nets.sbnet_id) + for ports_uuid in port_id_net: + client.delete_port(service_nets.sbnet_id, ports_uuid.uuid) + client.delete_network(service_nets.sbnet_id) + service_list = sdb.remove_services_binding(service_instance_id) + print ("Configuration Removed Successfully") + + +def disconnect_vm(vm_instance_id, *args): + """ + Deletes VMs and Port connection + """ + l2db.initialize() + print ("Terminating Service VM") + service_logic = servlogcs.ServicesLogistics() + vms_list = [] + vms_list.append(servconts.DELETE_VM_CMD) + vms_list.append(vm_instance_id) + result = subprocess.call(vms_list) + service_logic.image_shutdown_verification(vm_instance_id) + print ("VM Server Off") + + +def connect_vm(tenant_id, vm_image_id, service_instance_id, *args): + """ + Starts a VMs and is connected to southbound network + """ + l2db.initialize() + client = Client(HOST, PORT, USE_SSL, format='json', tenant=tenant_id) + print ("Connecting %s to Service %s " % (vm_image_id, service_instance_id)) + service_logic = servlogcs.ServicesLogistics() + service_nets = sdb.get_service_bindings(service_instance_id) + client.create_port(service_nets.mngnet_id) + client.create_port(service_nets.nbnet_id) + sb_port_id = client.create_port(service_nets.sbnet_id) + LOG.debug("Operation 'create_port' executed.") + new_port_id = sb_port_id[servconts.PORT][servconts.ID] + try: + create_vm_args = [] + create_vm_args.append(servconts.CREATE_VM_CMD) + create_vm_args.append(vm_image_id) + print ("Creating VM with image: %s" % (vm_image_id)) + process = subprocess.Popen(create_vm_args, stdout=subprocess.PIPE) + result = process.stdout.readlines() + tokens = re.search("i-[a-f0-9]*", str(result[1])) + vm_name = tokens.group(0) + print ("Image: %s instantiated successfully" % (vm_name)) + except Exception as exc: + print exc + + service_logic.image_status(vm_name) + print "Completed" + print "Attaching Ports To VM Service interfaces" + south_net = service_nets.sbnet_id + attachment = client.show_port_attachment(south_net, new_port_id) + attachment = attachment[servconts.ATTACHMENT][servconts.ID][:36] + LOG.debug("Plugging virtual interface: %s of VM %s \ + into port: %s on network: %s" % + (attachment, vm_name, new_port_id, service_nets.sbnet_id)) + attach_data = {servconts.ATTACHMENT: {servconts.ID: '%s' % attachment}} + client.attach_resource(service_nets.sbnet_id, new_port_id, attach_data) + print ("Connect VM Ended") + + +def create_multiport(tenant_id, networks_list, *args): + """Creates ports on a single host""" + ports_info = {'multiport': \ + {'status': 'ACTIVE', + 'net_id_list': networks_list, + 'ports_desc': {'key': 'value'}}} + request_url = "/multiport" + client = Client(HOST, PORT, USE_SSL, format='json', tenant=tenant_id, + action_prefix=servconts.ACTION_PREFIX_CSCO) + data = client.do_request('POST', request_url, body=ports_info) + return data + + +def build_args(cmd, cmdargs, arglist): + """Building the list of args for a particular CLI""" + args = [] + orig_arglist = arglist[:] + try: + for cmdarg in cmdargs: + args.append(arglist[0]) + del arglist[0] + except: + LOG.debug("Not enough arguments for \"%s\" (expected: %d, got: %d)" + % (cmd, len(cmdargs), len(orig_arglist))) + print "Service Insertion Usage:\n %s %s" % (cmd, + " ".join(["<%s>" % y for y in SERVICE_COMMANDS[cmd]["args"]])) + sys.exit() + if len(arglist) > 0: + LOG.debug("Too many arguments for \"%s\" (expected: %d, got: %d)" \ + % (cmd, len(cmdargs), len(orig_arglist))) + print "Service Insertion Usage:\n %s %s" % (cmd, + " ".join(["<%s>" % y for y in SERVICE_COMMANDS[cmd]["args"]])) + sys.exit() + return args + + +SERVICE_COMMANDS = { + "insert_inpath_service": { + "func": insert_inpath_service, + "args": ["tenant_id", "service_image_id", + "management_net_name", "northbound_net_name", + "southbound_net_name"]}, + "delete_service": { + "func": delete_service, + "args": ["tenant_id", "service_instance_id"]}, + "connect_vm": { + "func": connect_vm, + "args": ["tenant_id", "vm_image_id", + "service_instance_id"]}, + "disconnect_vm": { + "func": disconnect_vm, + "args": ["vm_instance_id"]}} + + +if __name__ == "__main__": + os.system("clear") + usagestr = "Usage: %prog [OPTIONS] [args]" + PARSER = OptionParser(usage=usagestr) + PARSER.add_option("-H", "--host", dest="host", + type="string", default="127.0.0.1", help="ip address of api host") + PARSER.add_option("-p", "--port", dest="port", + type="int", default=9696, help="api port") + PARSER.add_option("-s", "--ssl", dest="ssl", + action="store_true", default=False, help="use ssl") + PARSER.add_option("-v", "--verbose", dest="verbose", + action="store_true", default=False, help="turn on verbose logging") + PARSER.add_option("-f", "--logfile", dest="logfile", + type="string", default="syslog", help="log file path") + options, args = PARSER.parse_args() + if options.verbose: + LOG.setLevel(logging.DEBUG) + else: + LOG.setLevel(logging.WARN) + if options.logfile == "syslog": + LOG.addHandler(logging.handlers.SysLogHandler(address='/dev/log')) + else: + LOG.addHandler(logging.handlers.WatchedFileHandler(options.logfile)) + os.chmod(options.logfile, 0644) + service_logic = servlogcs.ServicesLogistics() + HOST = options.host + PORT = options.port + USE_SSL = options.ssl + CMD = args[0] + args = build_args(CMD, SERVICE_COMMANDS[CMD]["args"], args[1:]) + SERVICE_COMMANDS[CMD]["func"](*args) + sys.exit(0) diff --git a/quantum/plugins/cisco/services/services_constants.py b/quantum/plugins/cisco/services/services_constants.py new file mode 100644 index 000000000..0ee80129c --- /dev/null +++ b/quantum/plugins/cisco/services/services_constants.py @@ -0,0 +1,35 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2011 OpenStack LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Edgar Magana, Cisco Systems +# +""" +Services Constants for the Services insertion Library +""" + + +FORMAT = 'json' +ACTION_PREFIX_EXT = '/v1.0' +ACTION_PREFIX_CSCO = ACTION_PREFIX_EXT + \ + '/extensions/csco/tenants/{tenant_id}' +NETWORK = 'network' +ID = 'id' +PORTS = 'ports' +PORT = 'port' +NAME = 'name' +ATTACHMENT = 'attachment' +CREATE_VM_CMD = '/usr/bin/euca-run-instances' +DELETE_VM_CMD = '/usr/bin/euca-terminate-instances' +DESCRIBE_VM_CMD = '/usr/bin/euca-describe-instances' diff --git a/quantum/plugins/cisco/services/services_logistics.py b/quantum/plugins/cisco/services/services_logistics.py new file mode 100644 index 000000000..7550ac53e --- /dev/null +++ b/quantum/plugins/cisco/services/services_logistics.py @@ -0,0 +1,122 @@ +# 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: Edgar Magana, Cisco Systems +""" +Logistic components for Service Insertion utility +""" + +import logging +import subprocess +import re +import time + +from quantum.common import utils +from quantum.plugins.cisco import l2network_plugin_configuration as conf +from quantum.plugins.cisco.db import services_db as sdb +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.services import services_constants as servconts + +LOG = logging.getLogger(__name__) + + +class ServicesLogistics(): + """ + Services Logistics Modules + """ + def __init__(self): + pass + + def image_shutdown_verification(self, image_name): + """ + Verifies that the VM has been properly shutdown + """ + try: + service_args = [] + service_args.append(servconts.DESCRIBE_VM_CMD) + service_args.append(image_name) + counter = 0 + flag = False + while flag == False and counter <= 5: + counter = counter + 1 + time.sleep(2.5) + process = subprocess.Popen(service_args, \ + stdout=subprocess.PIPE) + result = process.stdout.readlines() + if not result: + flag = True + except Exception, exc: + print exc + + def image_status(self, image_name): + """ + Checks the status of the image + """ + try: + service_args = [] + service_args.append(servconts.DESCRIBE_VM_CMD) + service_args.append(image_name) + counter = 0 + flag = False + while flag == False and counter <= 10: + counter = counter + 1 + time.sleep(2.5) + process = subprocess.Popen(service_args, \ + stdout=subprocess.PIPE) + result = process.stdout.readlines() + if result: + tokens = re.search("running", str(result[1])) + if tokens: + service_status = tokens.group(0) + if service_status == "running": + flag = True + except Exception as exc: + print exc + + def image_exist(self, image_name): + """ + Verifies that the image id is available + """ + try: + service_vm = sdb.get_service_bindings(image_name) + if service_vm: + return True + else: + return False + except Exception as exc: + print exc + + def verify_plugin(self, plugin_key): + """ + Verifies the PlugIn available + """ + _plugins = {} + for key in conf.PLUGINS[const.PLUGINS].keys(): + _plugins[key] = \ + utils.import_object(conf.PLUGINS[const.PLUGINS][key]) + if not plugin_key in _plugins.keys(): + LOG.debug("No %s Plugin loaded" % plugin_key) + return False + else: + LOG.debug("Plugin %s founded" % const.UCS_PLUGIN) + return True + + def press_key(self): + """ + Waits for en external input + """ + key = raw_input("Press any key to continue") + return key diff --git a/quantum/plugins/cisco/tests/unit/test_database.py b/quantum/plugins/cisco/tests/unit/test_database.py index f8298deb8..f1e2f86f7 100644 --- a/quantum/plugins/cisco/tests/unit/test_database.py +++ b/quantum/plugins/cisco/tests/unit/test_database.py @@ -27,6 +27,7 @@ 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.services_db as services_db import quantum.plugins.cisco.db.ucs_db as ucs_db @@ -191,6 +192,59 @@ class NexusDB(object): % str(exc)) +class ServicesDB(object): + """Class consisting of methods to call services db methods""" + def get_all_servicesbindings(self): + """get all services port bindings""" + bindings = [] + try: + for bind in services_db.get_all_services_bindings(): + LOG.debug("Getting services bindings : %s" % bind.service_id) + bind_dict = {} + bind_dict["service_id"] = str(bind.service_id) + bind_dict["mngnet_id"] = str(bind.mngnet_id) + bind_dict["nbnet_id"] = str(bind.nbnet_id) + bind_dict["sbnet_id"] = str(bind.sbnet_id) + bindings.append(bind_dict) + except Exception, exc: + LOG.error("Failed to get all bindings: %s" % str(exc)) + return bindings + + def get_servicebindings(self, service_id): + """get service binding""" + try: + bind = services_db.get_service_bindings(service_id) + LOG.debug("Getting service binding : %s" % bind.service_id) + return bind + except Exception, exc: + LOG.error("Failed to get service binding: %s" % str(exc)) + + def create_servicebinding(self, service_id, mngnet_id, nbnet_id, sbnet_id): + """create service binding""" + bind_dict = {} + try: + res = services_db.add_services_binding(service_id, mngnet_id, \ + nbnet_id, sbnet_id) + LOG.debug("Created service binding : %s" % res.service_id) + bind_dict["service_id"] = str(res.service_id) + bind_dict["mngnet_id"] = str(res.mngnet_id) + bind_dict["nbnet_id"] = str(res.nbnet_id) + bind_dict["sbnet_id"] = str(res.sbnet_id) + return bind_dict + except Exception, exc: + LOG.error("Failed to create service binding: %s" % str(exc)) + + def delete_servicebinding(self, service_id): + """delete service binding""" + try: + bind = services_db.remove_services_binding(service_id) + for res in bind: + LOG.debug("Deleted service binding: %s" % res.service_id) + except Exception, exc: + raise Exception("Failed to delete service binding: %s" + % str(exc)) + + class L2networkDB(object): """Class conisting of methods to call L2network db methods""" def get_all_vlan_bindings(self): @@ -741,6 +795,72 @@ class NexusDBTest(unittest.TestCase): self.dbtest.delete_nexusportbinding(vlan_id) +class ServicesDBTest(unittest.TestCase): + """Class conisting of services DB unit tests""" + def setUp(self): + """Setup for services db tests""" + l2network_db.initialize() + self.dbtest = ServicesDB() + LOG.debug("Setup") + + def tearDown(self): + """Tear Down""" + db.clear_db() + + def testa_create_servicebinding(self): + """create service binding""" + service_id = self.dbtest.create_servicebinding("i-00001", \ + "mng_net", "northb_net", "northb_net") + self.assertTrue(service_id["service_id"] == "i-00001") + self.tearDown_servicebinding() + + def testb_get_servicesbindings(self): + """get all services binding""" + service_id = self.dbtest.create_servicebinding("i-00001", \ + "mng_net", "northb_net", "northb_net") + bindings = self.dbtest.get_servicebindings("i-00001") + count = 0 + if bindings: + count += 1 + self.assertTrue(count == 1) + self.tearDown_servicebinding() + + def testb_getall_servicesbindings(self): + """get all services binding""" + service_id = self.dbtest.create_servicebinding("i-00001", \ + "mng_net", "northb_net", "northb_net") + service_id = self.dbtest.create_servicebinding("i-00002", \ + "mng_net", "northb_net", "northb_net") + bindings = self.dbtest.get_all_servicesbindings() + count = 0 + for bind in bindings: + if "mng_net" in bind["mngnet_id"]: + count += 1 + self.assertTrue(count == 2) + self.tearDown_servicebinding() + + def testc_delete_servicesbinding(self): + """delete services binding""" + binding_serv = self.dbtest.create_servicebinding("i-00001", \ + "mng_net", "northb_net", "northb_net") + self.dbtest.delete_servicebinding("i-00001") + bindings = self.dbtest.get_all_servicesbindings() + count = 0 + for bind in bindings: + if "mng_net" in bind["mngnet_id"]: + count += 1 + self.assertTrue(count == 0) + self.tearDown_servicebinding() + + def tearDown_servicebinding(self): + """tear down nexusport binding table""" + LOG.debug("Tearing Down Nexus port Bindings") + binds = self.dbtest.get_all_servicesbindings() + for bind in binds: + service_id = bind["service_id"] + self.dbtest.delete_servicebinding(service_id) + + class L2networkDBTest(unittest.TestCase): """Class conisting of L2network DB unit tests""" def setUp(self): -- 2.45.2