blob: 297bb1b43bf9b09fda4db3513a5a9a2846ac95bf [file] [log] [blame]
Chip Boling27275992017-09-22 15:17:04 -05001# Copyright 2017-present Adtran, Inc.
Chip Boling3e3b1a92017-05-16 11:51:18 -05002#
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#
Chip Boling252c7772017-08-16 10:13:17 -05007# http://www.apache.org/licenses/LICENSE-2.0
Chip Boling3e3b1a92017-05-16 11:51:18 -05008#
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.
Chip Boling3e3b1a92017-05-16 11:51:18 -050014"""
15Adtran generic VOLTHA device handler
16"""
Chip Boling7294b252017-06-15 16:16:55 -050017import argparse
Chip Boling3e3b1a92017-05-16 11:51:18 -050018import datetime
19import pprint
Chip Boling7294b252017-06-15 16:16:55 -050020import shlex
21import time
Chip Boling3e3b1a92017-05-16 11:51:18 -050022
23import arrow
Chip Boling3e3b1a92017-05-16 11:51:18 -050024import structlog
Chip Boling5561d552017-07-07 15:11:26 -050025import json
Chip Boling3e3b1a92017-05-16 11:51:18 -050026from twisted.internet import reactor, defer
Chip Boling7294b252017-06-15 16:16:55 -050027from twisted.internet.defer import inlineCallbacks, returnValue
Chip Boling27275992017-09-22 15:17:04 -050028from twisted.python.failure import Failure
Chip Boling3e3b1a92017-05-16 11:51:18 -050029
Chip Boling7294b252017-06-15 16:16:55 -050030from voltha.adapters.adtran_olt.net.adtran_netconf import AdtranNetconfClient
Chip Boling3e3b1a92017-05-16 11:51:18 -050031from voltha.adapters.adtran_olt.net.adtran_rest import AdtranRestClient
32from voltha.protos import third_party
33from voltha.protos.common_pb2 import OperStatus, AdminState, ConnectStatus
34from voltha.protos.events_pb2 import AlarmEventType, \
35 AlarmEventSeverity, AlarmEventState, AlarmEventCategory
ggowdru236bd952017-06-20 20:32:55 -070036from voltha.protos.device_pb2 import Image
Chip Boling3e3b1a92017-05-16 11:51:18 -050037from voltha.protos.logical_device_pb2 import LogicalDevice
38from voltha.protos.openflow_13_pb2 import ofp_desc, ofp_switch_features, OFPC_PORT_STATS, \
39 OFPC_GROUP_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS
40from voltha.registry import registry
Chip Boling5561d552017-07-07 15:11:26 -050041from adapter_alarms import AdapterAlarms
42from common.frameio.frameio import BpfProgramFilter, hexify
43from adapter_pm_metrics import AdapterPmMetrics
Chip Boling7294b252017-06-15 16:16:55 -050044from common.utils.asleep import asleep
Chip Boling5561d552017-07-07 15:11:26 -050045from scapy.layers.l2 import Ether, Dot1Q
46from scapy.layers.inet import Raw
Chip Boling7294b252017-06-15 16:16:55 -050047
Chip Boling3e3b1a92017-05-16 11:51:18 -050048_ = third_party
49
Chip Boling27275992017-09-22 15:17:04 -050050DEFAULT_PACKET_IN_VLAN = 4000
51DEFAULT_MULTICAST_VLAN = 4050
Chip Boling252c7772017-08-16 10:13:17 -050052_MANAGEMENT_VLAN = 4093
Chip Boling5561d552017-07-07 15:11:26 -050053
Chip Bolingef0e2fa2017-10-06 14:33:01 -050054_DEFAULT_RESTCONF_USERNAME = "ADMIN"
55_DEFAULT_RESTCONF_PASSWORD = "PASSWORD"
Chip Boling252c7772017-08-16 10:13:17 -050056_DEFAULT_RESTCONF_PORT = 8081
57
Chip Bolingef0e2fa2017-10-06 14:33:01 -050058_DEFAULT_NETCONF_USERNAME = "hsvroot"
59_DEFAULT_NETCONF_PASSWORD = "BOSCO"
Chip Boling252c7772017-08-16 10:13:17 -050060_DEFAULT_NETCONF_PORT = 830
61
Chip Boling3e3b1a92017-05-16 11:51:18 -050062
63class AdtranDeviceHandler(object):
64 """
65 A device that supports the ADTRAN RESTCONF protocol for communications
66 with a VOLTHA/VANILLA managed device.
67
68 Port numbering guidelines for Adtran OLT devices. Derived classes may augment
69 the numbering scheme below as needed.
70
71 - Reserve port 0 for the CPU capture port. All ports to/from this port should
72 be related to messages destined to/from the OpenFlow controller.
73
74 - Begin numbering northbound ports (network facing) at port 1 contiguously.
75 Consider the northbound ports to typically be the highest speed uplinks.
76 If these ports are removable or provided by one or more slots in a chassis
77 subsystem, still reserve the appropriate amount of port numbers whether they
78 are populated or not.
79
80 - Number southbound ports (customer facing) ports next starting at the next
81 available port number. If chassis based, follow the same rules as northbound
82 ports and reserve enough port numbers.
83
84 - Number any out-of-band management ports (if any) last. It will be up to the
85 Device Adapter developer whether to expose these to openflow or not. If you do
86 not expose them, but do have the ports, still reserve the appropriate number of
87 port numbers just in case.
88 """
89 # HTTP shortcuts
90 HELLO_URI = '/restconf/adtran-hello:hello'
91
Chip Boling7294b252017-06-15 16:16:55 -050092 # RPC XML shortcuts
93 RESTART_RPC = '<system-restart xmlns="urn:ietf:params:xml:ns:yang:ietf-system"/>'
94
Chip Boling252c7772017-08-16 10:13:17 -050095 def __init__(self, adapter, device_id, timeout=20):
96 from net.adtran_zmq import DEFAULT_ZEROMQ_OMCI_TCP_PORT
97
Chip Boling3e3b1a92017-05-16 11:51:18 -050098 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)
Chip Boling27275992017-09-22 15:17:04 -0500102 self.startup = None # Startup/reboot defeered
Chip Boling3e3b1a92017-05-16 11:51:18 -0500103 self.channel = None # Proxy messaging channel with 'send' method
104 self.io_port = None
105 self.logical_device_id = None
106 self.interface = registry('main').get_args().interface
Chip Boling5561d552017-07-07 15:11:26 -0500107 self.pm_metrics = None
108 self.alarms = None
Chip Boling27275992017-09-22 15:17:04 -0500109 self.packet_in_vlan = DEFAULT_PACKET_IN_VLAN
110 self.multicast_vlans = [DEFAULT_MULTICAST_VLAN]
Chip Boling3e3b1a92017-05-16 11:51:18 -0500111
112 # Northbound and Southbound ports
113 self.northbound_ports = {} # port number -> Port
114 self.southbound_ports = {} # port number -> Port (For PON, use pon-id as key)
Chip Boling7294b252017-06-15 16:16:55 -0500115 # self.management_ports = {} # port number -> Port TODO: Not currently supported
Chip Boling3e3b1a92017-05-16 11:51:18 -0500116
117 self.num_northbound_ports = None
118 self.num_southbound_ports = None
Chip Boling7294b252017-06-15 16:16:55 -0500119 # self.num_management_ports = None
120
121 self.ip_address = None
122 self.timeout = timeout
123 self.restart_failure_timeout = 5 * 60 # 5 Minute timeout
Chip Boling3e3b1a92017-05-16 11:51:18 -0500124
125 # REST Client
Chip Boling252c7772017-08-16 10:13:17 -0500126 self.rest_port = _DEFAULT_RESTCONF_PORT
127 self.rest_username = _DEFAULT_RESTCONF_USERNAME
128 self.rest_password = _DEFAULT_RESTCONF_PASSWORD
Chip Boling5561d552017-07-07 15:11:26 -0500129 self._rest_client = None
Chip Boling3e3b1a92017-05-16 11:51:18 -0500130
Chip Boling7294b252017-06-15 16:16:55 -0500131 # NETCONF Client
Chip Boling252c7772017-08-16 10:13:17 -0500132 self.netconf_port = _DEFAULT_NETCONF_PORT
133 self.netconf_username = _DEFAULT_NETCONF_USERNAME
134 self.netconf_password = _DEFAULT_NETCONF_PASSWORD
Chip Boling5561d552017-07-07 15:11:26 -0500135 self._netconf_client = None
Chip Boling7294b252017-06-15 16:16:55 -0500136
Chip Boling252c7772017-08-16 10:13:17 -0500137 # If Auto-activate is true, all PON ports (up to a limit below) will be auto-enabled
138 # and any ONU's discovered will be auto-activated.
139 #
140 # If it is set to False, then the xPON API/CLI should be used to enable any PON
141 # ports. Before enabling a PON, set it's polling interval. If the polling interval
142 # is 0, then manual ONU discovery is in effect. If >0, then every 'polling' seconds
143 # autodiscover is requested. Any discovered ONUs will need to have their serial-numbers
144 # registered (via xPON API/CLI) before they are activated.
145
146 self._autoactivate = False
Chip Boling61a12792017-10-02 13:23:27 -0500147 self.max_nni_ports = 1 # TODO: This is a VOLTHA imposed limit in 'flow_decomposer.py
Chip Boling27275992017-09-22 15:17:04 -0500148 # and logical_device_agent.py
Chip Boling252c7772017-08-16 10:13:17 -0500149 # OMCI ZMQ Channel
150 self.zmq_port = DEFAULT_ZEROMQ_OMCI_TCP_PORT
151
Chip Boling3e3b1a92017-05-16 11:51:18 -0500152 # Heartbeat support
153 self.heartbeat_count = 0
154 self.heartbeat_miss = 0
Chip Boling61a12792017-10-02 13:23:27 -0500155 self.heartbeat_interval = 2 # TODO: Decrease before release or any scale testing
Chip Boling3e3b1a92017-05-16 11:51:18 -0500156 self.heartbeat_failed_limit = 3
157 self.heartbeat_timeout = 5
158 self.heartbeat = None
159 self.heartbeat_last_reason = ''
160
Chip Boling7294b252017-06-15 16:16:55 -0500161 # Virtualized OLT Support
162 self.is_virtual_olt = False
163
164 # Installed flows
Chip Boling69fba862017-08-18 15:11:32 -0500165 self._evcs = {} # Flow ID/name -> FlowEntry
Chip Boling7294b252017-06-15 16:16:55 -0500166
Chip Boling3e3b1a92017-05-16 11:51:18 -0500167 def __del__(self):
168 # Kill any startup or heartbeat defers
169
170 d, self.startup = self.startup, None
Chip Boling5561d552017-07-07 15:11:26 -0500171 h, self.heartbeat = self.heartbeat, None
172 ldi, self.logical_device_id = self.logical_device_id, None
173
Chip Boling48646962017-08-20 09:41:18 -0500174 if d is not None and not d.called:
Chip Boling3e3b1a92017-05-16 11:51:18 -0500175 d.cancel()
176
Chip Boling48646962017-08-20 09:41:18 -0500177 if h is not None and not h.called:
Chip Boling3e3b1a92017-05-16 11:51:18 -0500178 h.cancel()
179
Chip Boling5561d552017-07-07 15:11:26 -0500180 self._deactivate_io_port()
181
Chip Boling7294b252017-06-15 16:16:55 -0500182 # Remove the logical device
183
184 if ldi is not None:
185 logical_device = self.adapter_agent.get_logical_device(ldi)
186 self.adapter_agent.delete_logical_device(logical_device)
187
Chip Boling3e3b1a92017-05-16 11:51:18 -0500188 self.northbound_ports.clear()
189 self.southbound_ports.clear()
190
191 def __str__(self):
Chip Boling7294b252017-06-15 16:16:55 -0500192 return "AdtranDeviceHandler: {}".format(self.ip_address)
193
Chip Boling5561d552017-07-07 15:11:26 -0500194 @property
195 def netconf_client(self):
196 return self._netconf_client
197
198 @property
199 def rest_client(self):
200 return self._rest_client
201
Chip Boling69fba862017-08-18 15:11:32 -0500202 @property
203 def evcs(self):
204 return list(self._evcs.values())
205
206 def add_evc(self, evc):
Chip Boling27275992017-09-22 15:17:04 -0500207 if self._evcs is not None and evc.name not in self._evcs:
Chip Boling69fba862017-08-18 15:11:32 -0500208 self._evcs[evc.name] = evc
209
210 def remove_evc(self, evc):
211 if self._evcs is not None and evc.name in self._evcs:
212 del self._evcs[evc.name]
213
Chip Boling7294b252017-06-15 16:16:55 -0500214 def parse_provisioning_options(self, device):
Chip Boling252c7772017-08-16 10:13:17 -0500215 from net.adtran_zmq import DEFAULT_ZEROMQ_OMCI_TCP_PORT
216
Chip Boling7294b252017-06-15 16:16:55 -0500217 if not device.ipv4_address:
218 self.activate_failed(device, 'No ip_address field provided')
219
220 self.ip_address = device.ipv4_address
221
222 #############################################################
223 # Now optional parameters
224
225 def check_tcp_port(value):
226 ivalue = int(value)
227 if ivalue <= 0 or ivalue > 65535:
228 raise argparse.ArgumentTypeError("%s is a not a valid port number" % value)
229 return ivalue
230
Chip Boling27275992017-09-22 15:17:04 -0500231 def check_vid(value):
232 ivalue = int(value)
233 if ivalue <= 1 or ivalue > 4094:
234 raise argparse.ArgumentTypeError("Valid VLANs are 2..4094")
235 return ivalue
236
Chip Boling7294b252017-06-15 16:16:55 -0500237 parser = argparse.ArgumentParser(description='Adtran Device Adapter')
Chip Boling252c7772017-08-16 10:13:17 -0500238 parser.add_argument('--nc_username', '-u', action='store', default=_DEFAULT_NETCONF_USERNAME,
239 help='NETCONF username')
240 parser.add_argument('--nc_password', '-p', action='store', default=_DEFAULT_NETCONF_PASSWORD,
241 help='NETCONF Password')
242 parser.add_argument('--nc_port', '-t', action='store', default=_DEFAULT_NETCONF_PORT, type=check_tcp_port,
Chip Boling7294b252017-06-15 16:16:55 -0500243 help='NETCONF TCP Port')
Chip Boling252c7772017-08-16 10:13:17 -0500244 parser.add_argument('--rc_username', '-U', action='store', default=_DEFAULT_RESTCONF_USERNAME,
245 help='REST username')
246 parser.add_argument('--rc_password', '-P', action='store', default=_DEFAULT_RESTCONF_PASSWORD,
247 help='REST Password')
248 parser.add_argument('--rc_port', '-T', action='store', default=_DEFAULT_RESTCONF_PORT, type=check_tcp_port,
249 help='RESTCONF TCP Port')
250 parser.add_argument('--zmq_port', '-z', action='store', default=DEFAULT_ZEROMQ_OMCI_TCP_PORT,
251 type=check_tcp_port, help='ZeroMQ Port')
252 parser.add_argument('--autoactivate', '-a', action='store_true', default=False,
253 help='Autoactivate / Demo mode')
Chip Boling27275992017-09-22 15:17:04 -0500254 parser.add_argument('--multicast_vlan', '-M', action='store',
255 default='{}'.format(DEFAULT_MULTICAST_VLAN),
256 help='Multicast VLAN')
257 parser.add_argument('--packet_in_vlan', '-V', action='store', default=DEFAULT_PACKET_IN_VLAN,
258 type=check_vid, help='OpenFlow Packet-In/Out VLAN')
Chip Boling7294b252017-06-15 16:16:55 -0500259
260 try:
261 args = parser.parse_args(shlex.split(device.extra_args))
262
Chip Boling27275992017-09-22 15:17:04 -0500263 self.packet_in_vlan = args.packet_in_vlan
264 self._is_inband_frame = BpfProgramFilter('(ether[14:2] & 0xfff) = 0x{:03x}'.
265 format(self.packet_in_vlan))
266 # May have multiple multicast VLANs
267 self.multicast_vlans = [int(vid.strip()) for vid in args.multicast_vlan.split(',')]
268
Chip Boling7294b252017-06-15 16:16:55 -0500269 self.netconf_username = args.nc_username
270 self.netconf_password = args.nc_password
271 self.netconf_port = args.nc_port
272
273 self.rest_username = args.rc_username
274 self.rest_password = args.rc_password
275 self.rest_port = args.rc_port
276
Chip Boling252c7772017-08-16 10:13:17 -0500277 self.zmq_port = args.zmq_port
278
279 self._autoactivate = args.autoactivate
280
Chip Boling7294b252017-06-15 16:16:55 -0500281 except argparse.ArgumentError as e:
282 self.activate_failed(device,
283 'Invalid arguments: {}'.format(e.message),
284 reachable=False)
285 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500286 self.log.exception('option_parsing_error: {}'.format(e.message))
287
288 @property
289 def autoactivate(self):
290 """
291 Flag indicating if auto-discover/enable of PON ports is enabled as
292 well as ONU auto activation. useful for demos
293
294 If autoactivate is enabled, the default startup state (first time) for a PON port is disabled
Chip Boling27275992017-09-22 15:17:04 -0500295 If autoactivate is disabled, the default startup state for a PON port is enabled
Chip Boling252c7772017-08-16 10:13:17 -0500296 """
297 return self._autoactivate
Chip Boling3e3b1a92017-05-16 11:51:18 -0500298
299 @inlineCallbacks
Chip Boling7294b252017-06-15 16:16:55 -0500300 def activate(self, device, reconciling=False):
Chip Boling3e3b1a92017-05-16 11:51:18 -0500301 """
302 Activate the OLT device
303
304 :param device: A voltha.Device object, with possible device-type
Chip Boling7294b252017-06-15 16:16:55 -0500305 specific extensions.
306 :param reconciling: If True, this adapter is taking over for a previous adapter
307 for an existing OLT
Chip Boling3e3b1a92017-05-16 11:51:18 -0500308 """
Chip Boling5561d552017-07-07 15:11:26 -0500309 self.log.info('AdtranDeviceHandler.activating', reconciling=reconciling)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500310
311 if self.logical_device_id is None:
Chip Boling7294b252017-06-15 16:16:55 -0500312 # Parse our command line options for this device
313 self.parse_provisioning_options(device)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500314
315 ############################################################################
316 # Start initial discovery of RESTCONF support (if any)
Chip Boling7294b252017-06-15 16:16:55 -0500317
Chip Boling3e3b1a92017-05-16 11:51:18 -0500318 try:
Chip Boling7294b252017-06-15 16:16:55 -0500319 self.startup = self.make_restconf_connection()
Chip Boling3e3b1a92017-05-16 11:51:18 -0500320 results = yield self.startup
Chip Boling252c7772017-08-16 10:13:17 -0500321 self.log.debug('HELLO_Contents: {}'.format(pprint.PrettyPrinter().pformat(results)))
Chip Boling3e3b1a92017-05-16 11:51:18 -0500322
Chip Boling7294b252017-06-15 16:16:55 -0500323 # See if this is a virtualized OLT. If so, no NETCONF support available
324
325 self.is_virtual_olt = 'module-info' in results and\
326 any(mod.get('module-name', None) == 'adtran-ont-mock'
327 for mod in results['module-info'])
Chip Boling7294b252017-06-15 16:16:55 -0500328
Chip Boling3e3b1a92017-05-16 11:51:18 -0500329 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500330 self.log.exception('Initial_RESTCONF_hello_failed', e=e)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500331 self.activate_failed(device, e.message, reachable=False)
332
333 ############################################################################
Chip Boling7294b252017-06-15 16:16:55 -0500334 # Start initial discovery of NETCONF support (if any)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500335
Chip Boling5561d552017-07-07 15:11:26 -0500336 try:
337 self.startup = self.make_netconf_connection()
338 yield self.startup
Chip Boling3e3b1a92017-05-16 11:51:18 -0500339
Chip Boling5561d552017-07-07 15:11:26 -0500340 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500341 self.log.exception('NETCONF_connection_failed', e=e)
Chip Boling5561d552017-07-07 15:11:26 -0500342 self.activate_failed(device, e.message, reachable=False)
Chip Boling7294b252017-06-15 16:16:55 -0500343
344 ############################################################################
345 # Get the device Information
346
347 if reconciling:
348 device.connect_status = ConnectStatus.REACHABLE
349 self.adapter_agent.update_device(device)
350 else:
351 try:
352 self.startup = self.get_device_info(device)
353 results = yield self.startup
354
355 device.model = results.get('model', 'unknown')
356 device.hardware_version = results.get('hardware_version', 'unknown')
357 device.firmware_version = results.get('firmware_version', 'unknown')
Chip Boling7294b252017-06-15 16:16:55 -0500358 device.serial_number = results.get('serial_number', 'unknown')
359
Chip Boling5561d552017-07-07 15:11:26 -0500360 def get_software_images():
361 leafs = ['running-revision', 'candidate-revision', 'startup-revision']
362 image_names = list(set([results.get(img, 'unknown') for img in leafs]))
363
364 images = []
Chip Boling27275992017-09-22 15:17:04 -0500365 image_count = 1
Chip Boling5561d552017-07-07 15:11:26 -0500366 for name in image_names:
367 # TODO: Look into how to find out hash, is_valid, and install date/time
Chip Boling27275992017-09-22 15:17:04 -0500368 image = Image(name='Candidate_{}'.format(image_count),
369 version=name,
Chip Boling5561d552017-07-07 15:11:26 -0500370 is_active=(name == results.get('running-revision', 'xxx')),
Chip Boling27275992017-09-22 15:17:04 -0500371 is_committed=True,
372 is_valid=True,
373 install_datetime='Not Available')
374 image_count += 1
Chip Boling5561d552017-07-07 15:11:26 -0500375 images.append(image)
376 return images
377
378 device.images.image.extend(get_software_images())
Chip Boling7294b252017-06-15 16:16:55 -0500379 device.root = True
380 device.vendor = results.get('vendor', 'Adtran, Inc.')
381 device.connect_status = ConnectStatus.REACHABLE
382 self.adapter_agent.update_device(device)
383
384 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500385 self.log.exception('Device_info_failed', e=e)
Chip Boling7294b252017-06-15 16:16:55 -0500386 self.activate_failed(device, e.message, reachable=False)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500387
388 try:
389 # Enumerate and create Northbound NNI interfaces
390
Chip Boling27275992017-09-22 15:17:04 -0500391 device.reason = 'Enumerating NNI Interfaces'
392 self.adapter_agent.update_device(device)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500393 self.startup = self.enumerate_northbound_ports(device)
394 results = yield self.startup
395
396 self.startup = self.process_northbound_ports(device, results)
397 yield self.startup
398
Chip Boling27275992017-09-22 15:17:04 -0500399 device.reason = 'Adding NNI Interfaces to Adapter'
400 self.adapter_agent.update_device(device)
401
Chip Boling7294b252017-06-15 16:16:55 -0500402 if not reconciling:
403 for port in self.northbound_ports.itervalues():
404 self.adapter_agent.add_port(device.id, port.get_port())
Chip Boling3e3b1a92017-05-16 11:51:18 -0500405
406 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500407 self.log.exception('NNI_enumeration', e=e)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500408 self.activate_failed(device, e.message)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500409
410 try:
411 # Enumerate and create southbound interfaces
412
Chip Boling27275992017-09-22 15:17:04 -0500413 device.reason = 'Enumerating PON Interfaces'
414 self.adapter_agent.update_device(device)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500415 self.startup = self.enumerate_southbound_ports(device)
416 results = yield self.startup
417
418 self.startup = self.process_southbound_ports(device, results)
419 yield self.startup
420
Chip Boling27275992017-09-22 15:17:04 -0500421 device.reason = 'Adding PON Interfaces to Adapter'
422 self.adapter_agent.update_device(device)
423
Chip Boling7294b252017-06-15 16:16:55 -0500424 if not reconciling:
425 for port in self.southbound_ports.itervalues():
426 self.adapter_agent.add_port(device.id, port.get_port())
Chip Boling3e3b1a92017-05-16 11:51:18 -0500427
428 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500429 self.log.exception('PON_enumeration', e=e)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500430 self.activate_failed(device, e.message)
431
Chip Boling7294b252017-06-15 16:16:55 -0500432 if reconciling:
433 if device.admin_state == AdminState.ENABLED:
434 if device.parent_id:
435 self.logical_device_id = device.parent_id
436 self.adapter_agent.reconcile_logical_device(device.parent_id)
437 else:
438 self.log.info('no-logical-device-set')
Chip Boling3e3b1a92017-05-16 11:51:18 -0500439
Chip Boling7294b252017-06-15 16:16:55 -0500440 # Reconcile child devices
441 self.adapter_agent.reconcile_child_devices(device.id)
Chip Boling5561d552017-07-07 15:11:26 -0500442 ld_initialized = self.adapter_agent.get_logical_device()
Chip Boling27275992017-09-22 15:17:04 -0500443 assert device.parent_id == ld_initialized.id, \
444 'parent ID not Logical device ID'
Chip Boling5561d552017-07-07 15:11:26 -0500445
Chip Boling7294b252017-06-15 16:16:55 -0500446 else:
447 # Complete activation by setting up logical device for this OLT and saving
448 # off the devices parent_id
Chip Boling3e3b1a92017-05-16 11:51:18 -0500449
Chip Boling5561d552017-07-07 15:11:26 -0500450 ld_initialized = self.create_logical_device(device)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500451
Chip Boling5561d552017-07-07 15:11:26 -0500452 ############################################################################
453 # Setup PM configuration for this device
Chip Boling27275992017-09-22 15:17:04 -0500454 try:
455 device.reason = 'Setting up PM configuration'
456 self.adapter_agent.update_device(device)
Chip Boling5561d552017-07-07 15:11:26 -0500457
Chip Boling27275992017-09-22 15:17:04 -0500458 self.pm_metrics = AdapterPmMetrics(self.adapter, device)
459 pm_config = self.pm_metrics.make_proto()
460 self.log.info("initial-pm-config", pm_config=pm_config)
461 self.adapter_agent.update_device_pm_config(pm_config, init=True)
462
463 except Exception as e:
464 self.log.exception('pm-setup', e=e)
465 self.activate_failed(device, e.message)
Chip Boling5561d552017-07-07 15:11:26 -0500466
467 ############################################################################
468 # Setup Alarm handler
469
Chip Boling27275992017-09-22 15:17:04 -0500470 device.reason = 'Setting up Adapter Alarms'
471 self.adapter_agent.update_device(device)
472
Chip Boling5561d552017-07-07 15:11:26 -0500473 self.alarms = AdapterAlarms(self.adapter, device)
474
475 ############################################################################
Chip Boling7294b252017-06-15 16:16:55 -0500476 # Create logical ports for all southbound and northbound interfaces
Chip Boling5561d552017-07-07 15:11:26 -0500477 try:
Chip Boling27275992017-09-22 15:17:04 -0500478 device.reason = 'Creating logical ports'
479 self.adapter_agent.update_device(device)
Chip Boling5561d552017-07-07 15:11:26 -0500480 self.startup = self.create_logical_ports(device, ld_initialized, reconciling)
481 yield self.startup
Chip Boling3e3b1a92017-05-16 11:51:18 -0500482
Chip Boling5561d552017-07-07 15:11:26 -0500483 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500484 self.log.exception('logical-port', e=e)
Chip Boling5561d552017-07-07 15:11:26 -0500485 self.activate_failed(device, e.message)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500486
Chip Boling27275992017-09-22 15:17:04 -0500487 ############################################################################
488 # Register for ONU detection
489 # self.adapter_agent.register_for_onu_detect_state(device.id)
490
Chip Boling3e3b1a92017-05-16 11:51:18 -0500491 # Complete device specific steps
492 try:
Chip Boling252c7772017-08-16 10:13:17 -0500493 self.log.debug('device-activation-procedures')
Chip Boling7294b252017-06-15 16:16:55 -0500494 self.startup = self.complete_device_specific_activation(device, reconciling)
Chip Boling5561d552017-07-07 15:11:26 -0500495 yield self.startup
Chip Boling3e3b1a92017-05-16 11:51:18 -0500496
497 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500498 self.log.exception('device-activation-procedures', e=e)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500499 self.activate_failed(device, e.message)
500
501 # Schedule the heartbeat for the device
502
Chip Boling27275992017-09-22 15:17:04 -0500503 self.log.debug('starting-heartbeat')
504 self.start_heartbeat(delay=10)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500505
Chip Boling3e3b1a92017-05-16 11:51:18 -0500506 device = self.adapter_agent.get_device(device.id)
Chip Boling5561d552017-07-07 15:11:26 -0500507 device.parent_id = ld_initialized.id
Chip Boling3e3b1a92017-05-16 11:51:18 -0500508 device.oper_status = OperStatus.ACTIVE
Chip Boling5561d552017-07-07 15:11:26 -0500509 device.reason = ''
Chip Boling3e3b1a92017-05-16 11:51:18 -0500510 self.adapter_agent.update_device(device)
Chip Boling252c7772017-08-16 10:13:17 -0500511 self.logical_device_id = ld_initialized.id
Chip Boling3e3b1a92017-05-16 11:51:18 -0500512
Chip Boling5561d552017-07-07 15:11:26 -0500513 # finally, open the frameio port to receive in-band packet_in messages
514 self._activate_io_port()
515
516 # Start collecting stats from the device after a brief pause
Chip Boling27275992017-09-22 15:17:04 -0500517 reactor.callLater(10, self.start_kpi_collection, device.id)
Chip Boling5561d552017-07-07 15:11:26 -0500518
Chip Boling27275992017-09-22 15:17:04 -0500519 self.log.info('activated')
Chip Boling5561d552017-07-07 15:11:26 -0500520
Chip Boling3e3b1a92017-05-16 11:51:18 -0500521 def activate_failed(self, device, reason, reachable=True):
522 """
523 Activation process (adopt_device) has failed.
524
525 :param device: A voltha.Device object, with possible device-type
526 specific extensions. Such extensions shall be described as part of
527 the device type specification returned by device_types().
528 :param reason: (string) failure reason
529 :param reachable: (boolean) Flag indicating if device may be reachable
530 via RESTConf or NETConf even after this failure.
531 """
532 device.oper_status = OperStatus.FAILED
533 if not reachable:
534 device.connect_status = ConnectStatus.UNREACHABLE
535
536 device.reason = reason
537 self.adapter_agent.update_device(device)
538 raise RuntimeError('Failed to activate OLT: {}'.format(device.reason))
539
Chip Boling5561d552017-07-07 15:11:26 -0500540 @inlineCallbacks
Chip Boling7294b252017-06-15 16:16:55 -0500541 def make_netconf_connection(self, connect_timeout=None):
542 ############################################################################
543 # Start initial discovery of NETCONF support
544
Chip Boling5561d552017-07-07 15:11:26 -0500545 client = self._netconf_client
546
547 if client is None:
548 if not self.is_virtual_olt:
549 client = AdtranNetconfClient(self.ip_address,
550 self.netconf_port,
551 self.netconf_username,
552 self.netconf_password,
553 self.timeout)
554 else:
555 from voltha.adapters.adtran_olt.net.mock_netconf_client import MockNetconfClient
556 client = MockNetconfClient(self.ip_address,
557 self.netconf_port,
558 self.netconf_username,
559 self.netconf_password,
560 self.timeout)
561 if client.connected:
562 self._netconf_client = client
563 returnValue(True)
Chip Boling7294b252017-06-15 16:16:55 -0500564
565 timeout = connect_timeout or self.timeout
Chip Boling7294b252017-06-15 16:16:55 -0500566
Chip Boling5561d552017-07-07 15:11:26 -0500567 try:
568 request = client.connect(timeout)
569 results = yield request
570 self._netconf_client = client
571 returnValue(results)
572
573 except Exception as e:
574 self.log.exception('Failed to create NETCONF Client', e=e)
575 self._netconf_client = None
576 raise
577
578 @inlineCallbacks
Chip Boling7294b252017-06-15 16:16:55 -0500579 def make_restconf_connection(self, get_timeout=None):
Chip Boling5561d552017-07-07 15:11:26 -0500580 client = self._rest_client
581
582 if client is None:
583 client = AdtranRestClient(self.ip_address,
584 self.rest_port,
585 self.rest_username,
586 self.rest_password,
587 self.timeout)
Chip Boling7294b252017-06-15 16:16:55 -0500588
589 timeout = get_timeout or self.timeout
Chip Boling5561d552017-07-07 15:11:26 -0500590
591 try:
592 request = client.request('GET', self.HELLO_URI, name='hello', timeout=timeout)
593 results = yield request
594 if isinstance(results, dict) and 'module-info' in results:
595 self._rest_client = client
596 returnValue(results)
597 else:
598 from twisted.internet.error import ConnectError
599 self._rest_client = None
600 raise ConnectError(string='Results received but unexpected data type or contents')
601 except Exception:
602 self._rest_client = None
603 raise
Chip Boling7294b252017-06-15 16:16:55 -0500604
605 def create_logical_device(self, device):
Chip Boling5561d552017-07-07 15:11:26 -0500606 version = device.images.image[0].version
607
Chip Boling7294b252017-06-15 16:16:55 -0500608 ld = LogicalDevice(
609 # NOTE: not setting id and datapath_id will let the adapter agent pick id
610 desc=ofp_desc(mfr_desc=device.vendor,
611 hw_desc=device.hardware_version,
Chip Boling5561d552017-07-07 15:11:26 -0500612 sw_desc=version,
Chip Boling7294b252017-06-15 16:16:55 -0500613 serial_num=device.serial_number,
614 dp_desc='n/a'),
Chip Boling27275992017-09-22 15:17:04 -0500615 switch_features=ofp_switch_features(n_buffers=256,
616 n_tables=2,
Chip Boling7294b252017-06-15 16:16:55 -0500617 capabilities=(
Chip Boling5561d552017-07-07 15:11:26 -0500618 OFPC_FLOW_STATS |
619 OFPC_TABLE_STATS |
620 OFPC_GROUP_STATS |
Chip Boling7294b252017-06-15 16:16:55 -0500621 OFPC_PORT_STATS)),
622 root_device_id=device.id)
623
624 ld_initialized = self.adapter_agent.create_logical_device(ld)
625
626 return ld_initialized
627
628 @inlineCallbacks
629 def create_logical_ports(self, device, ld_initialized, reconciling):
Chip Boling7294b252017-06-15 16:16:55 -0500630 if not reconciling:
Chip Boling27275992017-09-22 15:17:04 -0500631 # Add the ports to the logical device
632
Chip Boling7294b252017-06-15 16:16:55 -0500633 for port in self.northbound_ports.itervalues():
634 lp = port.get_logical_port()
635 if lp is not None:
636 self.adapter_agent.add_logical_port(ld_initialized.id, lp)
637
638 for port in self.southbound_ports.itervalues():
639 lp = port.get_logical_port()
640 if lp is not None:
641 self.adapter_agent.add_logical_port(ld_initialized.id, lp)
642
643 # Set the ports in a known good initial state
644 try:
645 for port in self.northbound_ports.itervalues():
Chip Boling5561d552017-07-07 15:11:26 -0500646 self.startup = yield port.reset()
Chip Boling7294b252017-06-15 16:16:55 -0500647
Chip Boling7294b252017-06-15 16:16:55 -0500648 for port in self.southbound_ports.itervalues():
Chip Boling5561d552017-07-07 15:11:26 -0500649 self.startup = yield port.reset()
Chip Boling7294b252017-06-15 16:16:55 -0500650
651 except Exception as e:
Chip Boling27275992017-09-22 15:17:04 -0500652 self.log.exception('port-reset', e=e)
653 self.activate_failed(device, e.message)
Chip Boling7294b252017-06-15 16:16:55 -0500654
Chip Boling27275992017-09-22 15:17:04 -0500655 # Clean up all EVC and EVC maps (exceptions are ok)
Chip Boling252c7772017-08-16 10:13:17 -0500656 try:
657 from flow.evc import EVC
658 self.startup = yield EVC.remove_all(self.netconf_client)
659
660 except Exception as e:
Chip Boling27275992017-09-22 15:17:04 -0500661 self.log.exception('evc-cleanup', e=e)
Chip Boling252c7772017-08-16 10:13:17 -0500662
663 try:
664 from flow.evc_map import EVCMap
665 self.startup = yield EVCMap.remove_all(self.netconf_client)
666
667 except Exception as e:
Chip Boling27275992017-09-22 15:17:04 -0500668 self.log.exception('evc-map-cleanup', e=e)
Chip Boling252c7772017-08-16 10:13:17 -0500669
Chip Boling27275992017-09-22 15:17:04 -0500670 # Start/stop the interfaces as needed. These are deferred calls
Chip Boling7294b252017-06-15 16:16:55 -0500671
Chip Bolingef0e2fa2017-10-06 14:33:01 -0500672 dl = []
673 for port in self.northbound_ports.itervalues():
674 try:
Chip Boling27275992017-09-22 15:17:04 -0500675 dl.append(port.start())
Chip Bolingef0e2fa2017-10-06 14:33:01 -0500676 except Exception as e:
677 self.log.exception('northbound-port-startup', e=e)
Chip Boling7294b252017-06-15 16:16:55 -0500678
Chip Bolingef0e2fa2017-10-06 14:33:01 -0500679 for port in self.southbound_ports.itervalues():
680 try:
Chip Boling27275992017-09-22 15:17:04 -0500681 dl.append(port.start() if port.admin_state == AdminState.ENABLED else port.stop())
682
Chip Bolingef0e2fa2017-10-06 14:33:01 -0500683 except Exception as e:
684 self.log.exception('southbound-port-startup', e=e)
Chip Boling27275992017-09-22 15:17:04 -0500685
Chip Bolingef0e2fa2017-10-06 14:33:01 -0500686 results = yield defer.gatherResults(dl)
Chip Boling7294b252017-06-15 16:16:55 -0500687
Chip Boling5561d552017-07-07 15:11:26 -0500688 returnValue(results)
Chip Boling7294b252017-06-15 16:16:55 -0500689
690 @inlineCallbacks
691 def device_information(self, device):
692 """
693 Examine the various managment models and extract device information for
694 VOLTHA use
695
696 :param device: A voltha.Device object, with possible device-type
697 specific extensions.
698 :return: (Deferred or None).
699 """
700 yield defer.Deferred(lambda c: c.callback("Not Required"))
701
Chip Boling3e3b1a92017-05-16 11:51:18 -0500702 @inlineCallbacks
703 def enumerate_northbound_ports(self, device):
704 """
705 Enumerate all northbound ports of a device. You should override
706 this method in your derived class as necessary. Should you encounter
707 a non-recoverable error, throw an appropriate exception.
708
709 :param device: A voltha.Device object, with possible device-type
710 specific extensions.
711 :return: (Deferred or None).
712 """
713 yield defer.Deferred(lambda c: c.callback("Not Required"))
714
715 @inlineCallbacks
716 def process_northbound_ports(self, device, results):
717 """
718 Process the results from the 'enumerate_northbound_ports' method.
719 You should override this method in your derived class as necessary and
720 create an NNI Port object (of your own choosing) that supports a 'get_port'
721 method. Once created, insert it into this base class's northbound_ports
722 collection.
723
724 Should you encounter a non-recoverable error, throw an appropriate exception.
725
726 :param device: A voltha.Device object, with possible device-type
727 specific extensions.
728 :param results: Results from the 'enumerate_northbound_ports' method that
729 you implemented. The type and contents are up to you to
730 :return:
731 """
732 yield defer.Deferred(lambda c: c.callback("Not Required"))
733
734 @inlineCallbacks
735 def enumerate_southbound_ports(self, device):
736 """
737 Enumerate all southbound ports of a device. You should override
738 this method in your derived class as necessary. Should you encounter
739 a non-recoverable error, throw an appropriate exception.
740
741 :param device: A voltha.Device object, with possible device-type
742 specific extensions.
743 :return: (Deferred or None).
744 """
745 yield defer.Deferred(lambda c: c.callback("Not Required"))
746
747 @inlineCallbacks
748 def process_southbound_ports(self, device, results):
749 """
750 Process the results from the 'enumerate_southbound_ports' method.
751 You should override this method in your derived class as necessary and
752 create an Port object (of your own choosing) that supports a 'get_port'
753 method. Once created, insert it into this base class's southbound_ports
754 collection.
755
756 Should you encounter a non-recoverable error, throw an appropriate exception.
757
758 :param device: A voltha.Device object, with possible device-type
759 specific extensions.
760 :param results: Results from the 'enumerate_southbound_ports' method that
761 you implemented. The type and contents are up to you to
762 :return:
763 """
764 yield defer.Deferred(lambda c: c.callback("Not Required"))
765
Chip Boling5561d552017-07-07 15:11:26 -0500766 # TODO: Move some of the items below from here and the EVC to a utility class
767
768 def is_nni_port(self, port):
769 return port in self.northbound_ports
770
771 def is_uni_port(self, port):
772 raise NotImplementedError('implement in derived class')
773
774 def is_pon_port(self, port):
775 raise NotImplementedError('implement in derived class')
776
777 def is_logical_port(self, port):
778 return not self.is_nni_port(port) and not self.is_uni_port(port) and not self.is_pon_port(port)
779
780 def get_port_name(self, port):
781 raise NotImplementedError('implement in derived class')
782
Chip Boling3e3b1a92017-05-16 11:51:18 -0500783 @inlineCallbacks
Chip Boling7294b252017-06-15 16:16:55 -0500784 def complete_device_specific_activation(self, _device, _reconciling):
Chip Boling5561d552017-07-07 15:11:26 -0500785 return defer.succeed('NOP')
Chip Boling3e3b1a92017-05-16 11:51:18 -0500786
787 def deactivate(self, device):
788 # Clear off logical device ID
789 self.logical_device_id = None
790
791 # Kill any heartbeat poll
792 h, self.heartbeat = self.heartbeat, None
793
Chip Boling27275992017-09-22 15:17:04 -0500794 try:
795 if h is not None and not h.called:
796 h.cancel()
797 except:
798 pass
Chip Boling3e3b1a92017-05-16 11:51:18 -0500799
Chip Boling7294b252017-06-15 16:16:55 -0500800 # TODO: What else (delete logical device, ???)
801
Chip Boling7294b252017-06-15 16:16:55 -0500802 def disable(self):
803 """
804 This is called when a previously enabled device needs to be disabled based on a NBI call.
805 """
806 self.log.info('disabling', device_id=self.device_id)
807
Chip Boling69fba862017-08-18 15:11:32 -0500808 # Cancel any running enable/disable/... in progress
809 d, self.startup = self.startup, None
Chip Boling27275992017-09-22 15:17:04 -0500810 try:
811 if d is not None and not d.called:
812 d.cancel()
813 except:
814 pass
Chip Boling7294b252017-06-15 16:16:55 -0500815 # Get the latest device reference
816 device = self.adapter_agent.get_device(self.device_id)
817
Chip Boling5561d552017-07-07 15:11:26 -0500818 # Deactivate in-band packets
819 self._deactivate_io_port()
820
Chip Boling27275992017-09-22 15:17:04 -0500821 # Drop registration for ONU detection
822 # self.adapter_agent.unregister_for_onu_detect_state(self.device.id)
823
Chip Boling7294b252017-06-15 16:16:55 -0500824 # Suspend any active healthchecks / pings
825
826 h, self.heartbeat = self.heartbeat, None
Chip Boling27275992017-09-22 15:17:04 -0500827 try:
828 if h is not None and not h.called:
829 h.cancel()
830 except:
831 pass
Chip Boling7294b252017-06-15 16:16:55 -0500832 # Update the operational status to UNKNOWN
833
834 device.oper_status = OperStatus.UNKNOWN
835 device.connect_status = ConnectStatus.UNREACHABLE
836 self.adapter_agent.update_device(device)
837
838 # Remove the logical device
839 ldi, self.logical_device_id = self.logical_device_id, None
840
841 if ldi is not None:
842 logical_device = self.adapter_agent.get_logical_device(ldi)
843 self.adapter_agent.delete_logical_device(logical_device)
844
845 # Disable all child devices first
846 self.adapter_agent.update_child_devices_state(self.device_id,
847 admin_state=AdminState.DISABLED)
848
849 # Remove the peer references from this device
850 self.adapter_agent.delete_all_peer_references(self.device_id)
851
852 # Set all ports to disabled
853 self.adapter_agent.disable_all_ports(self.device_id)
854
Chip Boling5561d552017-07-07 15:11:26 -0500855 dl = []
Chip Boling7294b252017-06-15 16:16:55 -0500856 for port in self.northbound_ports.itervalues():
Chip Boling5561d552017-07-07 15:11:26 -0500857 dl.append(port.stop())
Chip Boling7294b252017-06-15 16:16:55 -0500858
859 for port in self.southbound_ports.itervalues():
Chip Boling5561d552017-07-07 15:11:26 -0500860 dl.append(port.stop())
Chip Boling7294b252017-06-15 16:16:55 -0500861
Chip Boling69fba862017-08-18 15:11:32 -0500862 # NOTE: Flows removed before this method is called
863 # Wait for completion
864
Chip Boling5561d552017-07-07 15:11:26 -0500865 self.startup = defer.gatherResults(dl)
Chip Boling7294b252017-06-15 16:16:55 -0500866
Chip Boling69fba862017-08-18 15:11:32 -0500867 def _drop_netconf():
868 return self.netconf_client.close() if \
869 self.netconf_client is not None else defer.succeed('NOP')
Chip Boling7294b252017-06-15 16:16:55 -0500870
871 def _null_clients():
Chip Boling5561d552017-07-07 15:11:26 -0500872 self._netconf_client = None
873 self._rest_client = None
Chip Boling7294b252017-06-15 16:16:55 -0500874
Chip Boling69fba862017-08-18 15:11:32 -0500875 # Shutdown communications with OLT
Chip Boling7294b252017-06-15 16:16:55 -0500876
Chip Boling69fba862017-08-18 15:11:32 -0500877 self.startup.addCallbacks(_drop_netconf, _null_clients)
878 self.startup.addCallbacks(_null_clients, _null_clients)
879
880 # Update the logical device mapping
Chip Boling252c7772017-08-16 10:13:17 -0500881 if ldi in self.adapter.logical_device_id_to_root_device_id:
882 del self.adapter.logical_device_id_to_root_device_id[ldi]
883
Chip Boling7294b252017-06-15 16:16:55 -0500884 self.log.info('disabled', device_id=device.id)
Chip Boling69fba862017-08-18 15:11:32 -0500885 return self.startup
Chip Boling7294b252017-06-15 16:16:55 -0500886
887 @inlineCallbacks
888 def reenable(self):
889 """
890 This is called when a previously disabled device needs to be enabled based on a NBI call.
891 """
892 self.log.info('re-enabling', device_id=self.device_id)
893
Chip Boling69fba862017-08-18 15:11:32 -0500894 # Cancel any running enable/disable/... in progress
895 d, self.startup = self.startup, None
Chip Boling27275992017-09-22 15:17:04 -0500896 try:
897 if d is not None and not d.called:
898 d.cancel()
899 except:
900 pass
Chip Boling7294b252017-06-15 16:16:55 -0500901 # Get the latest device reference
902 device = self.adapter_agent.get_device(self.device_id)
903
904 # Update the connect status to REACHABLE
905 device.connect_status = ConnectStatus.REACHABLE
906 self.adapter_agent.update_device(device)
907
908 # Set all ports to enabled
909 self.adapter_agent.enable_all_ports(self.device_id)
910
911 try:
912 yield self.make_restconf_connection()
913
914 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500915 self.log.exception('adtran-hello-reconnect', e=e)
Chip Boling7294b252017-06-15 16:16:55 -0500916
Chip Boling5561d552017-07-07 15:11:26 -0500917 try:
918 yield self.make_netconf_connection()
Chip Boling7294b252017-06-15 16:16:55 -0500919
Chip Boling5561d552017-07-07 15:11:26 -0500920 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500921 self.log.exception('NETCONF-re-connection', e=e)
Chip Boling7294b252017-06-15 16:16:55 -0500922
923 # Recreate the logical device
924
925 ld_initialized = self.create_logical_device(device)
926
927 # Create logical ports for all southbound and northbound interfaces
928
Chip Boling27275992017-09-22 15:17:04 -0500929 try:
930 self.startup = self.create_logical_ports(device, ld_initialized, False)
931 yield self.startup
932
933 except Exception as e:
934 self.log.exception('logical-port-creation', e=e)
Chip Boling7294b252017-06-15 16:16:55 -0500935
936 device = self.adapter_agent.get_device(device.id)
937 device.parent_id = ld_initialized.id
938 device.oper_status = OperStatus.ACTIVE
Chip Boling5561d552017-07-07 15:11:26 -0500939 device.reason = ''
Chip Boling7294b252017-06-15 16:16:55 -0500940 self.adapter_agent.update_device(device)
941 self.logical_device_id = ld_initialized.id
942
943 # Reenable all child devices
944 self.adapter_agent.update_child_devices_state(device.id,
945 admin_state=AdminState.ENABLED)
Chip Boling5561d552017-07-07 15:11:26 -0500946 dl = []
Chip Boling7294b252017-06-15 16:16:55 -0500947
948 for port in self.northbound_ports.itervalues():
Chip Boling5561d552017-07-07 15:11:26 -0500949 dl.append(port.start())
Chip Boling7294b252017-06-15 16:16:55 -0500950
951 for port in self.southbound_ports.itervalues():
Chip Boling5561d552017-07-07 15:11:26 -0500952 dl.append(port.start())
953
Chip Boling69fba862017-08-18 15:11:32 -0500954 # Flows should not exist on re-enable. They are re-pushed
955 if len(self._evcs):
956 self.log.error('evcs-found', evcs=self._evcs)
957 self._evcs.clear()
958
959 # Wait for completion
960
Chip Boling5561d552017-07-07 15:11:26 -0500961 self.startup = defer.gatherResults(dl)
962 results = yield self.startup
Chip Boling7294b252017-06-15 16:16:55 -0500963
Chip Boling27275992017-09-22 15:17:04 -0500964 # Re-subscribe for ONU detection
965 # self.adapter_agent.register_for_onu_detect_state(self.device.id)
966
Chip Boling7294b252017-06-15 16:16:55 -0500967 # TODO:
968 # 1) Restart health check / pings
Chip Boling5561d552017-07-07 15:11:26 -0500969
970 # Activate in-band packets
971 self._activate_io_port()
Chip Boling7294b252017-06-15 16:16:55 -0500972
973 self.log.info('re-enabled', device_id=device.id)
Chip Boling5561d552017-07-07 15:11:26 -0500974 returnValue(results)
Chip Boling7294b252017-06-15 16:16:55 -0500975
976 @inlineCallbacks
977 def reboot(self):
978 """
979 This is called to reboot a device based on a NBI call. The admin state of the device
980 will not change after the reboot.
981 """
982 self.log.debug('reboot')
983
Chip Boling69fba862017-08-18 15:11:32 -0500984 # Cancel any running enable/disable/... in progress
985 d, self.startup = self.startup, None
Chip Boling27275992017-09-22 15:17:04 -0500986 try:
987 if d is not None and not d.called:
988 d.cancel()
989 except:
990 pass
991 # Issue reboot command
992
993 if not self.is_virtual_olt:
994 try:
995 yield self.netconf_client.rpc(AdtranDeviceHandler.RESTART_RPC)
996
997 except Exception as e:
998 self.log.exception('NETCONF-shutdown', e=e)
999 returnValue(defer.fail(Failure()))
1000
1001 # self.adapter_agent.unregister_for_onu_detect_state(self.device.id)
Chip Boling69fba862017-08-18 15:11:32 -05001002
Chip Boling7294b252017-06-15 16:16:55 -05001003 # Update the operational status to ACTIVATING and connect status to
1004 # UNREACHABLE
1005
1006 device = self.adapter_agent.get_device(self.device_id)
1007 previous_oper_status = device.oper_status
1008 previous_conn_status = device.connect_status
1009 device.oper_status = OperStatus.ACTIVATING
1010 device.connect_status = ConnectStatus.UNREACHABLE
1011 self.adapter_agent.update_device(device)
1012
1013 # Update the child devices connect state to UNREACHABLE
1014 self.adapter_agent.update_child_devices_state(self.device_id,
1015 connect_status=ConnectStatus.UNREACHABLE)
Chip Boling7294b252017-06-15 16:16:55 -05001016
Chip Boling27275992017-09-22 15:17:04 -05001017 # Shutdown communications with OLT. Typically it takes about 2 seconds
1018 # or so after the reply before the restart actually occurs
Chip Boling7294b252017-06-15 16:16:55 -05001019
Chip Boling27275992017-09-22 15:17:04 -05001020 try:
1021 response = yield self.netconf_client.close()
1022 self.log.debug('Restart response XML was: {}'.format('ok' if response.ok else 'bad'))
Chip Boling7294b252017-06-15 16:16:55 -05001023
Chip Boling27275992017-09-22 15:17:04 -05001024 except Exception as e:
1025 self.log.exception('NETCONF-client-shutdown', e=e)
Chip Boling7294b252017-06-15 16:16:55 -05001026
Chip Boling27275992017-09-22 15:17:04 -05001027 # Clear off clients
Chip Boling7294b252017-06-15 16:16:55 -05001028
Chip Boling5561d552017-07-07 15:11:26 -05001029 self._netconf_client = None
1030 self._rest_client = None
Chip Boling7294b252017-06-15 16:16:55 -05001031
1032 # Run remainder of reboot process as a new task. The OLT then may be up in a
1033 # few moments or may take 3 minutes or more depending on any self tests enabled
1034
Chip Boling5561d552017-07-07 15:11:26 -05001035 current_time = time.time()
Chip Boling7294b252017-06-15 16:16:55 -05001036 timeout = current_time + self.restart_failure_timeout
1037
Chip Boling27275992017-09-22 15:17:04 -05001038 self.startup = reactor.callLater(10, self._finish_reboot, timeout,
1039 previous_oper_status,
1040 previous_conn_status)
1041 returnValue(self.startup)
Chip Boling7294b252017-06-15 16:16:55 -05001042
1043 @inlineCallbacks
1044 def _finish_reboot(self, timeout, previous_oper_status, previous_conn_status):
1045 # Now wait until REST & NETCONF are re-established or we timeout
1046
Chip Boling252c7772017-08-16 10:13:17 -05001047 self.log.info('Resuming-activity',
Chip Boling5561d552017-07-07 15:11:26 -05001048 remaining=timeout - time.time(), timeout=timeout, current=time.time())
1049
1050 if self.rest_client is None:
Chip Boling7294b252017-06-15 16:16:55 -05001051 try:
Chip Boling27275992017-09-22 15:17:04 -05001052 yield self.make_restconf_connection(get_timeout=10)
Chip Boling5561d552017-07-07 15:11:26 -05001053
1054 except Exception:
1055 self.log.debug('No RESTCONF connection yet')
1056 self._rest_client = None
1057
1058 if self.netconf_client is None:
1059 try:
1060 yield self.make_netconf_connection(connect_timeout=10)
Chip Boling7294b252017-06-15 16:16:55 -05001061
1062 except Exception as e:
Chip Boling7294b252017-06-15 16:16:55 -05001063 try:
Chip Boling5561d552017-07-07 15:11:26 -05001064 if self.netconf_client is not None:
1065 yield self.netconf_client.close()
Chip Boling7294b252017-06-15 16:16:55 -05001066 except Exception as e:
1067 self.log.exception(e.message)
1068 finally:
Chip Boling5561d552017-07-07 15:11:26 -05001069 self._netconf_client = None
Chip Boling7294b252017-06-15 16:16:55 -05001070
1071 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 -05001072 current_time = time.time()
Chip Boling7294b252017-06-15 16:16:55 -05001073 if current_time < timeout:
Chip Boling27275992017-09-22 15:17:04 -05001074 self.startup = reactor.callLater(5, self._finish_reboot, timeout,
1075 previous_oper_status,
1076 previous_conn_status)
1077 returnValue(self.startup)
Chip Boling7294b252017-06-15 16:16:55 -05001078
1079 if self.netconf_client is None and not self.is_virtual_olt:
Chip Boling252c7772017-08-16 10:13:17 -05001080 self.log.error('NETCONF-restore-failure')
Chip Boling7294b252017-06-15 16:16:55 -05001081 pass # TODO: What is best course of action if cannot get clients back?
1082
1083 if self.rest_client is None:
Chip Boling252c7772017-08-16 10:13:17 -05001084 self.log.error('RESTCONF-restore-failure')
Chip Boling7294b252017-06-15 16:16:55 -05001085 pass # TODO: What is best course of action if cannot get clients back?
1086
Chip Boling5561d552017-07-07 15:11:26 -05001087 # Pause additional 5 seconds to let allow OLT microservices to complete some more initialization
Chip Boling7294b252017-06-15 16:16:55 -05001088 yield asleep(5)
1089
1090 # Get the latest device reference
1091
1092 device = self.adapter_agent.get_device(self.device_id)
1093 device.oper_status = previous_oper_status
1094 device.connect_status = previous_conn_status
1095 self.adapter_agent.update_device(device)
1096
1097 # Update the child devices connect state to REACHABLE
1098 self.adapter_agent.update_child_devices_state(self.device_id,
1099 connect_status=ConnectStatus.REACHABLE)
Chip Boling69fba862017-08-18 15:11:32 -05001100 # Restart ports to previous state
1101
1102 dl = []
1103
1104 for port in self.northbound_ports.itervalues():
1105 dl.append(port.restart())
1106
1107 for port in self.southbound_ports.itervalues():
1108 dl.append(port.restart())
1109
1110 try:
1111 yield defer.gatherResults(dl)
1112 except Exception as e:
1113 self.log.exception('port-restart', e=e)
1114
Chip Boling27275992017-09-22 15:17:04 -05001115 # Re-subscribe for ONU detection
1116 # self.adapter_agent.register_for_onu_detect_state(self.device.id)
1117
Chip Boling69fba862017-08-18 15:11:32 -05001118 # Request reflow of any EVC/EVC-MAPs
1119
1120 if len(self._evcs) > 0:
1121 dl = []
1122 for evc in self.evcs:
1123 dl.append(evc.reflow())
1124
1125 try:
1126 yield defer.gatherResults(dl)
1127 except Exception as e:
1128 self.log.exception('flow-restart', e=e)
Chip Boling7294b252017-06-15 16:16:55 -05001129
Chip Boling7294b252017-06-15 16:16:55 -05001130 self.log.info('rebooted', device_id=self.device_id)
Chip Boling5561d552017-07-07 15:11:26 -05001131 returnValue('Rebooted')
Chip Boling7294b252017-06-15 16:16:55 -05001132
1133 @inlineCallbacks
1134 def delete(self):
1135 """
1136 This is called to delete a device from the PON based on a NBI call.
1137 If the device is an OLT then the whole PON will be deleted.
1138 """
1139 self.log.info('deleting', device_id=self.device_id)
1140
1141 # Cancel any outstanding tasks
1142
1143 d, self.startup = self.startup, None
Chip Boling27275992017-09-22 15:17:04 -05001144 try:
1145 if d is not None and not d.called:
1146 d.cancel()
1147 except:
1148 pass
Chip Boling7294b252017-06-15 16:16:55 -05001149 h, self.heartbeat = self.heartbeat, None
Chip Boling27275992017-09-22 15:17:04 -05001150 try:
1151 if h is not None and not h.called:
1152 h.cancel()
1153 except:
1154 pass
1155 # self.adapter_agent.unregister_for_onu_detect_state(self.device.id)
Chip Boling7294b252017-06-15 16:16:55 -05001156
Chip Boling5561d552017-07-07 15:11:26 -05001157 # Remove all flows from the device
1158 # TODO: Create a bulk remove-all by device-id
Chip Boling7294b252017-06-15 16:16:55 -05001159
Chip Boling69fba862017-08-18 15:11:32 -05001160 evcs = self._evcs()
1161 self._evcs.clear()
Chip Boling5561d552017-07-07 15:11:26 -05001162
Chip Boling69fba862017-08-18 15:11:32 -05001163 for evc in evcs:
1164 evc.delete() # TODO: implement bulk-flow procedures
Chip Boling7294b252017-06-15 16:16:55 -05001165
1166 # Remove all child devices
1167 self.adapter_agent.delete_all_child_devices(self.device_id)
1168
1169 # Remove the logical device
1170 logical_device = self.adapter_agent.get_logical_device(self.logical_device_id)
1171 self.adapter_agent.delete_logical_device(logical_device)
Chip Boling5561d552017-07-07 15:11:26 -05001172 # TODO: For some reason, the logical device does not seem to get deleted
Chip Boling7294b252017-06-15 16:16:55 -05001173
1174 # Remove the peer references from this device
1175 self.adapter_agent.delete_all_peer_references(self.device_id)
1176
1177 # Tell all ports to stop any background processing
1178
1179 for port in self.northbound_ports.itervalues():
1180 port.delete()
1181
1182 for port in self.southbound_ports.itervalues():
1183 port.delete()
1184
1185 self.northbound_ports.clear()
1186 self.southbound_ports.clear()
1187
1188 # Shutdown communications with OLT
1189
1190 if self.netconf_client is not None:
1191 try:
1192 yield self.netconf_client.close()
1193 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -05001194 self.log.exception('NETCONF-shutdown', e=e)
Chip Boling7294b252017-06-15 16:16:55 -05001195
Chip Boling5561d552017-07-07 15:11:26 -05001196 self._netconf_client = None
Chip Boling7294b252017-06-15 16:16:55 -05001197
Chip Boling5561d552017-07-07 15:11:26 -05001198 self._rest_client = None
Chip Boling7294b252017-06-15 16:16:55 -05001199
1200 self.log.info('deleted', device_id=self.device_id)
1201
Chip Boling5561d552017-07-07 15:11:26 -05001202 def _activate_io_port(self):
1203 if self.io_port is None:
1204 self.log.info('registering-frameio')
1205 self.io_port = registry('frameio').open_port(
Chip Boling27275992017-09-22 15:17:04 -05001206 self.interface, self._rcv_io, self._is_inband_frame)
Chip Boling5561d552017-07-07 15:11:26 -05001207
1208 def _deactivate_io_port(self):
1209 io, self.io_port = self.io_port, None
1210
1211 if io is not None:
1212 registry('frameio').close_port(io)
1213
1214 def _rcv_io(self, port, frame):
1215 self.log.info('received', iface_name=port.iface_name, frame_len=len(frame))
1216
1217 pkt = Ether(frame)
1218 if pkt.haslayer(Dot1Q):
1219 outer_shim = pkt.getlayer(Dot1Q)
1220
1221 if isinstance(outer_shim.payload, Dot1Q):
1222 inner_shim = outer_shim.payload
1223 cvid = inner_shim.vlan
1224 logical_port = cvid
1225 popped_frame = (Ether(src=pkt.src, dst=pkt.dst, type=inner_shim.type) /
1226 inner_shim.payload)
1227 kw = dict(
1228 logical_device_id=self.logical_device_id,
1229 logical_port_no=logical_port,
1230 )
1231 self.log.info('sending-packet-in', **kw)
1232 self.adapter_agent.send_packet_in(
1233 packet=str(popped_frame), **kw)
1234
1235 elif pkt.haslayer(Raw):
1236 raw_data = json.loads(pkt.getlayer(Raw).load)
1237 self.alarms.send_alarm(self, raw_data)
1238
1239 def packet_out(self, egress_port, msg):
1240 if self.io_port is not None:
1241 self.log.info('sending-packet-out', egress_port=egress_port,
1242 msg=hexify(msg))
1243 pkt = Ether(msg)
Chip Boling27275992017-09-22 15:17:04 -05001244
1245 #ADTRAN To remove any extra tags
1246 while ( pkt.type == 0x8100 ):
1247 msg_hex=hexify(msg)
1248 msg_hex=msg_hex[:24]+msg_hex[32:]
1249 bytes = []
1250 msg_hex = ''.join( msg_hex.split(" ") )
1251 for i in range(0, len(msg_hex), 2):
1252 bytes.append( chr( int (msg_hex[i:i+2], 16 ) ) )
1253 msg = ''.join( bytes )
1254 pkt = Ether(msg)
1255 #END
1256
Chip Boling5561d552017-07-07 15:11:26 -05001257 out_pkt = (
1258 Ether(src=pkt.src, dst=pkt.dst) /
Chip Boling27275992017-09-22 15:17:04 -05001259 Dot1Q(vlan=self.packet_in_vlan) /
Chip Boling5561d552017-07-07 15:11:26 -05001260 Dot1Q(vlan=egress_port, type=pkt.type) /
1261 pkt.payload
1262 )
1263 self.io_port.send(str(out_pkt))
1264
1265 def update_pm_config(self, device, pm_config):
1266 # TODO: This has not been tested
1267 self.log.info('update_pm_config', pm_config=pm_config)
1268 self.pm_metrics.update(pm_config)
1269
1270 def start_kpi_collection(self, device_id):
1271 # TODO: This has not been tested
1272 def _collect(device_id, prefix):
1273 from voltha.protos.events_pb2 import KpiEvent, KpiEventType, MetricValuePairs
Chip Boling27275992017-09-22 15:17:04 -05001274 import random
Chip Boling5561d552017-07-07 15:11:26 -05001275
1276 try:
1277 # Step 1: gather metrics from device
Chip Boling27275992017-09-22 15:17:04 -05001278 port_metrics = self.pm_metrics.collect_port_metrics()
Chip Boling5561d552017-07-07 15:11:26 -05001279
1280 # Step 2: prepare the KpiEvent for submission
1281 # we can time-stamp them here (or could use time derived from OLT
1282 ts = arrow.utcnow().timestamp
1283 kpi_event = KpiEvent(
1284 type=KpiEventType.slice,
1285 ts=ts,
1286 prefixes={
1287 # OLT NNI port
1288 prefix + '.nni': MetricValuePairs(metrics=port_metrics['nni']),
1289 # OLT PON port
1290 prefix + '.pon': MetricValuePairs(metrics=port_metrics['pon'])
1291 }
1292 )
1293 # Step 3: submit
1294 self.adapter_agent.submit_kpis(kpi_event)
1295
1296 except Exception as e:
1297 self.log.exception('failed-to-submit-kpis', e=e)
1298
Chip Boling27275992017-09-22 15:17:04 -05001299 self.pm_metrics.start_collector(_collect)
Chip Boling5561d552017-07-07 15:11:26 -05001300
Chip Boling3e3b1a92017-05-16 11:51:18 -05001301 @inlineCallbacks
1302 def get_device_info(self, device):
1303 """
1304 Perform an initial network operation to discover the device hardware
1305 and software version. Serial Number would be helpful as well.
1306
1307 Upon successfully retrieving the information, remember to call the
1308 'start_heartbeat' method to keep in contact with the device being managed
1309
1310 :param device: A voltha.Device object, with possible device-type
1311 specific extensions. Such extensions shall be described as part of
1312 the device type specification returned by device_types().
1313 """
Chip Boling7294b252017-06-15 16:16:55 -05001314 device = {}
Chip Boling7294b252017-06-15 16:16:55 -05001315 returnValue(device)
Chip Boling3e3b1a92017-05-16 11:51:18 -05001316
1317 def start_heartbeat(self, delay=10):
Chip Boling27275992017-09-22 15:17:04 -05001318 assert delay > 1, 'Minimum heartbeat is 1 second'
Chip Boling252c7772017-08-16 10:13:17 -05001319 self.log.info('Starting-Device-Heartbeat ***')
Chip Boling3e3b1a92017-05-16 11:51:18 -05001320 self.heartbeat = reactor.callLater(delay, self.check_pulse)
Chip Boling5561d552017-07-07 15:11:26 -05001321 return self.heartbeat
Chip Boling3e3b1a92017-05-16 11:51:18 -05001322
1323 def check_pulse(self):
1324 if self.logical_device_id is not None:
Chip Bolingef0e2fa2017-10-06 14:33:01 -05001325 try:
1326 self.heartbeat = self.rest_client.request('GET', self.HELLO_URI,
1327 name='hello', timeout=5)
1328 self.heartbeat.addCallbacks(self._heartbeat_success, self._heartbeat_fail)
Chip Boling3e3b1a92017-05-16 11:51:18 -05001329
Chip Bolingef0e2fa2017-10-06 14:33:01 -05001330 except Exception as e:
1331 self.heartbeat = reactor.callLater(5, self._heartbeat_fail, e)
1332
1333 def heartbeat_check_status(self, _):
Chip Boling3e3b1a92017-05-16 11:51:18 -05001334 """
1335 Check the number of heartbeat failures against the limit and emit an alarm if needed
1336 """
1337 device = self.adapter_agent.get_device(self.device_id)
1338
Chip Bolingef0e2fa2017-10-06 14:33:01 -05001339 try:
1340 if self.heartbeat_miss >= self.heartbeat_failed_limit:
1341 if device.connect_status == ConnectStatus.REACHABLE:
1342 self.log.warning('heartbeat-failed', count=self.heartbeat_miss)
1343 device.connect_status = ConnectStatus.UNREACHABLE
1344 device.oper_status = OperStatus.FAILED
1345 device.reason = self.heartbeat_last_reason
1346 self.adapter_agent.update_device(device)
1347 self.heartbeat_alarm(False, self.heartbeat_miss)
1348 else:
1349 # Update device states
1350 if device.connect_status != ConnectStatus.REACHABLE:
1351 device.connect_status = ConnectStatus.REACHABLE
1352 device.oper_status = OperStatus.ACTIVE
1353 device.reason = ''
1354 self.adapter_agent.update_device(device)
1355 self.heartbeat_alarm(True)
Chip Boling3e3b1a92017-05-16 11:51:18 -05001356
Chip Bolingef0e2fa2017-10-06 14:33:01 -05001357 except Exception as e:
1358 self.log.exception('heartbeat-check', e=e)
Chip Boling3e3b1a92017-05-16 11:51:18 -05001359
1360 # Reschedule next heartbeat
1361 if self.logical_device_id is not None:
Chip Bolingef0e2fa2017-10-06 14:33:01 -05001362 self.heartbeat_count += 1
Chip Boling3e3b1a92017-05-16 11:51:18 -05001363 self.heartbeat = reactor.callLater(self.heartbeat_interval, self.check_pulse)
1364
Chip Bolingef0e2fa2017-10-06 14:33:01 -05001365 def _heartbeat_success(self, results):
1366 self.log.debug('heartbeat-success')
1367 self.heartbeat_miss = 0
1368 self.heartbeat_last_reason = ''
1369 self.heartbeat_check_status(results)
1370
1371 def _heartbeat_fail(self, failure):
Chip Boling3e3b1a92017-05-16 11:51:18 -05001372 self.heartbeat_miss += 1
1373 self.log.info('heartbeat-miss', failure=failure,
Chip Bolingef0e2fa2017-10-06 14:33:01 -05001374 count=self.heartbeat_count,
1375 miss=self.heartbeat_miss)
1376 self.heartbeat_last_reason = 'RESTCONF connectivity error'
Chip Boling3e3b1a92017-05-16 11:51:18 -05001377 self.heartbeat_check_status(None)
1378
Chip Boling5561d552017-07-07 15:11:26 -05001379 def heartbeat_alarm(self, status, heartbeat_misses=0):
1380 alarm = 'Heartbeat'
1381 alarm_data = {
1382 'ts': arrow.utcnow().timestamp,
1383 'description': self.alarms.format_description('olt', alarm, status),
1384 'id': self.alarms.format_id(alarm),
1385 'type': AlarmEventType.EQUIPMENT,
1386 'category': AlarmEventCategory.PON,
1387 'severity': AlarmEventSeverity.CRITICAL,
1388 'state': AlarmEventState.RAISED if status else AlarmEventState.CLEARED
1389 }
1390 context_data = {'heartbeats_missed': heartbeat_misses}
1391 self.alarms.send_alarm(context_data, alarm_data)
Chip Boling3e3b1a92017-05-16 11:51:18 -05001392
1393 @staticmethod
1394 def parse_module_revision(revision):
1395 try:
1396 return datetime.datetime.strptime(revision, '%Y-%m-%d')
1397 except Exception:
1398 return None
Chip Bolingef0e2fa2017-10-06 14:33:01 -05001399
1400 @staticmethod
1401 def _dict_diff(lhs, rhs):
1402 """
1403 Compare the values of two dictionaries and return the items in 'rhs'
1404 that are different than 'lhs. The RHS dictionary keys can be a subset of the
1405 LHS dictionary, or the RHS dictionary keys can contain new values.
1406
1407 :param lhs: (dict) Original dictionary values
1408 :param rhs: (dict) New dictionary values to compare to the original (lhs) dict
1409 :return: (dict) Dictionary with differences from the RHS dictionary
1410 """
1411 assert len(lhs.keys()) == len(set(lhs.iterkeys()) & (rhs.iterkeys())), 'Dictionary Keys do not match'
1412 return {k: v for k, v in rhs.items() if k not in lhs or lhs[k] != rhs[k]}