blob: 37ee40396fb23d2c302ddf699ef2823d5eab4205 [file] [log] [blame]
Chip Boling3e3b1a92017-05-16 11:51:18 -05001#
2# Copyright 2017-present Adtran, Inc.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16"""
17Adtran generic VOLTHA device handler
18"""
Chip Boling7294b252017-06-15 16:16:55 -050019import argparse
Chip Boling3e3b1a92017-05-16 11:51:18 -050020import datetime
21import pprint
Chip Boling7294b252017-06-15 16:16:55 -050022import shlex
23import time
Chip Boling3e3b1a92017-05-16 11:51:18 -050024
25import arrow
Chip Boling3e3b1a92017-05-16 11:51:18 -050026import structlog
Chip Boling5561d552017-07-07 15:11:26 -050027import json
Chip Boling3e3b1a92017-05-16 11:51:18 -050028from twisted.internet import reactor, defer
Chip Boling7294b252017-06-15 16:16:55 -050029from twisted.internet.defer import inlineCallbacks, returnValue
Chip Boling3e3b1a92017-05-16 11:51:18 -050030
Chip Boling7294b252017-06-15 16:16:55 -050031from voltha.adapters.adtran_olt.net.adtran_netconf import AdtranNetconfClient
Chip Boling3e3b1a92017-05-16 11:51:18 -050032from voltha.adapters.adtran_olt.net.adtran_rest import AdtranRestClient
33from voltha.protos import third_party
34from voltha.protos.common_pb2 import OperStatus, AdminState, ConnectStatus
35from voltha.protos.events_pb2 import AlarmEventType, \
36 AlarmEventSeverity, AlarmEventState, AlarmEventCategory
ggowdru236bd952017-06-20 20:32:55 -070037from voltha.protos.device_pb2 import Image
Chip Boling3e3b1a92017-05-16 11:51:18 -050038from voltha.protos.logical_device_pb2 import LogicalDevice
39from voltha.protos.openflow_13_pb2 import ofp_desc, ofp_switch_features, OFPC_PORT_STATS, \
40 OFPC_GROUP_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS
41from voltha.registry import registry
Chip Boling5561d552017-07-07 15:11:26 -050042from adapter_alarms import AdapterAlarms
43from common.frameio.frameio import BpfProgramFilter, hexify
44from adapter_pm_metrics import AdapterPmMetrics
Chip Boling7294b252017-06-15 16:16:55 -050045from common.utils.asleep import asleep
Chip Boling5561d552017-07-07 15:11:26 -050046from scapy.layers.l2 import Ether, Dot1Q
47from scapy.layers.inet import Raw
Chip Boling7294b252017-06-15 16:16:55 -050048
Chip Boling3e3b1a92017-05-16 11:51:18 -050049_ = third_party
50
Chip Boling5561d552017-07-07 15:11:26 -050051_PACKET_IN_VLAN = 4000
52_is_inband_frame = BpfProgramFilter('(ether[14:2] & 0xfff) = 0x{:03x}'.format(_PACKET_IN_VLAN))
53
Chip Boling3e3b1a92017-05-16 11:51:18 -050054
55class AdtranDeviceHandler(object):
56 """
57 A device that supports the ADTRAN RESTCONF protocol for communications
58 with a VOLTHA/VANILLA managed device.
59
60 Port numbering guidelines for Adtran OLT devices. Derived classes may augment
61 the numbering scheme below as needed.
62
63 - Reserve port 0 for the CPU capture port. All ports to/from this port should
64 be related to messages destined to/from the OpenFlow controller.
65
66 - Begin numbering northbound ports (network facing) at port 1 contiguously.
67 Consider the northbound ports to typically be the highest speed uplinks.
68 If these ports are removable or provided by one or more slots in a chassis
69 subsystem, still reserve the appropriate amount of port numbers whether they
70 are populated or not.
71
72 - Number southbound ports (customer facing) ports next starting at the next
73 available port number. If chassis based, follow the same rules as northbound
74 ports and reserve enough port numbers.
75
76 - Number any out-of-band management ports (if any) last. It will be up to the
77 Device Adapter developer whether to expose these to openflow or not. If you do
78 not expose them, but do have the ports, still reserve the appropriate number of
79 port numbers just in case.
80 """
81 # HTTP shortcuts
82 HELLO_URI = '/restconf/adtran-hello:hello'
83
Chip Boling7294b252017-06-15 16:16:55 -050084 # RPC XML shortcuts
85 RESTART_RPC = '<system-restart xmlns="urn:ietf:params:xml:ns:yang:ietf-system"/>'
86
Chip Boling3e3b1a92017-05-16 11:51:18 -050087 def __init__(self, adapter, device_id, username='', password='', timeout=20):
88 self.adapter = adapter
89 self.adapter_agent = adapter.adapter_agent
90 self.device_id = device_id
91 self.log = structlog.get_logger(device_id=device_id)
92 self.startup = None
93 self.channel = None # Proxy messaging channel with 'send' method
94 self.io_port = None
95 self.logical_device_id = None
96 self.interface = registry('main').get_args().interface
Chip Boling5561d552017-07-07 15:11:26 -050097 self.pm_metrics = None
98 self.alarms = None
Chip Boling3e3b1a92017-05-16 11:51:18 -050099
100 # Northbound and Southbound ports
101 self.northbound_ports = {} # port number -> Port
102 self.southbound_ports = {} # port number -> Port (For PON, use pon-id as key)
Chip Boling7294b252017-06-15 16:16:55 -0500103 # self.management_ports = {} # port number -> Port TODO: Not currently supported
Chip Boling3e3b1a92017-05-16 11:51:18 -0500104
105 self.num_northbound_ports = None
106 self.num_southbound_ports = None
Chip Boling7294b252017-06-15 16:16:55 -0500107 # self.num_management_ports = None
108
109 self.ip_address = None
110 self.timeout = timeout
111 self.restart_failure_timeout = 5 * 60 # 5 Minute timeout
Chip Boling3e3b1a92017-05-16 11:51:18 -0500112
113 # REST Client
Chip Boling3e3b1a92017-05-16 11:51:18 -0500114 self.rest_port = None
Chip Boling3e3b1a92017-05-16 11:51:18 -0500115 self.rest_username = username
116 self.rest_password = password
Chip Boling5561d552017-07-07 15:11:26 -0500117 self._rest_client = None
Chip Boling3e3b1a92017-05-16 11:51:18 -0500118
Chip Boling7294b252017-06-15 16:16:55 -0500119 # NETCONF Client
120 self.netconf_port = None
121 self.netconf_username = username
122 self.netconf_password = password
Chip Boling5561d552017-07-07 15:11:26 -0500123 self._netconf_client = None
Chip Boling7294b252017-06-15 16:16:55 -0500124
Chip Boling3e3b1a92017-05-16 11:51:18 -0500125 # Heartbeat support
126 self.heartbeat_count = 0
127 self.heartbeat_miss = 0
Chip Boling7294b252017-06-15 16:16:55 -0500128 self.heartbeat_interval = 10 # TODO: Decrease before release or any scale testing
Chip Boling3e3b1a92017-05-16 11:51:18 -0500129 self.heartbeat_failed_limit = 3
130 self.heartbeat_timeout = 5
131 self.heartbeat = None
132 self.heartbeat_last_reason = ''
133
Chip Boling7294b252017-06-15 16:16:55 -0500134 # Virtualized OLT Support
135 self.is_virtual_olt = False
136
137 # Installed flows
Chip Boling5561d552017-07-07 15:11:26 -0500138 self.evcs = {} # Flow ID/name -> FlowEntry
Chip Boling7294b252017-06-15 16:16:55 -0500139
140 # TODO Remove items below after one PON fully supported and working as expected
Chip Boling5561d552017-07-07 15:11:26 -0500141 self.max_nni_ports = 1
142 self.max_pon_ports = 1
Chip Boling3e3b1a92017-05-16 11:51:18 -0500143
144 def __del__(self):
145 # Kill any startup or heartbeat defers
146
147 d, self.startup = self.startup, None
Chip Boling5561d552017-07-07 15:11:26 -0500148 h, self.heartbeat = self.heartbeat, None
149 ldi, self.logical_device_id = self.logical_device_id, None
150
Chip Boling3e3b1a92017-05-16 11:51:18 -0500151 if d is not None:
152 d.cancel()
153
Chip Boling3e3b1a92017-05-16 11:51:18 -0500154 if h is not None:
155 h.cancel()
156
Chip Boling5561d552017-07-07 15:11:26 -0500157 self._deactivate_io_port()
158
Chip Boling7294b252017-06-15 16:16:55 -0500159 # Remove the logical device
160
161 if ldi is not None:
162 logical_device = self.adapter_agent.get_logical_device(ldi)
163 self.adapter_agent.delete_logical_device(logical_device)
164
Chip Boling3e3b1a92017-05-16 11:51:18 -0500165 self.northbound_ports.clear()
166 self.southbound_ports.clear()
167
168 def __str__(self):
Chip Boling7294b252017-06-15 16:16:55 -0500169 return "AdtranDeviceHandler: {}".format(self.ip_address)
170
Chip Boling5561d552017-07-07 15:11:26 -0500171 @property
172 def netconf_client(self):
173 return self._netconf_client
174
175 @property
176 def rest_client(self):
177 return self._rest_client
178
Chip Boling7294b252017-06-15 16:16:55 -0500179 def parse_provisioning_options(self, device):
180 if not device.ipv4_address:
181 self.activate_failed(device, 'No ip_address field provided')
182
183 self.ip_address = device.ipv4_address
184
185 #############################################################
186 # Now optional parameters
187
188 def check_tcp_port(value):
189 ivalue = int(value)
190 if ivalue <= 0 or ivalue > 65535:
191 raise argparse.ArgumentTypeError("%s is a not a valid port number" % value)
192 return ivalue
193
194 parser = argparse.ArgumentParser(description='Adtran Device Adapter')
195 parser.add_argument('--nc_username', '-u', action='store', default='hsvroot', help='NETCONF username')
196 parser.add_argument('--nc_password', '-p', action='store', default='BOSCO', help='NETCONF Password')
197 parser.add_argument('--nc_port', '-t', action='store', default=830, type=check_tcp_port,
198 help='NETCONF TCP Port')
199 parser.add_argument('--rc_username', '-U', action='store', default='ADMIN', help='REST username')
200 parser.add_argument('--rc_password', '-P', action='store', default='PASSWORD', help='REST Password')
201 parser.add_argument('--rc_port', '-T', action='store', default=8081, type=check_tcp_port,
202 help='REST TCP Port')
203
204 try:
205 args = parser.parse_args(shlex.split(device.extra_args))
206
207 self.netconf_username = args.nc_username
208 self.netconf_password = args.nc_password
209 self.netconf_port = args.nc_port
210
211 self.rest_username = args.rc_username
212 self.rest_password = args.rc_password
213 self.rest_port = args.rc_port
214
215 except argparse.ArgumentError as e:
216 self.activate_failed(device,
217 'Invalid arguments: {}'.format(e.message),
218 reachable=False)
219 except Exception as e:
220 self.log.exception('parsing error: {}'.format(e.message))
Chip Boling3e3b1a92017-05-16 11:51:18 -0500221
222 @inlineCallbacks
Chip Boling7294b252017-06-15 16:16:55 -0500223 def activate(self, device, reconciling=False):
Chip Boling3e3b1a92017-05-16 11:51:18 -0500224 """
225 Activate the OLT device
226
227 :param device: A voltha.Device object, with possible device-type
Chip Boling7294b252017-06-15 16:16:55 -0500228 specific extensions.
229 :param reconciling: If True, this adapter is taking over for a previous adapter
230 for an existing OLT
Chip Boling3e3b1a92017-05-16 11:51:18 -0500231 """
Chip Boling5561d552017-07-07 15:11:26 -0500232 self.log.info('AdtranDeviceHandler.activating', reconciling=reconciling)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500233
234 if self.logical_device_id is None:
Chip Boling7294b252017-06-15 16:16:55 -0500235 # Parse our command line options for this device
236 self.parse_provisioning_options(device)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500237
238 ############################################################################
239 # Start initial discovery of RESTCONF support (if any)
Chip Boling7294b252017-06-15 16:16:55 -0500240
Chip Boling3e3b1a92017-05-16 11:51:18 -0500241 try:
Chip Boling7294b252017-06-15 16:16:55 -0500242 self.startup = self.make_restconf_connection()
Chip Boling3e3b1a92017-05-16 11:51:18 -0500243 results = yield self.startup
244 self.log.debug('HELLO Contents: {}'.format(pprint.PrettyPrinter().pformat(results)))
245
Chip Boling7294b252017-06-15 16:16:55 -0500246 # See if this is a virtualized OLT. If so, no NETCONF support available
247
248 self.is_virtual_olt = 'module-info' in results and\
249 any(mod.get('module-name', None) == 'adtran-ont-mock'
250 for mod in results['module-info'])
251 if self.is_virtual_olt:
252 self.log.info('*** VIRTUAL OLT detected ***')
253
Chip Boling3e3b1a92017-05-16 11:51:18 -0500254 except Exception as e:
Chip Boling3e3b1a92017-05-16 11:51:18 -0500255 self.log.exception('Initial RESTCONF adtran-hello failed', e=e)
256 self.activate_failed(device, e.message, reachable=False)
257
258 ############################################################################
Chip Boling7294b252017-06-15 16:16:55 -0500259 # Start initial discovery of NETCONF support (if any)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500260
Chip Boling5561d552017-07-07 15:11:26 -0500261 try:
262 self.startup = self.make_netconf_connection()
263 yield self.startup
Chip Boling3e3b1a92017-05-16 11:51:18 -0500264
Chip Boling5561d552017-07-07 15:11:26 -0500265 except Exception as e:
266 self.log.exception('Initial NETCONF connection failed', e=e)
267 self.activate_failed(device, e.message, reachable=False)
Chip Boling7294b252017-06-15 16:16:55 -0500268
269 ############################################################################
270 # Get the device Information
271
272 if reconciling:
273 device.connect_status = ConnectStatus.REACHABLE
274 self.adapter_agent.update_device(device)
275 else:
276 try:
277 self.startup = self.get_device_info(device)
278 results = yield self.startup
279
280 device.model = results.get('model', 'unknown')
281 device.hardware_version = results.get('hardware_version', 'unknown')
282 device.firmware_version = results.get('firmware_version', 'unknown')
Chip Boling7294b252017-06-15 16:16:55 -0500283 device.serial_number = results.get('serial_number', 'unknown')
284
Chip Boling5561d552017-07-07 15:11:26 -0500285 def get_software_images():
286 leafs = ['running-revision', 'candidate-revision', 'startup-revision']
287 image_names = list(set([results.get(img, 'unknown') for img in leafs]))
288
289 images = []
290 for name in image_names:
291 # TODO: Look into how to find out hash, is_valid, and install date/time
292 image = Image(name=name, version=name,
293 is_active=(name == results.get('running-revision', 'xxx')),
294 is_committed=(name == results.get('startup-revision', 'xxx')))
295 images.append(image)
296 return images
297
298 device.images.image.extend(get_software_images())
Chip Boling7294b252017-06-15 16:16:55 -0500299 device.root = True
300 device.vendor = results.get('vendor', 'Adtran, Inc.')
301 device.connect_status = ConnectStatus.REACHABLE
302 self.adapter_agent.update_device(device)
303
304 except Exception as e:
305 self.log.exception('Device Information request(s) failed', e=e)
306 self.activate_failed(device, e.message, reachable=False)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500307
308 try:
309 # Enumerate and create Northbound NNI interfaces
310
311 self.startup = self.enumerate_northbound_ports(device)
312 results = yield self.startup
313
314 self.startup = self.process_northbound_ports(device, results)
315 yield self.startup
316
Chip Boling7294b252017-06-15 16:16:55 -0500317 if not reconciling:
318 for port in self.northbound_ports.itervalues():
319 self.adapter_agent.add_port(device.id, port.get_port())
Chip Boling3e3b1a92017-05-16 11:51:18 -0500320
321 except Exception as e:
322 self.log.exception('Northbound port enumeration and creation failed', e=e)
323 self.activate_failed(device, e.message)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500324
325 try:
326 # Enumerate and create southbound interfaces
327
328 self.startup = self.enumerate_southbound_ports(device)
329 results = yield self.startup
330
331 self.startup = self.process_southbound_ports(device, results)
332 yield self.startup
333
Chip Boling7294b252017-06-15 16:16:55 -0500334 if not reconciling:
335 for port in self.southbound_ports.itervalues():
336 self.adapter_agent.add_port(device.id, port.get_port())
Chip Boling3e3b1a92017-05-16 11:51:18 -0500337
338 except Exception as e:
339 self.log.exception('Southbound port enumeration and creation failed', e=e)
340 self.activate_failed(device, e.message)
341
Chip Boling7294b252017-06-15 16:16:55 -0500342 if reconciling:
343 if device.admin_state == AdminState.ENABLED:
344 if device.parent_id:
345 self.logical_device_id = device.parent_id
346 self.adapter_agent.reconcile_logical_device(device.parent_id)
347 else:
348 self.log.info('no-logical-device-set')
Chip Boling3e3b1a92017-05-16 11:51:18 -0500349
Chip Boling7294b252017-06-15 16:16:55 -0500350 # Reconcile child devices
351 self.adapter_agent.reconcile_child_devices(device.id)
Chip Boling5561d552017-07-07 15:11:26 -0500352 ld_initialized = self.adapter_agent.get_logical_device()
353 assert device.parent_id == ld_initialized.id
354
Chip Boling7294b252017-06-15 16:16:55 -0500355 else:
356 # Complete activation by setting up logical device for this OLT and saving
357 # off the devices parent_id
Chip Boling3e3b1a92017-05-16 11:51:18 -0500358
Chip Boling5561d552017-07-07 15:11:26 -0500359 ld_initialized = self.create_logical_device(device)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500360
Chip Boling5561d552017-07-07 15:11:26 -0500361 ############################################################################
362 # Setup PM configuration for this device
363
364 # self.pm_metrics = AdapterPmMetrics(device)
365 # pm_config = self.pm_metrics.make_proto()
366 # self.log.info("initial-pm-config", pm_config=pm_config)
367 # self.adapter_agent.update_device_pm_config(pm_config, init=True)
368
369 ############################################################################
370 # Setup Alarm handler
371
372 self.alarms = AdapterAlarms(self.adapter, device)
373
374 ############################################################################
Chip Boling7294b252017-06-15 16:16:55 -0500375 # Create logical ports for all southbound and northbound interfaces
Chip Boling5561d552017-07-07 15:11:26 -0500376 try:
377 self.startup = self.create_logical_ports(device, ld_initialized, reconciling)
378 yield self.startup
Chip Boling3e3b1a92017-05-16 11:51:18 -0500379
Chip Boling5561d552017-07-07 15:11:26 -0500380 except Exception as e:
381 self.log.exception('Logical port creation failed', e=e)
382 self.activate_failed(device, e.message)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500383
384 # Complete device specific steps
385 try:
Chip Boling5561d552017-07-07 15:11:26 -0500386 self.log.debug('Performing final device specific activation procedures')
Chip Boling7294b252017-06-15 16:16:55 -0500387 self.startup = self.complete_device_specific_activation(device, reconciling)
Chip Boling5561d552017-07-07 15:11:26 -0500388 yield self.startup
Chip Boling3e3b1a92017-05-16 11:51:18 -0500389
390 except Exception as e:
391 self.log.exception('Device specific activation failed', e=e)
392 self.activate_failed(device, e.message)
393
394 # Schedule the heartbeat for the device
395
Chip Boling5561d552017-07-07 15:11:26 -0500396 self.log.debug('Starting heartbeat')
397 self.start_heartbeat(delay=5)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500398
Chip Boling3e3b1a92017-05-16 11:51:18 -0500399 device = self.adapter_agent.get_device(device.id)
Chip Boling5561d552017-07-07 15:11:26 -0500400 device.parent_id = ld_initialized.id
Chip Boling3e3b1a92017-05-16 11:51:18 -0500401 device.oper_status = OperStatus.ACTIVE
Chip Boling5561d552017-07-07 15:11:26 -0500402 device.reason = ''
Chip Boling3e3b1a92017-05-16 11:51:18 -0500403 self.adapter_agent.update_device(device)
404
Chip Boling5561d552017-07-07 15:11:26 -0500405 # finally, open the frameio port to receive in-band packet_in messages
406 self._activate_io_port()
407
408 # Start collecting stats from the device after a brief pause
409 reactor.callLater(5, self.start_kpi_collection, device.id)
410
411 self.log.info('Activated')
412
Chip Boling3e3b1a92017-05-16 11:51:18 -0500413 def activate_failed(self, device, reason, reachable=True):
414 """
415 Activation process (adopt_device) has failed.
416
417 :param device: A voltha.Device object, with possible device-type
418 specific extensions. Such extensions shall be described as part of
419 the device type specification returned by device_types().
420 :param reason: (string) failure reason
421 :param reachable: (boolean) Flag indicating if device may be reachable
422 via RESTConf or NETConf even after this failure.
423 """
424 device.oper_status = OperStatus.FAILED
425 if not reachable:
426 device.connect_status = ConnectStatus.UNREACHABLE
427
428 device.reason = reason
429 self.adapter_agent.update_device(device)
430 raise RuntimeError('Failed to activate OLT: {}'.format(device.reason))
431
Chip Boling5561d552017-07-07 15:11:26 -0500432 @inlineCallbacks
Chip Boling7294b252017-06-15 16:16:55 -0500433 def make_netconf_connection(self, connect_timeout=None):
434 ############################################################################
435 # Start initial discovery of NETCONF support
436
Chip Boling5561d552017-07-07 15:11:26 -0500437 client = self._netconf_client
438
439 if client is None:
440 if not self.is_virtual_olt:
441 client = AdtranNetconfClient(self.ip_address,
442 self.netconf_port,
443 self.netconf_username,
444 self.netconf_password,
445 self.timeout)
446 else:
447 from voltha.adapters.adtran_olt.net.mock_netconf_client import MockNetconfClient
448 client = MockNetconfClient(self.ip_address,
449 self.netconf_port,
450 self.netconf_username,
451 self.netconf_password,
452 self.timeout)
453 if client.connected:
454 self._netconf_client = client
455 returnValue(True)
Chip Boling7294b252017-06-15 16:16:55 -0500456
457 timeout = connect_timeout or self.timeout
Chip Boling7294b252017-06-15 16:16:55 -0500458
Chip Boling5561d552017-07-07 15:11:26 -0500459 try:
460 request = client.connect(timeout)
461 results = yield request
462 self._netconf_client = client
463 returnValue(results)
464
465 except Exception as e:
466 self.log.exception('Failed to create NETCONF Client', e=e)
467 self._netconf_client = None
468 raise
469
470 @inlineCallbacks
Chip Boling7294b252017-06-15 16:16:55 -0500471 def make_restconf_connection(self, get_timeout=None):
Chip Boling5561d552017-07-07 15:11:26 -0500472 client = self._rest_client
473
474 if client is None:
475 client = AdtranRestClient(self.ip_address,
476 self.rest_port,
477 self.rest_username,
478 self.rest_password,
479 self.timeout)
Chip Boling7294b252017-06-15 16:16:55 -0500480
481 timeout = get_timeout or self.timeout
Chip Boling5561d552017-07-07 15:11:26 -0500482
483 try:
484 request = client.request('GET', self.HELLO_URI, name='hello', timeout=timeout)
485 results = yield request
486 if isinstance(results, dict) and 'module-info' in results:
487 self._rest_client = client
488 returnValue(results)
489 else:
490 from twisted.internet.error import ConnectError
491 self._rest_client = None
492 raise ConnectError(string='Results received but unexpected data type or contents')
493 except Exception:
494 self._rest_client = None
495 raise
Chip Boling7294b252017-06-15 16:16:55 -0500496
497 def create_logical_device(self, device):
Chip Boling5561d552017-07-07 15:11:26 -0500498 version = device.images.image[0].version
499
Chip Boling7294b252017-06-15 16:16:55 -0500500 ld = LogicalDevice(
501 # NOTE: not setting id and datapath_id will let the adapter agent pick id
502 desc=ofp_desc(mfr_desc=device.vendor,
503 hw_desc=device.hardware_version,
Chip Boling5561d552017-07-07 15:11:26 -0500504 sw_desc=version,
Chip Boling7294b252017-06-15 16:16:55 -0500505 serial_num=device.serial_number,
506 dp_desc='n/a'),
507 switch_features=ofp_switch_features(n_buffers=256, # TODO fake for now
508 n_tables=2, # TODO ditto
509 capabilities=(
Chip Boling5561d552017-07-07 15:11:26 -0500510 OFPC_FLOW_STATS |
511 OFPC_TABLE_STATS |
512 OFPC_GROUP_STATS |
Chip Boling7294b252017-06-15 16:16:55 -0500513 OFPC_PORT_STATS)),
514 root_device_id=device.id)
515
516 ld_initialized = self.adapter_agent.create_logical_device(ld)
517
518 return ld_initialized
519
520 @inlineCallbacks
521 def create_logical_ports(self, device, ld_initialized, reconciling):
Chip Boling5561d552017-07-07 15:11:26 -0500522 results = defer.fail()
Chip Boling7294b252017-06-15 16:16:55 -0500523
524 if not reconciling:
525 for port in self.northbound_ports.itervalues():
526 lp = port.get_logical_port()
527 if lp is not None:
528 self.adapter_agent.add_logical_port(ld_initialized.id, lp)
529
530 for port in self.southbound_ports.itervalues():
531 lp = port.get_logical_port()
532 if lp is not None:
533 self.adapter_agent.add_logical_port(ld_initialized.id, lp)
534
535 # Set the ports in a known good initial state
536 try:
537 for port in self.northbound_ports.itervalues():
Chip Boling5561d552017-07-07 15:11:26 -0500538 self.startup = yield port.reset()
Chip Boling7294b252017-06-15 16:16:55 -0500539 results = yield self.startup
Chip Boling7294b252017-06-15 16:16:55 -0500540
Chip Boling7294b252017-06-15 16:16:55 -0500541 for port in self.southbound_ports.itervalues():
Chip Boling5561d552017-07-07 15:11:26 -0500542 self.startup = yield port.reset()
Chip Boling7294b252017-06-15 16:16:55 -0500543 results = yield self.startup
Chip Boling7294b252017-06-15 16:16:55 -0500544
545 except Exception as e:
Chip Boling5561d552017-07-07 15:11:26 -0500546 self.log.exception('Failed to reset ports to known good initial state', e=e)
547 self.activate_failed(device, e.message)
Chip Boling7294b252017-06-15 16:16:55 -0500548
549 # Start/stop the interfaces as needed
Chip Boling7294b252017-06-15 16:16:55 -0500550
Chip Boling5561d552017-07-07 15:11:26 -0500551 for port in self.northbound_ports.itervalues():
552 self.startup = port.start()
553 results = yield self.startup
Chip Boling7294b252017-06-15 16:16:55 -0500554
Chip Boling5561d552017-07-07 15:11:26 -0500555 if reconciling:
556 start_downlinks = device.admin_state == AdminState.ENABLED
557 else:
558 start_downlinks = self.initial_port_state == AdminState.ENABLED
Chip Boling7294b252017-06-15 16:16:55 -0500559
Chip Boling5561d552017-07-07 15:11:26 -0500560 for port in self.southbound_ports.itervalues():
561 self.startup = port.start() if start_downlinks else port.stop()
562 results = yield self.startup
Chip Boling7294b252017-06-15 16:16:55 -0500563
Chip Boling5561d552017-07-07 15:11:26 -0500564 returnValue(results)
Chip Boling7294b252017-06-15 16:16:55 -0500565
566 @inlineCallbacks
567 def device_information(self, device):
568 """
569 Examine the various managment models and extract device information for
570 VOLTHA use
571
572 :param device: A voltha.Device object, with possible device-type
573 specific extensions.
574 :return: (Deferred or None).
575 """
576 yield defer.Deferred(lambda c: c.callback("Not Required"))
577
Chip Boling3e3b1a92017-05-16 11:51:18 -0500578 @inlineCallbacks
579 def enumerate_northbound_ports(self, device):
580 """
581 Enumerate all northbound ports of a device. You should override
582 this method in your derived class as necessary. Should you encounter
583 a non-recoverable error, throw an appropriate exception.
584
585 :param device: A voltha.Device object, with possible device-type
586 specific extensions.
587 :return: (Deferred or None).
588 """
589 yield defer.Deferred(lambda c: c.callback("Not Required"))
590
591 @inlineCallbacks
592 def process_northbound_ports(self, device, results):
593 """
594 Process the results from the 'enumerate_northbound_ports' method.
595 You should override this method in your derived class as necessary and
596 create an NNI Port object (of your own choosing) that supports a 'get_port'
597 method. Once created, insert it into this base class's northbound_ports
598 collection.
599
600 Should you encounter a non-recoverable error, throw an appropriate exception.
601
602 :param device: A voltha.Device object, with possible device-type
603 specific extensions.
604 :param results: Results from the 'enumerate_northbound_ports' method that
605 you implemented. The type and contents are up to you to
606 :return:
607 """
608 yield defer.Deferred(lambda c: c.callback("Not Required"))
609
610 @inlineCallbacks
611 def enumerate_southbound_ports(self, device):
612 """
613 Enumerate all southbound ports of a device. You should override
614 this method in your derived class as necessary. Should you encounter
615 a non-recoverable error, throw an appropriate exception.
616
617 :param device: A voltha.Device object, with possible device-type
618 specific extensions.
619 :return: (Deferred or None).
620 """
621 yield defer.Deferred(lambda c: c.callback("Not Required"))
622
623 @inlineCallbacks
624 def process_southbound_ports(self, device, results):
625 """
626 Process the results from the 'enumerate_southbound_ports' method.
627 You should override this method in your derived class as necessary and
628 create an Port object (of your own choosing) that supports a 'get_port'
629 method. Once created, insert it into this base class's southbound_ports
630 collection.
631
632 Should you encounter a non-recoverable error, throw an appropriate exception.
633
634 :param device: A voltha.Device object, with possible device-type
635 specific extensions.
636 :param results: Results from the 'enumerate_southbound_ports' method that
637 you implemented. The type and contents are up to you to
638 :return:
639 """
640 yield defer.Deferred(lambda c: c.callback("Not Required"))
641
Chip Boling5561d552017-07-07 15:11:26 -0500642 # TODO: Move some of the items below from here and the EVC to a utility class
643
644 def is_nni_port(self, port):
645 return port in self.northbound_ports
646
647 def is_uni_port(self, port):
648 raise NotImplementedError('implement in derived class')
649
650 def is_pon_port(self, port):
651 raise NotImplementedError('implement in derived class')
652
653 def is_logical_port(self, port):
654 return not self.is_nni_port(port) and not self.is_uni_port(port) and not self.is_pon_port(port)
655
656 def get_port_name(self, port):
657 raise NotImplementedError('implement in derived class')
658
Chip Boling3e3b1a92017-05-16 11:51:18 -0500659 @inlineCallbacks
Chip Boling7294b252017-06-15 16:16:55 -0500660 def complete_device_specific_activation(self, _device, _reconciling):
Chip Boling5561d552017-07-07 15:11:26 -0500661 return defer.succeed('NOP')
Chip Boling3e3b1a92017-05-16 11:51:18 -0500662
663 def deactivate(self, device):
664 # Clear off logical device ID
665 self.logical_device_id = None
666
667 # Kill any heartbeat poll
668 h, self.heartbeat = self.heartbeat, None
669
670 if h is not None:
671 h.cancel()
672
Chip Boling7294b252017-06-15 16:16:55 -0500673 # TODO: What else (delete logical device, ???)
674
675 @inlineCallbacks
676 def disable(self):
677 """
678 This is called when a previously enabled device needs to be disabled based on a NBI call.
679 """
680 self.log.info('disabling', device_id=self.device_id)
681
682 # Get the latest device reference
683 device = self.adapter_agent.get_device(self.device_id)
684
Chip Boling5561d552017-07-07 15:11:26 -0500685 # Deactivate in-band packets
686 self._deactivate_io_port()
687
Chip Boling7294b252017-06-15 16:16:55 -0500688 # Suspend any active healthchecks / pings
689
690 h, self.heartbeat = self.heartbeat, None
691
692 if h is not None:
693 h.cancel()
694
695 # Update the operational status to UNKNOWN
696
697 device.oper_status = OperStatus.UNKNOWN
698 device.connect_status = ConnectStatus.UNREACHABLE
699 self.adapter_agent.update_device(device)
700
701 # Remove the logical device
702 ldi, self.logical_device_id = self.logical_device_id, None
703
704 if ldi is not None:
705 logical_device = self.adapter_agent.get_logical_device(ldi)
706 self.adapter_agent.delete_logical_device(logical_device)
707
708 # Disable all child devices first
709 self.adapter_agent.update_child_devices_state(self.device_id,
710 admin_state=AdminState.DISABLED)
711
712 # Remove the peer references from this device
713 self.adapter_agent.delete_all_peer_references(self.device_id)
714
Chip Boling5561d552017-07-07 15:11:26 -0500715 # Disable all flows TODO: Do we want to delete them?
716 # TODO: Create a bulk disable-all by device-id
717
718 for evc in self.evcs.itervalues():
719 evc.disable()
720
Chip Boling7294b252017-06-15 16:16:55 -0500721 # Set all ports to disabled
722 self.adapter_agent.disable_all_ports(self.device_id)
723
Chip Boling5561d552017-07-07 15:11:26 -0500724 dl = []
Chip Boling7294b252017-06-15 16:16:55 -0500725 for port in self.northbound_ports.itervalues():
Chip Boling5561d552017-07-07 15:11:26 -0500726 dl.append(port.stop())
Chip Boling7294b252017-06-15 16:16:55 -0500727
728 for port in self.southbound_ports.itervalues():
Chip Boling5561d552017-07-07 15:11:26 -0500729 dl.append(port.stop())
Chip Boling7294b252017-06-15 16:16:55 -0500730
Chip Boling5561d552017-07-07 15:11:26 -0500731 self.startup = defer.gatherResults(dl)
732 results = yield self.startup
Chip Boling7294b252017-06-15 16:16:55 -0500733
734 # Shutdown communications with OLT
735
736 if self.netconf_client is not None:
737 try:
738 yield self.netconf_client.close()
739 except Exception as e:
740 self.log.exception('NETCONF client shutdown failed', e=e)
741
742 def _null_clients():
Chip Boling5561d552017-07-07 15:11:26 -0500743 self._netconf_client = None
744 self._rest_client = None
Chip Boling7294b252017-06-15 16:16:55 -0500745
746 reactor.callLater(0, _null_clients)
747
748 self.log.info('disabled', device_id=device.id)
Chip Boling5561d552017-07-07 15:11:26 -0500749 returnValue(results)
Chip Boling7294b252017-06-15 16:16:55 -0500750
751 @inlineCallbacks
752 def reenable(self):
753 """
754 This is called when a previously disabled device needs to be enabled based on a NBI call.
755 """
756 self.log.info('re-enabling', device_id=self.device_id)
757
758 # Get the latest device reference
759 device = self.adapter_agent.get_device(self.device_id)
760
761 # Update the connect status to REACHABLE
762 device.connect_status = ConnectStatus.REACHABLE
763 self.adapter_agent.update_device(device)
764
765 # Set all ports to enabled
766 self.adapter_agent.enable_all_ports(self.device_id)
767
768 try:
769 yield self.make_restconf_connection()
770
771 except Exception as e:
772 self.log.exception('RESTCONF adtran-hello reconnect failed', e=e)
773 # TODO: What is best way to handle reenable failure?
774
Chip Boling5561d552017-07-07 15:11:26 -0500775 try:
776 yield self.make_netconf_connection()
Chip Boling7294b252017-06-15 16:16:55 -0500777
Chip Boling5561d552017-07-07 15:11:26 -0500778 except Exception as e:
779 self.log.exception('NETCONF re-connection failed', e=e)
780 # TODO: What is best way to handle reenable failure?
Chip Boling7294b252017-06-15 16:16:55 -0500781
782 # Recreate the logical device
783
784 ld_initialized = self.create_logical_device(device)
785
786 # Create logical ports for all southbound and northbound interfaces
787
788 self.create_logical_ports(device, ld_initialized, False)
789
790 device = self.adapter_agent.get_device(device.id)
791 device.parent_id = ld_initialized.id
792 device.oper_status = OperStatus.ACTIVE
Chip Boling5561d552017-07-07 15:11:26 -0500793 device.reason = ''
Chip Boling7294b252017-06-15 16:16:55 -0500794 self.adapter_agent.update_device(device)
795 self.logical_device_id = ld_initialized.id
796
797 # Reenable all child devices
798 self.adapter_agent.update_child_devices_state(device.id,
799 admin_state=AdminState.ENABLED)
Chip Boling5561d552017-07-07 15:11:26 -0500800 dl = []
Chip Boling7294b252017-06-15 16:16:55 -0500801
802 for port in self.northbound_ports.itervalues():
Chip Boling5561d552017-07-07 15:11:26 -0500803 dl.append(port.start())
Chip Boling7294b252017-06-15 16:16:55 -0500804
805 for port in self.southbound_ports.itervalues():
Chip Boling5561d552017-07-07 15:11:26 -0500806 dl.append(port.start())
807
808 self.startup = defer.gatherResults(dl)
809 results = yield self.startup
Chip Boling7294b252017-06-15 16:16:55 -0500810
811 # TODO:
812 # 1) Restart health check / pings
Chip Boling7294b252017-06-15 16:16:55 -0500813 # Enable all flows
Chip Boling5561d552017-07-07 15:11:26 -0500814 # TODO: Create a bulk enable-all by device-id
Chip Boling7294b252017-06-15 16:16:55 -0500815
Chip Boling5561d552017-07-07 15:11:26 -0500816 for evc in self.evcs:
817 evc.enable()
818
819 # Activate in-band packets
820 self._activate_io_port()
Chip Boling7294b252017-06-15 16:16:55 -0500821
822 self.log.info('re-enabled', device_id=device.id)
Chip Boling5561d552017-07-07 15:11:26 -0500823 returnValue(results)
Chip Boling7294b252017-06-15 16:16:55 -0500824
825 @inlineCallbacks
826 def reboot(self):
827 """
828 This is called to reboot a device based on a NBI call. The admin state of the device
829 will not change after the reboot.
830 """
831 self.log.debug('reboot')
832
833 # Update the operational status to ACTIVATING and connect status to
834 # UNREACHABLE
835
836 device = self.adapter_agent.get_device(self.device_id)
837 previous_oper_status = device.oper_status
838 previous_conn_status = device.connect_status
839 device.oper_status = OperStatus.ACTIVATING
840 device.connect_status = ConnectStatus.UNREACHABLE
841 self.adapter_agent.update_device(device)
842
843 # Update the child devices connect state to UNREACHABLE
844 self.adapter_agent.update_child_devices_state(self.device_id,
845 connect_status=ConnectStatus.UNREACHABLE)
846 # Issue reboot command
847
848 if not self.is_virtual_olt:
849 try:
850 yield self.netconf_client.rpc(AdtranDeviceHandler.RESTART_RPC)
851
852 except Exception as e:
853 self.log.exception('NETCONF client shutdown', e=e)
854 # TODO: On failure, what is the best thing to do?
855
856 # Shutdown communications with OLT. Typically it takes about 2 seconds
857 # or so after the reply before the restart actually occurs
858
859 try:
860 response = yield self.netconf_client.close()
861 self.log.debug('Restart response XML was: {}'.format('ok' if response.ok else 'bad'))
862
863 except Exception as e:
864 self.log.exception('NETCONF client shutdown', e=e)
865
Chip Boling5561d552017-07-07 15:11:26 -0500866 # Clear off clients
Chip Boling7294b252017-06-15 16:16:55 -0500867
Chip Boling5561d552017-07-07 15:11:26 -0500868 self._netconf_client = None
869 self._rest_client = None
Chip Boling7294b252017-06-15 16:16:55 -0500870
871 # Run remainder of reboot process as a new task. The OLT then may be up in a
872 # few moments or may take 3 minutes or more depending on any self tests enabled
873
Chip Boling5561d552017-07-07 15:11:26 -0500874 current_time = time.time()
Chip Boling7294b252017-06-15 16:16:55 -0500875 timeout = current_time + self.restart_failure_timeout
876
Chip Boling5561d552017-07-07 15:11:26 -0500877 try:
878 yield reactor.callLater(10, self._finish_reboot, timeout,
879 previous_oper_status, previous_conn_status)
880 except Exception as e:
881 self.log.exception('finish reboot scheduling', e=e)
Chip Boling7294b252017-06-15 16:16:55 -0500882
Chip Boling5561d552017-07-07 15:11:26 -0500883 returnValue('Waiting for reboot')
Chip Boling7294b252017-06-15 16:16:55 -0500884
885 @inlineCallbacks
886 def _finish_reboot(self, timeout, previous_oper_status, previous_conn_status):
887 # Now wait until REST & NETCONF are re-established or we timeout
888
Chip Boling5561d552017-07-07 15:11:26 -0500889 self.log.info('Resuming OLT activity after reboot requested',
890 remaining=timeout - time.time(), timeout=timeout, current=time.time())
891
892 if self.rest_client is None:
Chip Boling7294b252017-06-15 16:16:55 -0500893 try:
Chip Boling5561d552017-07-07 15:11:26 -0500894 response = yield self.make_restconf_connection(get_timeout=10)
895 self.log.debug('Restart RESTCONF connection JSON was: {}'.format(response))
896
897 except Exception:
898 self.log.debug('No RESTCONF connection yet')
899 self._rest_client = None
900
901 if self.netconf_client is None:
902 try:
903 yield self.make_netconf_connection(connect_timeout=10)
904 self.log.debug('Restart NETCONF connection succeeded')
Chip Boling7294b252017-06-15 16:16:55 -0500905
906 except Exception as e:
907 self.log.debug('No NETCONF connection yet: {}'.format(e.message))
908 try:
Chip Boling5561d552017-07-07 15:11:26 -0500909 if self.netconf_client is not None:
910 yield self.netconf_client.close()
Chip Boling7294b252017-06-15 16:16:55 -0500911 except Exception as e:
912 self.log.exception(e.message)
913 finally:
Chip Boling5561d552017-07-07 15:11:26 -0500914 self._netconf_client = None
Chip Boling7294b252017-06-15 16:16:55 -0500915
916 if (self.netconf_client is None and not self.is_virtual_olt) or self.rest_client is None:
Chip Boling5561d552017-07-07 15:11:26 -0500917 current_time = time.time()
Chip Boling7294b252017-06-15 16:16:55 -0500918 if current_time < timeout:
Chip Boling5561d552017-07-07 15:11:26 -0500919 try:
920 yield reactor.callLater(5, self._finish_reboot, timeout,
921 previous_oper_status, previous_conn_status)
922 except Exception:
923 self.log.debug('Rebooted check rescheduling')
924
925 returnValue('Waiting some more...')
Chip Boling7294b252017-06-15 16:16:55 -0500926
927 if self.netconf_client is None and not self.is_virtual_olt:
928 self.log.error('Could not restore NETCONF communications after device RESET')
929 pass # TODO: What is best course of action if cannot get clients back?
930
931 if self.rest_client is None:
932 self.log.error('Could not restore RESTCONF communications after device RESET')
933 pass # TODO: What is best course of action if cannot get clients back?
934
Chip Boling5561d552017-07-07 15:11:26 -0500935 # Pause additional 5 seconds to let allow OLT microservices to complete some more initialization
Chip Boling7294b252017-06-15 16:16:55 -0500936 yield asleep(5)
937
938 # Get the latest device reference
939
940 device = self.adapter_agent.get_device(self.device_id)
941 device.oper_status = previous_oper_status
942 device.connect_status = previous_conn_status
943 self.adapter_agent.update_device(device)
944
945 # Update the child devices connect state to REACHABLE
946 self.adapter_agent.update_child_devices_state(self.device_id,
947 connect_status=ConnectStatus.REACHABLE)
948
Chip Boling7294b252017-06-15 16:16:55 -0500949 self.log.info('rebooted', device_id=self.device_id)
Chip Boling5561d552017-07-07 15:11:26 -0500950 returnValue('Rebooted')
Chip Boling7294b252017-06-15 16:16:55 -0500951
952 @inlineCallbacks
953 def delete(self):
954 """
955 This is called to delete a device from the PON based on a NBI call.
956 If the device is an OLT then the whole PON will be deleted.
957 """
958 self.log.info('deleting', device_id=self.device_id)
959
960 # Cancel any outstanding tasks
961
962 d, self.startup = self.startup, None
963 if d is not None:
964 d.cancel()
965
966 h, self.heartbeat = self.heartbeat, None
967 if h is not None:
968 h.cancel()
969
Chip Boling5561d552017-07-07 15:11:26 -0500970 # Remove all flows from the device
971 # TODO: Create a bulk remove-all by device-id
Chip Boling7294b252017-06-15 16:16:55 -0500972
Chip Boling5561d552017-07-07 15:11:26 -0500973 for evc in self.evcs.itervalues():
974 evc.remove()
975
976 self.evcs.clear()
Chip Boling7294b252017-06-15 16:16:55 -0500977
978 # Remove all child devices
979 self.adapter_agent.delete_all_child_devices(self.device_id)
980
981 # Remove the logical device
982 logical_device = self.adapter_agent.get_logical_device(self.logical_device_id)
983 self.adapter_agent.delete_logical_device(logical_device)
Chip Boling5561d552017-07-07 15:11:26 -0500984 # TODO: For some reason, the logical device does not seem to get deleted
Chip Boling7294b252017-06-15 16:16:55 -0500985
986 # Remove the peer references from this device
987 self.adapter_agent.delete_all_peer_references(self.device_id)
988
989 # Tell all ports to stop any background processing
990
991 for port in self.northbound_ports.itervalues():
992 port.delete()
993
994 for port in self.southbound_ports.itervalues():
995 port.delete()
996
997 self.northbound_ports.clear()
998 self.southbound_ports.clear()
999
1000 # Shutdown communications with OLT
1001
1002 if self.netconf_client is not None:
1003 try:
1004 yield self.netconf_client.close()
1005 except Exception as e:
1006 self.log.exception('NETCONF client shutdown', e=e)
1007
Chip Boling5561d552017-07-07 15:11:26 -05001008 self._netconf_client = None
Chip Boling7294b252017-06-15 16:16:55 -05001009
Chip Boling5561d552017-07-07 15:11:26 -05001010 self._rest_client = None
Chip Boling7294b252017-06-15 16:16:55 -05001011
1012 self.log.info('deleted', device_id=self.device_id)
1013
Chip Boling5561d552017-07-07 15:11:26 -05001014 def _activate_io_port(self):
1015 if self.io_port is None:
1016 self.log.info('registering-frameio')
1017 self.io_port = registry('frameio').open_port(
1018 self.interface, self._rcv_io, _is_inband_frame)
1019
1020 def _deactivate_io_port(self):
1021 io, self.io_port = self.io_port, None
1022
1023 if io is not None:
1024 registry('frameio').close_port(io)
1025
1026 def _rcv_io(self, port, frame):
1027 self.log.info('received', iface_name=port.iface_name, frame_len=len(frame))
1028
1029 pkt = Ether(frame)
1030 if pkt.haslayer(Dot1Q):
1031 outer_shim = pkt.getlayer(Dot1Q)
1032
1033 if isinstance(outer_shim.payload, Dot1Q):
1034 inner_shim = outer_shim.payload
1035 cvid = inner_shim.vlan
1036 logical_port = cvid
1037 popped_frame = (Ether(src=pkt.src, dst=pkt.dst, type=inner_shim.type) /
1038 inner_shim.payload)
1039 kw = dict(
1040 logical_device_id=self.logical_device_id,
1041 logical_port_no=logical_port,
1042 )
1043 self.log.info('sending-packet-in', **kw)
1044 self.adapter_agent.send_packet_in(
1045 packet=str(popped_frame), **kw)
1046
1047 elif pkt.haslayer(Raw):
1048 raw_data = json.loads(pkt.getlayer(Raw).load)
1049 self.alarms.send_alarm(self, raw_data)
1050
1051 def packet_out(self, egress_port, msg):
1052 if self.io_port is not None:
1053 self.log.info('sending-packet-out', egress_port=egress_port,
1054 msg=hexify(msg))
1055 pkt = Ether(msg)
1056 out_pkt = (
1057 Ether(src=pkt.src, dst=pkt.dst) /
1058 Dot1Q(vlan=4000) /
1059 Dot1Q(vlan=egress_port, type=pkt.type) /
1060 pkt.payload
1061 )
1062 self.io_port.send(str(out_pkt))
1063
1064 def update_pm_config(self, device, pm_config):
1065 # TODO: This has not been tested
1066 self.log.info('update_pm_config', pm_config=pm_config)
1067 self.pm_metrics.update(pm_config)
1068
1069 def start_kpi_collection(self, device_id):
1070 # TODO: This has not been tested
1071 def _collect(device_id, prefix):
1072 from voltha.protos.events_pb2 import KpiEvent, KpiEventType, MetricValuePairs
1073
1074 try:
1075 # Step 1: gather metrics from device
1076 port_metrics = self.pm_metrics.collect_port_metrics(self.get_channel())
1077
1078 # Step 2: prepare the KpiEvent for submission
1079 # we can time-stamp them here (or could use time derived from OLT
1080 ts = arrow.utcnow().timestamp
1081 kpi_event = KpiEvent(
1082 type=KpiEventType.slice,
1083 ts=ts,
1084 prefixes={
1085 # OLT NNI port
1086 prefix + '.nni': MetricValuePairs(metrics=port_metrics['nni']),
1087 # OLT PON port
1088 prefix + '.pon': MetricValuePairs(metrics=port_metrics['pon'])
1089 }
1090 )
1091 # Step 3: submit
1092 self.adapter_agent.submit_kpis(kpi_event)
1093
1094 except Exception as e:
1095 self.log.exception('failed-to-submit-kpis', e=e)
1096
1097 # self.pm_metrics.start_collector(_collect)
1098
Chip Boling3e3b1a92017-05-16 11:51:18 -05001099 @inlineCallbacks
1100 def get_device_info(self, device):
1101 """
1102 Perform an initial network operation to discover the device hardware
1103 and software version. Serial Number would be helpful as well.
1104
1105 Upon successfully retrieving the information, remember to call the
1106 'start_heartbeat' method to keep in contact with the device being managed
1107
1108 :param device: A voltha.Device object, with possible device-type
1109 specific extensions. Such extensions shall be described as part of
1110 the device type specification returned by device_types().
1111 """
Chip Boling7294b252017-06-15 16:16:55 -05001112 device = {}
Chip Boling7294b252017-06-15 16:16:55 -05001113 returnValue(device)
Chip Boling3e3b1a92017-05-16 11:51:18 -05001114
1115 def start_heartbeat(self, delay=10):
1116 assert delay > 1
Chip Boling5561d552017-07-07 15:11:26 -05001117 self.log.info('*** Starting Device Heartbeat ***')
Chip Boling3e3b1a92017-05-16 11:51:18 -05001118 self.heartbeat = reactor.callLater(delay, self.check_pulse)
Chip Boling5561d552017-07-07 15:11:26 -05001119 return self.heartbeat
Chip Boling3e3b1a92017-05-16 11:51:18 -05001120
1121 def check_pulse(self):
1122 if self.logical_device_id is not None:
1123 self.heartbeat = self.rest_client.request('GET', self.HELLO_URI, name='hello')
1124 self.heartbeat.addCallbacks(self.heartbeat_check_status, self.heartbeat_fail)
1125
1126 def heartbeat_check_status(self, results):
1127 """
1128 Check the number of heartbeat failures against the limit and emit an alarm if needed
1129 """
1130 device = self.adapter_agent.get_device(self.device_id)
1131
1132 if self.heartbeat_miss >= self.heartbeat_failed_limit and device.connect_status == ConnectStatus.REACHABLE:
1133 self.log.warning('olt-heartbeat-failed', count=self.heartbeat_miss)
1134 device.connect_status = ConnectStatus.UNREACHABLE
1135 device.oper_status = OperStatus.FAILED
1136 device.reason = self.heartbeat_last_reason
1137 self.adapter_agent.update_device(device)
1138
Chip Boling5561d552017-07-07 15:11:26 -05001139 self.heartbeat_alarm(False, self.heartbeat_miss)
Chip Boling3e3b1a92017-05-16 11:51:18 -05001140 else:
1141 assert results
1142 # Update device states
1143
1144 self.log.info('heartbeat success')
1145
1146 if device.connect_status != ConnectStatus.REACHABLE:
1147 device.connect_status = ConnectStatus.REACHABLE
1148 device.oper_status = OperStatus.ACTIVE
1149 device.reason = ''
1150 self.adapter_agent.update_device(device)
1151
Chip Boling5561d552017-07-07 15:11:26 -05001152 self.heartbeat_alarm(True)
Chip Boling3e3b1a92017-05-16 11:51:18 -05001153
1154 self.heartbeat_miss = 0
1155 self.heartbeat_last_reason = ''
1156 self.heartbeat_count += 1
1157
1158 # Reschedule next heartbeat
1159 if self.logical_device_id is not None:
1160 self.heartbeat = reactor.callLater(self.heartbeat_interval, self.check_pulse)
1161
1162 def heartbeat_fail(self, failure):
1163 self.heartbeat_miss += 1
1164 self.log.info('heartbeat-miss', failure=failure,
1165 count=self.heartbeat_count, miss=self.heartbeat_miss)
1166 self.heartbeat_check_status(None)
1167
Chip Boling5561d552017-07-07 15:11:26 -05001168 def heartbeat_alarm(self, status, heartbeat_misses=0):
1169 alarm = 'Heartbeat'
1170 alarm_data = {
1171 'ts': arrow.utcnow().timestamp,
1172 'description': self.alarms.format_description('olt', alarm, status),
1173 'id': self.alarms.format_id(alarm),
1174 'type': AlarmEventType.EQUIPMENT,
1175 'category': AlarmEventCategory.PON,
1176 'severity': AlarmEventSeverity.CRITICAL,
1177 'state': AlarmEventState.RAISED if status else AlarmEventState.CLEARED
1178 }
1179 context_data = {'heartbeats_missed': heartbeat_misses}
1180 self.alarms.send_alarm(context_data, alarm_data)
Chip Boling3e3b1a92017-05-16 11:51:18 -05001181
1182 @staticmethod
1183 def parse_module_revision(revision):
1184 try:
1185 return datetime.datetime.strptime(revision, '%Y-%m-%d')
1186 except Exception:
1187 return None