blob: 79877b7b81620644461711e89a1c77c2c0bea9b9 [file] [log] [blame]
Chip Bolingf5af85d2019-02-12 15:36:17 -06001# Copyright 2017-present Adtran, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""
15Adtran generic VOLTHA device handler
16"""
17import argparse
18import datetime
19import shlex
20import time
21
22import structlog
23from twisted.internet import reactor, defer
24from twisted.internet.defer import inlineCallbacks, returnValue
25from twisted.python.failure import Failure
26
27from adapters.adtran_common.net.adtran_netconf import AdtranNetconfClient
28from adapters.adtran_common.net.adtran_rest import AdtranRestClient
29from pyvoltha.protos import third_party
30from pyvoltha.protos.common_pb2 import OperStatus, AdminState, ConnectStatus
31from pyvoltha.protos.logical_device_pb2 import LogicalDevice
32from pyvoltha.protos.openflow_13_pb2 import ofp_desc, ofp_switch_features, OFPC_PORT_STATS, \
33 OFPC_GROUP_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS
34from pyvoltha.adapters.extensions.alarms.adapter_alarms import AdapterAlarms
35from pyvoltha.adapters.extensions.kpi.olt.olt_pm_metrics import OltPmMetrics
36from pyvoltha.common.utils.asleep import asleep
37from flow.flow_tables import DeviceFlows, DownstreamFlows
38
39_ = third_party
40
41DEFAULT_MULTICAST_VLAN = 4000
42BROADCOM_UNTAGGED_VLAN = 4091
43DEFAULT_UTILITY_VLAN = BROADCOM_UNTAGGED_VLAN
44
45_DEFAULT_RESTCONF_USERNAME = ""
46_DEFAULT_RESTCONF_PASSWORD = ""
47_DEFAULT_RESTCONF_PORT = 8081
48
49_DEFAULT_NETCONF_USERNAME = ""
50_DEFAULT_NETCONF_PASSWORD = ""
51_DEFAULT_NETCONF_PORT = 830
52
53_STARTUP_RETRY_TIMEOUT = 5 # 5 seconds delay after activate failed before we
54_DEFAULT_RESOURCE_MGR_KEY = "adtran"
55
56
57class AdtranDeviceHandler(object):
58 """
59 A device that supports the ADTRAN RESTCONF protocol for communications
60 with a VOLTHA/VANILLA managed device.
61 Port numbering guidelines for Adtran OLT devices. Derived classes may augment
62 the numbering scheme below as needed.
63
64 - Reserve port 0 for the CPU capture port. All ports to/from this port should
65 be related to messages destined to/from the OpenFlow controller.
66
67 - Begin numbering northbound ports (network facing) at port 1 contiguously.
68 Consider the northbound ports to typically be the highest speed uplinks.
69 If these ports are removable or provided by one or more slots in a chassis
70 subsystem, still reserve the appropriate amount of port numbers whether they
71 are populated or not.
72
73 - Number southbound ports (customer facing) ports next starting at the next
74 available port number. If chassis based, follow the same rules as northbound
75 ports and reserve enough port numbers.
76
77 - Number any out-of-band management ports (if any) last. It will be up to the
78 Device Adapter developer whether to expose these to openflow or not. If you do
79 not expose them, but do have the ports, still reserve the appropriate number of
80 port numbers just in case.
81 """
82 # HTTP shortcuts
83 HELLO_URI = '/restconf/adtran-hello:hello'
84
85 # RPC XML shortcuts
86 RESTART_RPC = '<system-restart xmlns="urn:ietf:params:xml:ns:yang:ietf-system"/>'
87
88 def __init__(self, **kwargs):
89 from net.pio_zmq import DEFAULT_PIO_TCP_PORT
90 from net.pon_zmq import DEFAULT_PON_AGENT_TCP_PORT
91
92 super(AdtranDeviceHandler, self).__init__()
93
94 adapter = kwargs['adapter']
95 device_id = kwargs['device-id']
96 timeout = kwargs.get('timeout', 20)
97
98 self.adapter = adapter
99 self.adapter_agent = adapter.adapter_agent
100 self.device_id = device_id
101 self.log = structlog.get_logger(device_id=device_id)
102 self.startup = None # Startup/reboot deferred
103 self.channel = None # Proxy messaging channel with 'send' method
104 self.logical_device_id = None
105 self.pm_metrics = None
106 self.alarms = None
107 self.multicast_vlans = [DEFAULT_MULTICAST_VLAN]
108 self.utility_vlan = DEFAULT_UTILITY_VLAN
109 self.mac_address = '00:13:95:00:00:00'
110 self._rest_support = None
111 self._initial_enable_complete = False
112 self.resource_mgr = None
113 self.tech_profiles = None # dict(): intf_id -> ResourceMgr.TechProfile
114
115 # Northbound and Southbound ports
116 self.northbound_ports = {} # port number -> Port
117 self.southbound_ports = {} # port number -> Port (For PON, use pon-id as key)
118 # self.management_ports = {} # port number -> Port TODO: Not currently supported
119
120 self.num_northbound_ports = None
121 self.num_southbound_ports = None
122 # self.num_management_ports = None
123
124 self.ip_address = None
125 self.host_and_port = None
126 self.timeout = timeout
127 self.restart_failure_timeout = 5 * 60 # 5 Minute timeout
128
129 # REST Client
130 self.rest_port = _DEFAULT_RESTCONF_PORT
131 self.rest_username = _DEFAULT_RESTCONF_USERNAME
132 self.rest_password = _DEFAULT_RESTCONF_PASSWORD
133 self._rest_client = None
134
135 # NETCONF Client
136 self.netconf_port = _DEFAULT_NETCONF_PORT
137 self.netconf_username = _DEFAULT_NETCONF_USERNAME
138 self.netconf_password = _DEFAULT_NETCONF_PASSWORD
139 self._netconf_client = None
140
141 # Flow entries
142 self.upstream_flows = DeviceFlows()
143 self.downstream_flows = DownstreamFlows()
144
145 self.max_nni_ports = 1 # TODO: This is a VOLTHA imposed limit in 'flow_decomposer.py
146 # and logical_device_agent.py
147
148 self.resource_manager_key = _DEFAULT_RESOURCE_MGR_KEY
149 # OMCI ZMQ Channel
150 self.pon_agent_port = DEFAULT_PON_AGENT_TCP_PORT
151 self.pio_port = DEFAULT_PIO_TCP_PORT
152
153 # Heartbeat support
154 self.heartbeat_count = 0
155 self.heartbeat_miss = 0
156 self.heartbeat_interval = 2 # TODO: Decrease before release or any scale testing
157 self.heartbeat_failed_limit = 3
158 self.heartbeat_timeout = 5
159 self.heartbeat = None
160 self.heartbeat_last_reason = ''
161
162 # Virtualized OLT Support
163 self.is_virtual_olt = False
164
165 # Installed flows
166 self._evcs = {} # Flow ID/name -> FlowEntry
167
168 def _delete_logical_device(self):
169 ldi, self.logical_device_id = self.logical_device_id, None
170
171 if ldi is None:
172 return
173
174 self.log.debug('delete-logical-device', ldi=ldi)
175
176 logical_device = self.adapter_agent.get_logical_device(ldi)
177 self.adapter_agent.delete_logical_device(logical_device)
178
179 device = self.adapter_agent.get_device(self.device_id)
180 device.parent_id = ''
181
182 # Update the logical device mapping
183 if ldi in self.adapter.logical_device_id_to_root_device_id:
184 del self.adapter.logical_device_id_to_root_device_id[ldi]
185
186 def __del__(self):
187 # Kill any startup or heartbeat defers
188
189 d, self.startup = self.startup, None
190 h, self.heartbeat = self.heartbeat, None
191
192 if d is not None and not d.called:
193 d.cancel()
194
195 if h is not None and not h.called:
196 h.cancel()
197
198 # Remove the logical device
199 self._delete_logical_device()
200
201 self.northbound_ports.clear()
202 self.southbound_ports.clear()
203
204 def __str__(self):
205 return "AdtranDeviceHandler: {}".format(self.ip_address)
206
207 @property
208 def netconf_client(self):
209 return self._netconf_client
210
211 @property
212 def rest_client(self):
213 return self._rest_client
214
215 @property
216 def evcs(self):
217 return list(self._evcs.values())
218
219 def add_evc(self, evc):
220 if self._evcs is not None and evc.name not in self._evcs:
221 self._evcs[evc.name] = evc
222
223 def remove_evc(self, evc):
224 if self._evcs is not None and evc.name in self._evcs:
225 del self._evcs[evc.name]
226
227 def parse_provisioning_options(self, device):
228 if device.ipv4_address:
229 self.ip_address = device.ipv4_address
230 self.host_and_port = '{}:{}'.format(self.ip_address,
231 self.netconf_port)
232 elif device.host_and_port:
233 self.host_and_port = device.host_and_port.split(":")
234 self.ip_address = self.host_and_port[0]
235 self.netconf_port = int(self.host_and_port[1])
236 self.adapter_agent.update_device(device)
237
238 else:
239 self.activate_failed(device, 'No IP_address field provided')
240
241 #############################################################
242 # Now optional parameters
243 def check_tcp_port(value):
244 ivalue = int(value)
245 if ivalue <= 0 or ivalue > 65535:
246 raise argparse.ArgumentTypeError("%s is a not a valid port number" % value)
247 return ivalue
248
249 def check_vid(value):
250 ivalue = int(value)
251 if ivalue < 1 or ivalue > 4094:
252 raise argparse.ArgumentTypeError("Valid VLANs are 1..4094")
253 return ivalue
254
255 parser = argparse.ArgumentParser(description='Adtran Device Adapter')
256 parser.add_argument('--nc_username', '-u', action='store', default=_DEFAULT_NETCONF_USERNAME,
257 help='NETCONF username')
258 parser.add_argument('--nc_password', '-p', action='store', default=_DEFAULT_NETCONF_PASSWORD,
259 help='NETCONF Password')
260 parser.add_argument('--nc_port', '-t', action='store', default=_DEFAULT_NETCONF_PORT,
261 type=check_tcp_port, help='NETCONF TCP Port')
262 parser.add_argument('--rc_username', '-U', action='store', default=_DEFAULT_RESTCONF_USERNAME,
263 help='REST username')
264 parser.add_argument('--rc_password', '-P', action='store', default=_DEFAULT_RESTCONF_PASSWORD,
265 help='REST Password')
266 parser.add_argument('--rc_port', '-T', action='store', default=_DEFAULT_RESTCONF_PORT,
267 type=check_tcp_port, help='RESTCONF TCP Port')
268 parser.add_argument('--zmq_port', '-z', action='store', default=DEFAULT_PON_AGENT_TCP_PORT,
269 type=check_tcp_port, help='PON Agent ZeroMQ Port')
270 parser.add_argument('--pio_port', '-Z', action='store', default=DEFAULT_PIO_TCP_PORT,
271 type=check_tcp_port, help='PIO Service ZeroMQ Port')
272 parser.add_argument('--multicast_vlan', '-M', action='store',
273 default='{}'.format(DEFAULT_MULTICAST_VLAN),
274 help='Multicast VLAN'),
275 parser.add_argument('--utility_vlan', '-B', action='store',
276 default='{}'.format(DEFAULT_UTILITY_VLAN),
277 type=check_vid, help='VLAN for Controller based upstream flows from ONUs')
278 parser.add_argument('--resource_mgr_key', '-o', action='store',
279 default=_DEFAULT_RESOURCE_MGR_KEY,
280 help='OLT Type to look up associated resource manager configuration')
281 try:
282 args = parser.parse_args(shlex.split(device.extra_args))
283
284 # May have multiple multicast VLANs
285 self.multicast_vlans = [int(vid.strip()) for vid in args.multicast_vlan.split(',')]
286
287 self.netconf_username = args.nc_username
288 self.netconf_password = args.nc_password
289 self.netconf_port = args.nc_port
290
291 self.rest_username = args.rc_username
292 self.rest_password = args.rc_password
293 self.rest_port = args.rc_port
294
295 self.pon_agent_port = args.zmq_port
296 self.pio_port = args.pio_port
297 self.resource_manager_key = args.resource_mgr_key
298
299 if not self.rest_username:
300 self.rest_username = 'NDE0NDRkNDk0ZQ==\n'. \
301 decode('base64').decode('hex')
302 if not self.rest_password:
303 self.rest_password = 'NTA0MTUzNTM1NzRmNTI0NA==\n'. \
304 decode('base64').decode('hex')
305 if not self.netconf_username:
306 self.netconf_username = 'Njg3Mzc2NzI2ZjZmNzQ=\n'. \
307 decode('base64').decode('hex')
308 if not self.netconf_password:
309 self.netconf_password = 'NDI0ZjUzNDM0Zg==\n'. \
310 decode('base64').decode('hex')
311
312 except argparse.ArgumentError as e:
313 self.activate_failed(device,
314 'Invalid arguments: {}'.format(e.message),
315 reachable=False)
316 except Exception as e:
317 self.log.exception('option_parsing_error: {}'.format(e.message))
318
319 @inlineCallbacks
320 def activate(self, done_deferred, reconciling):
321 """
322 Activate the OLT device
323
324 :param done_deferred: (Deferred) Deferred to fire when done
325 :param reconciling: If True, this adapter is taking over for a previous adapter
326 for an existing OLT
327 """
328 self.log.info('AdtranDeviceHandler.activating', reconciling=reconciling)
329
330 if self.logical_device_id is None:
331 device = self.adapter_agent.get_device(self.device_id)
332
333 try:
334 # Parse our command line options for this device
335 self.parse_provisioning_options(device)
336
337 ############################################################################
338 # Currently, only virtual OLT (pizzabox) is supported
339 # self.is_virtual_olt = Add test for MOCK Device if we want to support it
340
341 ############################################################################
342 # Start initial discovery of NETCONF support (if any)
343 try:
344 device.reason = 'establishing NETCONF connection'
345 self.adapter_agent.update_device(device)
346
347 self.startup = self.make_netconf_connection()
348 yield self.startup
349
350 except Exception as e:
351 self.log.exception('netconf-connection', e=e)
352 returnValue(self.restart_activate(done_deferred, reconciling))
353
354 ############################################################################
355 # Update access information on network device for full protocol support
356 try:
357 device.reason = 'device networking validation'
358 self.adapter_agent.update_device(device)
359 self.startup = self.ready_network_access()
360 yield self.startup
361
362 except Exception as e:
363 self.log.exception('network-setup', e=e)
364 returnValue(self.restart_activate(done_deferred, reconciling))
365
366 ############################################################################
367 # Restconf setup
368 try:
369 device.reason = 'establishing RESTConf connections'
370 self.adapter_agent.update_device(device)
371 self.startup = self.make_restconf_connection()
372 yield self.startup
373
374 except Exception as e:
375 self.log.exception('restconf-setup', e=e)
376 returnValue(self.restart_activate(done_deferred, reconciling))
377
378 ############################################################################
379 # Get the device Information
380 if reconciling:
381 device.connect_status = ConnectStatus.REACHABLE
382 self.adapter_agent.update_device(device)
383 else:
384 try:
385 device.reason = 'retrieving device information'
386 self.adapter_agent.update_device(device)
387 self.startup = self.get_device_info(device)
388 results = yield self.startup
389
390 device.model = results.get('model', 'unknown')
391 device.hardware_version = results.get('hardware_version', 'unknown')
392 device.firmware_version = results.get('firmware_version', 'unknown')
393 device.serial_number = results.get('serial_number', 'unknown')
394 device.images.image.extend(results.get('software-images', []))
395
396 device.root = True
397 device.vendor = results.get('vendor', 'Adtran Inc.')
398 device.connect_status = ConnectStatus.REACHABLE
399 self.adapter_agent.update_device(device)
400
401 except Exception as e:
402 self.log.exception('device-info', e=e)
403 returnValue(self.restart_activate(done_deferred, reconciling))
404
405 try:
406 # Enumerate and create Northbound NNI interfaces
407 device.reason = 'enumerating northbound interfaces'
408 self.adapter_agent.update_device(device)
409 self.startup = self.enumerate_northbound_ports(device)
410 results = yield self.startup
411
412 self.startup = self.process_northbound_ports(device, results)
413 yield self.startup
414
415 device.reason = 'adding northbound interfaces to adapter'
416 self.adapter_agent.update_device(device)
417
418 if not reconciling:
419 for port in self.northbound_ports.itervalues():
420 self.adapter_agent.add_port(device.id, port.get_port())
421
422 except Exception as e:
423 self.log.exception('NNI-enumeration', e=e)
424 returnValue(self.restart_activate(done_deferred, reconciling))
425
426 try:
427 # Enumerate and create southbound interfaces
428 device.reason = 'enumerating southbound interfaces'
429 self.adapter_agent.update_device(device)
430 self.startup = self.enumerate_southbound_ports(device)
431 results = yield self.startup
432
433 self.startup = self.process_southbound_ports(device, results)
434 yield self.startup
435
436 device.reason = 'adding southbound interfaces to adapter'
437 self.adapter_agent.update_device(device)
438
439 if not reconciling:
440 for port in self.southbound_ports.itervalues():
441 self.adapter_agent.add_port(device.id, port.get_port())
442
443 except Exception as e:
444 self.log.exception('PON_enumeration', e=e)
445 returnValue(self.restart_activate(done_deferred, reconciling))
446
447 # Initialize resource manager
448 self.initialize_resource_manager()
449
450 if reconciling:
451 if device.admin_state == AdminState.ENABLED:
452 if device.parent_id:
453 self.logical_device_id = device.parent_id
454 self.adapter_agent.reconcile_logical_device(device.parent_id)
455 else:
456 self.log.info('no-logical-device-set')
457
458 # Reconcile child devices
459 self.adapter_agent.reconcile_child_devices(device.id)
460 ld_initialized = self.adapter_agent.get_logical_device()
461 assert device.parent_id == ld_initialized.id, \
462 'parent ID not Logical device ID'
463
464 else:
465 # Complete activation by setting up logical device for this OLT and saving
466 # off the devices parent_id
467 ld_initialized = self.create_logical_device(device)
468
469 ############################################################################
470 # Setup PM configuration for this device
471 if self.pm_metrics is None:
472 try:
473 device.reason = 'setting up Performance Monitoring configuration'
474 self.adapter_agent.update_device(device)
475
476 kwargs = {
477 'nni-ports': self.northbound_ports.values(),
478 'pon-ports': self.southbound_ports.values()
479 }
480 self.pm_metrics = OltPmMetrics(self.adapter_agent, self.device_id,
481 ld_initialized.id, grouped=True,
482 freq_override=False, **kwargs)
483
484 pm_config = self.pm_metrics.make_proto()
485 self.log.debug("initial-pm-config", pm_config=pm_config)
486 self.adapter_agent.update_device_pm_config(pm_config, init=True)
487
488 except Exception as e:
489 self.log.exception('pm-setup', e=e)
490 self.activate_failed(device, e.message, reachable=False)
491
492 ############################################################################
493 # Set the ports in a known good initial state
494 if not reconciling:
495 device.reason = 'setting device to a known initial state'
496 self.adapter_agent.update_device(device)
497 try:
498 for port in self.northbound_ports.itervalues():
499 self.startup = yield port.reset()
500
501 for port in self.southbound_ports.itervalues():
502 self.startup = yield port.reset()
503
504 except Exception as e:
505 self.log.exception('port-reset', e=e)
506 returnValue(self.restart_activate(done_deferred, reconciling))
507
508 ############################################################################
509 # Create logical ports for all southbound and northbound interfaces
510 try:
511 device.reason = 'creating logical ports'
512 self.adapter_agent.update_device(device)
513 self.startup = self.create_logical_ports(device, ld_initialized, reconciling)
514 yield self.startup
515
516 except Exception as e:
517 self.log.exception('logical-port', e=e)
518 returnValue(self.restart_activate(done_deferred, reconciling))
519
520 ############################################################################
521 # Setup Alarm handler
522 device.reason = 'setting up adapter alarms'
523 self.adapter_agent.update_device(device)
524
525 self.alarms = AdapterAlarms(self.adapter_agent, device.id, ld_initialized.id)
526
527 ############################################################################
528 # Register for ONU detection
529 # self.adapter_agent.register_for_onu_detect_state(device.id)
530 # Complete device specific steps
531 try:
532 self.log.debug('device-activation-procedures')
533 device.reason = 'performing model specific activation procedures'
534 self.adapter_agent.update_device(device)
535 self.startup = self.complete_device_specific_activation(device, reconciling)
536 yield self.startup
537
538 except Exception as e:
539 self.log.exception('device-activation-procedures', e=e)
540 returnValue(self.restart_activate(done_deferred, reconciling))
541
542 # Schedule the heartbeat for the device
543 self.log.debug('starting-heartbeat')
544 self.start_heartbeat(delay=10)
545
546 device = self.adapter_agent.get_device(device.id)
547 device.parent_id = ld_initialized.id
548 device.oper_status = OperStatus.ACTIVE
549 device.reason = ''
550 self.adapter_agent.update_device(device)
551 self.logical_device_id = ld_initialized.id
552
553 # Start collecting stats from the device after a brief pause
554 reactor.callLater(10, self.pm_metrics.start_collector)
555
556 # Signal completion
557 self._initial_enable_complete = True
558 self.log.info('activated')
559
560 except Exception as e:
561 self.log.exception('activate', e=e)
562 if done_deferred is not None:
563 done_deferred.errback(e)
564
565 if done_deferred is not None:
566 done_deferred.callback('activated')
567
568 returnValue('activated')
569
570 def restart_activate(self, done_deferred, reconciling):
571 """
572 Startup activation failed, pause a short period of time and retry
573
574 :param done_deferred: (deferred) Deferred to fire upon completion of activation
575 :param reconciling: (bool) If true, we are reconciling after moving to a new vCore
576 """
577 d, self.startup = self.startup, None
578 try:
579 if d is not None and not d.called:
580 d.cancel()
581 except:
582 pass
583 device = self.adapter_agent.get_device(self.device_id)
584 device.reason = 'Failed during {}, retrying'.format(device.reason)
585 self.adapter_agent.update_device(device)
586 self.startup = reactor.callLater(_STARTUP_RETRY_TIMEOUT, self.activate,
587 done_deferred, reconciling)
588 return 'retrying'
589
590 @inlineCallbacks
591 def ready_network_access(self):
592 # Override in device specific class if needed
593 returnValue('nop')
594
595 def activate_failed(self, device, reason, reachable=True):
596 """
597 Activation process (adopt_device) has failed.
598
599 :param device: A voltha.Device object, with possible device-type
600 specific extensions. Such extensions shall be described as part of
601 the device type specification returned by device_types().
602 :param reason: (string) failure reason
603 :param reachable: (boolean) Flag indicating if device may be reachable
604 via RESTConf or NETConf even after this failure.
605 """
606 device.oper_status = OperStatus.FAILED
607 if not reachable:
608 device.connect_status = ConnectStatus.UNREACHABLE
609
610 device.reason = reason
611 self.adapter_agent.update_device(device)
612 raise Exception('Failed to activate OLT: {}'.format(device.reason))
613
614 @inlineCallbacks
615 def make_netconf_connection(self, connect_timeout=None,
616 close_existing_client=False):
617
618 if close_existing_client and self._netconf_client is not None:
619 try:
620 yield self._netconf_client.close()
621 except:
622 pass
623 self._netconf_client = None
624
625 client = self._netconf_client
626
627 if client is None:
628 if not self.is_virtual_olt:
629 client = AdtranNetconfClient(self.ip_address,
630 self.netconf_port,
631 self.netconf_username,
632 self.netconf_password,
633 self.timeout)
634 else:
635 from python.adapters.adtran.adtran_common.net.mock_netconf_client import MockNetconfClient
636 client = MockNetconfClient(self.ip_address,
637 self.netconf_port,
638 self.netconf_username,
639 self.netconf_password,
640 self.timeout)
641 if client.connected:
642 self._netconf_client = client
643 returnValue(True)
644
645 timeout = connect_timeout or self.timeout
646
647 try:
648 request = client.connect(timeout)
649 results = yield request
650 self._netconf_client = client
651 returnValue(results)
652
653 except Exception as e:
654 self.log.exception('Failed to create NETCONF Client', e=e)
655 self._netconf_client = None
656 raise
657
658 @inlineCallbacks
659 def make_restconf_connection(self, get_timeout=None):
660 client = self._rest_client
661
662 if client is None:
663 client = AdtranRestClient(self.ip_address,
664 self.rest_port,
665 self.rest_username,
666 self.rest_password,
667 self.timeout)
668
669 timeout = get_timeout or self.timeout
670
671 try:
672 request = client.request('GET', self.HELLO_URI, name='hello', timeout=timeout)
673 results = yield request
674 if isinstance(results, dict) and 'module-info' in results:
675 self._rest_client = client
676 returnValue(results)
677 else:
678 from twisted.internet.error import ConnectError
679 self._rest_client = None
680 raise ConnectError(string='Results received but unexpected data type or contents')
681 except Exception:
682 self._rest_client = None
683 raise
684
685 def create_logical_device(self, device):
686 version = device.images.image[0].version
687
688 ld = LogicalDevice(
689 # NOTE: not setting id and datapath_id will let the adapter agent pick id
690 desc=ofp_desc(mfr_desc='VOLTHA Project',
691 hw_desc=device.hardware_version,
692 sw_desc=version,
693 serial_num=device.serial_number,
694 dp_desc='n/a'),
695 switch_features=ofp_switch_features(n_buffers=256,
696 n_tables=2,
697 capabilities=(
698 OFPC_FLOW_STATS |
699 OFPC_TABLE_STATS |
700 OFPC_GROUP_STATS |
701 OFPC_PORT_STATS)),
702 root_device_id=device.id)
703
704 ld_initialized = self.adapter_agent.create_logical_device(ld,
705 dpid=self.mac_address)
706 return ld_initialized
707
708 @inlineCallbacks
709 def create_logical_ports(self, device, ld_initialized, reconciling):
710 if not reconciling:
711 # Add the ports to the logical device
712
713 for port in self.northbound_ports.itervalues():
714 lp = port.get_logical_port()
715 if lp is not None:
716 self.adapter_agent.add_logical_port(ld_initialized.id, lp)
717
718 for port in self.southbound_ports.itervalues():
719 lp = port.get_logical_port()
720 if lp is not None:
721 self.adapter_agent.add_logical_port(ld_initialized.id, lp)
722
723 # Clean up all EVCs, EVC maps and ACLs (exceptions are ok)
724 try:
725 from flow.evc import EVC
726 self.startup = yield EVC.remove_all(self.netconf_client)
727 from flow.utility_evc import UtilityEVC
728 self.startup = yield UtilityEVC.remove_all(self.netconf_client)
729
730 except Exception as e:
731 self.log.exception('evc-cleanup', e=e)
732
733 try:
734 from flow.evc_map import EVCMap
735 self.startup = yield EVCMap.remove_all(self.netconf_client)
736
737 except Exception as e:
738 self.log.exception('evc-map-cleanup', e=e)
739
740 from flow.acl import ACL
741 ACL.clear_all(device.id)
742 try:
743 self.startup = yield ACL.remove_all(self.netconf_client)
744
745 except Exception as e:
746 self.log.exception('acl-cleanup', e=e)
747
748 from flow.flow_entry import FlowEntry
749 FlowEntry.clear_all(self)
750
751 from download import Download
752 Download.clear_all(self.netconf_client)
753
754 # Start/stop the interfaces as needed. These are deferred calls
755
756 dl = []
757 for port in self.northbound_ports.itervalues():
758 try:
759 dl.append(port.start())
760 except Exception as e:
761 self.log.exception('northbound-port-startup', e=e)
762
763 for port in self.southbound_ports.itervalues():
764 try:
765 dl.append(port.start() if port.admin_state == AdminState.ENABLED else port.stop())
766
767 except Exception as e:
768 self.log.exception('southbound-port-startup', e=e)
769
770 results = yield defer.gatherResults(dl, consumeErrors=True)
771
772 returnValue(results)
773
774 @inlineCallbacks
775 def device_information(self, device):
776 """
777 Examine the various managment models and extract device information for
778 VOLTHA use
779
780 :param device: A voltha.Device object, with possible device-type
781 specific extensions.
782 :return: (Deferred or None).
783 """
784 yield defer.Deferred(lambda c: c.callback("Not Required"))
785
786 @inlineCallbacks
787 def enumerate_northbound_ports(self, device):
788 """
789 Enumerate all northbound ports of a device. You should override
790 a non-recoverable error, throw an appropriate exception.
791
792 :param device: A voltha.Device object, with possible device-type
793 specific extensions.
794 :return: (Deferred or None).
795 """
796 yield defer.Deferred(lambda c: c.callback("Not Required"))
797
798 @inlineCallbacks
799 def process_northbound_ports(self, device, results):
800 """
801 Process the results from the 'enumerate_northbound_ports' method.
802 You should override this method in your derived class as necessary and
803 create an NNI Port object (of your own choosing) that supports a 'get_port'
804 method. Once created, insert it into this base class's northbound_ports
805 collection.
806
807 Should you encounter a non-recoverable error, throw an appropriate exception.
808
809 :param device: A voltha.Device object, with possible device-type
810 specific extensions.
811 :param results: Results from the 'enumerate_northbound_ports' method that
812 you implemented. The type and contents are up to you to
813 :return:
814 """
815 yield defer.Deferred(lambda c: c.callback("Not Required"))
816
817 @inlineCallbacks
818 def enumerate_southbound_ports(self, device):
819 """
820 Enumerate all southbound ports of a device. You should override
821 this method in your derived class as necessary. Should you encounter
822 a non-recoverable error, throw an appropriate exception.
823
824 :param device: A voltha.Device object, with possible device-type
825 specific extensions.
826 :return: (Deferred or None).
827 """
828 yield defer.Deferred(lambda c: c.callback("Not Required"))
829
830 @inlineCallbacks
831 def process_southbound_ports(self, device, results):
832 """
833 Process the results from the 'enumerate_southbound_ports' method.
834 You should override this method in your derived class as necessary and
835 create an Port object (of your own choosing) that supports a 'get_port'
836 method. Once created, insert it into this base class's southbound_ports
837 collection.
838
839 Should you encounter a non-recoverable error, throw an appropriate exception.
840
841 :param device: A voltha.Device object, with possible device-type
842 specific extensions.
843 :param results: Results from the 'enumerate_southbound_ports' method that
844 you implemented. The type and contents are up to you to
845 :return:
846 """
847 yield defer.Deferred(lambda c: c.callback("Not Required"))
848
849 # TODO: Move some of the items below from here and the EVC to a utility class
850
851 def is_nni_port(self, port):
852 return port in self.northbound_ports
853
854 def is_uni_port(self, port):
855 raise NotImplementedError('implement in derived class')
856
857 def is_pon_port(self, port):
858 raise NotImplementedError('implement in derived class')
859
860 def is_logical_port(self, port):
861 return not self.is_nni_port(port) and not self.is_uni_port(port) and not self.is_pon_port(port)
862
863 def get_port_name(self, port):
864 raise NotImplementedError('implement in derived class')
865
866 def initialize_resource_manager(self):
867 raise NotImplementedError('implement in derived class')
868
869 @inlineCallbacks
870 def complete_device_specific_activation(self, _device, _reconciling):
871 # NOTE: Override this in your derived class for any device startup completion
872 return defer.succeed('NOP')
873
874 @inlineCallbacks
875 def disable(self):
876 """
877 This is called when a previously enabled device needs to be disabled based on a NBI call.
878 """
879 self.log.info('disabling', device_id=self.device_id)
880
881 # Cancel any running enable/disable/... in progress
882 d, self.startup = self.startup, None
883 try:
884 if d is not None and not d.called:
885 d.cancel()
886 except:
887 pass
888
889 # Get the latest device reference
890 device = self.adapter_agent.get_device(self.device_id)
891 device.reason = 'Disabling'
892 self.adapter_agent.update_device(device)
893
894 # Drop registration for ONU detection
895 # self.adapter_agent.unregister_for_onu_detect_state(self.device.id)
896 # Suspend any active healthchecks / pings
897
898 h, self.heartbeat = self.heartbeat, None
899 try:
900 if h is not None and not h.called:
901 h.cancel()
902 except:
903 pass
904 # Update the operational status to UNKNOWN
905
906 device.oper_status = OperStatus.UNKNOWN
907 device.connect_status = ConnectStatus.UNREACHABLE
908 self.adapter_agent.update_device(device)
909
910 # Disable all child devices first
911 self.adapter_agent.update_child_devices_state(self.device_id,
912 admin_state=AdminState.DISABLED)
913
914 # Remove the peer references from this device
915 self.adapter_agent.delete_all_peer_references(self.device_id)
916
917 # Remove the logical device to clear out logical device ports for any
918 # previously activated ONUs
919 self._delete_logical_device()
920
921 # Set all ports to disabled
922 self.adapter_agent.disable_all_ports(self.device_id)
923
924 dl = []
925 for port in self.northbound_ports.itervalues():
926 dl.append(port.stop())
927
928 for port in self.southbound_ports.itervalues():
929 dl.append(port.stop())
930
931 # NOTE: Flows removed before this method is called
932 # Wait for completion
933
934 self.startup = defer.gatherResults(dl, consumeErrors=True)
935 yield self.startup
936
937 if self.netconf_client:
938 self.netconf_client.close()
939
940 self._netconf_client = None
941 self._rest_client = None
942
943 device.reason = ''
944 self.adapter_agent.update_device(device)
945 self.log.info('disabled', device_id=device.id)
946 returnValue(None)
947
948 @inlineCallbacks
949 def reenable(self, done_deferred=None):
950 """
951 This is called when a previously disabled device needs to be enabled based on a NBI call.
952 :param done_deferred: (Deferred) Deferred to fire when done
953 """
954 self.log.info('re-enabling', device_id=self.device_id)
955
956 # Cancel any running enable/disable/... in progress
957 d, self.startup = self.startup, None
958 try:
959 if d is not None and not d.called:
960 d.cancel()
961 except:
962 pass
963
964 if not self._initial_enable_complete:
965 # Never contacted the device on the initial startup, do 'activate' steps instead
966 self.startup = reactor.callLater(0, self.activate, done_deferred, False)
967 returnValue('activating')
968
969 # Get the latest device reference
970 device = self.adapter_agent.get_device(self.device_id)
971
972 # Update the connect status to REACHABLE
973 device.connect_status = ConnectStatus.REACHABLE
974 device.oper_status = OperStatus.ACTIVATING
975 self.adapter_agent.update_device(device)
976
977 # Reenable any previously configured southbound ports
978 for port in self.southbound_ports.itervalues():
979 self.log.debug('reenable-pon-port', pon_id=port.pon_id)
980 port.enabled = True
981
982 # Flows should not exist on re-enable. They are re-pushed
983 if len(self._evcs):
984 self.log.warn('evcs-found', evcs=self._evcs)
985 self._evcs.clear()
986
987 try:
988 yield self.make_restconf_connection()
989
990 except Exception as e:
991 self.log.exception('adtran-hello-reconnect', e=e)
992
993 try:
994 yield self.make_netconf_connection()
995
996 except Exception as e:
997 self.log.exception('NETCONF-re-connection', e=e)
998
999 # Recreate the logical device
1000 # NOTE: This causes a flow update event
1001 ld_initialized = self.create_logical_device(device)
1002
1003 # Create logical ports for all southbound and northbound interfaces
1004 try:
1005 self.startup = self.create_logical_ports(device, ld_initialized, False)
1006 yield self.startup
1007
1008 except Exception as e:
1009 self.log.exception('logical-port-creation', e=e)
1010
1011 device = self.adapter_agent.get_device(device.id)
1012 device.parent_id = ld_initialized.id
1013 device.oper_status = OperStatus.ACTIVE
1014 device.reason = ''
1015 self.logical_device_id = ld_initialized.id
1016
1017 # update device active status now
1018 self.adapter_agent.update_device(device)
1019
1020 # Reenable all child devices
1021 self.adapter_agent.update_child_devices_state(device.id,
1022 admin_state=AdminState.ENABLED)
1023 # Schedule the heartbeat for the device
1024 self.log.debug('starting-heartbeat')
1025 self.start_heartbeat(delay=5)
1026
1027 self.log.info('re-enabled', device_id=device.id)
1028
1029 if done_deferred is not None:
1030 done_deferred.callback('Done')
1031
1032 returnValue('reenabled')
1033
1034 @inlineCallbacks
1035 def reboot(self):
1036 """
1037 This is called to reboot a device based on a NBI call. The admin state of the device
1038 will not change after the reboot.
1039 """
1040 self.log.debug('reboot')
1041
1042 if not self._initial_enable_complete:
1043 # Never contacted the device on the initial startup, do 'activate' steps instead
1044 returnValue('failed')
1045
1046 # Cancel any running enable/disable/... in progress
1047 d, self.startup = self.startup, None
1048 try:
1049 if d is not None and not d.called:
1050 d.cancel()
1051 except:
1052 pass
1053 # Issue reboot command
1054
1055 if not self.is_virtual_olt:
1056 try:
1057 yield self.netconf_client.rpc(AdtranDeviceHandler.RESTART_RPC)
1058
1059 except Exception as e:
1060 self.log.exception('NETCONF-shutdown', e=e)
1061 returnValue(defer.fail(Failure()))
1062
1063 # self.adapter_agent.unregister_for_onu_detect_state(self.device.id)
1064
1065 # Update the operational status to ACTIVATING and connect status to
1066 # UNREACHABLE
1067
1068 device = self.adapter_agent.get_device(self.device_id)
1069 previous_oper_status = device.oper_status
1070 previous_conn_status = device.connect_status
1071 device.oper_status = OperStatus.ACTIVATING
1072 device.connect_status = ConnectStatus.UNREACHABLE
1073 self.adapter_agent.update_device(device)
1074
1075 # Update the child devices connect state to UNREACHABLE
1076 self.adapter_agent.update_child_devices_state(self.device_id,
1077 connect_status=ConnectStatus.UNREACHABLE)
1078
1079 # Shutdown communications with OLT. Typically it takes about 2 seconds
1080 # or so after the reply before the restart actually occurs
1081
1082 try:
1083 response = yield self.netconf_client.close()
1084 self.log.debug('Restart response XML was: {}'.format('ok' if response.ok else 'bad'))
1085
1086 except Exception as e:
1087 self.log.exception('NETCONF-client-shutdown', e=e)
1088
1089 # Clear off clients
1090
1091 self._netconf_client = None
1092 self._rest_client = None
1093
1094 # Run remainder of reboot process as a new task. The OLT then may be up in a
1095 # few moments or may take 3 minutes or more depending on any self tests enabled
1096
1097 current_time = time.time()
1098 timeout = current_time + self.restart_failure_timeout
1099
1100 self.startup = reactor.callLater(10, self._finish_reboot, timeout,
1101 previous_oper_status,
1102 previous_conn_status)
1103 returnValue(self.startup)
1104
1105 @inlineCallbacks
1106 def _finish_reboot(self, timeout, previous_oper_status, previous_conn_status):
1107 # Now wait until REST & NETCONF are re-established or we timeout
1108
1109 self.log.info('Resuming-activity',
1110 remaining=timeout - time.time(), timeout=timeout, current=time.time())
1111
1112 if self.rest_client is None:
1113 try:
1114 yield self.make_restconf_connection(get_timeout=10)
1115
1116 except Exception:
1117 self.log.debug('No RESTCONF connection yet')
1118 self._rest_client = None
1119
1120 if self.netconf_client is None:
1121 try:
1122 yield self.make_netconf_connection(connect_timeout=10)
1123
1124 except Exception as e:
1125 try:
1126 if self.netconf_client is not None:
1127 yield self.netconf_client.close()
1128 except Exception as e:
1129 self.log.exception(e.message)
1130 finally:
1131 self._netconf_client = None
1132
1133 if (self.netconf_client is None and not self.is_virtual_olt) or self.rest_client is None:
1134 current_time = time.time()
1135 if current_time < timeout:
1136 self.startup = reactor.callLater(5, self._finish_reboot, timeout,
1137 previous_oper_status,
1138 previous_conn_status)
1139 returnValue(self.startup)
1140
1141 if self.netconf_client is None and not self.is_virtual_olt:
1142 self.log.error('NETCONF-restore-failure')
1143 pass # TODO: What is best course of action if cannot get clients back?
1144
1145 if self.rest_client is None:
1146 self.log.error('RESTCONF-restore-failure')
1147 pass # TODO: What is best course of action if cannot get clients back?
1148
1149 # Pause additional 5 seconds to let allow OLT microservices to complete some more initialization
1150 yield asleep(5)
1151 # TODO: Update device info. The software images may have changed...
1152 # Get the latest device reference
1153
1154 device = self.adapter_agent.get_device(self.device_id)
1155 device.oper_status = previous_oper_status
1156 device.connect_status = previous_conn_status
1157 self.adapter_agent.update_device(device)
1158
1159 # Update the child devices connect state to REACHABLE
1160 self.adapter_agent.update_child_devices_state(self.device_id,
1161 connect_status=ConnectStatus.REACHABLE)
1162 # Restart ports to previous state
1163 dl = []
1164
1165 for port in self.northbound_ports.itervalues():
1166 dl.append(port.restart())
1167
1168 for port in self.southbound_ports.itervalues():
1169 dl.append(port.restart())
1170
1171 try:
1172 yield defer.gatherResults(dl, consumeErrors=True)
1173
1174 except Exception as e:
1175 self.log.exception('port-restart', e=e)
1176
1177 # Re-subscribe for ONU detection
1178 # self.adapter_agent.register_for_onu_detect_state(self.device.id)
1179 # Request reflow of any EVC/EVC-MAPs
1180 if len(self._evcs) > 0:
1181 dl = []
1182 for evc in self.evcs:
1183 dl.append(evc.reflow())
1184
1185 try:
1186 yield defer.gatherResults(dl)
1187 except Exception as e:
1188 self.log.exception('flow-restart', e=e)
1189
1190 self.log.info('rebooted', device_id=self.device_id)
1191 returnValue('Rebooted')
1192
1193 @inlineCallbacks
1194 def delete(self):
1195 """
1196 This is called to delete a device from the PON based on a NBI call.
1197 If the device is an OLT then the whole PON will be deleted.
1198 """
1199 self.log.info('deleting', device_id=self.device_id)
1200
1201 # Cancel any outstanding tasks
1202
1203 d, self.startup = self.startup, None
1204 try:
1205 if d is not None and not d.called:
1206 d.cancel()
1207 except:
1208 pass
1209 h, self.heartbeat = self.heartbeat, None
1210 try:
1211 if h is not None and not h.called:
1212 h.cancel()
1213 except:
1214 pass
1215
1216 # Get the latest device reference
1217 device = self.adapter_agent.get_device(self.device_id)
1218 device.reason = 'Deleting'
1219 self.adapter_agent.update_device(device)
1220
1221 # self.adapter_agent.unregister_for_onu_detect_state(self.device.id)
1222
1223 # Remove all flows from the device
1224 # TODO: Create a bulk remove-all by device-id
1225
1226 evcs = self._evcs
1227 self._evcs.clear()
1228
1229 for evc in evcs:
1230 evc.delete() # TODO: implement bulk-flow procedures
1231
1232 # Remove all child devices
1233 self.adapter_agent.delete_all_child_devices(self.device_id)
1234
1235 # Remove the logical device (should already be gone if disable came first)
1236 self._delete_logical_device()
1237
1238 # Remove the peer references from this device
1239 self.adapter_agent.delete_all_peer_references(self.device_id)
1240
1241 # Tell all ports to stop any background processing
1242
1243 for port in self.northbound_ports.itervalues():
1244 port.delete()
1245
1246 for port in self.southbound_ports.itervalues():
1247 port.delete()
1248
1249 self.northbound_ports.clear()
1250 self.southbound_ports.clear()
1251
1252 # Shutdown communications with OLT
1253
1254 if self.netconf_client is not None:
1255 try:
1256 yield self.netconf_client.close()
1257 except Exception as e:
1258 self.log.exception('NETCONF-shutdown', e=e)
1259
1260 self._netconf_client = None
1261
1262 self._rest_client = None
1263 mgr, self.resource_mgr = self.resource_mgr, None
1264 if mgr is not None:
1265 del mgr
1266
1267 self.log.info('deleted', device_id=self.device_id)
1268
1269 def delete_child_device(self, proxy_address):
1270 self.log.debug('sending-deactivate-onu',
1271 olt_device_id=self.device_id,
1272 proxy_address=proxy_address)
1273 try:
1274 children = self.adapter_agent.get_child_devices(self.device_id)
1275 for child in children:
1276 if child.proxy_address.onu_id == proxy_address.onu_id and \
1277 child.proxy_address.channel_id == proxy_address.channel_id:
1278 self.adapter_agent.delete_child_device(self.device_id,
1279 child.id,
1280 onu_device=child)
1281 break
1282
1283 except Exception as e:
1284 self.log.error('adapter_agent error', error=e)
1285
1286 def packet_out(self, egress_port, msg):
1287 raise NotImplementedError('Overload in a derived class')
1288
1289 def update_pm_config(self, device, pm_config):
1290 # TODO: This has not been tested
1291 self.log.info('update_pm_config', pm_config=pm_config)
1292 self.pm_metrics.update(pm_config)
1293
1294 @inlineCallbacks
1295 def get_device_info(self, device):
1296 """
1297 Perform an initial network operation to discover the device hardware
1298 and software version. Serial Number would be helpful as well.
1299
1300 Upon successfully retrieving the information, remember to call the
1301 'start_heartbeat' method to keep in contact with the device being managed
1302
1303 :param device: A voltha.Device object, with possible device-type
1304 specific extensions. Such extensions shall be described as part of
1305 the device type specification returned by device_types().
1306 """
1307 device = {}
1308 returnValue(device)
1309
1310 def start_heartbeat(self, delay=10):
1311 assert delay > 1, 'Minimum heartbeat is 1 second'
1312 self.log.info('Starting-Device-Heartbeat ***')
1313 self.heartbeat = reactor.callLater(delay, self.check_pulse)
1314 return self.heartbeat
1315
1316 def check_pulse(self):
1317 if self.logical_device_id is not None:
1318 try:
1319 self.heartbeat = self.rest_client.request('GET', self.HELLO_URI,
1320 name='hello', timeout=5)
1321 self.heartbeat.addCallbacks(self._heartbeat_success, self._heartbeat_fail)
1322
1323 except Exception as e:
1324 self.heartbeat = reactor.callLater(5, self._heartbeat_fail, e)
1325
1326 def on_heatbeat_alarm(self, active):
1327 if active and self.netconf_client is None or not self.netconf_client.connected:
1328 self.make_netconf_connection(close_existing_client=True)
1329
1330 def heartbeat_check_status(self, _):
1331 """
1332 Check the number of heartbeat failures against the limit and emit an alarm if needed
1333 """
1334 device = self.adapter_agent.get_device(self.device_id)
1335
1336 try:
1337 from pyvoltha.adapters.extensions.alarms.heartbeat_alarm import HeartbeatAlarm
1338
1339 if self.heartbeat_miss >= self.heartbeat_failed_limit:
1340 if device.connect_status == ConnectStatus.REACHABLE:
1341 self.log.warning('heartbeat-failed', count=self.heartbeat_miss)
1342 device.connect_status = ConnectStatus.UNREACHABLE
1343 device.oper_status = OperStatus.FAILED
1344 device.reason = self.heartbeat_last_reason
1345 self.adapter_agent.update_device(device)
1346 HeartbeatAlarm(self.alarms, 'olt', self.heartbeat_miss).raise_alarm()
1347 self.on_heatbeat_alarm(True)
1348 else:
1349 # Update device states
1350 if device.connect_status != ConnectStatus.REACHABLE:
1351 device.connect_status = ConnectStatus.REACHABLE
1352 device.oper_status = OperStatus.ACTIVE
1353 device.reason = ''
1354 self.adapter_agent.update_device(device)
1355 HeartbeatAlarm(self.alarms, 'olt').clear_alarm()
1356 self.on_heatbeat_alarm(False)
1357
1358 if self.netconf_client is None or not self.netconf_client.connected:
1359 self.make_netconf_connection(close_existing_client=True)
1360
1361 except Exception as e:
1362 self.log.exception('heartbeat-check', e=e)
1363
1364 # Reschedule next heartbeat
1365 if self.logical_device_id is not None:
1366 self.heartbeat_count += 1
1367 self.heartbeat = reactor.callLater(self.heartbeat_interval, self.check_pulse)
1368
1369 def _heartbeat_success(self, results):
1370 self.log.debug('heartbeat-success')
1371 self.heartbeat_miss = 0
1372 self.heartbeat_last_reason = ''
1373 self.heartbeat_check_status(results)
1374
1375 def _heartbeat_fail(self, failure):
1376 self.heartbeat_miss += 1
1377 self.log.info('heartbeat-miss', failure=failure,
1378 count=self.heartbeat_count,
1379 miss=self.heartbeat_miss)
1380 self.heartbeat_last_reason = 'RESTCONF connectivity error'
1381 self.heartbeat_check_status(None)
1382
1383 @staticmethod
1384 def parse_module_revision(revision):
1385 try:
1386 return datetime.datetime.strptime(revision, '%Y-%m-%d')
1387 except Exception:
1388 return None
1389
1390 def remove_from_flow_table(self, _flows):
1391 """
1392 Remove flows from the device
1393 :param _flows: (list) Flows
1394 """
1395 raise NotImplementedError()
1396
1397 def add_to_flow_table(self, _flows):
1398 """
1399 Remove flows from the device
1400 :param _flows: (list) Flows
1401 """
1402 raise NotImplementedError()
1403
1404 def process_inter_adapter_message(self, msg):
1405 """
1406 Called when the adapter receives a message that was sent to it directly
1407 from another adapter. An adapter is automatically registered for these
1408 messages when creating the inter-container kafka proxy. Note that it is
1409 the responsibility of the sending and receiving adapters to properly encode
1410 and decode the message.
1411 :param msg: Proto Message (any)
1412 :return: Proto Message Response
1413 """
1414 raise NotImplementedError()
1415
1416 def get_ofp_device_info(self, device):
1417 """
1418 Retrieve the OLT device info. This includes the ofp_desc and
1419 ofp_switch_features. The existing ofp structures can be used,
1420 or all the attributes get added to the Device definition or a new proto
1421 definition gets created. This API will allow the Core to create a
1422 LogicalDevice associated with this device (OLT only).
1423 :param device: device
1424 :return: Proto Message (TBD)
1425 """
1426 raise NotImplementedError()
1427
1428 def get_ofp_port_info(self, device, port_no):
1429 """
1430 Retrieve the port info. This includes the ofp_port. The existing ofp
1431 structure can be used, or all the attributes get added to the Port
1432 definitions or a new proto definition gets created. This API will allow
1433 the Core to create a LogicalPort associated with this device.
1434 :param device: device
1435 :param port_no: port number
1436 :return: Proto Message (TBD)
1437 """
1438 raise NotImplementedError()