blob: 1c3db692291c2f0be5ab349914a812b58691efd0 [file] [log] [blame]
Zsolt Haraszti66862032016-11-28 14:28:39 -08001#
Zsolt Haraszti3eb27a52017-01-03 21:56:48 -08002# Copyright 2017 the original author or authors.
Zsolt Haraszti66862032016-11-28 14:28:39 -08003#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17"""
18Agent to play gateway between CORE and an individual adapter.
19"""
20from uuid import uuid4
21
Stephane Barbarie4db8ca22017-04-24 10:30:20 -040022import arrow
Zsolt Haraszti66862032016-11-28 14:28:39 -080023import structlog
Zsolt Harasztief05ad22017-01-07 22:08:06 -080024from google.protobuf.json_format import MessageToJson
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -080025from scapy.packet import Packet
Zsolt Haraszti66862032016-11-28 14:28:39 -080026from twisted.internet.defer import inlineCallbacks, returnValue
27from zope.interface import implementer
28
Zsolt Haraszti89a27302016-12-08 16:53:06 -080029from common.event_bus import EventBusClient
Zsolt Harasztief05ad22017-01-07 22:08:06 -080030from common.frameio.frameio import hexify
khenaidooa8588f22017-06-16 12:13:34 -040031from common.utils.id_generation import create_cluster_logical_device_ids
Zsolt Haraszti66862032016-11-28 14:28:39 -080032from voltha.adapters.interface import IAdapterAgent
33from voltha.protos import third_party
Stephane Barbarie4db8ca22017-04-24 10:30:20 -040034from voltha.core.flow_decomposer import OUTPUT
Sergio Slobodriana2eb52b2017-03-07 12:24:46 -050035from voltha.protos.device_pb2 import Device, Port, PmConfigs
Stephane Barbarie4db8ca22017-04-24 10:30:20 -040036from voltha.protos.events_pb2 import AlarmEvent, AlarmEventType, \
37 AlarmEventSeverity, AlarmEventState, AlarmEventCategory
Sergio Slobodriana2eb52b2017-03-07 12:24:46 -050038from voltha.protos.events_pb2 import KpiEvent
Zsolt Haraszti66862032016-11-28 14:28:39 -080039from voltha.protos.voltha_pb2 import DeviceGroup, LogicalDevice, \
Stephane Barbarie4db8ca22017-04-24 10:30:20 -040040 LogicalPort, AdminState, OperStatus, AlarmFilterRuleKey
Zsolt Haraszti66862032016-11-28 14:28:39 -080041from voltha.registry import registry
khenaidoo08d48d22017-06-29 19:42:49 -040042from common.utils.id_generation import create_cluster_device_id
khenaidoo507d9222017-10-10 16:23:49 -040043import re
44
45
46class MacAddressError(BaseException):
47 def __init__(self, error):
48 self.error = error
Zsolt Haraszti66862032016-11-28 14:28:39 -080049
khenaidooe9677d42018-02-20 17:41:05 -050050
51class IDError(BaseException):
52 def __init__(self, error):
53 self.error = error
54
55
Zsolt Haraszti66862032016-11-28 14:28:39 -080056@implementer(IAdapterAgent)
57class AdapterAgent(object):
58 """
59 Gate-keeper between CORE and device adapters.
60
61 On one side it interacts with Core's internal model and update/dispatch
62 mechanisms.
63
64 On the other side, it interacts with the adapters standard interface as
65 defined in
66 """
67
68 def __init__(self, adapter_name, adapter_cls):
69 self.adapter_name = adapter_name
70 self.adapter_cls = adapter_cls
71 self.core = registry('core')
72 self.adapter = None
73 self.adapter_node_proxy = None
74 self.root_proxy = self.core.get_proxy('/')
Zsolt Haraszti89a27302016-12-08 16:53:06 -080075 self._rx_event_subscriptions = {}
76 self._tx_event_subscriptions = {}
77 self.event_bus = EventBusClient()
Khen Nursimulud068d812017-03-06 11:44:18 -050078 self.packet_out_subscription = None
Zsolt Haraszti89a27302016-12-08 16:53:06 -080079 self.log = structlog.get_logger(adapter_name=adapter_name)
Nikolay Titov89004ec2017-06-19 18:22:42 -040080 self._onu_detect_event_subscriptions = {}
Zsolt Haraszti66862032016-11-28 14:28:39 -080081
82 @inlineCallbacks
83 def start(self):
Zsolt Haraszti89a27302016-12-08 16:53:06 -080084 self.log.debug('starting')
Zsolt Haraszti66862032016-11-28 14:28:39 -080085 config = self._get_adapter_config() # this may be None
Zsolt Haraszti89a27302016-12-08 16:53:06 -080086 try:
87 adapter = self.adapter_cls(self, config)
88 yield adapter.start()
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080089 self.adapter = adapter
90 self.adapter_node_proxy = self._update_adapter_node()
91 self._update_device_types()
Zsolt Haraszti89a27302016-12-08 16:53:06 -080092 except Exception, e:
93 self.log.exception(e)
Zsolt Haraszti89a27302016-12-08 16:53:06 -080094 self.log.info('started')
Zsolt Haraszti66862032016-11-28 14:28:39 -080095 returnValue(self)
96
97 @inlineCallbacks
98 def stop(self):
Zsolt Haraszti89a27302016-12-08 16:53:06 -080099 self.log.debug('stopping')
Zsolt Haraszti66862032016-11-28 14:28:39 -0800100 if self.adapter is not None:
101 yield self.adapter.stop()
102 self.adapter = None
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800103 self.log.info('stopped')
Zsolt Haraszti66862032016-11-28 14:28:39 -0800104
105 def _get_adapter_config(self):
106 """
107 Opportunistically load persisted adapter configuration.
108 Return None if no configuration exists yet.
109 """
110 proxy = self.core.get_proxy('/')
111 try:
112 config = proxy.get('/adapters/' + self.adapter_name)
113 return config
114 except KeyError:
115 return None
116
117 def _update_adapter_node(self):
118 """
119 Creates or updates the adapter node object based on self
120 description from the adapter.
121 """
122
123 adapter_desc = self.adapter.adapter_descriptor()
124 assert adapter_desc.id == self.adapter_name
125 path = self._make_up_to_date(
126 '/adapters', self.adapter_name, adapter_desc)
127 return self.core.get_proxy(path)
128
129 def _update_device_types(self):
130 """
131 Make sure device types are registered in Core
132 """
133 device_types = self.adapter.device_types()
134 for device_type in device_types.items:
135 key = device_type.id
136 self._make_up_to_date('/device_types', key, device_type)
137
138 def _make_up_to_date(self, container_path, key, data):
139 full_path = container_path + '/' + str(key)
140 root_proxy = self.core.get_proxy('/')
141 try:
142 root_proxy.get(full_path)
143 root_proxy.update(full_path, data)
144 except KeyError:
145 root_proxy.add(container_path, data)
146 return full_path
147
Khen Nursimulud068d812017-03-06 11:44:18 -0500148 def _remove_node(self, container_path, key):
149 """
150 Remove a node from the data model
151 :param container_path: path to node
152 :param key: node
153 :return: None
154 """
155 full_path = container_path + '/' + str(key)
156 root_proxy = self.core.get_proxy('/')
157 try:
158 root_proxy.get(full_path)
159 root_proxy.remove(full_path)
160 except KeyError:
161 # Node does not exist
162 pass
163
Zsolt Haraszti66862032016-11-28 14:28:39 -0800164 # ~~~~~~~~~~~~~~~~~~~~~ Core-Facing Service ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
165
166 def adopt_device(self, device):
167 return self.adapter.adopt_device(device)
168
khenaidoo032d3302017-06-09 14:50:04 -0400169 def reconcile_device(self, device):
170 return self.adapter.reconcile_device(device)
171
Zsolt Haraszti66862032016-11-28 14:28:39 -0800172 def abandon_device(self, device):
173 return self.adapter.abandon_device(device)
174
Khen Nursimulud068d812017-03-06 11:44:18 -0500175 def disable_device(self, device):
176 return self.adapter.disable_device(device)
177
178 def reenable_device(self, device):
179 return self.adapter.reenable_device(device)
180
181 def reboot_device(self, device):
182 return self.adapter.reboot_device(device)
183
Lydia Fang01f2e852017-06-28 17:24:58 -0700184 def download_image(self, device, request):
185 return self.adapter.download_image(device, request)
186
187 def get_image_download_status(self, device, request):
188 return self.adapter.get_image_download_status(device, request)
189
190 def cancel_image_download(self, device, request):
191 return self.adapter.cancel_image_download(device, request)
192
193 def activate_image_update(self, device, request):
194 return self.adapter.activate_image_update(device, request)
195
196 def revert_image_update(self, device, request):
197 return self.adapter.revert_image_update(device, request)
198
sathishg5ae86222017-06-28 15:16:29 +0530199 def self_test(self, device):
200 return self.adapter.self_test_device(device)
201
Khen Nursimulud068d812017-03-06 11:44:18 -0500202 def delete_device(self, device):
203 return self.adapter.delete_device(device)
204
205 def get_device_details(self, device):
206 return self.adapter.get_device_details(device)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800207
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800208 def update_flows_bulk(self, device, flows, groups):
209 return self.adapter.update_flows_bulk(device, flows, groups)
210
211 def update_flows_incrementally(self, device, flow_changes, group_changes):
212 return self.update_flows_incrementally(
213 device, flow_changes, group_changes)
214
Stephane Barbarie980a0912017-05-11 11:27:06 -0400215 def suppress_alarm(self, filter):
216 return self.adapter.suppress_alarm(filter)
217
218 def unsuppress_alarm(self, filter):
219 return self.adapter.unsuppress_alarm(filter)
220
Stephane Barbarie4db8ca22017-04-24 10:30:20 -0400221 # def update_pm_collection(self, device, pm_collection_config):
Sergio Slobodriana2eb52b2017-03-07 12:24:46 -0500222 # return self.adapter.update_pm_collection(device, pm_collection_config)
223
Nikolay Titov176f1db2017-08-10 12:38:43 -0400224 def create_interface(self, device, data):
225 return self.adapter.create_interface(device, data)
Nikolay Titov89004ec2017-06-19 18:22:42 -0400226
Nikolay Titov176f1db2017-08-10 12:38:43 -0400227 def update_interface(self, device, data):
228 return self.adapter.update_interface(device, data)
Nikolay Titov89004ec2017-06-19 18:22:42 -0400229
230 def remove_interface(self, device, data):
231 return self.adapter.remove_interface(device, data)
232
Nikolay Titov176f1db2017-08-10 12:38:43 -0400233 def create_tcont(self, device, tcont_data, traffic_descriptor_data):
234 return self.adapter.create_tcont(device, tcont_data,
235 traffic_descriptor_data)
236
237 def update_tcont(self, device, tcont_data, traffic_descriptor_data):
238 return self.adapter.update_tcont(device, tcont_data,
239 traffic_descriptor_data)
240
241 def remove_tcont(self, device, tcont_data, traffic_descriptor_data):
242 return self.adapter.remove_tcont(device, tcont_data,
243 traffic_descriptor_data)
244
245 def create_gemport(self, device, data):
246 return self.adapter.create_gemport(device, data)
247
248 def update_gemport(self, device, data):
249 return self.adapter.update_gemport(device, data)
250
251 def remove_gemport(self, device, data):
252 return self.adapter.remove_gemport(device, data)
253
254 def create_multicast_gemport(self, device, data):
255 return self.adapter.create_multicast_gemport(device, data)
256
257 def update_multicast_gemport(self, device, data):
258 return self.adapter.update_multicast_gemport(device, data)
259
260 def remove_multicast_gemport(self, device, data):
261 return self.adapter.remove_multicast_gemport(device, data)
262
263 def create_multicast_distribution_set(self, device, data):
264 return self.adapter.create_multicast_distribution_set(device, data)
265
266 def update_multicast_distribution_set(self, device, data):
267 return self.adapter.update_multicast_distribution_set(device, data)
268
269 def remove_multicast_distribution_set(self, device, data):
270 return self.adapter.remove_multicast_distribution_set(device, data)
Sergio Slobodriana2eb52b2017-03-07 12:24:46 -0500271
Zsolt Haraszti66862032016-11-28 14:28:39 -0800272 # ~~~~~~~~~~~~~~~~~~~ Adapter-Facing Service ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
273
274 def get_device(self, device_id):
275 return self.root_proxy.get('/devices/{}'.format(device_id))
276
Peter Shafik9107f2e2017-05-02 15:54:39 -0400277 def get_child_device(self, parent_device_id, **kwargs):
278 """
279 Retrieve a child device object belonging
280 to the specified parent device based on some match
281 criteria. The first child device that matches the
282 provided criteria is returned.
283 :param parent_device_id: parent's device id
284 :param **kwargs: arbitrary list of match criteria
285 :return: Child Device Object or None
286 """
287 # Get all arguments to be used for comparison
Nikolay Titov89004ec2017-06-19 18:22:42 -0400288 # Note that for now we are only matching on the ONU ID & SERIAL NUMBER
Peter Shafik9107f2e2017-05-02 15:54:39 -0400289 # Other matching fields can be added as required in the future
290 onu_id = kwargs.pop('onu_id', None)
Nikolay Titov89004ec2017-06-19 18:22:42 -0400291 serial_number = kwargs.pop('serial_number', None)
292 if onu_id is None and serial_number is None: return None
Peter Shafik9107f2e2017-05-02 15:54:39 -0400293
294 # Get all devices
295 devices = self.root_proxy.get('/devices')
296
297 # Get all child devices with the same parent ID
khenaidooe9677d42018-02-20 17:41:05 -0500298 children_ids = set(
299 d.id for d in devices if d.parent_id == parent_device_id)
Peter Shafik9107f2e2017-05-02 15:54:39 -0400300
301 # Loop through all the child devices with this parent ID
302 for child_id in children_ids:
303 found = True
304 device = self.get_device(child_id)
305
306 # Does this child device match the passed in ONU ID?
Nikolay Titov89004ec2017-06-19 18:22:42 -0400307 found_onu_id = False
308 if onu_id is not None:
309 if device.proxy_address.onu_id == onu_id:
310 found_onu_id = True
311
312 # Does this child device match the passed in SERIAL NUMBER?
313 found_serial_number = False
314 if serial_number is not None:
315 if device.serial_number == serial_number:
316 found_serial_number = True
317 # Match ONU ID and SERIAL NUMBER
318 if onu_id is not None and serial_number is not None:
319 found = found_onu_id & found_serial_number
320 # Otherwise ONU ID or SERIAL NUMBER
321 else:
322 found = found_onu_id | found_serial_number
Peter Shafik9107f2e2017-05-02 15:54:39 -0400323
324 # Return the matched child device
325 if found is True:
326 return device
327
328 return None
329
Zsolt Haraszti66862032016-11-28 14:28:39 -0800330 def add_device(self, device):
331 assert isinstance(device, Device)
332 self._make_up_to_date('/devices', device.id, device)
333
alshabibbe8ca2e2017-02-01 18:28:57 -0800334 # Ultimately, assign devices to device grpups.
335 # see https://jira.opencord.org/browse/CORD-838
Zsolt Haraszti66862032016-11-28 14:28:39 -0800336
337 dg = DeviceGroup(id='1')
338 self._make_up_to_date('/device_groups', dg.id, dg)
339
340 # add device to device group
alshabibbe8ca2e2017-02-01 18:28:57 -0800341 # see https://jira.opencord.org/browse/CORD-838
Zsolt Haraszti66862032016-11-28 14:28:39 -0800342
343 def update_device(self, device):
344 assert isinstance(device, Device)
345
346 # we run the update through the device_agent so that the change
347 # does not loop back to the adapter unnecessarily
348 device_agent = self.core.get_device_agent(device.id)
349 device_agent.update_device(device)
350
Sergio Slobodrian2db4c102017-03-09 22:29:23 -0500351 def update_device_pm_config(self, device_pm_config, init=False):
352 assert isinstance(device_pm_config, PmConfigs)
353
354 # we run the update through the device_agent so that the change
355 # does not loop back to the adapter unnecessarily
356 device_agent = self.core.get_device_agent(device_pm_config.id)
Stephane Barbarie4db8ca22017-04-24 10:30:20 -0400357 device_agent.update_device_pm_config(device_pm_config, init)
Sergio Slobodrian2db4c102017-03-09 22:29:23 -0500358
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400359 def update_adapter_pm_config(self, device_id, device_pm_config):
360 device = self.get_device(device_id)
Sergio Slobodrian2db4c102017-03-09 22:29:23 -0500361 self.adapter.update_pm_config(device, device_pm_config)
362
Lydia Fang01f2e852017-06-28 17:24:58 -0700363 def update_image_download(self, img_dnld):
364 self.log.info('update-image-download', img_dnld=img_dnld)
365 try:
366 # we run the update through the device_agent so that the change
367 # does not loop back to the adapter unnecessarily
368 device_agent = self.core.get_device_agent(img_dnld.id)
369 device_agent.update_device_image_download(img_dnld)
370 except Exception as e:
371 self.log.exception(e.message)
372
373 def delete_image_download(self, img_dnld):
374 self.log.info('delete-image-download', img_dnld=img_dnld)
375 try:
376 root_proxy = self.core.get_proxy('/')
khenaidooe9677d42018-02-20 17:41:05 -0500377 path = '/devices/{}/image_downloads/{}'. \
378 format(img_dnld.id, img_dnld.name)
Lydia Fang01f2e852017-06-28 17:24:58 -0700379 root_proxy.get(path)
380 root_proxy.remove(path)
381 device_agent = self.core.get_device_agent(img_dnld.id)
382 device_agent.unregister_device_image_download(img_dnld.name)
383 except Exception as e:
384 self.log.exception(e.message)
385
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400386 def _add_peer_reference(self, device_id, port):
Zsolt Haraszti66862032016-11-28 14:28:39 -0800387 # for referential integrity, add/augment references
388 port.device_id = device_id
389 me_as_peer = Port.PeerPort(device_id=device_id, port_no=port.port_no)
390 for peer in port.peers:
391 peer_port_path = '/devices/{}/ports/{}'.format(
392 peer.device_id, peer.port_no)
393 peer_port = self.root_proxy.get(peer_port_path)
394 if me_as_peer not in peer_port.peers:
395 new = peer_port.peers.add()
396 new.CopyFrom(me_as_peer)
397 self.root_proxy.update(peer_port_path, peer_port)
398
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400399 def _del_peer_reference(self, device_id, port):
400 me_as_peer = Port.PeerPort(device_id=device_id, port_no=port.port_no)
401 for peer in port.peers:
402 peer_port_path = '/devices/{}/ports/{}'.format(
403 peer.device_id, peer.port_no)
404 peer_port = self.root_proxy.get(peer_port_path)
405 if me_as_peer in peer_port.peers:
406 peer_port.peers.remove(me_as_peer)
407 self.root_proxy.update(peer_port_path, peer_port)
408
409 def add_port(self, device_id, port):
410 assert isinstance(port, Port)
411
412 # for referential integrity, add/augment references
413 self._add_peer_reference(device_id, port)
414
415 # Add port
Zsolt Haraszti66862032016-11-28 14:28:39 -0800416 self._make_up_to_date('/devices/{}/ports'.format(device_id),
417 port.port_no, port)
418
khenaidoo032d3302017-06-09 14:50:04 -0400419 def get_ports(self, device_id, port_type):
420 # assert Port.PortType.DESCRIPTOR.values_by_name[port_type]
421 ports = self.root_proxy.get('/devices/{}/ports'.format(device_id))
422 return [p for p in ports if p.type == port_type]
423
Niren R Chidrawar06bbb6f2017-08-31 02:37:06 -0400424 def delete_port(self, device_id, port):
425 assert isinstance(port, Port)
426 # for referential integrity, add/augment references
427 self._del_peer_reference(device_id, port)
428 # Delete port
429 self._remove_node('/devices/{}/ports'.format(device_id), port.port_no)
khenaidoo032d3302017-06-09 14:50:04 -0400430
Khen Nursimulud068d812017-03-06 11:44:18 -0500431 def disable_all_ports(self, device_id):
432 """
433 Disable all ports on that device, i.e. change the admin status to
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400434 disable and operational status to UNKNOWN.
Khen Nursimulud068d812017-03-06 11:44:18 -0500435 :param device_id: device id
436 :return: None
437 """
438
439 # get all device ports
440 ports = self.root_proxy.get('/devices/{}/ports'.format(device_id))
441 for port in ports:
442 port.admin_state = AdminState.DISABLED
443 port.oper_status = OperStatus.UNKNOWN
444 self._make_up_to_date('/devices/{}/ports'.format(device_id),
445 port.port_no, port)
446
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400447 def enable_all_ports(self, device_id):
Khen Nursimulud068d812017-03-06 11:44:18 -0500448 """
449 Re-enable all ports on that device, i.e. change the admin status to
450 enabled and operational status to ACTIVE
451 :param device_id: device id
452 :return: None
453 """
454
455 # get all device ports
456 ports = self.root_proxy.get('/devices/{}/ports'.format(device_id))
457 for port in ports:
458 port.admin_state = AdminState.ENABLED
459 port.oper_status = OperStatus.ACTIVE
460 self._make_up_to_date('/devices/{}/ports'.format(device_id),
461 port.port_no, port)
462
Niren R Chidrawar06bbb6f2017-08-31 02:37:06 -0400463 def update_operstatus_all_ports(self, device_id, oper_status):
464 ports = self.root_proxy.get('/devices/{}/ports'.format(device_id))
465 for port in ports:
466 port.oper_status = oper_status
467 self._make_up_to_date('/devices/{}/ports'.format(device_id),
468 port.port_no, port)
469
Khen Nursimulud068d812017-03-06 11:44:18 -0500470 def delete_all_peer_references(self, device_id):
471 """
472 Remove all peer port references for that device
473 :param device_id: device_id of device
474 :return: None
475 """
476 ports = self.root_proxy.get('/devices/{}/ports'.format(device_id))
477 for port in ports:
478 port_path = '/devices/{}/ports/{}'.format(device_id, port.port_no)
479 for peer in port.peers:
480 port.peers.remove(peer)
481 self.root_proxy.update(port_path, port)
482
483 def delete_port_reference_from_parent(self, device_id, port):
484 """
485 Delete the port reference from the parent device
486 :param device_id: id of device containing the port
487 :param port: port to remove
488 :return: None
489 """
490 assert isinstance(port, Port)
491 self.log.info('delete-port-reference', device_id=device_id, port=port)
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400492 self._del_peer_reference(device_id, port)
Khen Nursimulud068d812017-03-06 11:44:18 -0500493
rshetty1cc73982017-09-02 03:31:12 +0530494 # update child port details
495 self._make_up_to_date('/devices/{}/ports'.format(device_id),
496 port.port_no, port)
497
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400498 def add_port_reference_to_parent(self, device_id, port):
499 """
500 Add the port reference to the parent device
501 :param device_id: id of device containing the port
502 :param port: port to add
503 :return: None
504 """
505 assert isinstance(port, Port)
506 self.log.info('add-port-reference', device_id=device_id, port=port)
507 self._add_peer_reference(device_id, port)
rshetty1cc73982017-09-02 03:31:12 +0530508 # update child port details
509 self._make_up_to_date('/devices/{}/ports'.format(device_id),
510 port.port_no, port)
Khen Nursimulud068d812017-03-06 11:44:18 -0500511
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800512 def get_logical_device(self, logical_device_id):
513 return self.root_proxy.get('/logical_devices/{}'.format(
514 logical_device_id))
515
Khen Nursimulud068d812017-03-06 11:44:18 -0500516 def get_logical_port(self, logical_device_id, port_id):
517 return self.root_proxy.get('/logical_devices/{}/ports/{}'.format(
518 logical_device_id, port_id))
519
khenaidooe9677d42018-02-20 17:41:05 -0500520 def _create_cluster_ids_from_dpid(self, dpid):
521 """
522 Create a logical device id using a datapath id.
523 :param dpid: Must be present and formatted as a mac address
524 :return: a unique logical device id and a formatted datapath id. If
525 the dpid was already registered then an exception will be raised.
526 """
527 switch_id = int(dpid.replace(':', ''), 16)
528 logical_devices = self.root_proxy.get('/logical_devices')
529 existing_ids = set(ld.id for ld in logical_devices)
530 existing_datapath_ids = set(ld.datapath_id for ld in logical_devices)
531 core_id = registry('core').core_store_id
532
533 ld_id, dp_id = create_cluster_logical_device_ids(core_id, switch_id)
534 ids_exist = dp_id in existing_datapath_ids or \
535 ld_id in existing_ids
536 if not ids_exist:
537 return ld_id, dp_id
538 else:
539 self.log.error('ID-already-registered', logical_id=ld_id,
540 dpid=dpid)
541 raise IDError('ID-already-registered')
542
543 def _is_valid_mac_address(self, data):
544 return re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$",
545 data)
546
khenaidoo507d9222017-10-10 16:23:49 -0400547 def create_logical_device(self, logical_device, dpid=None):
548 """
549 Allow the adapters to provide their own datapath id. This must
khenaidooe9677d42018-02-20 17:41:05 -0500550 be the OLT MAC address. If the dpid is None or is not a mac
551 address then an exception will be raised.
552 :param logical_device: logical device
553 :param dpid: OLT MAC address. dpid default param is None just to be
554 backward compatible with existing adapters.
khenaidoo507d9222017-10-10 16:23:49 -0400555 :return: updated logical device
556 """
Zsolt Haraszti66862032016-11-28 14:28:39 -0800557 assert isinstance(logical_device, LogicalDevice)
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800558
khenaidooe9677d42018-02-20 17:41:05 -0500559 # Validate the dpid - it needs to be present and formatted as a mac
560 # address
561 if dpid:
562 dpid = dpid.lower()
563 if not self._is_valid_mac_address(dpid):
564 self.log.error('DPID-not-a-mac-address', dpid=dpid)
565 raise MacAddressError('DPID-not-a-mac-address')
566 else:
567 self.log.error('DPID-cannot-be-none')
568 raise MacAddressError("DPID-cannot-be-none")
569
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800570 if not logical_device.id:
khenaidooe9677d42018-02-20 17:41:05 -0500571 ld_id, dp_id = self._create_cluster_ids_from_dpid(dpid)
khenaidooa8588f22017-06-16 12:13:34 -0400572 logical_device.id = ld_id
573 logical_device.datapath_id = dp_id
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800574
Zsolt Haraszti66862032016-11-28 14:28:39 -0800575 self._make_up_to_date('/logical_devices',
576 logical_device.id, logical_device)
577
Khen Nursimulud068d812017-03-06 11:44:18 -0500578 # Keep a reference to the packet out subscription as it will be
579 # referred during removal
580 self.packet_out_subscription = self.event_bus.subscribe(
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800581 topic='packet-out:{}'.format(logical_device.id),
582 callback=lambda _, p: self.receive_packet_out(logical_device.id, p)
583 )
584
Zsolt Harasztid036b7e2016-12-23 15:36:01 -0800585 return logical_device
586
khenaidoo032d3302017-06-09 14:50:04 -0400587 def reconcile_logical_device(self, logical_device_id):
588 """
589 This is called by the adapter to reconcile the physical device with
590 the logical device. For now, we only set the packet-out subscription
591 :param logical_device_id:
592 :return:
593 """
594 # Keep a reference to the packet out subscription as it will be
595 # referred during removal
596 self.packet_out_subscription = self.event_bus.subscribe(
597 topic='packet-out:{}'.format(logical_device_id),
598 callback=lambda _, p: self.receive_packet_out(logical_device_id, p)
599 )
600
Khen Nursimulud068d812017-03-06 11:44:18 -0500601 def delete_logical_device(self, logical_device):
602 """
603 This will remove the logical device as well as all logical ports
604 associated with it
605 :param logical_device: The logical device to remove
606 :return: None
607 """
608 assert isinstance(logical_device, LogicalDevice)
609
610 # Remove packet out subscription
611 self.event_bus.unsubscribe(self.packet_out_subscription)
612
613 # Remove node from the data model - this will trigger the logical
614 # device 'remove callbacks' as well as logical ports 'remove
615 # callbacks' if present
616 self._remove_node('/logical_devices', logical_device.id)
617
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800618 def receive_packet_out(self, logical_device_id, ofp_packet_out):
619
620 def get_port_out(opo):
621 for action in opo.actions:
622 if action.type == OUTPUT:
623 return action.output.port
624
625 out_port = get_port_out(ofp_packet_out)
626 frame = ofp_packet_out.data
627 self.adapter.receive_packet_out(logical_device_id, out_port, frame)
628
Zsolt Haraszti66862032016-11-28 14:28:39 -0800629 def add_logical_port(self, logical_device_id, port):
630 assert isinstance(port, LogicalPort)
631 self._make_up_to_date(
632 '/logical_devices/{}/ports'.format(logical_device_id),
633 port.id, port)
634
Khen Nursimulud068d812017-03-06 11:44:18 -0500635 def delete_logical_port(self, logical_device_id, port):
636 assert isinstance(port, LogicalPort)
637 self._remove_node('/logical_devices/{}/ports'.format(
638 logical_device_id), port.id)
639
Niren R Chidrawar06bbb6f2017-08-31 02:37:06 -0400640 def delete_logical_port_by_id(self, logical_device_id, port_id):
641 self._remove_node('/logical_devices/{}/ports'.format(
642 logical_device_id), port_id)
643
Khen Nursimulud068d812017-03-06 11:44:18 -0500644 def update_logical_port(self, logical_device_id, port):
645 assert isinstance(port, LogicalPort)
646 self.log.debug('update-logical-port',
647 logical_device_id=logical_device_id,
648 port=port)
Khen Nursimulud068d812017-03-06 11:44:18 -0500649 self._make_up_to_date(
650 '/logical_devices/{}/ports'.format(logical_device_id),
651 port.id, port)
652
khenaidoo032d3302017-06-09 14:50:04 -0400653 def get_child_devices(self, parent_device_id):
654 try:
655 devices = self.root_proxy.get('/devices')
656 children = [d for d in devices if d.parent_id == parent_device_id]
657 return children
658 except Exception, e:
659 self.log.exception('failure', e=e)
660
661 def subscribe_to_proxy_child_messages(self, proxy_address):
662 topic = self._gen_tx_proxy_address_topic(proxy_address)
663 self._tx_event_subscriptions[topic] = self.event_bus.subscribe(
664 topic, lambda t, m: self._send_proxied_message(proxy_address, m))
665
666 def reconcile_child_devices(self, parent_device_id):
667 children = self.get_child_devices(parent_device_id)
668 for child in children:
669 # First subscribe to proxy messages from a chile device
670 self.subscribe_to_proxy_child_messages(child.proxy_address)
671
672 # Then trigger the reconciliation of the existing child device
673 device_agent = self.core.get_device_agent(child.id)
674 device_agent.reconcile_existing_device(child)
675
khenaidooe9677d42018-02-20 17:41:05 -0500676 # Obselete API - discouraged to be decommissioned after
677 # adapters are align to new APIs
Zsolt Haraszti66862032016-11-28 14:28:39 -0800678 def child_device_detected(self,
679 parent_device_id,
680 parent_port_no,
681 child_device_type,
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800682 proxy_address,
Nikolay Titov89004ec2017-06-19 18:22:42 -0400683 admin_state,
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800684 **kw):
Zsolt Haraszti66862032016-11-28 14:28:39 -0800685 # we create new ONU device objects and insert them into the config
Zsolt Haraszti66862032016-11-28 14:28:39 -0800686 device = Device(
khenaidoo08d48d22017-06-29 19:42:49 -0400687 id=create_cluster_device_id(self.core.core_store_id),
688 # id=uuid4().hex[:12],
Zsolt Haraszti66862032016-11-28 14:28:39 -0800689 type=child_device_type,
690 parent_id=parent_device_id,
691 parent_port_no=parent_port_no,
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800692 proxy_address=proxy_address,
Nikolay Titov89004ec2017-06-19 18:22:42 -0400693 admin_state=admin_state,
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800694 **kw
Zsolt Haraszti66862032016-11-28 14:28:39 -0800695 )
696 self._make_up_to_date(
697 '/devices', device.id, device)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800698
699 topic = self._gen_tx_proxy_address_topic(proxy_address)
700 self._tx_event_subscriptions[topic] = self.event_bus.subscribe(
701 topic, lambda t, m: self._send_proxied_message(proxy_address, m))
702
Niren R Chidrawarefcebcd2017-07-19 20:03:39 -0400703 def add_onu_device(self,
704 parent_device_id,
705 parent_port_no,
706 vendor_id,
707 proxy_address,
708 admin_state,
709 **kw):
710 device_type = next((dt for dt in self.root_proxy.get('/device_types')
Sireesha Kora8fbcb822017-08-22 00:46:46 -0400711 if dt.vendor_id == vendor_id and \
712 dt.id.endswith("_onu")), None)
Niren R Chidrawarefcebcd2017-07-19 20:03:39 -0400713 # we create new ONU device objects and insert them into the config
714 device = Device(
715 id=create_cluster_device_id(self.core.core_store_id),
716 # id=uuid4().hex[:12],
717 type=device_type.id,
718 vendor_id=vendor_id,
719 parent_id=parent_device_id,
720 parent_port_no=parent_port_no,
721 proxy_address=proxy_address,
722 admin_state=admin_state,
723 adapter=device_type.adapter,
724 **kw
725 )
726 self._make_up_to_date('/devices', device.id, device)
727
728 topic = self._gen_tx_proxy_address_topic(proxy_address)
729 self._tx_event_subscriptions[topic] = self.event_bus.subscribe(
730 topic, lambda t, m: self._send_proxied_message(proxy_address, m))
731
khenaidoo032d3302017-06-09 14:50:04 -0400732 def get_child_device_with_proxy_address(self, proxy_address):
733 # Proxy address is defined as {parent id, channel_id}
734 devices = self.root_proxy.get('/devices')
735 children_ids = set(d.id for d in devices if d.parent_id ==
736 proxy_address.device_id)
737 for child_id in children_ids:
738 device = self.get_device(child_id)
739 if device.proxy_address == proxy_address:
740 return device
741
Khen Nursimulud068d812017-03-06 11:44:18 -0500742 def remove_all_logical_ports(self, logical_device_id):
743 """ Remove all logical ports from a given logical device"""
744 ports = self.root_proxy.get('/logical_devices/{}/ports')
745 for port in ports:
746 self._remove_node('/logical_devices/{}/ports', port.id)
747
748 def delete_all_child_devices(self, parent_device_id):
749 """ Remove all ONUs from a given OLT """
750 devices = self.root_proxy.get('/devices')
Girish61687212018-01-08 12:48:58 +0530751 children_ids = set()
752 for device in devices:
753 if device.parent_id == parent_device_id:
754 children_ids.add(device.id)
755 topic = self._gen_tx_proxy_address_topic(device.proxy_address)
756 self.event_bus.unsubscribe(self._tx_event_subscriptions[topic])
757 del self._tx_event_subscriptions[topic]
758
Khen Nursimulud068d812017-03-06 11:44:18 -0500759 self.log.debug('devices-to-delete',
760 parent_id=parent_device_id,
761 children_ids=children_ids)
762 for child_id in children_ids:
763 self._remove_node('/devices', child_id)
764
khenaidoo2d7af132017-03-23 15:45:51 -0400765 def update_child_devices_state(self,
766 parent_device_id,
767 oper_status=None,
768 connect_status=None,
769 admin_state=None):
770 """ Update status of all child devices """
Khen Nursimulud068d812017-03-06 11:44:18 -0500771 devices = self.root_proxy.get('/devices')
khenaidooe9677d42018-02-20 17:41:05 -0500772 children_ids = set(
773 d.id for d in devices if d.parent_id == parent_device_id)
khenaidoo2d7af132017-03-23 15:45:51 -0400774 self.log.debug('update-devices',
Khen Nursimulud068d812017-03-06 11:44:18 -0500775 parent_id=parent_device_id,
khenaidoo2d7af132017-03-23 15:45:51 -0400776 children_ids=children_ids,
777 oper_status=oper_status,
778 connect_status=connect_status,
779 admin_state=admin_state)
Khen Nursimulud068d812017-03-06 11:44:18 -0500780
khenaidoo71d0a6c2017-03-22 21:46:04 -0400781 for child_id in children_ids:
782 device = self.get_device(child_id)
Niren R Chidrawar06bbb6f2017-08-31 02:37:06 -0400783 if oper_status is not None:
khenaidoo2d7af132017-03-23 15:45:51 -0400784 device.oper_status = oper_status
785 if connect_status:
786 device.connect_status = connect_status
787 if admin_state:
788 device.admin_state = admin_state
khenaidoo71d0a6c2017-03-22 21:46:04 -0400789 self._make_up_to_date(
790 '/devices', device.id, device)
791
Nikolay Titov89004ec2017-06-19 18:22:42 -0400792 def delete_child_device(self, parent_device_id, child_device_id):
793 onu_device = self.root_proxy.get('/devices/{}'.format(child_device_id))
794 if onu_device is not None:
795 if onu_device.parent_id == parent_device_id:
Girish61687212018-01-08 12:48:58 +0530796 self.log.debug('deleting-child-device',
khenaidooe9677d42018-02-20 17:41:05 -0500797 parent_device_id=parent_device_id,
798 child_device_id=child_device_id)
799 topic = self._gen_tx_proxy_address_topic(
800 onu_device.proxy_address)
Girish61687212018-01-08 12:48:58 +0530801 self.event_bus.unsubscribe(self._tx_event_subscriptions[topic])
802 del self._tx_event_subscriptions[topic]
Nikolay Titov89004ec2017-06-19 18:22:42 -0400803 self._remove_node('/devices', child_device_id)
804
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800805 def _gen_rx_proxy_address_topic(self, proxy_address):
806 """Generate unique topic name specific to this proxy address for rx"""
Zsolt Harasztief05ad22017-01-07 22:08:06 -0800807 topic = 'rx:' + MessageToJson(proxy_address)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800808 return topic
809
810 def _gen_tx_proxy_address_topic(self, proxy_address):
811 """Generate unique topic name specific to this proxy address for tx"""
Zsolt Harasztief05ad22017-01-07 22:08:06 -0800812 topic = 'tx:' + MessageToJson(proxy_address)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800813 return topic
814
815 def register_for_proxied_messages(self, proxy_address):
816 topic = self._gen_rx_proxy_address_topic(proxy_address)
817 self._rx_event_subscriptions[topic] = self.event_bus.subscribe(
Stephane Barbariecc6b2e62017-03-02 14:35:55 -0500818 topic,
819 lambda t, m: self._receive_proxied_message(proxy_address, m))
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800820
Khen Nursimulud068d812017-03-06 11:44:18 -0500821 def unregister_for_proxied_messages(self, proxy_address):
822 topic = self._gen_rx_proxy_address_topic(proxy_address)
823 self.event_bus.unsubscribe(self._rx_event_subscriptions[topic])
824 del self._rx_event_subscriptions[topic]
825
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800826 def _receive_proxied_message(self, proxy_address, msg):
827 self.adapter.receive_proxied_message(proxy_address, msg)
828
829 def send_proxied_message(self, proxy_address, msg):
830 topic = self._gen_tx_proxy_address_topic(proxy_address)
831 self.event_bus.publish(topic, msg)
832
833 def _send_proxied_message(self, proxy_address, msg):
834 self.adapter.send_proxied_message(proxy_address, msg)
835
836 def receive_proxied_message(self, proxy_address, msg):
837 topic = self._gen_rx_proxy_address_topic(proxy_address)
838 self.event_bus.publish(topic, msg)
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -0800839
Peter Shafik9107f2e2017-05-02 15:54:39 -0400840 def register_for_inter_adapter_messages(self):
841 self.event_bus.subscribe(self.adapter_name,
khenaidooe9677d42018-02-20 17:41:05 -0500842 lambda t,
843 m: self.adapter.receive_inter_adapter_message(
844 m))
Peter Shafik9107f2e2017-05-02 15:54:39 -0400845
846 def unregister_for_inter_adapter_messages(self):
847 self.event_bus.unsubscribe(self.adapter_name)
848
849 def publish_inter_adapter_message(self, device_id, msg):
850 # Get the device from the device_id
851 device = self.get_device(device_id)
852 assert device is not None
853
854 # Publish a message to the adapter that is responsible
855 # for managing this device
856 self.event_bus.publish(device.type, msg)
857
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -0800858 # ~~~~~~~~~~~~~~~~~~ Handling packet-in and packet-out ~~~~~~~~~~~~~~~~~~~~
859
860 def send_packet_in(self, logical_device_id, logical_port_no, packet):
861 self.log.debug('send-packet-in', logical_device_id=logical_device_id,
Zsolt Harasztief05ad22017-01-07 22:08:06 -0800862 logical_port_no=logical_port_no, packet=hexify(packet))
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -0800863
864 if isinstance(packet, Packet):
865 packet = str(packet)
866
867 topic = 'packet-in:' + logical_device_id
868 self.event_bus.publish(topic, (logical_port_no, packet))
Zsolt Haraszti749b0952017-01-18 09:02:35 -0800869
870 # ~~~~~~~~~~~~~~~~~~~ Handling KPI metric submissions ~~~~~~~~~~~~~~~~~~~~~
Zsolt Harasztic5f740b2017-01-18 09:53:17 -0800871
Zsolt Haraszti749b0952017-01-18 09:02:35 -0800872 def submit_kpis(self, kpi_event_msg):
873 try:
874 assert isinstance(kpi_event_msg, KpiEvent)
875 self.event_bus.publish('kpis', kpi_event_msg)
876 except Exception as e:
877 self.log.exception('failed-kpi-submission',
878 type=type(kpi_event_msg))
Stephane Barbarie52198b92017-03-02 13:44:46 -0500879
880 # ~~~~~~~~~~~~~~~~~~~ Handle alarm submissions ~~~~~~~~~~~~~~~~~~~~~
881
Stephane Barbariecc6b2e62017-03-02 14:35:55 -0500882 def create_alarm(self, id=None, resource_id=None, description=None,
883 raised_ts=0, changed_ts=0,
884 type=AlarmEventType.EQUIPMENT,
Stephane Barbariebf3e10c2017-03-03 10:15:58 -0500885 category=AlarmEventCategory.PON,
Stephane Barbariecc6b2e62017-03-02 14:35:55 -0500886 severity=AlarmEventSeverity.MINOR,
887 state=AlarmEventState.RAISED,
Stephane Barbarie52198b92017-03-02 13:44:46 -0500888 context=None):
889
890 # Construct the ID if it is not provided
891 if id == None:
892 id = 'voltha.{}.{}'.format(self.adapter_name, resource_id)
893
894 return AlarmEvent(
895 id=id,
896 resource_id=resource_id,
897 type=type,
898 category=category,
899 severity=severity,
900 state=state,
901 description=description,
902 reported_ts=arrow.utcnow().timestamp,
903 raised_ts=raised_ts,
904 changed_ts=changed_ts,
905 context=context
906 )
907
Stephane Barbarie4db8ca22017-04-24 10:30:20 -0400908 def filter_alarm(self, device_id, alarm_event):
909 alarm_filters = self.root_proxy.get('/alarm_filters')
910
911 rule_values = {
912 'id': alarm_event.id,
913 'type': AlarmEventType.AlarmEventType.Name(alarm_event.type),
khenaidooe9677d42018-02-20 17:41:05 -0500914 'category': AlarmEventCategory.AlarmEventCategory.Name(
915 alarm_event.category),
916 'severity': AlarmEventSeverity.AlarmEventSeverity.Name(
917 alarm_event.severity),
Stephane Barbarie4db8ca22017-04-24 10:30:20 -0400918 'resource_id': alarm_event.resource_id,
919 'device_id': device_id
920 }
921
922 for alarm_filter in alarm_filters:
923 if alarm_filter.rules:
924 exclude = True
925 for rule in alarm_filter.rules:
926 self.log.debug("compare-alarm-event",
khenaidooe9677d42018-02-20 17:41:05 -0500927 key=AlarmFilterRuleKey.AlarmFilterRuleKey.Name(
928 rule.key),
929 actual=rule_values[
930 AlarmFilterRuleKey.AlarmFilterRuleKey.Name(
931 rule.key)].lower(),
Stephane Barbarie4db8ca22017-04-24 10:30:20 -0400932 expected=rule.value.lower())
933 exclude = exclude and \
khenaidooe9677d42018-02-20 17:41:05 -0500934 (rule_values[
935 AlarmFilterRuleKey.AlarmFilterRuleKey.Name(
936 rule.key)].lower() == rule.value.lower())
Stephane Barbarie4db8ca22017-04-24 10:30:20 -0400937 if not exclude:
938 break
939
940 if exclude:
941 self.log.info("filtered-alarm-event", alarm=alarm_event)
942 return True
943
944 return False
945
946 def submit_alarm(self, device_id, alarm_event_msg):
Stephane Barbarie52198b92017-03-02 13:44:46 -0500947 try:
948 assert isinstance(alarm_event_msg, AlarmEvent)
Stephane Barbarie4db8ca22017-04-24 10:30:20 -0400949 if not self.filter_alarm(device_id, alarm_event_msg):
950 self.event_bus.publish('alarms', alarm_event_msg)
Stephane Barbarie52198b92017-03-02 13:44:46 -0500951
952 except Exception as e:
953 self.log.exception('failed-alarm-submission',
954 type=type(alarm_event_msg))
Nikolay Titov89004ec2017-06-19 18:22:42 -0400955
956 # ~~~~~~~~~~~~~~~~~~~ Handle ONU detect ~~~~~~~~~~~~~~~~~~~~~
957
958 def _gen_onu_detect_proxy_address_topic(self, device_id):
959 """Generate unique topic name specific to this device id for onu detect"""
960 topic = str('onu_detect:{}'.format(device_id))
961 return topic
962
963 def register_for_onu_detect_state(self, device_id):
964 topic = self._gen_onu_detect_proxy_address_topic(device_id)
965 self._onu_detect_event_subscriptions[topic] = self.event_bus.subscribe(
966 topic,
967 lambda t, m: self._forward_onu_detect_state(device_id, m))
968
969 def unregister_for_onu_detect_state(self, device_id):
970 topic = self._gen_onu_detect_proxy_address_topic(device_id)
971 self.event_bus.unsubscribe(self._onu_detect_event_subscriptions[topic])
972 del self._onu_detect_event_subscriptions[topic]
973
974 def _forward_onu_detect_state(self, device_id, state):
975 self.adapter.receive_onu_detect_state(device_id, state)
976
977 def forward_onu_detect_state(self, device_id, state):
978 topic = self._gen_onu_detect_proxy_address_topic(device_id)
979 self.event_bus.publish(topic, state)