1 # -*- coding: utf-8 -*-
3 # Copyright 2018 University of Bristol - High Performance Networks Research
7 # Contributors: Anderson Bravalheri, Dimitrios Gkounis, Abubakar Siddique
8 # Muqaddas, Navdeep Uniyal, Reza Nejabati and Dimitra Simeonidou
10 # Licensed under the Apache License, Version 2.0 (the "License"); you may
11 # not use this file except in compliance with the License. You may obtain
12 # a copy of the License at
14 # http://www.apache.org/licenses/LICENSE-2.0
16 # Unless required by applicable law or agreed to in writing, software
17 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
18 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
19 # License for the specific language governing permissions and limitations
22 # For those usages not covered by the Apache License, Version 2.0 please
23 # contact with: <highperformance-networks@bristol.ac.uk>
25 # Neither the name of the University of Bristol nor the names of its
26 # contributors may be used to endorse or promote products derived from
27 # this software without specific prior written permission.
29 # This work has been performed in the context of DCMS UK 5G Testbeds
30 # & Trials Programme and in the framework of the Metro-Haul project -
31 # funded by the European Commission under Grant number 761727 through the
32 # Horizon 2020 and 5G-PPP programmes.
34 # pylint: disable=E1101,E0203,W0201
38 from ..utils
import filter_dict_keys
as filter_keys
39 from ..utils
import merge_dicts
, remove_none_items
, safe_get
, truncate
40 from .actions
import CreateAction
, DeleteAction
, FindAction
46 from wimconn
import WimConnectorError
48 INSTANCE_NET_STATUS_ERROR
= ('DOWN', 'ERROR', 'VIM_ERROR',
49 'DELETED', 'SCHEDULED_DELETION')
50 INSTANCE_NET_STATUS_PENDING
= ('BUILD', 'INACTIVE', 'SCHEDULED_CREATION')
51 INSTANCE_VM_STATUS_ERROR
= ('ERROR', 'VIM_ERROR',
52 'DELETED', 'SCHEDULED_DELETION')
55 class RefreshMixin(object):
56 def refresh(self
, connector
, persistence
):
57 """Ask the external WAN Infrastructure Manager system for updates on
58 the status of the task.
61 connector: object with API for accessing the WAN
62 Infrastructure Manager system
63 persistence: abstraction layer for the database
65 fields
= ('wim_status', 'wim_info', 'error_msg')
66 result
= dict.fromkeys(fields
)
71 .get_connectivity_service_status(self
.wim_internal_id
))
72 except WimConnectorError
as ex
:
73 self
.logger
.exception(ex
)
74 result
.update(wim_status
='WIM_ERROR', error_msg
=truncate(ex
))
76 result
= filter_keys(result
, fields
)
78 action_changes
= remove_none_items({
79 'extra': merge_dicts(self
.extra
, result
),
80 'status': 'BUILD' if result
['wim_status'] == 'BUILD' else None,
81 'error_msg': result
['error_msg'],
82 'modified_at': time()})
83 link_changes
= merge_dicts(result
, status
=result
.pop('wim_status'))
84 # ^ Rename field: wim_status => status
86 persistence
.update_wan_link(self
.item_id
,
87 remove_none_items(link_changes
))
89 self
.save(persistence
, **action_changes
)
94 class WanLinkCreate(RefreshMixin
, CreateAction
):
95 def fail(self
, persistence
, reason
, status
='FAILED'):
96 changes
= {'status': 'ERROR', 'error_msg': truncate(reason
)}
97 persistence
.update_wan_link(self
.item_id
, changes
)
98 return super(WanLinkCreate
, self
).fail(persistence
, reason
, status
)
100 def process(self
, connector
, persistence
, ovim
):
101 """Process the current task.
102 First we check if all the dependencies are ready,
103 then we call ``execute`` to actually execute the action.
106 connector: object with API for accessing the WAN
107 Infrastructure Manager system
108 persistence: abstraction layer for the database
109 ovim: instance of openvim, abstraction layer that enable
110 SDN-related operations
112 wan_link
= persistence
.get_by_uuid('instance_wim_nets', self
.item_id
)
114 # First we check if all the dependencies are solved
115 instance_nets
= persistence
.get_instance_nets(
116 wan_link
['instance_scenario_id'], wan_link
['sce_net_id'])
119 dependency_statuses
= [n
['status'] for n
in instance_nets
]
121 self
.logger
.debug('`status` not found in\n\n%s\n\n',
122 json
.dumps(instance_nets
, indent
=4))
123 errored
= [instance_nets
[i
]
124 for i
, status
in enumerate(dependency_statuses
)
125 if status
in INSTANCE_NET_STATUS_ERROR
]
129 'Impossible to stablish WAN connectivity due to an issue '
130 'with the local networks:\n\t' +
131 '\n\t'.join('{uuid}: {status}'.format(**n
) for n
in errored
))
133 pending
= [instance_nets
[i
]
134 for i
, status
in enumerate(dependency_statuses
)
135 if status
in INSTANCE_NET_STATUS_PENDING
]
139 'Still waiting for the local networks to be active:\n\t' +
140 '\n\t'.join('{uuid}: {status}'.format(**n
) for n
in pending
))
142 return self
.execute(connector
, persistence
, ovim
, instance_nets
)
144 def get_endpoint(self
, persistence
, ovim
, instance_net
):
145 """Retrieve the endpoint (information about the connection PoP <> WAN
147 wim_account
= persistence
.get_wim_account_by(uuid
=self
.wim_account_id
)
149 # TODO: make more generic to support networks that are not created with
150 # the SDN assist. This method should have a consistent way of getting
151 # the endpoint for all different types of networks used in the VIM
152 # (provider networks, SDN assist, overlay networks, ...)
153 if instance_net
.get('sdn_net_id'):
154 return self
.get_endpoint_sdn(
155 persistence
, ovim
, instance_net
, wim_account
['wim_id'])
157 raise InconsistentState(
158 'The field `instance_nets.sdn_net_id` was expected to be '
159 'found in the database for the record %s after the network '
160 'become active, but it is still NULL', instance_net
['uuid'])
162 def get_endpoint_sdn(self
, persistence
, ovim
, instance_net
, wim_id
):
163 criteria
= {'net_id': instance_net
['sdn_net_id']}
164 local_port_mapping
= ovim
.get_ports(filter=criteria
)
166 if len(local_port_mapping
) > 1:
167 raise MultipleRecordsFound(criteria
, 'ovim.ports')
168 local_port_mapping
= local_port_mapping
[0]
172 'pop_switch_dpid': local_port_mapping
['switch_dpid'],
173 'pop_switch_port': local_port_mapping
['switch_port'],
174 'datacenter_id': instance_net
['datacenter_id']}
176 wan_port_mapping
= persistence
.query_one(
177 FROM
='wim_port_mappings',
180 if local_port_mapping
.get('vlan'):
181 wan_port_mapping
['wan_service_mapping_info']['vlan'] = (
182 local_port_mapping
['vlan'])
184 return wan_port_mapping
187 def _derive_connection_point(endpoint
):
188 point
= {'service_endpoint_id': endpoint
['wan_service_endpoint_id']}
189 # TODO: Cover other scenarios, e.g. VXLAN.
190 info
= endpoint
.get('wan_service_mapping_info', {})
192 point
['service_endpoint_encapsulation_type'] = 'dot1q'
193 point
['service_endpoint_encapsulation_info'] = {
197 point
['service_endpoint_encapsulation_type'] = 'none'
201 def _derive_service_type(connection_points
):
202 # TODO: add multipoint and L3 connectivity.
203 if len(connection_points
) == 2:
206 raise NotImplementedError('Multipoint connectivity is not '
209 def _update_persistent_data(self
, persistence
, service_uuid
,
210 endpoints
, conn_info
):
211 """Store plugin/connector specific information in the database"""
212 persistence
.update_wan_link(self
.item_id
, {
213 'wim_internal_id': service_uuid
,
214 'wim_info': {'conn_info': conn_info
},
217 def execute(self
, connector
, persistence
, ovim
, instance_nets
):
218 """Actually execute the action, since now we are sure all the
219 dependencies are solved
222 endpoints
= [self
.get_endpoint(persistence
, ovim
, net
)
223 for net
in instance_nets
]
224 connection_points
= [self
._derive
_connection
_point
(e
)
227 uuid
, info
= connector
.create_connectivity_service(
228 self
._derive
_service
_type
(connection_points
),
230 # TODO: other properties, e.g. bandwidth
232 except (WimConnectorError
, InconsistentState
) as ex
:
233 self
.logger
.exception(ex
)
236 'Impossible to stablish WAN connectivity.\n\t{}'.format(ex
))
238 self
.logger
.debug('WAN connectivity established %s\n%s\n',
239 uuid
, json
.dumps(info
, indent
=4))
240 self
.wim_internal_id
= uuid
241 self
._update
_persistent
_data
(persistence
, uuid
, endpoints
, info
)
242 self
.succeed(persistence
)
246 class WanLinkDelete(DeleteAction
):
247 def succeed(self
, persistence
):
249 persistence
.update_wan_link(self
.item_id
, {'status': 'DELETED'})
250 except NoRecordFound
:
251 self
.logger
.debug('%s(%s) record already deleted',
252 self
.item
, self
.item_id
)
254 return super(WanLinkDelete
, self
).succeed(persistence
)
256 def get_wan_link(self
, persistence
):
257 """Retrieve information about the wan_link
259 It might be cached, or arrive from the database
261 if self
.extra
.get('wan_link'):
262 # First try a cached version of the data
263 return self
.extra
['wan_link']
265 return persistence
.get_by_uuid(
266 'instance_wim_nets', self
.item_id
)
268 def process(self
, connector
, persistence
, ovim
):
269 """Delete a WAN link previously created"""
270 wan_link
= self
.get_wan_link(persistence
)
271 if 'ERROR' in (wan_link
.get('status') or ''):
274 'Impossible to delete WAN connectivity, '
275 'it was never successfully established:'
276 '\n\t{}'.format(wan_link
['error_msg']))
278 internal_id
= wan_link
.get('wim_internal_id') or self
.internal_id
281 self
.logger
.debug('No wim_internal_id found in\n%s\n%s\n'
282 'Assuming no network was created yet, '
283 'so no network have to be deleted.',
284 json
.dumps(wan_link
, indent
=4),
285 json
.dumps(self
.as_dict(), indent
=4))
286 return self
.succeed(persistence
)
289 id = self
.wim_internal_id
290 conn_info
= safe_get(wan_link
, 'wim_info.conn_info')
291 self
.logger
.debug('Connection Service %s (wan_link: %s):\n%s\n',
292 id, wan_link
['uuid'],
293 json
.dumps(conn_info
, indent
=4))
294 result
= connector
.delete_connectivity_service(id, conn_info
)
295 except (WimConnectorError
, InconsistentState
) as ex
:
296 self
.logger
.exception(ex
)
299 'Impossible to delete WAN connectivity.\n\t{}'.format(ex
))
301 self
.logger
.debug('WAN connectivity removed %s', result
)
302 self
.succeed(persistence
)
307 class WanLinkFind(RefreshMixin
, FindAction
):
312 'CREATE': WanLinkCreate
,
313 'DELETE': WanLinkDelete
,