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