Reformatting RO
Change-Id: I86e6a102b5bf2e0221b29096bbb132ca656844c5
Signed-off-by: sousaedu <eduardo.sousa@canonical.com>
diff --git a/RO-plugin/osm_ro_plugin/openflow_conn.py b/RO-plugin/osm_ro_plugin/openflow_conn.py
index f46c6cf..17351b0 100644
--- a/RO-plugin/osm_ro_plugin/openflow_conn.py
+++ b/RO-plugin/osm_ro_plugin/openflow_conn.py
@@ -31,6 +31,7 @@
class OpenflowConnException(Exception):
"""Common and base class Exception for all vimconnector exceptions"""
+
def __init__(self, message, http_code=HTTPStatus.BAD_REQUEST.value):
Exception.__init__(self, message)
self.http_code = http_code
@@ -38,42 +39,49 @@
class OpenflowConnConnectionException(OpenflowConnException):
"""Connectivity error with the VIM"""
+
def __init__(self, message, http_code=HTTPStatus.SERVICE_UNAVAILABLE.value):
OpenflowConnException.__init__(self, message, http_code)
class OpenflowConnUnexpectedResponse(OpenflowConnException):
"""Get an wrong response from VIM"""
+
def __init__(self, message, http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value):
OpenflowConnException.__init__(self, message, http_code)
class OpenflowConnAuthException(OpenflowConnException):
"""Invalid credentials or authorization to perform this action over the VIM"""
+
def __init__(self, message, http_code=HTTPStatus.UNAUTHORIZED.value):
OpenflowConnException.__init__(self, message, http_code)
class OpenflowConnNotFoundException(OpenflowConnException):
"""The item is not found at VIM"""
+
def __init__(self, message, http_code=HTTPStatus.NOT_FOUND.value):
OpenflowConnException.__init__(self, message, http_code)
class OpenflowConnConflictException(OpenflowConnException):
"""There is a conflict, e.g. more item found than one"""
+
def __init__(self, message, http_code=HTTPStatus.CONFLICT.value):
OpenflowConnException.__init__(self, message, http_code)
class OpenflowConnNotSupportedException(OpenflowConnException):
"""The request is not supported by connector"""
+
def __init__(self, message, http_code=HTTPStatus.SERVICE_UNAVAILABLE.value):
OpenflowConnException.__init__(self, message, http_code)
class OpenflowConnNotImplemented(OpenflowConnException):
"""The method is not implemented by the connected"""
+
def __init__(self, message, http_code=HTTPStatus.NOT_IMPLEMENTED.value):
OpenflowConnException.__init__(self, message, http_code)
@@ -82,14 +90,15 @@
"""
Openflow controller connector abstract implementeation.
"""
+
def __init__(self, params):
self.name = "openflow_conector"
self.pp2ofi = {} # From Physical Port to OpenFlow Index
self.ofi2pp = {} # From OpenFlow Index to Physical Port
- self.logger = logging.getLogger('ro.sdn.openflow_conn')
+ self.logger = logging.getLogger("ro.sdn.openflow_conn")
def get_of_switches(self):
- """"
+ """
Obtain a a list of switches or DPID detected by this controller
:return: list length, and a list where each element a tuple pair (DPID, IP address), text_error: if fails
"""
@@ -146,7 +155,7 @@
raise OpenflowConnNotImplemented("Should have implemented this")
def clear_all_flows(self):
- """"
+ """
Delete all existing rules
:return: None if ok, text_error if fails
"""
@@ -157,13 +166,24 @@
"""
This class is the base engine of SDN plugins base on openflow rules
"""
- flow_fields = ('priority', 'vlan', 'ingress_port', 'actions', 'dst_mac', 'src_mac', 'net_id')
+
+ flow_fields = (
+ "priority",
+ "vlan",
+ "ingress_port",
+ "actions",
+ "dst_mac",
+ "src_mac",
+ "net_id",
+ )
def __init__(self, wim, wim_account, config=None, logger=None, of_connector=None):
- self.logger = logger or logging.getLogger('ro.sdn.openflow_conn')
+ self.logger = logger or logging.getLogger("ro.sdn.openflow_conn")
self.of_connector = of_connector
config = config or {}
- self.of_controller_nets_with_same_vlan = config.get("of_controller_nets_with_same_vlan", False)
+ self.of_controller_nets_with_same_vlan = config.get(
+ "of_controller_nets_with_same_vlan", False
+ )
def check_credentials(self):
try:
@@ -182,16 +202,23 @@
def create_connectivity_service(self, service_type, connection_points, **kwargs):
net_id = str(uuid4())
ports = []
+
for cp in connection_points:
port = {
"uuid": cp["service_endpoint_id"],
"vlan": cp.get("service_endpoint_encapsulation_info", {}).get("vlan"),
"mac": cp.get("service_endpoint_encapsulation_info", {}).get("mac"),
- "switch_port": cp.get("service_endpoint_encapsulation_info", {}).get("switch_port"),
+ "switch_port": cp.get("service_endpoint_encapsulation_info", {}).get(
+ "switch_port"
+ ),
}
ports.append(port)
+
try:
- created_items = self._set_openflow_rules(service_type, net_id, ports, created_items=None)
+ created_items = self._set_openflow_rules(
+ service_type, net_id, ports, created_items=None
+ )
+
return net_id, created_items
except (SdnConnectorError, OpenflowConnException) as e:
raise SdnConnectorError(e, http_code=e.http_code)
@@ -200,24 +227,36 @@
try:
service_type = "ELAN"
ports = []
- self._set_openflow_rules(service_type, service_uuid, ports, created_items=conn_info)
+ self._set_openflow_rules(
+ service_type, service_uuid, ports, created_items=conn_info
+ )
+
return None
except (SdnConnectorError, OpenflowConnException) as e:
raise SdnConnectorError(e, http_code=e.http_code)
- def edit_connectivity_service(self, service_uuid, conn_info=None, connection_points=None, **kwargs):
+ def edit_connectivity_service(
+ self, service_uuid, conn_info=None, connection_points=None, **kwargs
+ ):
ports = []
for cp in connection_points:
port = {
"uuid": cp["service_endpoint_id"],
"vlan": cp.get("service_endpoint_encapsulation_info", {}).get("vlan"),
"mac": cp.get("service_endpoint_encapsulation_info", {}).get("mac"),
- "switch_port": cp.get("service_endpoint_encapsulation_info", {}).get("switch_port"),
+ "switch_port": cp.get("service_endpoint_encapsulation_info", {}).get(
+ "switch_port"
+ ),
}
ports.append(port)
+
service_type = "ELAN" # TODO. Store at conn_info for later use
+
try:
- created_items = self._set_openflow_rules(service_type, service_uuid, ports, created_items=conn_info)
+ created_items = self._set_openflow_rules(
+ service_type, service_uuid, ports, created_items=conn_info
+ )
+
return created_items
except (SdnConnectorError, OpenflowConnException) as e:
raise SdnConnectorError(e, http_code=e.http_code)
@@ -234,8 +273,13 @@
def _set_openflow_rules(self, net_type, net_id, ports, created_items=None):
ifaces_nb = len(ports)
+
if not created_items:
- created_items = {"status": None, "error_msg": None, "installed_rules_ids": []}
+ created_items = {
+ "status": None,
+ "error_msg": None,
+ "installed_rules_ids": [],
+ }
rules_to_delete = created_items.get("installed_rules_ids") or []
new_installed_rules_ids = []
error_list = []
@@ -244,22 +288,31 @@
step = "Checking ports and network type compatibility"
if ifaces_nb < 2:
pass
- elif net_type == 'ELINE':
+ elif net_type == "ELINE":
if ifaces_nb > 2:
- raise SdnConnectorError("'ELINE' type network cannot connect {} interfaces, only 2".format(
- ifaces_nb))
- elif net_type == 'ELAN':
+ raise SdnConnectorError(
+ "'ELINE' type network cannot connect {} interfaces, only 2".format(
+ ifaces_nb
+ )
+ )
+ elif net_type == "ELAN":
if ifaces_nb > 2 and self.of_controller_nets_with_same_vlan:
# check all ports are VLAN (tagged) or none
vlan_tags = []
+
for port in ports:
if port["vlan"] not in vlan_tags:
vlan_tags.append(port["vlan"])
+
if len(vlan_tags) > 1:
- raise SdnConnectorError("This pluging cannot connect ports with diferent VLAN tags when flag "
- "'of_controller_nets_with_same_vlan' is active")
+ raise SdnConnectorError(
+ "This pluging cannot connect ports with diferent VLAN tags when flag "
+ "'of_controller_nets_with_same_vlan' is active"
+ )
else:
- raise SdnConnectorError('Only ELINE or ELAN network types are supported for openflow')
+ raise SdnConnectorError(
+ "Only ELINE or ELAN network types are supported for openflow"
+ )
# Get the existing flows at openflow controller
step = "Getting installed openflow rules"
@@ -274,16 +327,20 @@
for flow in new_flows:
# 1 check if an equal flow is already present
index = self._check_flow_already_present(flow, existing_flows)
+
if index >= 0:
flow_id = existing_flows[index]["name"]
self.logger.debug("Skipping already present flow %s", str(flow))
else:
# 2 look for a non used name
flow_name = flow["net_id"] + "." + str(name_index)
+
while flow_name in existing_flows_ids:
name_index += 1
flow_name = flow["net_id"] + "." + str(name_index)
- flow['name'] = flow_name
+
+ flow["name"] = flow_name
+
# 3 insert at openflow
try:
self.of_connector.new_flow(flow)
@@ -291,8 +348,11 @@
existing_flows_ids.append(flow_id)
except OpenflowConnException as e:
flow_id = None
- error_list.append("Cannot create rule for ingress_port={}, dst_mac={}: {}"
- .format(flow["ingress_port"], flow["dst_mac"], e))
+ error_list.append(
+ "Cannot create rule for ingress_port={}, dst_mac={}: {}".format(
+ flow["ingress_port"], flow["dst_mac"], e
+ )
+ )
# 4 insert at database
if flow_id:
@@ -311,13 +371,16 @@
error_text = "Cannot remove rule '{}': {}".format(flow_id, e)
error_list.append(error_text)
self.logger.error(error_text)
+
created_items["installed_rules_ids"] = new_installed_rules_ids
+
if error_list:
created_items["error_msg"] = ";".join(error_list)[:1000]
created_items["error_msg"] = "ERROR"
else:
created_items["error_msg"] = None
created_items["status"] = "ACTIVE"
+
return created_items
except (SdnConnectorError, OpenflowConnException) as e:
raise SdnConnectorError("Error while {}: {}".format(step, e)) from e
@@ -334,54 +397,69 @@
# Check switch_port information is right
for port in ports:
nb_ports += 1
- if str(port['switch_port']) not in self.of_connector.pp2ofi:
- raise SdnConnectorError("switch port name '{}' is not valid for the openflow controller".
- format(port['switch_port']))
+
+ if str(port["switch_port"]) not in self.of_connector.pp2ofi:
+ raise SdnConnectorError(
+ "switch port name '{}' is not valid for the openflow controller".format(
+ port["switch_port"]
+ )
+ )
+
priority = 1000 # 1100
for src_port in ports:
# if src_port.get("groups")
- vlan_in = src_port['vlan']
+ vlan_in = src_port["vlan"]
# BROADCAST:
- broadcast_key = src_port['uuid'] + "." + str(vlan_in)
+ broadcast_key = src_port["uuid"] + "." + str(vlan_in)
if broadcast_key in new_broadcast_flows:
flow_broadcast = new_broadcast_flows[broadcast_key]
else:
- flow_broadcast = {'priority': priority,
- 'net_id': net_id,
- 'dst_mac': 'ff:ff:ff:ff:ff:ff',
- "ingress_port": str(src_port['switch_port']),
- 'vlan_id': vlan_in,
- 'actions': []
- }
+ flow_broadcast = {
+ "priority": priority,
+ "net_id": net_id,
+ "dst_mac": "ff:ff:ff:ff:ff:ff",
+ "ingress_port": str(src_port["switch_port"]),
+ "vlan_id": vlan_in,
+ "actions": [],
+ }
new_broadcast_flows[broadcast_key] = flow_broadcast
+
if vlan_in is not None:
- flow_broadcast['vlan_id'] = str(vlan_in)
+ flow_broadcast["vlan_id"] = str(vlan_in)
for dst_port in ports:
- vlan_out = dst_port['vlan']
- if src_port['switch_port'] == dst_port['switch_port'] and vlan_in == vlan_out:
+ vlan_out = dst_port["vlan"]
+
+ if (
+ src_port["switch_port"] == dst_port["switch_port"]
+ and vlan_in == vlan_out
+ ):
continue
+
flow = {
"priority": priority,
- 'net_id': net_id,
- "ingress_port": str(src_port['switch_port']),
- 'vlan_id': vlan_in,
- 'actions': []
+ "net_id": net_id,
+ "ingress_port": str(src_port["switch_port"]),
+ "vlan_id": vlan_in,
+ "actions": [],
}
+
# allow that one port have no mac
- if dst_port['mac'] is None or nb_ports == 2: # point to point or nets with 2 elements
- flow['priority'] = priority - 5 # less priority
+ # point to point or nets with 2 elements
+ if dst_port["mac"] is None or nb_ports == 2:
+ flow["priority"] = priority - 5 # less priority
else:
- flow['dst_mac'] = str(dst_port['mac'])
+ flow["dst_mac"] = str(dst_port["mac"])
if vlan_out is None:
if vlan_in:
- flow['actions'].append(('vlan', None))
+ flow["actions"].append(("vlan", None))
else:
- flow['actions'].append(('vlan', vlan_out))
- flow['actions'].append(('out', str(dst_port['switch_port'])))
+ flow["actions"].append(("vlan", vlan_out))
+
+ flow["actions"].append(("out", str(dst_port["switch_port"])))
if self._check_flow_already_present(flow, new_flows) >= 0:
self.logger.debug("Skipping repeated flow '%s'", str(flow))
@@ -390,33 +468,45 @@
new_flows.append(flow)
# BROADCAST:
- if nb_ports <= 2: # point to multipoint or nets with more than 2 elements
+ # point to multipoint or nets with more than 2 elements
+ if nb_ports <= 2:
continue
- out = (vlan_out, str(dst_port['switch_port']))
- if out not in flow_broadcast['actions']:
- flow_broadcast['actions'].append(out)
+
+ out = (vlan_out, str(dst_port["switch_port"]))
+
+ if out not in flow_broadcast["actions"]:
+ flow_broadcast["actions"].append(out)
# BROADCAST
for flow_broadcast in new_broadcast_flows.values():
- if len(flow_broadcast['actions']) == 0:
+ if len(flow_broadcast["actions"]) == 0:
continue # nothing to do, skip
- flow_broadcast['actions'].sort()
- if 'vlan_id' in flow_broadcast:
- previous_vlan = 0 # indicates that a packet contains a vlan, and the vlan
+
+ flow_broadcast["actions"].sort()
+
+ if "vlan_id" in flow_broadcast:
+ # indicates that a packet contains a vlan, and the vlan
+ previous_vlan = 0
else:
previous_vlan = None
+
final_actions = []
action_number = 0
- for action in flow_broadcast['actions']:
+
+ for action in flow_broadcast["actions"]:
if action[0] != previous_vlan:
- final_actions.append(('vlan', action[0]))
+ final_actions.append(("vlan", action[0]))
previous_vlan = action[0]
+
if self.of_controller_nets_with_same_vlan and action_number:
- raise SdnConnectorError("Cannot interconnect different vlan tags in a network when flag "
- "'of_controller_nets_with_same_vlan' is True.")
+ raise SdnConnectorError(
+ "Cannot interconnect different vlan tags in a network when flag "
+ "'of_controller_nets_with_same_vlan' is True."
+ )
+
action_number += 1
- final_actions.append(('out', action[1]))
- flow_broadcast['actions'] = final_actions
+ final_actions.append(("out", action[1]))
+ flow_broadcast["actions"] = final_actions
if self._check_flow_already_present(flow_broadcast, new_flows) >= 0:
self.logger.debug("Skipping repeated flow '%s'", str(flow_broadcast))
@@ -427,39 +517,51 @@
# UNIFY openflow rules with the same input port and vlan and the same output actions
# These flows differ at the dst_mac; and they are unified by not filtering by dst_mac
# this can happen if there is only two ports. It is converted to a point to point connection
- flow_dict = {} # use as key vlan_id+ingress_port and as value the list of flows matching these values
+ # use as key vlan_id+ingress_port and as value the list of flows matching these values
+ flow_dict = {}
for flow in new_flows:
key = str(flow.get("vlan_id")) + ":" + flow["ingress_port"]
+
if key in flow_dict:
flow_dict[key].append(flow)
else:
flow_dict[key] = [flow]
+
new_flows2 = []
+
for flow_list in flow_dict.values():
convert2ptp = False
+
if len(flow_list) >= 2:
convert2ptp = True
+
for f in flow_list:
- if f['actions'] != flow_list[0]['actions']:
+ if f["actions"] != flow_list[0]["actions"]:
convert2ptp = False
break
+
if convert2ptp: # add only one unified rule without dst_mac
- self.logger.debug("Convert flow rules to NON mac dst_address " + str(flow_list))
- flow_list[0].pop('dst_mac')
+ self.logger.debug(
+ "Convert flow rules to NON mac dst_address " + str(flow_list)
+ )
+ flow_list[0].pop("dst_mac")
flow_list[0]["priority"] -= 5
new_flows2.append(flow_list[0])
else: # add all the rules
new_flows2 += flow_list
+
return new_flows2
def _check_flow_already_present(self, new_flow, flow_list):
- '''check if the same flow is already present in the flow list
+ """check if the same flow is already present in the flow list
The flow is repeated if all the fields, apart from name, are equal
- Return the index of matching flow, -1 if not match'''
+ Return the index of matching flow, -1 if not match
+ """
for index, flow in enumerate(flow_list):
for f in self.flow_fields:
if flow.get(f) != new_flow.get(f):
break
else:
return index
+
return -1
diff --git a/RO-plugin/osm_ro_plugin/sdn_dummy.py b/RO-plugin/osm_ro_plugin/sdn_dummy.py
index 687d4ab..fb7c552 100644
--- a/RO-plugin/osm_ro_plugin/sdn_dummy.py
+++ b/RO-plugin/osm_ro_plugin/sdn_dummy.py
@@ -24,6 +24,7 @@
from uuid import uuid4
from osm_ro_plugin.sdnconn import SdnConnectorBase, SdnConnectorError
from http import HTTPStatus
+
__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
@@ -43,8 +44,9 @@
The arguments of the constructor are converted to object attributes.
An extra property, ``service_endpoint_mapping`` is created from ``config``.
"""
+
def __init__(self, wim, wim_account, config=None, logger=None):
- self.logger = logger or logging.getLogger('ro.sdn.dummy')
+ self.logger = logger or logging.getLogger("ro.sdn.dummy")
super(SdnDummyConnector, self).__init__(wim, wim_account, config, self.logger)
self.logger.debug("__init: wim='{}' wim_account='{}'".format(wim, wim_account))
self.connections = {}
@@ -58,6 +60,7 @@
external URLs, etc are detected.
"""
self.logger.debug("check_credentials")
+
return None
def get_connectivity_service_status(self, service_uuid, conn_info=None):
@@ -77,48 +80,66 @@
keys can be used to provide additional status explanation or
new information available for the connectivity service.
"""
- self.logger.debug("get_connectivity_service_status: service_uuid='{}' conn_info='{}'".format(service_uuid,
- conn_info))
- return {'sdn_status': 'ACTIVE', 'sdn_info': self.connections.get(service_uuid)}
+ self.logger.debug(
+ "get_connectivity_service_status: service_uuid='{}' conn_info='{}'".format(
+ service_uuid, conn_info
+ )
+ )
- def create_connectivity_service(self, service_type, connection_points,
- **kwargs):
- """
- Stablish WAN connectivity between the endpoints
+ return {"sdn_status": "ACTIVE", "sdn_info": self.connections.get(service_uuid)}
- """
- self.logger.debug("create_connectivity_service: service_type='{}' connection_points='{}', kwargs='{}'".
- format(service_type, connection_points, kwargs))
+ def create_connectivity_service(self, service_type, connection_points, **kwargs):
+ """Establish WAN connectivity between the endpoints"""
+ self.logger.debug(
+ "create_connectivity_service: service_type='{}' connection_points='{}', kwargs='{}'".format(
+ service_type, connection_points, kwargs
+ )
+ )
_id = str(uuid4())
self.connections[_id] = connection_points.copy()
self.counter += 1
+
return _id, None
def delete_connectivity_service(self, service_uuid, conn_info=None):
- """Disconnect multi-site endpoints previously connected
+ """Disconnect multi-site endpoints previously connected"""
+ self.logger.debug(
+ "delete_connectivity_service: service_uuid='{}' conn_info='{}'".format(
+ service_uuid, conn_info
+ )
+ )
- """
- self.logger.debug("delete_connectivity_service: service_uuid='{}' conn_info='{}'".format(service_uuid,
- conn_info))
if service_uuid not in self.connections:
- raise SdnConnectorError("connectivity {} not found".format(service_uuid),
- http_code=HTTPStatus.NOT_FOUND.value)
+ raise SdnConnectorError(
+ "connectivity {} not found".format(service_uuid),
+ http_code=HTTPStatus.NOT_FOUND.value,
+ )
+
self.connections.pop(service_uuid, None)
+
return None
- def edit_connectivity_service(self, service_uuid, conn_info=None,
- connection_points=None, **kwargs):
+ def edit_connectivity_service(
+ self, service_uuid, conn_info=None, connection_points=None, **kwargs
+ ):
"""Change an existing connectivity service.
This method's arguments and return value follow the same convention as
:meth:`~.create_connectivity_service`.
"""
- self.logger.debug("edit_connectivity_service: service_uuid='{}' conn_info='{}', connection_points='{}'"
- "kwargs='{}'".format(service_uuid, conn_info, connection_points, kwargs))
+ self.logger.debug(
+ "edit_connectivity_service: service_uuid='{}' conn_info='{}', connection_points='{}'"
+ "kwargs='{}'".format(service_uuid, conn_info, connection_points, kwargs)
+ )
+
if service_uuid not in self.connections:
- raise SdnConnectorError("connectivity {} not found".format(service_uuid),
- http_code=HTTPStatus.NOT_FOUND.value)
+ raise SdnConnectorError(
+ "connectivity {} not found".format(service_uuid),
+ http_code=HTTPStatus.NOT_FOUND.value,
+ )
+
self.connections[service_uuid] = connection_points.copy()
+
return None
def clear_all_connectivity_services(self):
@@ -131,6 +152,7 @@
"""
self.logger.debug("clear_all_connectivity_services")
self.connections.clear()
+
return None
def get_all_active_connectivity_services(self):
@@ -141,4 +163,5 @@
SdnConnectorException: In case of error.
"""
self.logger.debug("get_all_active_connectivity_services")
+
return self.connections
diff --git a/RO-plugin/osm_ro_plugin/sdn_failing.py b/RO-plugin/osm_ro_plugin/sdn_failing.py
index fac28aa..8c373e6 100644
--- a/RO-plugin/osm_ro_plugin/sdn_failing.py
+++ b/RO-plugin/osm_ro_plugin/sdn_failing.py
@@ -47,6 +47,7 @@
This way we can make sure that all the other parts of the program will work
but the user will have all the information available to fix the problem.
"""
+
def __init__(self, error_msg):
self.error_msg = error_msg
@@ -57,26 +58,34 @@
raise Exception(self.error_msg)
def check_credentials(self):
- raise SdnConnectorError('Impossible to use WIM:\n' + self.error_msg)
+ raise SdnConnectorError("Impossible to use WIM:\n" + self.error_msg)
def get_connectivity_service_status(self, service_uuid, _conn_info=None):
- raise SdnConnectorError('Impossible to retrieve status for {}: {}'
- .format(service_uuid, self.error_msg))
+ raise SdnConnectorError(
+ "Impossible to retrieve status for {}: {}".format(
+ service_uuid, self.error_msg
+ )
+ )
def create_connectivity_service(self, service_uuid, *args, **kwargs):
- raise SdnConnectorError('Impossible to create connectivity: {}'
- .format(self.error_msg))
+ raise SdnConnectorError(
+ "Impossible to create connectivity: {}".format(self.error_msg)
+ )
def delete_connectivity_service(self, service_uuid, _conn_info=None):
- raise SdnConnectorError('Impossible to delete {}: {}'
- .format(service_uuid, self.error_msg))
+ raise SdnConnectorError(
+ "Impossible to delete {}: {}".format(service_uuid, self.error_msg)
+ )
def edit_connectivity_service(self, service_uuid, *args, **kwargs):
- raise SdnConnectorError('Impossible to change connection {}: {}'
- .format(service_uuid, self.error_msg))
+ raise SdnConnectorError(
+ "Impossible to change connection {}: {}".format(
+ service_uuid, self.error_msg
+ )
+ )
def clear_all_connectivity_services(self):
- raise SdnConnectorError('Impossible to use WIM: {}'.format(self.error_msg))
+ raise SdnConnectorError("Impossible to use WIM: {}".format(self.error_msg))
def get_all_active_connectivity_services(self):
- raise SdnConnectorError('Impossible to use WIM: {}'.format(self.error_msg))
+ raise SdnConnectorError("Impossible to use WIM: {}".format(self.error_msg))
diff --git a/RO-plugin/osm_ro_plugin/sdnconn.py b/RO-plugin/osm_ro_plugin/sdnconn.py
index ccf16b1..a1849c9 100644
--- a/RO-plugin/osm_ro_plugin/sdnconn.py
+++ b/RO-plugin/osm_ro_plugin/sdnconn.py
@@ -36,24 +36,26 @@
It receives information from ports to be connected .
"""
+
import logging
from http import HTTPStatus
class SdnConnectorError(Exception):
"""Base Exception for all connector related errors
- provide the parameter 'http_code' (int) with the error code:
- Bad_Request = 400
- Unauthorized = 401 (e.g. credentials are not valid)
- Not_Found = 404 (e.g. try to edit or delete a non existing connectivity service)
- Forbidden = 403
- Method_Not_Allowed = 405
- Not_Acceptable = 406
- Request_Timeout = 408 (e.g timeout reaching server, or cannot reach the server)
- Conflict = 409
- Service_Unavailable = 503
- Internal_Server_Error = 500
+ provide the parameter 'http_code' (int) with the error code:
+ Bad_Request = 400
+ Unauthorized = 401 (e.g. credentials are not valid)
+ Not_Found = 404 (e.g. try to edit or delete a non existing connectivity service)
+ Forbidden = 403
+ Method_Not_Allowed = 405
+ Not_Acceptable = 406
+ Request_Timeout = 408 (e.g timeout reaching server, or cannot reach the server)
+ Conflict = 409
+ Service_Unavailable = 503
+ Internal_Server_Error = 500
"""
+
def __init__(self, message, http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value):
Exception.__init__(self, message)
self.http_code = http_code
@@ -69,36 +71,34 @@
The arguments of the constructor are converted to object attributes.
An extra property, ``service_endpoint_mapping`` is created from ``config``.
"""
+
def __init__(self, wim, wim_account, config=None, logger=None):
"""
-
:param wim: (dict). Contains among others 'wim_url'
:param wim_account: (dict). Contains among others 'uuid' (internal id), 'name',
'sdn' (True if is intended for SDN-assist or False if intended for WIM), 'user', 'password'.
:param config: (dict or None): Particular information of plugin. These keys if present have a common meaning:
'mapping_not_needed': (bool) False by default or if missing, indicates that mapping is not needed.
'service_endpoint_mapping': (list) provides the internal endpoint mapping. The meaning is:
- KEY meaning for WIM meaning for SDN assist
+ KEY meaning for WIM meaning for SDN assist
-------- -------- --------
- device_id pop_switch_dpid compute_id
- device_interface_id pop_switch_port compute_pci_address
- service_endpoint_id wan_service_endpoint_id SDN_service_endpoint_id
- service_mapping_info wan_service_mapping_info SDN_service_mapping_info
+ device_id pop_switch_dpid compute_id
+ device_interface_id pop_switch_port compute_pci_address
+ service_endpoint_id wan_service_endpoint_id SDN_service_endpoint_id
+ service_mapping_info wan_service_mapping_info SDN_service_mapping_info
contains extra information if needed. Text in Yaml format
- switch_dpid wan_switch_dpid SDN_switch_dpid
- switch_port wan_switch_port SDN_switch_port
+ switch_dpid wan_switch_dpid SDN_switch_dpid
+ switch_port wan_switch_port SDN_switch_port
datacenter_id vim_account vim_account
- id: (internal, do not use)
- wim_id: (internal, do not use)
+ id: (internal, do not use)
+ wim_id: (internal, do not use)
:param logger (logging.Logger): optional logger object. If none is passed 'openmano.sdn.sdnconn' is used.
"""
- self.logger = logger or logging.getLogger('ro.sdn')
-
+ self.logger = logger or logging.getLogger("ro.sdn")
self.wim = wim
self.wim_account = wim_account
self.config = config or {}
- self.service_endpoint_mapping = (
- self.config.get('service_endpoint_mapping', []))
+ self.service_endpoint_mapping = self.config.get("service_endpoint_mapping", [])
def check_credentials(self):
"""Check if the connector itself can access the SDN/WIM with the provided url (wim.wim_url),
@@ -151,7 +151,7 @@
def create_connectivity_service(self, service_type, connection_points, **kwargs):
"""
- Stablish SDN/WAN connectivity between the endpoints
+ Establish SDN/WAN connectivity between the endpoints
:param service_type: (str): ``ELINE`` (L2), ``ELAN`` (L2), ``ETREE`` (L2), ``L3``.
:param connection_points: (list): each point corresponds to
an entry point to be connected. For WIM: from the DC to the transport network.
@@ -199,8 +199,10 @@
"""
raise NotImplementedError
- def edit_connectivity_service(self, service_uuid, conn_info=None, connection_points=None, **kwargs):
- """ Change an existing connectivity service.
+ def edit_connectivity_service(
+ self, service_uuid, conn_info=None, connection_points=None, **kwargs
+ ):
+ """Change an existing connectivity service.
This method's arguments and return value follow the same convention as
:meth:`~.create_connectivity_service`.
diff --git a/RO-plugin/osm_ro_plugin/vim_dummy.py b/RO-plugin/osm_ro_plugin/vim_dummy.py
index 22379b6..c00071f 100644
--- a/RO-plugin/osm_ro_plugin/vim_dummy.py
+++ b/RO-plugin/osm_ro_plugin/vim_dummy.py
@@ -37,19 +37,45 @@
vm_ip: ip address to provide at VM creation. For some tests must be a valid reachable VM
ssh_key: private ssh key to use for inserting an authorized ssh key
"""
- def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None,
- config={}, persistent_info={}):
- super().__init__(uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
- config, persistent_info)
- self.logger = logging.getLogger('ro.vim.dummy')
+
+ def __init__(
+ self,
+ uuid,
+ name,
+ tenant_id,
+ tenant_name,
+ url,
+ url_admin=None,
+ user=None,
+ passwd=None,
+ log_level=None,
+ config={},
+ persistent_info={},
+ ):
+ super().__init__(
+ uuid,
+ name,
+ tenant_id,
+ tenant_name,
+ url,
+ url_admin,
+ user,
+ passwd,
+ log_level,
+ config,
+ persistent_info,
+ )
+ self.logger = logging.getLogger("ro.vim.dummy")
+
if log_level:
self.logger.setLevel(getattr(logging, log_level))
+
self.nets = {
"mgmt": {
"id": "mgmt",
"name": "mgmt",
"status": "ACTIVE",
- "vim_info": '{status: ACTIVE}'
+ "vim_info": "{status: ACTIVE}",
}
}
self.vms = {}
@@ -60,44 +86,54 @@
"90681b39-dc09-49b7-ba2e-2c00c6b33b76": {
"id": "90681b39-dc09-49b7-ba2e-2c00c6b33b76",
"name": "cirros034",
- "checksum": "ee1eca47dc88f4879d8a229cc70a07c6"
+ "checksum": "ee1eca47dc88f4879d8a229cc70a07c6",
},
"83a39656-65db-47dc-af03-b55289115a53": {
"id": "",
"name": "cirros040",
- "checksum": "443b7623e27ecf03dc9e01ee93f67afe"
+ "checksum": "443b7623e27ecf03dc9e01ee93f67afe",
},
"208314f2-8eb6-4101-965d-fe2ffbaedf3c": {
"id": "208314f2-8eb6-4101-965d-fe2ffbaedf3c",
"name": "ubuntu18.04",
- "checksum": "b6fc7b9b91bca32e989e1edbcdeecb95"
+ "checksum": "b6fc7b9b91bca32e989e1edbcdeecb95",
},
"c03321f8-4b6e-4045-a309-1b3878bd32c1": {
"id": "c03321f8-4b6e-4045-a309-1b3878bd32c1",
"name": "ubuntu16.04",
- "checksum": "8f08442faebad2d4a99fedb22fca11b5"
+ "checksum": "8f08442faebad2d4a99fedb22fca11b5",
},
"4f6399a2-3554-457e-916e-ada01f8b950b": {
"id": "4f6399a2-3554-457e-916e-ada01f8b950b",
"name": "ubuntu1604",
- "checksum": "8f08442faebad2d4a99fedb22fca11b5"
+ "checksum": "8f08442faebad2d4a99fedb22fca11b5",
},
"59ac0b79-5c7d-4e83-b517-4c6c6a8ac1d3": {
"id": "59ac0b79-5c7d-4e83-b517-4c6c6a8ac1d3",
"name": "hackfest3-mgmt",
- "checksum": "acec1e5d5ad7be9be7e6342a16bcf66a"
+ "checksum": "acec1e5d5ad7be9be7e6342a16bcf66a",
},
"f8818a03-f099-4c18-b1c7-26b1324203c1": {
"id": "f8818a03-f099-4c18-b1c7-26b1324203c1",
"name": "hackfest-pktgen",
- "checksum": "f8818a03-f099-4c18-b1c7-26b1324203c1"
+ "checksum": "f8818a03-f099-4c18-b1c7-26b1324203c1",
},
}
- def new_network(self, net_name, net_type, ip_profile=None, shared=False, provider_network_profile=None):
+ def new_network(
+ self,
+ net_name,
+ net_type,
+ ip_profile=None,
+ shared=False,
+ provider_network_profile=None,
+ ):
net_id = str(uuid4())
- self.logger.debug("new network id={}, name={}, net_type={}, ip_profile={}, provider_network_profile={}".
- format(net_id, net_name, net_type, ip_profile, provider_network_profile))
+ self.logger.debug(
+ "new network id={}, name={}, net_type={}, ip_profile={}, provider_network_profile={}".format(
+ net_id, net_name, net_type, ip_profile, provider_network_profile
+ )
+ )
net = {
"id": net_id,
"name": net_name,
@@ -105,65 +141,96 @@
"status": "ACTIVE",
}
self.nets[net_id] = net
+
return net_id, net
def get_network_list(self, filter_dict=None):
nets = []
+
for net_id, net in self.nets.items():
if filter_dict and filter_dict.get("name"):
if net["name"] != filter_dict.get("name"):
continue
+
if filter_dict and filter_dict.get("id"):
if net_id != filter_dict.get("id"):
continue
+
nets.append(net)
+
return nets
def get_network(self, net_id):
if net_id not in self.nets:
- raise vimconn.VimConnNotFoundException("network with id {} not found".format(net_id))
+ raise vimconn.VimConnNotFoundException(
+ "network with id {} not found".format(net_id)
+ )
+
return self.nets[net_id]
def delete_network(self, net_id, created_items=None):
if net_id not in self.nets:
- raise vimconn.VimConnNotFoundException("network with id {} not found".format(net_id))
- self.logger.debug("delete network id={}, created_items={}".format(net_id, created_items))
+ raise vimconn.VimConnNotFoundException(
+ "network with id {} not found".format(net_id)
+ )
+
+ self.logger.debug(
+ "delete network id={}, created_items={}".format(net_id, created_items)
+ )
self.nets.pop(net_id)
+
return net_id
def refresh_nets_status(self, net_list):
nets = {}
+
for net_id in net_list:
if net_id not in self.nets:
net = {"status": "DELETED"}
else:
net = self.nets[net_id].copy()
- net["vim_info"] = yaml.dump({"status": "ACTIVE", "name": net["name"]},
- default_flow_style=True, width=256)
+ net["vim_info"] = yaml.dump(
+ {"status": "ACTIVE", "name": net["name"]},
+ default_flow_style=True,
+ width=256,
+ )
+
nets[net_id] = net
return nets
def get_flavor(self, flavor_id):
if flavor_id not in self.flavors:
- raise vimconn.VimConnNotFoundException("flavor with id {} not found".format(flavor_id))
+ raise vimconn.VimConnNotFoundException(
+ "flavor with id {} not found".format(flavor_id)
+ )
+
return self.flavors[flavor_id]
def new_flavor(self, flavor_data):
flavor_id = str(uuid4())
- self.logger.debug("new flavor id={}, flavor_data={}".format(flavor_id, flavor_data))
+ self.logger.debug(
+ "new flavor id={}, flavor_data={}".format(flavor_id, flavor_data)
+ )
flavor = deepcopy(flavor_data)
flavor["id"] = flavor_id
+
if "name" not in flavor:
flavor["name"] = flavor_id
+
self.flavors[flavor_id] = flavor
+
return flavor_id
def delete_flavor(self, flavor_id):
if flavor_id not in self.flavors:
- raise vimconn.VimConnNotFoundException("flavor with id {} not found".format(flavor_id))
+ raise vimconn.VimConnNotFoundException(
+ "flavor with id {} not found".format(flavor_id)
+ )
+
self.logger.debug("delete flavor id={}".format(flavor_id))
self.flavors.pop(flavor_id)
+
return flavor_id
def get_flavor_id_from_data(self, flavor_dict):
@@ -173,34 +240,55 @@
break
else:
return flavor_id
- raise vimconn.VimConnNotFoundException("flavor with ram={} cpu={} disk={} {} not found".format(
- flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"],
- "and extended" if flavor_dict.get("extended") else ""))
+
+ raise vimconn.VimConnNotFoundException(
+ "flavor with ram={} cpu={} disk={} {} not found".format(
+ flavor_dict["ram"],
+ flavor_dict["vcpus"],
+ flavor_dict["disk"],
+ "and extended" if flavor_dict.get("extended") else "",
+ )
+ )
def new_tenant(self, tenant_name, tenant_description):
tenant_id = str(uuid4())
- self.logger.debug("new tenant id={}, description={}".format(tenant_id, tenant_description))
- tenant = {'name': tenant_name, 'description': tenant_description, 'id': tenant_id}
+ self.logger.debug(
+ "new tenant id={}, description={}".format(tenant_id, tenant_description)
+ )
+ tenant = {
+ "name": tenant_name,
+ "description": tenant_description,
+ "id": tenant_id,
+ }
self.tenants[tenant_id] = tenant
+
return tenant_id
def delete_tenant(self, tenant_id):
if tenant_id not in self.tenants:
- raise vimconn.VimConnNotFoundException("tenant with id {} not found".format(tenant_id))
+ raise vimconn.VimConnNotFoundException(
+ "tenant with id {} not found".format(tenant_id)
+ )
+
self.tenants.pop(tenant_id)
self.logger.debug("delete tenant id={}".format(tenant_id))
+
return tenant_id
def get_tenant_list(self, filter_dict=None):
tenants = []
+
for tenant_id, tenant in self.tenants.items():
if filter_dict and filter_dict.get("name"):
if tenant["name"] != filter_dict.get("name"):
continue
+
if filter_dict and filter_dict.get("id"):
if tenant_id != filter_dict.get("id"):
continue
+
tenants.append(tenant)
+
return tenants
def new_image(self, image_dict):
@@ -208,16 +296,23 @@
self.logger.debug("new image id={}, iamge_dict={}".format(image_id, image_dict))
image = deepcopy(image_dict)
image["id"] = image_id
+
if "name" not in image:
image["id"] = image_id
+
self.images[image_id] = image
+
return image_id
def delete_image(self, image_id):
if image_id not in self.images:
- raise vimconn.VimConnNotFoundException("image with id {} not found".format(image_id))
+ raise vimconn.VimConnNotFoundException(
+ "image with id {} not found".format(image_id)
+ )
+
self.logger.debug("delete image id={}".format(image_id))
self.images.pop(image_id)
+
return image_id
def get_image_list(self, filter_dict=None):
@@ -226,36 +321,69 @@
if filter_dict and filter_dict.get("name"):
if image["name"] != filter_dict.get("name"):
continue
+
if filter_dict and filter_dict.get("checksum"):
if image["checksum"] != filter_dict.get("checksum"):
continue
+
if filter_dict and filter_dict.get("id"):
if image_id != filter_dict.get("id"):
continue
+
images.append(image)
+
return images
- def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
- availability_zone_index=None, availability_zone_list=None):
+ def new_vminstance(
+ self,
+ name,
+ description,
+ start,
+ image_id,
+ flavor_id,
+ net_list,
+ cloud_config=None,
+ disk_list=None,
+ availability_zone_index=None,
+ availability_zone_list=None,
+ ):
vm_id = str(uuid4())
interfaces = []
- self.logger.debug("new vm id={}, name={}, image_id={}, flavor_id={}, net_list={}, cloud_config={}".
- format(vm_id, name, image_id, flavor_id, net_list, cloud_config))
+ self.logger.debug(
+ "new vm id={}, name={}, image_id={}, flavor_id={}, net_list={}, cloud_config={}".format(
+ vm_id, name, image_id, flavor_id, net_list, cloud_config
+ )
+ )
+
for iface_index, iface in enumerate(net_list):
iface["vim_id"] = str(iface_index)
interface = {
- "ip_address": iface.get("ip_address") or self.config.get("vm_ip") or "192.168.4.2",
- "mac_address": iface.get("mac_address") or self.config.get("vm_mac") or "00:11:22:33:44:55",
+ "ip_address": iface.get("ip_address")
+ or self.config.get("vm_ip")
+ or "192.168.4.2",
+ "mac_address": iface.get("mac_address")
+ or self.config.get("vm_mac")
+ or "00:11:22:33:44:55",
"vim_interface_id": str(iface_index),
"vim_net_id": iface["net_id"],
}
- if iface.get("type") in ("SR-IOV", "PCI-PASSTHROUGH") and self.config.get("sdn-port-mapping"):
+
+ if iface.get("type") in ("SR-IOV", "PCI-PASSTHROUGH") and self.config.get(
+ "sdn-port-mapping"
+ ):
compute_index = randrange(len(self.config["sdn-port-mapping"]))
- port_index = randrange(len(self.config["sdn-port-mapping"][compute_index]["ports"]))
- interface["compute_node"] = self.config["sdn-port-mapping"][compute_index]["compute_node"]
- interface["pci"] = self.config["sdn-port-mapping"][compute_index]["ports"][port_index]["pci"]
+ port_index = randrange(
+ len(self.config["sdn-port-mapping"][compute_index]["ports"])
+ )
+ interface["compute_node"] = self.config["sdn-port-mapping"][
+ compute_index
+ ]["compute_node"]
+ interface["pci"] = self.config["sdn-port-mapping"][compute_index][
+ "ports"
+ ][port_index]["pci"]
interfaces.append(interface)
+
vm = {
"id": vm_id,
"name": name,
@@ -265,41 +393,69 @@
"image_id": image_id,
"flavor_id": flavor_id,
}
+
if image_id not in self.images:
- self.logger.error("vm create, image_id '{}' not found. Skip".format(image_id))
+ self.logger.error(
+ "vm create, image_id '{}' not found. Skip".format(image_id)
+ )
+
if flavor_id not in self.flavors:
- self.logger.error("vm create flavor_id '{}' not found. Skip".format(flavor_id))
+ self.logger.error(
+ "vm create flavor_id '{}' not found. Skip".format(flavor_id)
+ )
+
self.vms[vm_id] = vm
+
return vm_id, vm
def get_vminstance(self, vm_id):
if vm_id not in self.vms:
- raise vimconn.VimConnNotFoundException("vm with id {} not found".format(vm_id))
+ raise vimconn.VimConnNotFoundException(
+ "vm with id {} not found".format(vm_id)
+ )
+
return self.vms[vm_id]
def delete_vminstance(self, vm_id, created_items=None):
if vm_id not in self.vms:
- raise vimconn.VimConnNotFoundException("vm with id {} not found".format(vm_id))
+ raise vimconn.VimConnNotFoundException(
+ "vm with id {} not found".format(vm_id)
+ )
+
self.vms.pop(vm_id)
- self.logger.debug("delete vm id={}, created_items={}".format(vm_id, created_items))
+ self.logger.debug(
+ "delete vm id={}, created_items={}".format(vm_id, created_items)
+ )
+
return vm_id
def refresh_vms_status(self, vm_list):
vms = {}
+
for vm_id in vm_list:
if vm_id not in self.vms:
vm = {"status": "DELETED"}
else:
vm = deepcopy(self.vms[vm_id])
- vm["vim_info"] = yaml.dump({"status": "ACTIVE", "name": vm["name"]},
- default_flow_style=True, width=256)
+ vm["vim_info"] = yaml.dump(
+ {"status": "ACTIVE", "name": vm["name"]},
+ default_flow_style=True,
+ width=256,
+ )
+
vms[vm_id] = vm
+
return vms
def action_vminstance(self, vm_id, action_dict, created_items={}):
return None
- def inject_user_key(self, ip_addr=None, user=None, key=None, ro_key=None, password=None):
+ def inject_user_key(
+ self, ip_addr=None, user=None, key=None, ro_key=None, password=None
+ ):
if self.config.get("ssh_key"):
ro_key = self.config.get("ssh_key")
- return super().inject_user_key(ip_addr=ip_addr, user=user, key=key, ro_key=ro_key, password=password)
+
+ return super().inject_user_key(
+ ip_addr=ip_addr, user=user, key=key, ro_key=ro_key, password=password
+ )
diff --git a/RO-plugin/osm_ro_plugin/vimconn.py b/RO-plugin/osm_ro_plugin/vimconn.py
index c71e821..cc087f0 100644
--- a/RO-plugin/osm_ro_plugin/vimconn.py
+++ b/RO-plugin/osm_ro_plugin/vimconn.py
@@ -44,12 +44,17 @@
def deprecated(message):
def deprecated_decorator(func):
def deprecated_func(*args, **kwargs):
- warnings.warn("{} is a deprecated function. {}".format(func.__name__, message),
- category=DeprecationWarning,
- stacklevel=2)
- warnings.simplefilter('default', DeprecationWarning)
+ warnings.warn(
+ "{} is a deprecated function. {}".format(func.__name__, message),
+ category=DeprecationWarning,
+ stacklevel=2,
+ )
+ warnings.simplefilter("default", DeprecationWarning)
+
return func(*args, **kwargs)
+
return deprecated_func
+
return deprecated_decorator
@@ -67,6 +72,7 @@
class VimConnException(Exception):
"""Common and base class Exception for all VimConnector exceptions"""
+
def __init__(self, message, http_code=HTTP_Bad_Request):
Exception.__init__(self, message)
self.http_code = http_code
@@ -74,53 +80,73 @@
class VimConnConnectionException(VimConnException):
"""Connectivity error with the VIM"""
+
def __init__(self, message, http_code=HTTP_Service_Unavailable):
VimConnException.__init__(self, message, http_code)
class VimConnUnexpectedResponse(VimConnException):
"""Get an wrong response from VIM"""
+
def __init__(self, message, http_code=HTTP_Service_Unavailable):
VimConnException.__init__(self, message, http_code)
class VimConnAuthException(VimConnException):
"""Invalid credentials or authorization to perform this action over the VIM"""
+
def __init__(self, message, http_code=HTTP_Unauthorized):
VimConnException.__init__(self, message, http_code)
class VimConnNotFoundException(VimConnException):
"""The item is not found at VIM"""
+
def __init__(self, message, http_code=HTTP_Not_Found):
VimConnException.__init__(self, message, http_code)
class VimConnConflictException(VimConnException):
"""There is a conflict, e.g. more item found than one"""
+
def __init__(self, message, http_code=HTTP_Conflict):
VimConnException.__init__(self, message, http_code)
class VimConnNotSupportedException(VimConnException):
"""The request is not supported by connector"""
+
def __init__(self, message, http_code=HTTP_Service_Unavailable):
VimConnException.__init__(self, message, http_code)
class VimConnNotImplemented(VimConnException):
"""The method is not implemented by the connected"""
+
def __init__(self, message, http_code=HTTP_Not_Implemented):
VimConnException.__init__(self, message, http_code)
-class VimConnector():
+class VimConnector:
"""Abstract base class for all the VIM connector plugins
These plugins must implement a VimConnector class derived from this
and all these privated methods
"""
- def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None,
- config={}, persistent_info={}):
+
+ def __init__(
+ self,
+ uuid,
+ name,
+ tenant_id,
+ tenant_name,
+ url,
+ url_admin=None,
+ user=None,
+ passwd=None,
+ log_level=None,
+ config={},
+ persistent_info={},
+ ):
"""
Constructor of VIM. Raise an exception is some needed parameter is missing, but it must not do any connectivity
checking against the VIM
@@ -150,28 +176,31 @@
self.passwd = passwd
self.config = config or {}
self.availability_zone = None
- self.logger = logging.getLogger('ro.vim')
+ self.logger = logging.getLogger("ro.vim")
+
if log_level:
self.logger.setLevel(getattr(logging, log_level))
- if not self.url_admin: # try to use normal url
+
+ if not self.url_admin: # try to use normal url
self.url_admin = self.url
def __getitem__(self, index):
- if index == 'tenant_id':
+ if index == "tenant_id":
return self.tenant_id
- if index == 'tenant_name':
+
+ if index == "tenant_name":
return self.tenant_name
- elif index == 'id':
+ elif index == "id":
return self.id
- elif index == 'name':
+ elif index == "name":
return self.name
- elif index == 'user':
+ elif index == "user":
return self.user
- elif index == 'passwd':
+ elif index == "passwd":
return self.passwd
- elif index == 'url':
+ elif index == "url":
return self.url
- elif index == 'url_admin':
+ elif index == "url_admin":
return self.url_admin
elif index == "config":
return self.config
@@ -179,21 +208,22 @@
raise KeyError("Invalid key '{}'".format(index))
def __setitem__(self, index, value):
- if index == 'tenant_id':
+ if index == "tenant_id":
self.tenant_id = value
- if index == 'tenant_name':
+
+ if index == "tenant_name":
self.tenant_name = value
- elif index == 'id':
+ elif index == "id":
self.id = value
- elif index == 'name':
+ elif index == "name":
self.name = value
- elif index == 'user':
+ elif index == "user":
self.user = value
- elif index == 'passwd':
+ elif index == "passwd":
self.passwd = value
- elif index == 'url':
+ elif index == "url":
self.url = value
- elif index == 'url_admin':
+ elif index == "url_admin":
self.url_admin = value
else:
raise KeyError("Invalid key '{}'".format(index))
@@ -209,28 +239,32 @@
return None
elif len(content_list) == 1:
return content_list[0]
+
combined_message = MIMEMultipart()
+
for content in content_list:
- if content.startswith('#include'):
- mime_format = 'text/x-include-url'
- elif content.startswith('#include-once'):
- mime_format = 'text/x-include-once-url'
- elif content.startswith('#!'):
- mime_format = 'text/x-shellscript'
- elif content.startswith('#cloud-config'):
- mime_format = 'text/cloud-config'
- elif content.startswith('#cloud-config-archive'):
- mime_format = 'text/cloud-config-archive'
- elif content.startswith('#upstart-job'):
- mime_format = 'text/upstart-job'
- elif content.startswith('#part-handler'):
- mime_format = 'text/part-handler'
- elif content.startswith('#cloud-boothook'):
- mime_format = 'text/cloud-boothook'
+ if content.startswith("#include"):
+ mime_format = "text/x-include-url"
+ elif content.startswith("#include-once"):
+ mime_format = "text/x-include-once-url"
+ elif content.startswith("#!"):
+ mime_format = "text/x-shellscript"
+ elif content.startswith("#cloud-config"):
+ mime_format = "text/cloud-config"
+ elif content.startswith("#cloud-config-archive"):
+ mime_format = "text/cloud-config-archive"
+ elif content.startswith("#upstart-job"):
+ mime_format = "text/upstart-job"
+ elif content.startswith("#part-handler"):
+ mime_format = "text/part-handler"
+ elif content.startswith("#cloud-boothook"):
+ mime_format = "text/cloud-boothook"
else: # by default
- mime_format = 'text/x-shellscript'
+ mime_format = "text/x-shellscript"
+
sub_message = MIMEText(content, mime_format, sys.getdefaultencoding())
combined_message.attach(sub_message)
+
return combined_message.as_string()
def _create_user_data(self, cloud_config):
@@ -256,6 +290,7 @@
config_drive = None
userdata = None
userdata_list = []
+
if isinstance(cloud_config, dict):
if cloud_config.get("user-data"):
if isinstance(cloud_config["user-data"], str):
@@ -263,48 +298,70 @@
else:
for u in cloud_config["user-data"]:
userdata_list.append(u)
+
if cloud_config.get("boot-data-drive") is not None:
config_drive = cloud_config["boot-data-drive"]
- if cloud_config.get("config-files") or cloud_config.get("users") or cloud_config.get("key-pairs"):
+
+ if (
+ cloud_config.get("config-files")
+ or cloud_config.get("users")
+ or cloud_config.get("key-pairs")
+ ):
userdata_dict = {}
+
# default user
if cloud_config.get("key-pairs"):
userdata_dict["ssh-authorized-keys"] = cloud_config["key-pairs"]
- userdata_dict["users"] = [{"default": None, "ssh-authorized-keys": cloud_config["key-pairs"]}]
+ userdata_dict["users"] = [
+ {
+ "default": None,
+ "ssh-authorized-keys": cloud_config["key-pairs"],
+ }
+ ]
+
if cloud_config.get("users"):
if "users" not in userdata_dict:
userdata_dict["users"] = ["default"]
+
for user in cloud_config["users"]:
user_info = {
"name": user["name"],
- "sudo": "ALL = (ALL)NOPASSWD:ALL"
+ "sudo": "ALL = (ALL)NOPASSWD:ALL",
}
+
if "user-info" in user:
user_info["gecos"] = user["user-info"]
+
if user.get("key-pairs"):
user_info["ssh-authorized-keys"] = user["key-pairs"]
+
userdata_dict["users"].append(user_info)
if cloud_config.get("config-files"):
userdata_dict["write_files"] = []
for file in cloud_config["config-files"]:
- file_info = {
- "path": file["dest"],
- "content": file["content"]
- }
+ file_info = {"path": file["dest"], "content": file["content"]}
+
if file.get("encoding"):
file_info["encoding"] = file["encoding"]
+
if file.get("permissions"):
file_info["permissions"] = file["permissions"]
+
if file.get("owner"):
file_info["owner"] = file["owner"]
+
userdata_dict["write_files"].append(file_info)
- userdata_list.append("#cloud-config\n" + yaml.safe_dump(userdata_dict, indent=4,
- default_flow_style=False))
+
+ userdata_list.append(
+ "#cloud-config\n"
+ + yaml.safe_dump(userdata_dict, indent=4, default_flow_style=False)
+ )
userdata = self._create_mimemultipart(userdata_list)
self.logger.debug("userdata: %s", userdata)
elif isinstance(cloud_config, str):
userdata = cloud_config
+
return config_drive, userdata
def check_vim_connectivity(self):
@@ -325,7 +382,14 @@
"""
raise VimConnNotImplemented("Should have implemented this")
- def new_network(self, net_name, net_type, ip_profile=None, shared=False, provider_network_profile=None):
+ def new_network(
+ self,
+ net_name,
+ net_type,
+ ip_profile=None,
+ shared=False,
+ provider_network_profile=None,
+ ):
"""Adds a tenant network to VIM
Params:
'net_name': name of the network
@@ -453,28 +517,31 @@
disk: disk size
is_public:
#TODO to concrete
- Returns the flavor identifier"""
+ Returns the flavor identifier
+ """
raise VimConnNotImplemented("Should have implemented this")
def delete_flavor(self, flavor_id):
"""Deletes a tenant flavor from VIM identify by its id
- Returns the used id or raise an exception"""
+ Returns the used id or raise an exception
+ """
raise VimConnNotImplemented("Should have implemented this")
def new_image(self, image_dict):
- """ Adds a tenant image to VIM
+ """Adds a tenant image to VIM
Returns the image id or raises an exception if failed
"""
raise VimConnNotImplemented("Should have implemented this")
def delete_image(self, image_id):
"""Deletes a tenant image from VIM
- Returns the image_id if image is deleted or raises an exception on error"""
+ Returns the image_id if image is deleted or raises an exception on error
+ """
raise VimConnNotImplemented("Should have implemented this")
def get_image_id_from_path(self, path):
"""Get the image id from image path in the VIM database.
- Returns the image_id or raises a VimConnNotFoundException
+ Returns the image_id or raises a VimConnNotFoundException
"""
raise VimConnNotImplemented("Should have implemented this")
@@ -491,8 +558,19 @@
"""
raise VimConnNotImplemented("Should have implemented this")
- def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
- availability_zone_index=None, availability_zone_list=None):
+ def new_vminstance(
+ self,
+ name,
+ description,
+ start,
+ image_id,
+ flavor_id,
+ net_list,
+ cloud_config=None,
+ disk_list=None,
+ availability_zone_index=None,
+ availability_zone_list=None,
+ ):
"""Adds a VM instance to VIM
Params:
'start': (boolean) indicates if VM must start or created in pause mode.
@@ -500,13 +578,13 @@
'net_list': list of interfaces, each one is a dictionary with:
'name': (optional) name for the interface.
'net_id': VIM network id where this interface must be connect to. Mandatory for type==virtual
- 'vpci': (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM
+ 'vpci': (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM
capabilities
'model': (optional and only have sense for type==virtual) interface model: virtio, e1000, ...
'mac_address': (optional) mac address to assign to this interface
'ip_address': (optional) IP address to assign to this interface
#TODO: CHECK if an optional 'vlan' parameter is needed for VIMs when type if VF and net_id is not
- provided, the VLAN tag to be used. In case net_id is provided, the internal network vlan is used
+ provided, the VLAN tag to be used. In case net_id is provided, the internal network vlan is used
for tagging VF
'type': (mandatory) can be one of:
'virtual', in this case always connected to a network of type 'net_type=bridge'
@@ -567,29 +645,29 @@
def refresh_vms_status(self, vm_list):
"""Get the status of the virtual machines and their interfaces/ports
- Params: the list of VM identifiers
- Returns a dictionary with:
- vm_id: #VIM id of this Virtual Machine
- status: #Mandatory. Text with one of:
- # DELETED (not found at vim)
- # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
- # OTHER (Vim reported other status not understood)
- # ERROR (VIM indicates an ERROR status)
- # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
- # BUILD (on building process), ERROR
- # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
- #
- error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
- vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
- interfaces: list with interface info. Each item a dictionary with:
- vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
- mac_address: #Text format XX:XX:XX:XX:XX:XX
- vim_net_id: #network id where this interface is connected, if provided at creation
- vim_interface_id: #interface/port VIM id
- ip_address: #null, or text with IPv4, IPv6 address
- compute_node: #identification of compute node where PF,VF interface is allocated
- pci: #PCI address of the NIC that hosts the PF,VF
- vlan: #physical VLAN used for VF
+ Params: the list of VM identifiers
+ Returns a dictionary with:
+ vm_id: #VIM id of this Virtual Machine
+ status: #Mandatory. Text with one of:
+ # DELETED (not found at vim)
+ # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
+ # OTHER (Vim reported other status not understood)
+ # ERROR (VIM indicates an ERROR status)
+ # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
+ # BUILD (on building process), ERROR
+ # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
+ #
+ error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
+ vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
+ interfaces: list with interface info. Each item a dictionary with:
+ vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
+ mac_address: #Text format XX:XX:XX:XX:XX:XX
+ vim_net_id: #network id where this interface is connected, if provided at creation
+ vim_interface_id: #interface/port VIM id
+ ip_address: #null, or text with IPv4, IPv6 address
+ compute_node: #identification of compute node where PF,VF interface is allocated
+ pci: #PCI address of the NIC that hosts the PF,VF
+ vlan: #physical VLAN used for VF
"""
raise VimConnNotImplemented("Should have implemented this")
@@ -623,7 +701,9 @@
"""
raise VimConnNotImplemented("Should have implemented this")
- def inject_user_key(self, ip_addr=None, user=None, key=None, ro_key=None, password=None):
+ def inject_user_key(
+ self, ip_addr=None, user=None, key=None, ro_key=None, password=None
+ ):
"""
Inject a ssh public key in a VM
Params:
@@ -635,35 +715,59 @@
The function doesn't return a value:
"""
if not ip_addr or not user:
- raise VimConnNotSupportedException("All parameters should be different from 'None'")
+ raise VimConnNotSupportedException(
+ "All parameters should be different from 'None'"
+ )
elif not ro_key and not password:
- raise VimConnNotSupportedException("All parameters should be different from 'None'")
+ raise VimConnNotSupportedException(
+ "All parameters should be different from 'None'"
+ )
else:
- commands = {'mkdir -p ~/.ssh/', 'echo "{}" >> ~/.ssh/authorized_keys'.format(key),
- 'chmod 644 ~/.ssh/authorized_keys', 'chmod 700 ~/.ssh/'}
+ commands = {
+ "mkdir -p ~/.ssh/",
+ 'echo "{}" >> ~/.ssh/authorized_keys'.format(key),
+ "chmod 644 ~/.ssh/authorized_keys",
+ "chmod 700 ~/.ssh/",
+ }
client = paramiko.SSHClient()
+
try:
if ro_key:
pkey = paramiko.RSAKey.from_private_key(StringIO(ro_key))
else:
pkey = None
+
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- client.connect(ip_addr, username=user, password=password, pkey=pkey, timeout=10)
+ client.connect(
+ ip_addr, username=user, password=password, pkey=pkey, timeout=10
+ )
+
for command in commands:
(i, o, e) = client.exec_command(command, timeout=10)
returncode = o.channel.recv_exit_status()
outerror = e.read()
+
if returncode != 0:
text = "run_command='{}' Error='{}'".format(command, outerror)
- raise VimConnUnexpectedResponse("Cannot inject ssh key in VM: '{}'".format(text))
+ raise VimConnUnexpectedResponse(
+ "Cannot inject ssh key in VM: '{}'".format(text)
+ )
+
return
- except (socket.error, paramiko.AuthenticationException, paramiko.SSHException) as message:
+ except (
+ socket.error,
+ paramiko.AuthenticationException,
+ paramiko.SSHException,
+ ) as message:
raise VimConnUnexpectedResponse(
- "Cannot inject ssh key in VM: '{}' - {}".format(ip_addr, str(message)))
+ "Cannot inject ssh key in VM: '{}' - {}".format(
+ ip_addr, str(message)
+ )
+ )
+
return
-# Optional methods
-
+ # Optional methods
def new_tenant(self, tenant_name, tenant_description):
"""Adds a new tenant to VIM with this name and description, this is done using admin_url if provided
"tenant_name": string max lenght 64
@@ -672,7 +776,7 @@
"""
raise VimConnNotImplemented("Should have implemented this")
- def delete_tenant(self, tenant_id,):
+ def delete_tenant(self, tenant_id):
"""Delete a tenant from VIM
tenant_id: returned VIM tenant_id on "new_tenant"
Returns None on success. Raises and exception of failure. If tenant is not found raises VimConnNotFoundException
@@ -726,20 +830,20 @@
raise VimConnNotImplemented("SFC support not implemented")
def refresh_classifications_status(self, classification_list):
- '''Get the status of the classifications
- Params: the list of classification identifiers
- Returns a dictionary with:
- vm_id: #VIM id of this classifier
- status: #Mandatory. Text with one of:
- # DELETED (not found at vim)
- # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
- # OTHER (Vim reported other status not understood)
- # ERROR (VIM indicates an ERROR status)
- # ACTIVE,
- # CREATING (on building process)
- error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
- vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
- '''
+ """Get the status of the classifications
+ Params: the list of classification identifiers
+ Returns a dictionary with:
+ vm_id: #VIM id of this classifier
+ status: #Mandatory. Text with one of:
+ # DELETED (not found at vim)
+ # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
+ # OTHER (Vim reported other status not understood)
+ # ERROR (VIM indicates an ERROR status)
+ # ACTIVE,
+ # CREATING (on building process)
+ error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
+ vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
+ """
raise VimConnNotImplemented("Should have implemented this")
def delete_classification(self, classification_id):
@@ -799,20 +903,20 @@
raise VimConnNotImplemented("SFC support not implemented")
def refresh_sfis_status(self, sfi_list):
- '''Get the status of the service function instances
- Params: the list of sfi identifiers
- Returns a dictionary with:
- vm_id: #VIM id of this service function instance
- status: #Mandatory. Text with one of:
- # DELETED (not found at vim)
- # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
- # OTHER (Vim reported other status not understood)
- # ERROR (VIM indicates an ERROR status)
- # ACTIVE,
- # CREATING (on building process)
- error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
- vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
- '''
+ """Get the status of the service function instances
+ Params: the list of sfi identifiers
+ Returns a dictionary with:
+ vm_id: #VIM id of this service function instance
+ status: #Mandatory. Text with one of:
+ # DELETED (not found at vim)
+ # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
+ # OTHER (Vim reported other status not understood)
+ # ERROR (VIM indicates an ERROR status)
+ # ACTIVE,
+ # CREATING (on building process)
+ error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
+ vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
+ """
raise VimConnNotImplemented("Should have implemented this")
def new_sf(self, name, sfis, sfc_encap=True):
@@ -863,20 +967,20 @@
raise VimConnNotImplemented("SFC support not implemented")
def refresh_sfs_status(self, sf_list):
- '''Get the status of the service functions
- Params: the list of sf identifiers
- Returns a dictionary with:
- vm_id: #VIM id of this service function
- status: #Mandatory. Text with one of:
- # DELETED (not found at vim)
- # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
- # OTHER (Vim reported other status not understood)
- # ERROR (VIM indicates an ERROR status)
- # ACTIVE,
- # CREATING (on building process)
- error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
- vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
- '''
+ """Get the status of the service functions
+ Params: the list of sf identifiers
+ Returns a dictionary with:
+ vm_id: #VIM id of this service function
+ status: #Mandatory. Text with one of:
+ # DELETED (not found at vim)
+ # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
+ # OTHER (Vim reported other status not understood)
+ # ERROR (VIM indicates an ERROR status)
+ # ACTIVE,
+ # CREATING (on building process)
+ error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
+ vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
+ """
raise VimConnNotImplemented("Should have implemented this")
def new_sfp(self, name, classifications, sfs, sfc_encap=True, spi=None):
@@ -924,20 +1028,20 @@
raise VimConnNotImplemented("SFC support not implemented")
def refresh_sfps_status(self, sfp_list):
- '''Get the status of the service function path
- Params: the list of sfp identifiers
- Returns a dictionary with:
- vm_id: #VIM id of this service function path
- status: #Mandatory. Text with one of:
- # DELETED (not found at vim)
- # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
- # OTHER (Vim reported other status not understood)
- # ERROR (VIM indicates an ERROR status)
- # ACTIVE,
- # CREATING (on building process)
- error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
- vim_info: #Text with plain information obtained from vim (yaml.safe_dump)F
- '''
+ """Get the status of the service function path
+ Params: the list of sfp identifiers
+ Returns a dictionary with:
+ vm_id: #VIM id of this service function path
+ status: #Mandatory. Text with one of:
+ # DELETED (not found at vim)
+ # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
+ # OTHER (Vim reported other status not understood)
+ # ERROR (VIM indicates an ERROR status)
+ # ACTIVE,
+ # CREATING (on building process)
+ error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
+ vim_info: #Text with plain information obtained from vim (yaml.safe_dump)F
+ """
raise VimConnNotImplemented("Should have implemented this")
def delete_sfp(self, sfp_id):
@@ -946,8 +1050,7 @@
"""
raise VimConnNotImplemented("SFC support not implemented")
-# NOT USED METHODS in current version. Deprecated
-
+ # NOT USED METHODS in current version. Deprecated
@deprecated
def host_vim2gui(self, host, server_dict):
"""Transform host dictionary from VIM format to GUI format,
diff --git a/RO-plugin/setup.py b/RO-plugin/setup.py
index 095e0d0..09c921e 100644
--- a/RO-plugin/setup.py
+++ b/RO-plugin/setup.py
@@ -30,27 +30,32 @@
setup(
name=_name,
- description='OSM ro base class for vim and SDN plugins',
+ description="OSM ro base class for vim and SDN plugins",
long_description=README,
- version_command=('git describe --match v* --tags --long --dirty', 'pep440-git-full'),
+ version_command=(
+ "git describe --match v* --tags --long --dirty",
+ "pep440-git-full",
+ ),
# version=VERSION,
# python_requires='>3.5.0',
- author='ETSI OSM',
- author_email='alfonso.tiernosepulveda@telefonica.com',
- maintainer='Alfonso Tierno',
- maintainer_email='alfonso.tiernosepulveda@telefonica.com',
- url='https://osm.etsi.org/gitweb/?p=osm/RO.git;a=summary',
- license='Apache 2.0',
-
+ author="ETSI OSM",
+ author_email="alfonso.tiernosepulveda@telefonica.com",
+ maintainer="Alfonso Tierno",
+ maintainer_email="alfonso.tiernosepulveda@telefonica.com",
+ url="https://osm.etsi.org/gitweb/?p=osm/RO.git;a=summary",
+ license="Apache 2.0",
packages=[_name],
include_package_data=True,
install_requires=[
- "requests", "paramiko", "PyYAML",
+ "requests",
+ "paramiko",
+ "PyYAML",
],
- setup_requires=['setuptools-version-command'],
+ setup_requires=["setuptools-version-command"],
entry_points={
- 'osm_ro.plugins': ['rovim_plugin = osm_ro_plugin.vimconn:VimConnector',
- 'rosdn_plugin = osm_ro_plugin.sdnconn:SdnConnectorBase'
- ],
+ "osm_ro.plugins": [
+ "rovim_plugin = osm_ro_plugin.vimconn:VimConnector",
+ "rosdn_plugin = osm_ro_plugin.sdnconn:SdnConnectorBase",
+ ],
},
)
diff --git a/RO-plugin/tox.ini b/RO-plugin/tox.ini
index a55c83e..4d8b9be 100644
--- a/RO-plugin/tox.ini
+++ b/RO-plugin/tox.ini
@@ -27,7 +27,7 @@
basepython = python3
deps = flake8
commands = flake8 osm_ro_plugin --max-line-length 120 \
- --exclude .svn,CVS,.gz,.git,__pycache__,.tox,local,temp --ignore W291,W293,E226,W504
+ --exclude .svn,CVS,.gz,.git,__pycache__,.tox,local,temp --ignore W291,W293,W503,W605,E123,E125,E203,E226,E241
[testenv:unittest]
basepython = python3