wim_create_parser.add_argument("url", action="store",
help="url for the wim")
wim_create_parser.add_argument("--type", action="store",
- help="wim type: tapi, onos or odl (default)")
+ help="wim type: tapi, onos, dynpac or odl (default)")
wim_create_parser.add_argument("--config", action="store",
help="additional configuration in json/yaml format")
wim_create_parser.add_argument("--description", action="store",
from .wim.engine import WimEngine
from .wim.persistence import WimPersistence
from copy import deepcopy
+from pprint import pformat
#
global global_config
# Auxiliary dictionaries from x to y
sce_net2instance = {}
net2task_id = {'scenario': {}}
+ # Mapping between local networks and WIMs
+ wim_usage = {}
def ip_profile_IM2RO(ip_profile_im):
# translate from input format to database format
if not involved_datacenters:
involved_datacenters.append(default_datacenter_id)
+ # --> WIM
+ # TODO: use this information during network creation
+ wim_account_id = None
+ if len(involved_datacenters) > 1 and 'uuid' in sce_net:
+ # OBS: sce_net without uuid are used internally to VNFs
+ # and the assumption is that VNFs will not be split among
+ # different datacenters
+ wim_account_id = wim_engine.find_suitable_wim_account(
+ involved_datacenters, tenant_id)
+ wim_usage[sce_net['uuid']] = wim_account_id
+ # <-- WIM
+
descriptor_net = {}
if instance_dict.get("networks") and instance_dict["networks"].get(sce_net["name"]):
descriptor_net = instance_dict["networks"][sce_net["name"]]
db_instance_action["number_tasks"] = task_index
# --> WIM
- wan_links = wim_engine.derive_wan_links(db_instance_nets, tenant_id)
+ logger.debug('wim_usage:\n%s\n\n', pformat(wim_usage))
+ wan_links = wim_engine.derive_wan_links(wim_usage, db_instance_nets, tenant_id)
wim_actions = wim_engine.create_actions(wan_links)
wim_actions, db_instance_action = (
wim_engine.incorporate_actions(wim_actions, db_instance_action))
subnets.append(subnet)
net["subnets"] = subnets
net["encapsulation"] = net.get('provider:network_type')
+ net["encapsulation_type"] = net.get('provider:network_type')
net["segmentation_id"] = net.get('provider:segmentation_id')
+ net["encapsulation_id"] = net.get('provider:segmentation_id')
return net
def delete_network(self, net_id):
from contextlib import contextmanager
from itertools import groupby
from operator import itemgetter
+from sys import exc_info
from uuid import uuid4
+from six import reraise
+
from ..utils import remove_none_items
from .actions import Action
from .errors import (
+ DbBaseException,
NoWimConnectedToDatacenters,
+ UnexpectedDatabaseError,
WimAccountNotActive
)
from .wim_thread import WimThread
Please check the wim schema to have more information about
``properties``.
+ The ``config`` property might contain a ``wim_port_mapping`` dict,
+ In this case, the method ``create_wim_port_mappings`` will be
+ automatically invoked.
+
Returns:
str: uuid of the newly created WIM record
"""
- return self.persist.create_wim(properties)
+ port_mapping = ((properties.get('config', {}) or {})
+ .pop('wim_port_mapping', {}))
+ uuid = self.persist.create_wim(properties)
+
+ if port_mapping:
+ try:
+ self.create_wim_port_mappings(uuid, port_mapping)
+ except DbBaseException:
+ # Rollback
+ self.delete_wim(uuid)
+ ex = UnexpectedDatabaseError('Failed to create port mappings'
+ 'Rolling back wim creation')
+ self.logger.exception(str(ex))
+ reraise(ex.__class__, ex, exc_info()[2])
+
+ return uuid
def get_wim(self, uuid_or_name, tenant_id=None):
"""Retrieve existing WIM record by name or id.
``properties`` is a dictionary with the properties being changed,
if a property is not present, the old value will be preserved
+
+ Similarly to create_wim, the ``config`` property might contain a
+ ``wim_port_mapping`` dict, In this case, port mappings will be
+ automatically updated.
"""
- return self.persist.update_wim(uuid_or_name, properties)
+ port_mapping = ((properties.get('config', {}) or {})
+ .pop('wim_port_mapping', {}))
+ orig_props = self.persist.get_by_name_or_uuid('wims', uuid_or_name)
+ uuid = orig_props['uuid']
+
+ response = self.persist.update_wim(uuid, properties)
+
+ if port_mapping:
+ try:
+ # It is very complex to diff and update individually all the
+ # port mappings. Therefore a practical approach is just delete
+ # and create it again.
+ self.persist.delete_wim_port_mappings(uuid)
+ # ^ Calling from persistence avoid reloading twice the thread
+ self.create_wim_port_mappings(uuid, port_mapping)
+ except DbBaseException:
+ # Rollback
+ self.update_wim(uuid_or_name, orig_props)
+ ex = UnexpectedDatabaseError('Failed to update port mappings'
+ 'Rolling back wim updates\n')
+ self.logger.exception(str(ex))
+ reraise(ex.__class__, ex, exc_info()[2])
+
+ return response
def delete_wim(self, uuid_or_name):
"""Kill the corresponding wim threads and erase the WIM record"""
"""Find a single WIM that is able to connect all the datacenters
listed
- Raises
+ Raises:
NoWimConnectedToDatacenters: if no WIM connected to all datacenters
at once is found
"""
# used here)
return suitable_wim_ids[0]
+ def find_suitable_wim_account(self, datacenter_ids, tenant):
+ """Find a WIM account that is able to connect all the datacenters
+ listed
+
+ Arguments:
+ datacenter_ids (list): List of UUIDs of all the datacenters (vims),
+ that need to be connected.
+ tenant (str): UUID of the OSM tenant
+
+ Returns:
+ str: UUID of the WIM account that is able to connect all the
+ datacenters.
+ """
+ wim_id = self.find_common_wim(datacenter_ids, tenant)
+ return self.persist.get_wim_account_by(wim_id, tenant)['uuid']
+
def derive_wan_link(self,
+ wim_usage,
instance_scenario_id, sce_net_id,
networks, tenant):
"""Create a instance_wim_nets record for the given information"""
- datacenters = [n['datacenter_id'] for n in networks]
- wim_id = self.find_common_wim(datacenters, tenant)
-
- account = self.persist.get_wim_account_by(wim_id, tenant)
+ if sce_net_id in wim_usage:
+ account_id = wim_usage[sce_net_id]
+ account = self.persist.get_wim_account_by(uuid=account_id)
+ wim_id = account['wim_id']
+ else:
+ datacenters = [n['datacenter_id'] for n in networks]
+ wim_id = self.find_common_wim(datacenters, tenant)
+ account = self.persist.get_wim_account_by(wim_id, tenant)
return {
'uuid': str(uuid4()),
'wim_account_id': account['uuid']
}
- def derive_wan_links(self, networks, tenant=None):
+ def derive_wan_links(self, wim_usage, networks, tenant=None):
"""Discover and return what are the wan_links that have to be created
considering a set of networks (VLDs) required for a scenario instance
(NSR).
Arguments:
+ wim_usage(dict): Mapping between sce_net_id and wim_id
networks(list): Dicts containing the information about the networks
that will be instantiated to materialize a Network Service
(scenario) instance.
+ Corresponding to the ``instance_net`` record.
Returns:
list: list of WAN links to be written to the database
if counter > 1]
return [
- self.derive_wan_link(key[0], key[1], grouped_networks[key], tenant)
+ self.derive_wan_link(wim_usage,
+ key[0], key[1], grouped_networks[key], tenant)
for key in wan_groups
]
message += ('\nThe thread responsible for processing the actions have '
'suddenly stopped, or have never being spawned')
super(WimAccountNotActive, self).__init__(message, http_code)
+
+
+class NoExternalPortFound(HttpMappedError):
+ """No external port associated to the instance_net"""
+
+ def __init__(self, instance_net):
+ super(NoExternalPortFound, self).__init__(
+ '{} uuid({})'.format(self.__class__.__doc__, instance_net['uuid']),
+ http_code=Not_Found)
def http_get_wim(self, tenant_id, wim_id):
tenant_id = None if tenant_id == 'any' else tenant_id
wim = self.engine.get_wim(wim_id, tenant_id)
+ mappings = self.engine.get_wim_port_mappings(wim_id)
+ wim['config'] = utils.merge_dicts(wim.get('config', {}) or {},
+ wim_port_mapping=mappings)
return format_out({'wim': wim})
@route('POST', '/wims')
where = {'uuid': wim['uuid']}
# unserialize config, edit and serialize it again
- if wim_descriptor.get('config'):
- new_config_dict = wim_descriptor["config"]
- config_dict = remove_none_items(merge_dicts(
- wim.get('config') or {}, new_config_dict))
- wim_descriptor['config'] = (
- _serialize(config_dict) if config_dict else None)
+ new_config_dict = wim_descriptor.get('config', {}) or {}
+ config_dict = remove_none_items(merge_dicts(
+ wim.get('config', {}) or {}, new_config_dict))
+ wim_descriptor['config'] = (
+ _serialize(config_dict) if config_dict else None)
with self.lock:
self.db.update_rows('wims', wim_descriptor, where)
kwargs.setdefault('WHERE', {'wim_account.uuid': uuid})
return self.query(FROM=_WIM_ACCOUNT_JOIN, **kwargs)
- def get_wim_account_by(self, wim=None, tenant=None, **kwargs):
+ def get_wim_account_by(self, wim=None, tenant=None, uuid=None, **kwargs):
"""Similar to ``get_wim_accounts_by``, but ensuring just one result"""
kwargs.setdefault('error_if_multiple', True)
- return self.get_wim_accounts_by(wim, tenant, **kwargs)[0]
+ return self.get_wim_accounts_by(wim, tenant, uuid, **kwargs)[0]
def get_wim_accounts(self, **kwargs):
"""Retrieve all the accounts from the database"""
See :obj:`~.query` for additional keyword arguments.
"""
- kwargs.update(datacenter=datacenter, tenant=tenant)
- return self.query(_DATACENTER_JOIN, **kwargs)
+ if tenant:
+ kwargs.update(datacenter=datacenter, tenant=tenant)
+ return self.query(_DATACENTER_JOIN, **kwargs)
+ else:
+ return [self.get_by_name_or_uuid('datacenters',
+ datacenter, **kwargs)]
def get_datacenter_by(self, datacenter=None, tenant=None, **kwargs):
"""Similar to ``get_datacenters_by``, but ensuring just one result"""
return [
{'wim_id': key[0],
'datacenter_id': key[1],
- 'wan_pop_port_mappings': [
+ 'pop_wan_mappings': [
filter_out_dict_keys(mapping, (
'id', 'wim_id', 'datacenter_id',
'created_at', 'modified_at'))
)
# WIM -------------------------------------------------------------------------
-wim_types = ["tapi", "onos", "odl"]
+wim_types = ["tapi", "onos", "odl", "dynpac"]
+
+dpid_type = {
+ "type": "string",
+ "pattern":
+ "^[0-9a-zA-Z]+(:[0-9a-zA-Z]+)*$"
+}
+
+port_type = {
+ "oneOf": [
+ {"type": "string",
+ "minLength": 1,
+ "maxLength": 5},
+ {"type": "integer",
+ "minimum": 1,
+ "maximum": 65534}
+ ]
+}
+
+wim_port_mapping_desc = {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "datacenter_name": nameshort_schema,
+ "pop_wan_mappings": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "pop_switch_dpid": dpid_type,
+ "pop_switch_port": port_type,
+ "wan_service_endpoint_id": name_schema,
+ "wan_service_mapping_info": {
+ "type": "object",
+ "properties": {
+ "mapping_type": name_schema,
+ "wan_switch_dpid": dpid_type,
+ "wan_switch_port": port_type
+ },
+ "additionalProperties": True,
+ "required": ["mapping_type"]
+ }
+ },
+ "oneOf": [
+ {
+ "required": [
+ "pop_switch_dpid",
+ "pop_switch_port",
+ "wan_service_endpoint_id"
+ ]
+ },
+ {
+ "required": [
+ "pop_switch_dpid",
+ "pop_switch_port",
+ "wan_service_mapping_info"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "required": ["datacenter_name", "pop_wan_mappings"]
+ }
+}
wim_schema_properties = {
"name": name_schema,
"description": description_schema,
"type": {
"type": "string",
- "enum": ["tapi", "onos", "odl"]
+ "enum": ["tapi", "onos", "odl", "dynpac"]
},
"wim_url": description_schema,
- "config": {"type": "object"}
+ "config": {
+ "type": "object",
+ "properties": {
+ "wim_port_mapping": wim_port_mapping_desc
+ }
+ }
}
wim_schema = {
"additionalProperties": False
}
-dpid_type = {
- "type": "string",
- "pattern":
- "^[0-9a-zA-Z]+(:[0-9a-zA-Z]+)*$"
-}
-
-port_type = {
- "oneOf": [
- {"type": "string",
- "minLength": 1,
- "maxLength": 5},
- {"type": "integer",
- "minimum": 1,
- "maximum": 65534}
- ]
-}
-
wim_port_mapping_schema = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "wim mapping information schema",
"type": "object",
"properties": {
- "wim_port_mapping": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "datacenter_name": nameshort_schema,
- "pop_wan_mappings": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "pop_switch_dpid": dpid_type,
- "pop_switch_port": port_type,
- "wan_service_endpoint_id": name_schema,
- "wan_service_mapping_info": {
- "type": "object",
- "properties": {
- "mapping_type": name_schema,
- "wan_switch_dpid": dpid_type,
- "wan_switch_port": port_type
- },
- "additionalProperties": True,
- "required": ["mapping_type"]
- }
- },
- "oneOf": [
- {
- "required": [
- "pop_switch_dpid",
- "pop_switch_port",
- "wan_service_endpoint_id"
- ]
- },
- {
- "required": [
- "pop_switch_dpid",
- "pop_switch_port",
- "wan_service_mapping_info"
- ]
- }
- ]
- }
- }
- },
- "required": ["datacenter_name", "pop_wan_mappings"]
- }
- }
+ "wim_port_mapping": wim_port_mapping_desc
},
"required": ["wim_port_mapping"]
}
from __future__ import unicode_literals
import json
+from itertools import izip
from time import time
+from textwrap import wrap
from six.moves import range
]
-def datacenter(identifier):
+def _datacenter_to_switch_port(dc_id, port=None):
+ digits = 16
+ switch = ':'.join(wrap(('%0' + str(digits) + 'x') % int(dc_id), 2))
+ return (switch, str((port or int(dc_id)) + 1))
+
+
+def datacenter(identifier, external_ports_config=False):
+ config = '' if not external_ports_config else json.dumps({
+ 'external_connections': [
+ {'condition': {
+ 'provider:physical_network': 'provider',
+ 'encapsulation_type': 'vlan'},
+ 'vim_external_port':
+ dict(izip(('switch', 'port'),
+ _datacenter_to_switch_port(identifier)))}
+ ]})
+
return {'uuid': uuid('dc%d' % identifier),
'name': 'dc%d' % identifier,
'type': 'openvim',
- 'vim_url': 'localhost'}
+ 'vim_url': 'localhost',
+ 'config': config}
def datacenter_account(datacenter, tenant):
uuid('dc-account%d%d' % (tenant, datacenter))}
-def datacenter_set(identifier, tenant):
+def datacenter_set(identifier=0, tenant=0):
"""Records necessary to create a datacenter and connect it to a tenant"""
return [
{'datacenters': [datacenter(identifier)]},
def wim_port_mapping(wim, datacenter,
- pop_dpid='AA:AA:AA:AA:AA:AA:AA:AA', pop_port=0,
- wan_dpid='BB:BB:BB:BB:BB:BB:BB:BB', wan_port=0):
+ pop_dpid='AA:AA:AA:AA:AA:AA:AA:AA', pop_port=None,
+ wan_dpid='BB:BB:BB:BB:BB:BB:BB:BB', wan_port=None):
mapping_info = {'mapping_type': 'dpid-port',
'wan_switch_dpid': wan_dpid,
- 'wan_switch_port': wan_port + datacenter + 1}
+ 'wan_switch_port': (str(wan_port) if wan_port else
+ str(int(datacenter) + int(wim) + 1))}
id_ = 'dpid-port|' + sha1(json.dumps(mapping_info, sort_keys=True))
return {'wim_id': uuid('wim%d' % wim),
'datacenter_id': uuid('dc%d' % datacenter),
'pop_switch_dpid': pop_dpid,
- 'pop_switch_port': pop_port + wim + 1,
+ 'pop_switch_port': (str(pop_port) if pop_port else
+ str(int(datacenter) + int(wim) + 1)),
# ^ Datacenter router have one port managed by each WIM
'wan_service_endpoint_id': id_,
# ^ WIM managed router have one port connected to each DC
return {
'wim_id': uuid('wim%d' % wim),
'datacenter_id': uuid('dc%d' % datacenter),
- 'wan_pop_port_mappings': [
+ 'pop_wan_mappings': [
{'pop_switch_dpid': pop_dpid,
'pop_switch_port': wim + 1 + i,
'wan_service_endpoint_id':
def consistent_set(num_wims=NUM_WIMS, num_tenants=NUM_TENANTS,
- num_datacenters=NUM_DATACENTERS):
+ num_datacenters=NUM_DATACENTERS,
+ external_ports_config=False):
return [
{'nfvo_tenants': [tenant(i) for i in range(num_tenants)]},
{'wims': [wim(j) for j in range(num_wims)]},
for j in range(num_wims)
]},
{'datacenters': [
- datacenter(k)
+ datacenter(k, external_ports_config)
for k in range(num_datacenters)
]},
{'datacenter_tenants': [
for k in range(num_datacenters)
]},
{'wim_port_mappings': [
- wim_port_mapping(j, k)
+ (wim_port_mapping(j, k, *_datacenter_to_switch_port(k))
+ if external_ports_config else wim_port_mapping(j, k))
for j in range(num_wims)
for k in range(num_datacenters)
]},
]
-def instance_nets(num_datacenters=2, num_links=2):
+def instance_nets(num_datacenters=2, num_links=2, status='BUILD'):
"""Example of multi-site deploy with N datacenters and M WAN links between
them (e.g M = 2 -> back and forth)
"""
# ^ instance_scenario_id == NS Record id
'sce_net_id': uuid('vld%d' % l),
# ^ scenario net id == VLD id
- 'status': 'BUILD',
+ 'status': status,
'vim_net_id': None,
'created': True}
for k in range(num_datacenters)
disable_foreign_keys,
uuid,
)
-from ..persistence import WimPersistence
+from ..persistence import WimPersistence, preprocess_record
from ..wan_link_actions import WanLinkCreate, WanLinkDelete
from ..wimconn import WimConnectorError
self.assertIn('issue with the local networks', action.error_msg)
self.assertIn('SCHEDULED_DELETION', action.error_msg)
+ def prepare_create__rules(self):
+ db_state = eg.consistent_set(num_wims=1, num_tenants=1,
+ num_datacenters=2,
+ external_ports_config=True)
+
+ instance_nets = eg.instance_nets(num_datacenters=2, num_links=1,
+ status='ACTIVE')
+ for i, net in enumerate(instance_nets):
+ net['vim_info'] = {}
+ net['vim_info']['provider:physical_network'] = 'provider'
+ net['vim_info']['encapsulation_type'] = 'vlan'
+ net['vim_info']['encapsulation_id'] = i
+ net['sdn_net_id'] = uuid('sdn-net%d' % i)
+
+ instance_action = eg.instance_action(action_id='ACTION-000')
+
+ db_state += [
+ {'instance_wim_nets': eg.instance_wim_nets()},
+ {'instance_nets': [preprocess_record(r) for r in instance_nets]},
+ {'instance_actions': instance_action}]
+
+ action = WanLinkCreate(
+ eg.wim_actions('CREATE', action_id='ACTION-000')[0])
+ # --> ensure it is in the database for updates --> #
+ action_record = action.as_record()
+ action_record['extra'] = json.dumps(action_record['extra'])
+ db_state += [{'vim_wim_actions': action_record}]
+
+ return db_state, action
+
+ @disable_foreign_keys
+ def test_process__rules(self):
+ # Given we want 1 WAN link between 2 datacenters
+ # and the local network in each datacenter is already created
+ db_state, action = self.prepare_create__rules()
+ self.populate(db_state)
+
+ instance_action = self.persist.get_by_uuid(
+ 'instance_actions', action.instance_action_id)
+ number_done = instance_action['number_done']
+ number_failed = instance_action['number_failed']
+
+ # If the connector works fine
+ with patch.object(self.connector, 'create_connectivity_service',
+ lambda *_, **__: (uuid('random-id'), None)):
+ # When we try to process a CREATE action that refers to the same
+ # instance_scenario_id and sce_net_id
+ action.process(self.connector, self.persist, self.ovim)
+
+ # Then the action should be succeeded
+ db_action = self.persist.query_one('vim_wim_actions', WHERE={
+ 'instance_action_id': action.instance_action_id,
+ 'task_index': action.task_index})
+ self.assertEqual(db_action['status'], 'DONE')
+
+ instance_action = self.persist.get_by_uuid(
+ 'instance_actions', action.instance_action_id)
+ self.assertEqual(instance_action['number_done'], number_done + 1)
+ self.assertEqual(instance_action['number_failed'], number_failed)
+
+ @disable_foreign_keys
+ def test_process__rules_fail(self):
+ # Given we want 1 WAN link between 2 datacenters
+ # and the local network in each datacenter is already created
+ db_state, action = self.prepare_create__rules()
+ self.populate(db_state)
+
+ instance_action = self.persist.get_by_uuid(
+ 'instance_actions', action.instance_action_id)
+ number_done = instance_action['number_done']
+ number_failed = instance_action['number_failed']
+
+ # If the connector raises an error
+ with patch.object(self.connector, 'create_connectivity_service',
+ MagicMock(side_effect=WimConnectorError('foobar'))):
+ # When we try to process a CREATE action that refers to the same
+ # instance_scenario_id and sce_net_id
+ action.process(self.connector, self.persist, self.ovim)
+
+ # Then the action should be fail
+ db_action = self.persist.query_one('vim_wim_actions', WHERE={
+ 'instance_action_id': action.instance_action_id,
+ 'task_index': action.task_index})
+ self.assertEqual(db_action['status'], 'FAILED')
+
+ instance_action = self.persist.get_by_uuid(
+ 'instance_actions', action.instance_action_id)
+ self.assertEqual(instance_action['number_done'], number_done)
+ self.assertEqual(instance_action['number_failed'], number_failed + 1)
+
def prepare_create__sdn(self):
- db_state = [{'nfvo_tenants': eg.tenant()}] + eg.wim_set()
+ db_state = eg.consistent_set(num_wims=1, num_tenants=1,
+ num_datacenters=2,
+ external_ports_config=False)
+
+ # Make sure all port_mappings are predictable
+ switch = 'AA:AA:AA:AA:AA:AA:AA:AA'
+ port = 1
+ port_mappings = next(r['wim_port_mappings']
+ for r in db_state if 'wim_port_mappings' in r)
+ for mapping in port_mappings:
+ mapping['pop_switch_dpid'] = switch
+ mapping['pop_switch_port'] = port
- instance_nets = eg.instance_nets(num_datacenters=2, num_links=1)
- port_mappings = [
- eg.wim_port_mapping(0, 0),
- eg.wim_port_mapping(0, 1)
- ]
instance_action = eg.instance_action(action_id='ACTION-000')
+ instance_nets = eg.instance_nets(num_datacenters=2, num_links=1,
+ status='ACTIVE')
for i, net in enumerate(instance_nets):
- net['status'] = 'ACTIVE'
net['sdn_net_id'] = uuid('sdn-net%d' % i)
db_state += [{'instance_nets': instance_nets},
{'instance_wim_nets': eg.instance_wim_nets()},
- {'wim_port_mappings': port_mappings},
{'instance_actions': instance_action}]
action = WanLinkCreate(
# --> ensure it is in the database for updates --> #
action_record = action.as_record()
action_record['extra'] = json.dumps(action_record['extra'])
- self.populate([{'vim_wim_actions': action_record}])
+ db_state += [{'vim_wim_actions': action_record}]
- return db_state, action
+ ovim_patch = patch.object(
+ self.ovim, 'get_ports', MagicMock(return_value=[{
+ 'switch_dpid': switch,
+ 'switch_port': port,
+ }]))
+
+ return db_state, action, ovim_patch
@disable_foreign_keys
def test_process__sdn(self):
# Given we want 1 WAN link between 2 datacenters
# and the local network in each datacenter is already created
- db_state, action = self.prepare_create__sdn()
+ db_state, action, ovim_patch = self.prepare_create__sdn()
self.populate(db_state)
instance_action = self.persist.get_by_uuid(
self.connector, 'create_connectivity_service',
lambda *_, **__: (uuid('random-id'), None))
- ovim_patch = patch.object(
- self.ovim, 'get_ports', MagicMock(return_value=[{
- 'switch_dpid': 'AA:AA:AA:AA:AA:AA:AA:AA',
- 'switch_port': 1,
- }]))
-
# If the connector works fine
with connector_patch, ovim_patch:
# When we try to process a CREATE action that refers to the same
def test_process__sdn_fail(self):
# Given we want 1 WAN link between 2 datacenters
# and the local network in each datacenter is already created
- db_state, action = self.prepare_create__sdn()
+ db_state, action, ovim_patch = self.prepare_create__sdn()
self.populate(db_state)
instance_action = self.persist.get_by_uuid(
self.connector, 'create_connectivity_service',
MagicMock(side_effect=WimConnectorError('foobar')))
- ovim_patch = patch.object(
- self.ovim, 'get_ports', MagicMock(return_value=[{
- 'switch_dpid': 'AA:AA:AA:AA:AA:AA:AA:AA',
- 'switch_port': 1,
- }]))
-
# If the connector throws an error
with connector_patch, ovim_patch:
# When we try to process a CREATE action that refers to the same
assert action.is_done
def prepare_delete(self):
- db_state = [{'nfvo_tenants': eg.tenant()}] + eg.wim_set()
+ db_state = eg.consistent_set(num_wims=1, num_tenants=1,
+ num_datacenters=2,
+ external_ports_config=True)
- instance_nets = eg.instance_nets(num_datacenters=2, num_links=1)
- port_mappings = [
- eg.wim_port_mapping(0, 0),
- eg.wim_port_mapping(0, 1)
- ]
- instance_action = eg.instance_action(action_id='ACTION-000')
+ instance_nets = eg.instance_nets(num_datacenters=2, num_links=1,
+ status='ACTIVE')
for i, net in enumerate(instance_nets):
- net['status'] = 'ACTIVE'
+ net['vim_info'] = {}
+ net['vim_info']['provider:physical_network'] = 'provider'
+ net['vim_info']['encapsulation_type'] = 'vlan'
+ net['vim_info']['encapsulation_id'] = i
net['sdn_net_id'] = uuid('sdn-net%d' % i)
- db_state += [{'instance_nets': instance_nets},
- {'instance_wim_nets': eg.instance_wim_nets()},
- {'wim_port_mappings': port_mappings},
- {'instance_actions': instance_action}]
+ instance_action = eg.instance_action(action_id='ACTION-000')
+
+ db_state += [
+ {'instance_wim_nets': eg.instance_wim_nets()},
+ {'instance_nets': [preprocess_record(r) for r in instance_nets]},
+ {'instance_actions': instance_action}]
action = WanLinkDelete(
eg.wim_actions('DELETE', action_id='ACTION-000')[0])
# --> ensure it is in the database for updates --> #
action_record = action.as_record()
action_record['extra'] = json.dumps(action_record['extra'])
- self.populate([{'vim_wim_actions': action_record}])
+ db_state += [{'vim_wim_actions': action_record}]
return db_state, action
def test_create_and_delete(self):
# Given a CREATE action was well succeeded
db_state, delete_action = self.prepare_delete()
- delete_action.save(self.persist, task_index=1)
self.populate(db_state)
+
+ delete_action.save(self.persist, task_index=1)
create_action = self.create_action()
connector_patch = patch.multiple(
create_connectivity_service=(
lambda *_, **__: (uuid('random-id'), None)))
- ovim_patch = patch.object(
- self.ovim, 'get_ports', MagicMock(return_value=[{
- 'switch_dpid': 'AA:AA:AA:AA:AA:AA:AA:AA',
- 'switch_port': 1,
- }]))
-
- with connector_patch, ovim_patch:
+ with connector_patch: # , ovim_patch:
create_action.process(self.connector, self.persist, self.ovim)
# When we try to process a CREATE action that refers to the same
# When we receive a list of 4 instance nets, representing
# 2 VLDs connecting 2 datacenters each
instance_nets = eg.instance_nets(2, 2)
- wan_links = engine.derive_wan_links(
- instance_nets, uuid('tenant0'))
+ wan_links = engine.derive_wan_links({}, instance_nets, uuid('tenant0'))
# Then we should derive 2 wan_links with the same instance_scenario_id
# and different scenario_network_id
merge_dicts(eg.wim(1), name='My-New-Name'),
response.json['wim'])
+ def test_edit_wim__port_mappings(self):
+ # Given a WIM exists in the database
+ self.populate()
+ # when a PUT /wims/<wim_id> request arrives
+ wim_id = uuid('wim1')
+ response = self.app.put_json(
+ '/wims/{}'.format(wim_id), {
+ 'wim': dict(
+ name='My-New-Name',
+ config={'wim_port_mapping': [{
+ 'datacenter_name': 'dc0',
+ 'pop_wan_mappings': [{
+ 'pop_switch_dpid': '00:AA:11:BB:22:CC:33:DD',
+ 'pop_switch_port': 1,
+ 'wan_service_mapping_info': {
+ 'mapping_type': 'dpid-port',
+ 'wan_switch_dpid': 'BB:BB:BB:BB:BB:BB:BB:0A',
+ 'wan_switch_port': 1
+ }
+ }]}]
+ }
+ )
+ }
+ )
+
+ # then the request should be well succeeded
+ self.assertEqual(response.status_code, OK)
+ # and the registered wim (wim1) should be present
+ self.assertDictContainsSubset(
+ merge_dicts(eg.wim(1), name='My-New-Name'),
+ response.json['wim'])
+ # and the port mappings hould be updated
+ mappings = response.json['wim']['config']['wim_port_mapping']
+ self.assertEqual(len(mappings), 1)
+ self.assertEqual(
+ mappings[0]['pop_wan_mappings'][0]['pop_switch_dpid'],
+ '00:AA:11:BB:22:CC:33:DD')
+
def test_delete_wim(self):
# Given a WIM exists in the database
self.populate()
self.assertEqual(response.status_code, OK)
self.assertEqual(response.json['wim']['name'], 'wim999')
+ def test_create_wim__port_mappings(self):
+ self.populate()
+ # when a POST /wims request arrives with the right payload
+ response = self.app.post_json(
+ '/wims', {
+ 'wim': merge_dicts(
+ eg.wim(999),
+ config={'wim_port_mapping': [{
+ 'datacenter_name': 'dc0',
+ 'pop_wan_mappings': [{
+ 'pop_switch_dpid': 'AA:AA:AA:AA:AA:AA:AA:01',
+ 'pop_switch_port': 1,
+ 'wan_service_mapping_info': {
+ 'mapping_type': 'dpid-port',
+ 'wan_switch_dpid': 'BB:BB:BB:BB:BB:BB:BB:01',
+ 'wan_switch_port': 1
+ }
+ }]}]
+ }
+ )
+ }
+ )
+
+ # then the request should be well succeeded
+ self.assertEqual(response.status_code, OK)
+ self.assertEqual(response.json['wim']['name'], 'wim999')
+ self.assertEqual(
+ len(response.json['wim']['config']['wim_port_mapping']), 1)
+
def test_create_wim_account(self):
# Given a WIM and a NFVO tenant exist but are not associated
self.populate([{'wims': [eg.wim(0)]},
self.assertIsNot(wim, None)
# and a array of pairs 'wan' <> 'pop' connections
- pairs = chain(*(m['wan_pop_port_mappings'] for m in mappings))
+ pairs = chain(*(m['pop_wan_mappings'] for m in mappings))
self.assertEqual(len(list(pairs)), 2 * eg.NUM_WIMS)
def test_get_wim_port_mappings_multiple(self):
self.assertEqual(mappings[0]['wim_id'], uuid('wim0'))
self.assertEqual(mappings[0]['datacenter_id'], uuid('dc0'))
- self.assertEqual(len(mappings[0]['wan_pop_port_mappings']), 3)
+ self.assertEqual(len(mappings[0]['pop_wan_mappings']), 3)
# when we retreive the mappings for more then one wim/datacenter
# the grouping should still work properly
mappings = self.persist.get_wim_port_mappings(
wim=['wim0', 'wim1'], datacenter=['dc0', 'dc1'])
self.assertEqual(len(mappings), 4)
- pairs = chain(*(m['wan_pop_port_mappings'] for m in mappings))
+ pairs = chain(*(m['pop_wan_mappings'] for m in mappings))
self.assertEqual(len(list(pairs)), 6)
def test_get_actions_in_group(self):
##
# pylint: disable=E1101,E0203,W0201
import json
+from pprint import pformat
+from sys import exc_info
from time import time
+from six import reraise
+
from ..utils import filter_dict_keys as filter_keys
from ..utils import merge_dicts, remove_none_items, safe_get, truncate
from .actions import CreateAction, DeleteAction, FindAction
from .errors import (
InconsistentState,
- MultipleRecordsFound,
NoRecordFound,
+ NoExternalPortFound
)
from wimconn import WimConnectorError
Returns:
dict: Record representing the wan_port_mapping associated to the
given instance_net. The expected fields are:
- **wim_id**, **datacenter_id**, **pop_switch_id** (the local
+ **wim_id**, **datacenter_id**, **pop_switch_dpid** (the local
network is expected to be connected at this switch),
**pop_switch_port**, **wan_service_endpoint_id**,
**wan_service_mapping_info**.
"""
- wim_account = persistence.get_wim_account_by(uuid=self.wim_account_id)
-
- # TODO: make more generic to support networks that are not created with
- # the SDN assist. This method should have a consistent way of getting
- # the endpoint for all different types of networks used in the VIM
- # (provider networks, SDN assist, overlay networks, ...)
- if instance_net.get('sdn_net_id'):
- return self._get_connection_point_info_sdn(
- persistence, ovim, instance_net, wim_account['wim_id'])
- else:
- raise InconsistentState(
- 'The field `instance_nets.sdn_net_id` was expected to be '
- 'found in the database for the record %s after the network '
- 'become active, but it is still NULL', instance_net['uuid'])
+ # First, we need to find a route from the datacenter to the outside
+ # world. For that, we can use the rules given in the datacenter
+ # configuration:
+ datacenter_id = instance_net['datacenter_id']
+ datacenter = persistence.get_datacenter_by(datacenter_id)
+ rules = safe_get(datacenter, 'config.external_connections', {}) or {}
+ vim_info = instance_net.get('vim_info', {}) or {}
+ # Alternatively, we can look for it, using the SDN assist
+ external_port = (self._evaluate_rules(rules, vim_info) or
+ self._get_port_sdn(ovim, instance_net))
+
+ if not external_port:
+ raise NoExternalPortFound(instance_net)
+
+ # Then, we find the WAN switch that is connected to this external port
+ try:
+ wim_account = persistence.get_wim_account_by(
+ uuid=self.wim_account_id)
+
+ criteria = {
+ 'wim_id': wim_account['wim_id'],
+ 'pop_switch_dpid': external_port[0],
+ 'pop_switch_port': external_port[1],
+ 'datacenter_id': datacenter_id}
+
+ wan_port_mapping = persistence.query_one(
+ FROM='wim_port_mappings',
+ WHERE=criteria)
+ except NoRecordFound:
+ ex = InconsistentState('No WIM port mapping found:'
+ 'wim_account: {}\ncriteria:\n{}'.format(
+ self.wim_account_id, pformat(criteria)))
+ reraise(ex.__class__, ex, exc_info()[2])
+
+ # It is important to return encapsulation information if present
+ mapping = merge_dicts(
+ wan_port_mapping.get('wan_service_mapping_info'),
+ filter_keys(vim_info, ('encapsulation_type', 'encapsulation_id'))
+ )
+
+ return merge_dicts(wan_port_mapping, wan_service_mapping_info=mapping)
- def _get_connection_point_info_sdn(self, persistence, ovim,
- instance_net, wim_id):
+ def _get_port_sdn(self, ovim, instance_net):
criteria = {'net_id': instance_net['sdn_net_id']}
- local_port_mapping = ovim.get_ports(filter=criteria)
+ try:
+ local_port_mapping = ovim.get_ports(filter=criteria)
+
+ if local_port_mapping:
+ return (local_port_mapping[0]['switch_dpid'],
+ local_port_mapping[0]['switch_port'])
+ except: # noqa
+ self.logger.exception('Problems when calling OpenVIM')
+
+ self.logger.debug('No ports found using criteria:\n%r\n.', criteria)
+ return None
+
+ def _evaluate_rules(self, rules, vim_info):
+ """Given a ``vim_info`` dict from a ``instance_net`` record, evaluate
+ the set of rules provided during the VIM/datacenter registration to
+ determine an external port used to connect that VIM/datacenter to
+ other ones where different parts of the NS will be instantiated.
+
+ For example, considering a VIM/datacenter is registered like the
+ following::
+
+ vim_record = {
+ "uuid": ...
+ ... # Other properties associated with the VIM/datacenter
+ "config": {
+ ... # Other configuration
+ "external_connections": [
+ {
+ "condition": {
+ "provider:physical_network": "provider_net1",
+ ... # This method will look up all the keys listed here
+ # in the instance_nets.vim_info dict and compare the
+ # values. When all the values match, the associated
+ # vim_external_port will be selected.
+ },
+ "vim_external_port": {"switch": "switchA", "port": "portB"}
+ },
+ ... # The user can provide as many rules as needed, however
+ # only the first one to match will be applied.
+ ]
+ }
+ }
+
+ When an ``instance_net`` record is instantiated in that datacenter with
+ the following information::
+
+ instance_net = {
+ "uuid": ...
+ ...
+ "vim_info": {
+ ...
+ "provider_physical_network": "provider_net1",
+ }
+ }
- if len(local_port_mapping) > 1:
- raise MultipleRecordsFound(criteria, 'ovim.ports')
- local_port_mapping = local_port_mapping[0]
+ Then, ``switchA`` and ``portB`` will be used to stablish the WAN
+ connection.
- criteria = {
- 'wim_id': wim_id,
- 'pop_switch_dpid': local_port_mapping['switch_dpid'],
- 'pop_switch_port': local_port_mapping['switch_port'],
- 'datacenter_id': instance_net['datacenter_id']}
+ Arguments:
+ rules (list): Set of dicts containing the keys ``condition`` and
+ ``vim_external_port``. This list should be extracted from
+ ``vim['config']['external_connections']`` (as stored in the
+ database).
+ vim_info (dict): Information given by the VIM Connector, against
+ which the rules will be evaluated.
+
+ Returns:
+ tuple: switch id (local datacenter switch) and port or None if
+ the rule does not match.
+ """
+ rule = next((r for r in rules if self._evaluate_rule(r, vim_info)), {})
+ if 'vim_external_port' not in rule:
+ self.logger.debug('No external port found.\n'
+ 'rules:\n%r\nvim_info:\n%r\n\n', rules, vim_info)
+ return None
- wan_port_mapping = persistence.query_one(
- FROM='wim_port_mappings',
- WHERE=criteria)
+ return (rule['vim_external_port']['switch'],
+ rule['vim_external_port']['port'])
- if local_port_mapping.get('vlan'):
- wan_port_mapping['wan_service_mapping_info']['vlan'] = (
- local_port_mapping['vlan'])
+ @staticmethod
+ def _evaluate_rule(rule, vim_info):
+ """Evaluate the conditions from a single rule to ``vim_info`` and
+ determine if the rule should be applicable or not.
- return wan_port_mapping
+ Please check :obj:`~._evaluate_rules` for more information.
+
+ Arguments:
+ rule (dict): Data structure containing the keys ``condition`` and
+ ``vim_external_port``. This should be one of the elements in
+ ``vim['config']['external_connections']`` (as stored in the
+ database).
+ vim_info (dict): Information given by the VIM Connector, against
+ which the rules will be evaluated.
+
+ Returns:
+ True or False: If all the conditions are met.
+ """
+ condition = rule.get('condition', {}) or {}
+ return all(safe_get(vim_info, k) == v for k, v in condition.items())
@staticmethod
def _derive_connection_point(wan_info):
point = {'service_endpoint_id': wan_info['wan_service_endpoint_id']}
# TODO: Cover other scenarios, e.g. VXLAN.
details = wan_info.get('wan_service_mapping_info', {})
- if 'vlan' in details:
+ if details.get('encapsulation_type') == 'vlan':
point['service_endpoint_encapsulation_type'] = 'dot1q'
point['service_endpoint_encapsulation_info'] = {
- 'vlan': details['vlan']
+ 'vlan': details['encapsulation_id']
}
else:
point['service_endpoint_encapsulation_type'] = 'none'
connection_points
# TODO: other properties, e.g. bandwidth
)
- except (WimConnectorError, InconsistentState) as ex:
+ except (WimConnectorError, InconsistentState,
+ NoExternalPortFound) as ex:
self.logger.exception(ex)
return self.fail(
persistence,
from six import reraise
from six.moves import queue
-from . import wan_link_actions, wimconn_odl # wimconn_tapi
+from . import wan_link_actions, wimconn_odl, wimconn_dynpac # wimconn_tapi
from ..utils import ensure, partition, pipe
from .actions import IGNORE, PENDING, REFRESH
from .errors import (
"odl": wimconn_odl.OdlConnector,
# "tapi": wimconn_tapi
# Add extra connectors here
+ "dynpac": wimconn_dynpac.DynpacConnector
}
--- /dev/null
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+##
+# Copyright 2018 David GarcĂa, University of the Basque Country
+# Copyright 2018 University of the Basque Country
+# This file is part of openmano
+# All Rights Reserved.
+# Contact information at http://i2t.ehu.eus
+#
+# # Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import requests
+import json
+import logging
+from enum import Enum
+
+from wimconn import WimConnector, WimConnectorError
+
+
+class WimError(Enum):
+ UNREACHABLE = 'Unable to reach the WIM.',
+ SERVICE_TYPE_ERROR = 'Unexpected service_type. Only "L2" is accepted.',
+ CONNECTION_POINTS_SIZE = \
+ 'Unexpected number of connection points: 2 expected.',
+ ENCAPSULATION_TYPE = \
+ 'Unexpected service_endpoint_encapsulation_type. \
+ Only "dotq1" is accepted.',
+ BANDWIDTH = 'Unable to get the bandwidth.',
+ STATUS = 'Unable to get the status for the service.',
+ DELETE = 'Unable to delete service.',
+ CLEAR_ALL = 'Unable to clear all the services',
+ UNKNOWN_ACTION = 'Unknown action invoked.',
+ BACKUP = 'Unable to get the backup parameter.',
+ UNSUPPORTED_FEATURE = "Unsupported feature",
+ UNAUTHORIZED = "Failed while authenticating"
+
+
+class WimAPIActions(Enum):
+ CHECK_CONNECTIVITY = "CHECK_CONNECTIVITY",
+ CREATE_SERVICE = "CREATE_SERVICE",
+ DELETE_SERVICE = "DELETE_SERVICE",
+ CLEAR_ALL = "CLEAR_ALL",
+ SERVICE_STATUS = "SERVICE_STATUS",
+
+
+class DynpacConnector(WimConnector):
+ __supported_service_types = ["ELINE (L2)"]
+ __supported_encapsulation_types = ["dot1q"]
+ __WIM_LOGGER = 'openmano.wimconn.dynpac'
+ __ENCAPSULATION_TYPE_PARAM = "service_endpoint_encapsulation_type"
+ __ENCAPSULATION_INFO_PARAM = "service_endpoint_encapsulation_info"
+ __BACKUP_PARAM = "backup"
+ __BANDWIDTH_PARAM = "bandwidth"
+ __SERVICE_ENDPOINT_PARAM = "service_endpoint_id"
+ __WAN_SERVICE_ENDPOINT_PARAM = "wan_service_endpoint_id"
+ __WAN_MAPPING_INFO_PARAM = "wan_service_mapping_info"
+ __SW_ID_PARAM = "wan_switch_dpid"
+ __SW_PORT_PARAM = "wan_switch_port"
+ __VLAN_PARAM = "vlan"
+
+ # Public functions exposed to the Resource Orchestrator
+ def __init__(self, wim, wim_account, config):
+ self.logger = logging.getLogger(self.__WIM_LOGGER)
+ self.__wim = wim
+ self.__wim_account = wim_account
+ self.__config = config
+ self.__wim_url = self.__wim.get("wim_url")
+ self.__user = wim_account.get("user")
+ self.__passwd = wim_account.get("passwd")
+ self.logger.info("Initialized.")
+
+ def create_connectivity_service(self,
+ service_type,
+ connection_points,
+ **kwargs):
+ self.__check_service(service_type, connection_points, kwargs)
+
+ body = self.__get_body(service_type, connection_points, kwargs)
+
+ headers = {'Content-type': 'application/x-www-form-urlencoded'}
+ endpoint = "{}/service/create".format(self.__wim_url)
+
+ try:
+ response = requests.post(endpoint, data=body, headers=headers)
+ except requests.exceptions.RequestException as e:
+ self.__exception(e.message, http_code=503)
+
+ if response.status_code != 200:
+ error = json.loads(response.content)
+ reason = "Reason: {}. ".format(error.get("code"))
+ description = "Description: {}.".format(error.get("description"))
+ exception = reason + description
+ self.__exception(exception, http_code=response.status_code)
+ uuid = response.content
+ self.logger.info("Service with uuid {} created.".format(uuid))
+ return (uuid, None)
+
+ def edit_connectivity_service(self, service_uuid,
+ conn_info, connection_points,
+ **kwargs):
+ self.__exception(WimError.UNSUPPORTED_FEATURE, http_code=501)
+
+ def get_connectivity_service_status(self, service_uuid):
+ endpoint = "{}/service/status/{}".format(self.__wim_url, service_uuid)
+ try:
+ response = requests.get(endpoint)
+ except requests.exceptions.RequestException as e:
+ self.__exception(e.message, http_code=503)
+
+ if response.status_code != 200:
+ self.__exception(WimError.STATUS, http_code=response.status_code)
+ self.logger.info("Status for service with uuid {}: {}"
+ .format(service_uuid, response.content))
+ return response.content
+
+ def delete_connectivity_service(self, service_uuid, conn_info):
+ endpoint = "{}/service/delete/{}".format(self.__wim_url, service_uuid)
+ try:
+ response = requests.delete(endpoint)
+ except requests.exceptions.RequestException as e:
+ self.__exception(e.message, http_code=503)
+ if response.status_code != 200:
+ self.__exception(WimError.DELETE, http_code=response.status_code)
+
+ self.logger.info("Service with uuid: {} deleted".format(service_uuid))
+
+ def clear_all_connectivity_services(self):
+ endpoint = "{}/service/clearAll".format(self.__wim_url)
+ try:
+ response = requests.delete(endpoint)
+ http_code = response.status_code
+ except requests.exceptions.RequestException as e:
+ self.__exception(e.message, http_code=503)
+ if http_code != 200:
+ self.__exception(WimError.CLEAR_ALL, http_code=http_code)
+
+ self.logger.info("{} services deleted".format(response.content))
+ return "{} services deleted".format(response.content)
+
+ def check_connectivity(self):
+ endpoint = "{}/checkConnectivity".format(self.__wim_url)
+
+ try:
+ response = requests.get(endpoint)
+ http_code = response.status_code
+ except requests.exceptions.RequestException as e:
+ self.__exception(e.message, http_code=503)
+
+ if http_code != 200:
+ self.__exception(WimError.UNREACHABLE, http_code=http_code)
+ self.logger.info("Connectivity checked")
+
+ def check_credentials(self):
+ endpoint = "{}/checkCredentials".format(self.__wim_url)
+ auth = (self.__user, self.__passwd)
+
+ try:
+ response = requests.get(endpoint, auth=auth)
+ http_code = response.status_code
+ except requests.exceptions.RequestException as e:
+ self.__exception(e.message, http_code=503)
+
+ if http_code != 200:
+ self.__exception(WimError.UNAUTHORIZED, http_code=http_code)
+ self.logger.info("Credentials checked")
+
+ # Private functions
+ def __exception(self, x, **kwargs):
+ http_code = kwargs.get("http_code")
+ if hasattr(x, "value"):
+ error = x.value
+ else:
+ error = x
+ self.logger.error(error)
+ raise WimConnectorError(error, http_code=http_code)
+
+ def __check_service(self, service_type, connection_points, kwargs):
+ if service_type not in self.__supported_service_types:
+ self.__exception(WimError.SERVICE_TYPE_ERROR, http_code=400)
+
+ if len(connection_points) != 2:
+ self.__exception(WimError.CONNECTION_POINTS_SIZE, http_code=400)
+
+ for connection_point in connection_points:
+ enc_type = connection_point.get(self.__ENCAPSULATION_TYPE_PARAM)
+ if enc_type not in self.__supported_encapsulation_types:
+ self.__exception(WimError.ENCAPSULATION_TYPE, http_code=400)
+
+ bandwidth = kwargs.get(self.__BANDWIDTH_PARAM)
+ if not isinstance(bandwidth, int):
+ self.__exception(WimError.BANDWIDTH, http_code=400)
+
+ backup = kwargs.get(self.__BACKUP_PARAM)
+ if not isinstance(backup, bool):
+ self.__exception(WimError.BACKUP, http_code=400)
+
+ def __get_body(self, service_type, connection_points, kwargs):
+ port_mapping = self.__config.get("port_mapping")
+ selected_ports = []
+ for connection_point in connection_points:
+ endpoint_id = connection_point.get(self.__SERVICE_ENDPOINT_PARAM)
+ port = filter(lambda x: x.get(self.__WAN_SERVICE_ENDPOINT_PARAM)
+ == endpoint_id, port_mapping)[0]
+ wsmpi_json = port.get(self.__WAN_MAPPING_INFO_PARAM)
+ port_info = json.loads(wsmpi_json)
+ selected_ports.append(port_info)
+ if service_type == "ELINE (L2)":
+ service_type = "L2"
+ body = {
+ "connection_points": [{
+ "wan_switch_dpid": selected_ports[0].get(self.__SW_ID_PARAM),
+ "wan_switch_port": selected_ports[0].get(self.__SW_PORT_PARAM),
+ "wan_vlan": connection_points[0].get(self.__ENCAPSULATION_INFO_PARAM).get(self.__VLAN_PARAM)
+ }, {
+ "wan_switch_dpid": selected_ports[1].get(self.__SW_ID_PARAM),
+ "wan_switch_port": selected_ports[1].get(self.__SW_PORT_PARAM),
+ "wan_vlan": connection_points[1].get(self.__ENCAPSULATION_INFO_PARAM).get(self.__VLAN_PARAM)
+ }],
+ "bandwidth": kwargs.get(self.__BANDWIDTH_PARAM),
+ "service_type": service_type,
+ "backup": kwargs.get(self.__BACKUP_PARAM)
+ }
+ return "body={}".format(json.dumps(body))