blob: 92c3398752fc16930f87810cf796778f466f57d5 [file] [log] [blame]
Chip Bolingf5af85d2019-02-12 15:36:17 -06001# Copyright 2017-present Adtran, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import datetime
16import random
17import xmltodict
18
19from twisted.internet import reactor
20from twisted.internet.defer import returnValue, inlineCallbacks, succeed
21
22from codec.olt_state import OltState
23from adapters.adtran_common.download import Download
24from adapters.adtran_common.flow.flow_entry import FlowEntry
25from net.pio_zmq import PioClient
26from net.pon_zmq import PonClient
27from resources.adtran_olt_resource_manager import AdtranOltResourceMgr
28from adapters.adtran_common.adtran_device_handler import AdtranDeviceHandler
29from resources import adtranolt_platform as platform
30from adapters.adtran_common.net.rcmd import RCmd
31
32from pyvoltha.common.tech_profile.tech_profile import *
33from pyvoltha.common.openflow.utils import ofp, mk_flow_stat, in_port, output, vlan_vid
34from pyvoltha.adapters.common.frameio.frameio import hexify
35from pyvoltha.adapters.extensions.omci.omci import *
36from pyvoltha.protos.voltha_pb2 import Device
37from pyvoltha.protos.common_pb2 import AdminState, OperStatus
38from pyvoltha.protos.device_pb2 import ImageDownload, Image, Port
39from pyvoltha.protos.openflow_13_pb2 import OFPP_MAX, OFPC_GROUP_STATS, OFPC_PORT_STATS, \
40 OFPC_TABLE_STATS, OFPC_FLOW_STATS, ofp_switch_features, ofp_desc
41
42
43class AdtranOltHandler(AdtranDeviceHandler):
44 """
45 The OLT Handler is used to wrap a single instance of a 10G OLT 1-U pizza-box
46 """
47 MIN_OLT_HW_VERSION = datetime.datetime(2017, 1, 5)
48
49 # Full table output
50
51 GPON_OLT_HW_URI = '/restconf/data/gpon-olt-hw'
52 GPON_OLT_HW_STATE_URI = GPON_OLT_HW_URI + ':olt-state'
53 GPON_OLT_HW_CONFIG_URI = GPON_OLT_HW_URI + ':olt'
54 GPON_PON_CONFIG_LIST_URI = GPON_OLT_HW_CONFIG_URI + '/pon'
55
56 # Per-PON info
57
58 GPON_PON_STATE_URI = GPON_OLT_HW_STATE_URI + '/pon={}' # .format(pon-id)
59 GPON_PON_CONFIG_URI = GPON_PON_CONFIG_LIST_URI + '={}' # .format(pon-id)
60
61 GPON_ONU_CONFIG_LIST_URI = GPON_PON_CONFIG_URI + '/onus/onu' # .format(pon-id)
62 GPON_ONU_CONFIG_URI = GPON_ONU_CONFIG_LIST_URI + '={}' # .format(pon-id,onu-id)
63
64 GPON_TCONT_CONFIG_LIST_URI = GPON_ONU_CONFIG_URI + '/t-conts/t-cont' # .format(pon-id,onu-id)
65 GPON_TCONT_CONFIG_URI = GPON_TCONT_CONFIG_LIST_URI + '={}' # .format(pon-id,onu-id,alloc-id)
66
67 GPON_GEM_CONFIG_LIST_URI = GPON_ONU_CONFIG_URI + '/gem-ports/gem-port' # .format(pon-id,onu-id)
68 GPON_GEM_CONFIG_URI = GPON_GEM_CONFIG_LIST_URI + '={}' # .format(pon-id,onu-id,gem-id)
69
70 GPON_PON_DISCOVER_ONU = '/restconf/operations/gpon-olt-hw:discover-onu'
71
72 BASE_ONU_OFFSET = 64
73
74 def __init__(self, **kwargs):
75 super(AdtranOltHandler, self).__init__(**kwargs)
76
77 self.status_poll = None
78 self.status_poll_interval = 5.0
79 self.status_poll_skew = self.status_poll_interval / 10
80 self._pon_agent = None
81 self._pio_agent = None
82 self._ssh_deferred = None
83 self._system_id = None
84 self._download_protocols = None
85 self._download_deferred = None
86 self._downloads = {} # name -> Download obj
87 self._pio_exception_map = []
88
89 self.downstream_shapping_supported = True # 1971320F1-ML-4154 and later
90
91 # FIXME: Remove once we containerize. Only exists to keep BroadCom OpenOMCI ONU Happy
92 # when it reaches up our rear and tries to yank out a UNI port number
93 self.platform_class = None
94
95 # To keep broadcom ONU happy
96 self.platform = platform() # TODO: Remove once tech-profiles & containerization are done !!!
97
98 def __del__(self):
99 # OLT Specific things here.
100 #
101 # If you receive this during 'enable' of the object, you probably threw an
102 # uncaught exception which triggered an errback in the VOLTHA core.
103 d, self.status_poll = self.status_poll, None
104
105 # Clean up base class as well
106 AdtranDeviceHandler.__del__(self)
107
108 def _cancel_deferred(self):
109 d1, self.status_poll = self.status_poll, None
110 d2, self._ssh_deferred = self._ssh_deferred, None
111 d3, self._download_deferred = self._download_deferred, None
112
113 for d in [d1, d2, d3]:
114 try:
115 if d is not None and not d.called:
116 d.cancel()
117 except:
118 pass
119
120 def __str__(self):
121 return "AdtranOltHandler: {}".format(self.ip_address)
122
123 @property
124 def system_id(self):
125 return self._system_id
126
127 @system_id.setter
128 def system_id(self, value):
129 if self._system_id != value:
130 self._system_id = value
131
132 data = json.dumps({'olt-id': str(value)})
133 uri = AdtranOltHandler.GPON_OLT_HW_CONFIG_URI
134 self.rest_client.request('PATCH', uri, data=data, name='olt-system-id')
135
136 @inlineCallbacks
137 def get_device_info(self, device):
138 """
139 Perform an initial network operation to discover the device hardware
140 and software version. Serial Number would be helpful as well.
141
142 Upon successfully retrieving the information, remember to call the
143 'start_heartbeat' method to keep in contact with the device being managed
144
145 :param device: A voltha.Device object, with possible device-type
146 specific extensions. Such extensions shall be described as part of
147 the device type specification returned by device_types().
148 """
149 from codec.physical_entities_state import PhysicalEntitiesState
150 # TODO: After a CLI 'reboot' command, the device info may get messed up (prints labels and not values)
151 # # Enter device and type 'show'
152 device = {
153 'model': 'n/a',
154 'hardware_version': 'unknown',
155 'serial_number': 'unknown',
156 'vendor': 'ADTRAN, Inc.',
157 'firmware_version': 'unknown',
158 'running-revision': 'unknown',
159 'candidate-revision': 'unknown',
160 'startup-revision': 'unknown',
161 'software-images': []
162 }
Chip Bolingf5af85d2019-02-12 15:36:17 -0600163 try:
164 pe_state = PhysicalEntitiesState(self.netconf_client)
165 self.startup = pe_state.get_state()
166 results = yield self.startup
167
168 if results.ok:
169 modules = pe_state.get_physical_entities('adtn-phys-mod:module')
170
171 if isinstance(modules, list):
172 module = modules[0]
173
174 name = str(module.get('model-name', 'n/a')).translate(None, '?')
175 model = str(module.get('model-number', 'n/a')).translate(None, '?')
176
177 device['model'] = '{} - {}'.format(name, model) if len(name) > 0 else \
178 module.get('parent-entity', 'n/a')
179 device['hardware_version'] = str(module.get('hardware-revision',
180 'n/a')).translate(None, '?')
181 device['serial_number'] = str(module.get('serial-number',
182 'n/a')).translate(None, '?')
183 if 'software' in module:
184 if 'software' in module['software']:
185 software = module['software']['software']
186 if isinstance(software, dict):
187 device['running-revision'] = str(software.get('running-revision',
188 'n/a')).translate(None, '?')
189 device['candidate-revision'] = str(software.get('candidate-revision',
190 'n/a')).translate(None, '?')
191 device['startup-revision'] = str(software.get('startup-revision',
192 'n/a')).translate(None, '?')
193 elif isinstance(software, list):
194 for sw_item in software:
195 sw_type = sw_item.get('name', '').lower()
196 if sw_type == 'firmware':
197 device['firmware_version'] = str(sw_item.get('running-revision',
198 'unknown')).translate(None, '?')
199 elif sw_type == 'software':
200 for rev_type in ['startup-revision',
201 'running-revision',
202 'candidate-revision']:
203 if rev_type in sw_item:
204 image = Image(name=rev_type,
205 version=sw_item[rev_type],
206 is_active=(rev_type == 'running-revision'),
207 is_committed=True,
208 is_valid=True,
209 install_datetime='Not Available',
210 hash='Not Available')
211 device['software-images'].append(image)
212
213 # Update features based on version
214 # Format expected to be similar to: 1971320F1-ML-4154
215
216 running_version = next((image.version for image in device.get('software-images', list())
217 if image.is_active), '').split('-')
218 if len(running_version) > 2:
219 try:
220 self.downstream_shapping_supported = int(running_version[-1]) >= 4154
221 except ValueError:
222 pass
223
224 except Exception as e:
225 self.log.exception('dev-info-failure', e=e)
226 raise
227
228 returnValue(device)
229
230 def initialize_resource_manager(self):
231 # Initialize the resource and tech profile managers
232 extra_args = '--olt_model {}'.format(self.resource_manager_key)
233 self.resource_mgr = AdtranOltResourceMgr(self.device_id,
234 self.host_and_port,
235 extra_args,
236 self.default_resource_mgr_device_info)
237 self._populate_tech_profile_per_pon_port()
238
239 @property
240 def default_resource_mgr_device_info(self):
241 class AdtranOltDevInfo(object):
242 def __init__(self, pon_ports):
243 self.technology = "xgspon"
244 self.onu_id_start = 0
245 self.onu_id_end = platform.MAX_ONUS_PER_PON
246 self.alloc_id_start = platform.MIN_TCONT_ALLOC_ID
247 self.alloc_id_end = platform.MAX_TCONT_ALLOC_ID
248 self.gemport_id_start = platform.MIN_GEM_PORT_ID
249 self.gemport_id_end = platform.MAX_GEM_PORT_ID
250 self.pon_ports = len(pon_ports)
251 self.max_tconts = platform.MAX_TCONTS_PER_ONU
252 self.max_gem_ports = platform.MAX_GEM_PORTS_PER_ONU
253 self.intf_ids = pon_ports.keys() # PON IDs
254
255 return AdtranOltDevInfo(self.southbound_ports)
256
257 def _populate_tech_profile_per_pon_port(self):
258 self.tech_profiles = {intf_id: self.resource_mgr.resource_managers[intf_id].tech_profile
259 for intf_id in self.resource_mgr.device_info.intf_ids}
260
261 # Make sure we have as many tech_profiles as there are pon ports on
262 # the device
263 assert len(self.tech_profiles) == self.resource_mgr.device_info.pon_ports
264
265 def get_tp_path(self, intf_id, ofp_port_name):
266 # TODO: Should get Table id form the flow, as of now hardcoded to DEFAULT_TECH_PROFILE_TABLE_ID (64)
267 # 'tp_path' contains the suffix part of the tech_profile_instance path.
268 # The prefix to the 'tp_path' should be set to \
269 # TechProfile.KV_STORE_TECH_PROFILE_PATH_PREFIX by the ONU adapter.
270 return self.tech_profiles[intf_id].get_tp_path(DEFAULT_TECH_PROFILE_TABLE_ID,
271 ofp_port_name)
272
273 def delete_tech_profile_instance(self, intf_id, onu_id, logical_port):
274 # Remove the TP instance associated with the ONU
275 ofp_port_name = self.get_ofp_port_name(intf_id, onu_id, logical_port)
276 tp_path = self.get_tp_path(intf_id, ofp_port_name)
277 return self.tech_profiles[intf_id].delete_tech_profile_instance(tp_path)
278
279 def get_ofp_port_name(self, pon_id, onu_id, logical_port_number):
280 parent_port_no = self.pon_id_to_port_number(pon_id)
281 child_device = self.adapter_agent.get_child_device(self.device_id,
282 parent_port_no=parent_port_no, onu_id=onu_id)
283 if child_device is None:
284 self.log.error("could-not-find-child-device", parent_port_no=pon_id, onu_id=onu_id)
285 return None, None
286
287 ports = self.adapter_agent.get_ports(child_device.id, Port.ETHERNET_UNI)
288 port = next((port for port in ports if port.port_no == logical_port_number), None)
289 logical_port = self.adapter_agent.get_logical_port(self.logical_device_id,
290 port.label)
291 ofp_port_name = (logical_port.ofp_port.name, logical_port.ofp_port.port_no)
292
293 return ofp_port_name
294
295 @inlineCallbacks
296 def enumerate_northbound_ports(self, device):
297 """
298 Enumerate all northbound ports of this device.
299
300 :param device: A voltha.Device object, with possible device-type
301 specific extensions.
302 :return: (Deferred or None).
303 """
304 try:
305 # Also get the MAC Address for the OLT
306 command = "ip link | grep -A1 eth0 | sed -n -e 's/^.*ether //p' | awk '{ print $1 }'"
307 rcmd = RCmd(self.ip_address, self.netconf_username, self.netconf_password,
308 command)
309 address = yield rcmd.execute()
310 self.mac_address = address.replace('\n', '')
311 self.log.info("mac-addr", mac_addr=self.mac_address)
312
313 except Exception as e:
314 log.exception('mac-address', e=e)
315 raise
316
317 try:
318 from codec.ietf_interfaces import IetfInterfacesState
319 from nni_port import MockNniPort
320
321 ietf_interfaces = IetfInterfacesState(self.netconf_client)
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500322 self.startup = ietf_interfaces.get_state()
323 results = yield self.startup
Chip Bolingf5af85d2019-02-12 15:36:17 -0600324
325 ports = ietf_interfaces.get_port_entries(results, 'ethernet')
326 returnValue(ports)
327
328 except Exception as e:
329 log.exception('enumerate_northbound_ports', e=e)
330 raise
331
332 def process_northbound_ports(self, device, results):
333 """
334 Process the results from the 'enumerate_northbound_ports' method.
335
336 :param device: A voltha.Device object, with possible device-type
337 specific extensions.
338 :param results: Results from the 'enumerate_northbound_ports' method that
339 you implemented. The type and contents are up to you to
340 :return: (Deferred or None).
341 """
342 from nni_port import NniPort, MockNniPort
343
344 for port in results.itervalues():
345 port_no = port.get('port_no')
346 assert port_no, 'Port number not found'
347
348 # May already exist if device was not fully reachable when first enabled
349 if port_no not in self.northbound_ports:
350 self.log.info('processing-nni', port_no=port_no, name=port['port_no'])
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500351 self.northbound_ports[port_no] = NniPort(self, **port)
Chip Bolingf5af85d2019-02-12 15:36:17 -0600352
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500353 if len(self.northbound_ports) >= self.max_nni_ports: # TODO: For now, limit number of NNI ports to make debugging easier
Chip Bolingf5af85d2019-02-12 15:36:17 -0600354 break
355
356 self.num_northbound_ports = len(self.northbound_ports)
357
358 def _olt_version(self):
359 # Version
360 # 0 Unknown
361 # 1 V1 OMCI format
362 # 2 V2 OMCI format
363 # 3 2018-01-11 or later
364 version = 0
365 info = self._rest_support.get('module-info', [dict()])
366 hw_mod_ver_str = next((mod.get('revision') for mod in info
367 if mod.get('module-name', '').lower() == 'gpon-olt-hw'), None)
368
369 if hw_mod_ver_str is not None:
370 try:
371 from datetime import datetime
372 hw_mod_dt = datetime.strptime(hw_mod_ver_str, '%Y-%m-%d')
373 version = 2 if hw_mod_dt >= datetime(2017, 9, 21) else 2
374
375 except Exception as e:
376 self.log.exception('ver-str-check', e=e)
377
378 return version
379
380 @inlineCallbacks
381 def enumerate_southbound_ports(self, device):
382 """
383 Enumerate all southbound ports of this device.
384
385 :param device: A voltha.Device object, with possible device-type
386 specific extensions.
387 :return: (Deferred or None).
388 """
389 ###############################################################################
390 # Determine number of southbound ports. We know it is 16, but this keeps this
391 # device adapter generic for our other OLTs up to this point.
392
393 self.startup = self.rest_client.request('GET', self.GPON_PON_CONFIG_LIST_URI,
394 'pon-config')
395 try:
396 from codec.ietf_interfaces import IetfInterfacesState
397 from nni_port import MockNniPort
398
399 results = yield self.startup
400
401 ietf_interfaces = IetfInterfacesState(self.netconf_client)
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500402 self.startup = ietf_interfaces.get_state()
403 nc_results = yield self.startup
Chip Bolingf5af85d2019-02-12 15:36:17 -0600404
405 ports = ietf_interfaces.get_port_entries(nc_results, 'xpon')
406 if len(ports) == 0:
407 ports = ietf_interfaces.get_port_entries(nc_results,
408 'channel-termination')
409 for data in results:
410 pon_id = data['pon-id']
411 port = ports[pon_id + 1]
412 port['pon-id'] = pon_id
413 port['admin_state'] = AdminState.ENABLED \
414 if data.get('enabled', True)\
415 else AdminState.DISABLED
416
417 except Exception as e:
418 log.exception('enumerate_southbound_ports', e=e)
419 raise
420
421 returnValue(ports)
422
423 def process_southbound_ports(self, device, results):
424 """
425 Process the results from the 'enumerate_southbound_ports' method.
426
427 :param device: A voltha.Device object, with possible device-type
428 specific extensions.
429 :param results: Results from the 'enumerate_southbound_ports' method that
430 you implemented. The type and contents are up to you to
431 :return: (Deferred or None).
432 """
433 from pon_port import PonPort
434
435 for pon in results.itervalues():
436 pon_id = pon.get('pon-id')
437 assert pon_id is not None, 'PON ID not found'
438 if pon['ifIndex'] is None:
439 pon['port_no'] = self.pon_id_to_port_number(pon_id)
440 else:
441 pass # Need to adjust ONU numbering !!!!
442
443 # May already exist if device was not fully reachable when first enabled
444 if pon_id not in self.southbound_ports:
445 self.southbound_ports[pon_id] = PonPort(self, **pon)
446
447 self.num_southbound_ports = len(self.southbound_ports)
448
449 def pon(self, pon_id):
450 return self.southbound_ports.get(pon_id)
451
452 def complete_device_specific_activation(self, device, reconciling):
453 """
454 Perform an initial network operation to discover the device hardware
455 and software version. Serial Number would be helpful as well.
456
457 This method is called from within the base class's activate generator.
458
459 :param device: A voltha.Device object, with possible device-type
460 specific extensions. Such extensions shall be described as part of
461 the device type specification returned by device_types().
462
463 :param reconciling: (boolean) True if taking over for another VOLTHA
464 """
465 # ZeroMQ clients
466 self._zmq_startup()
467
468 # Download support
469 self._download_deferred = reactor.callLater(0, self._get_download_protocols)
470
471 # Register for adapter messages
472 self.adapter_agent.register_for_inter_adapter_messages()
473
474 # PON Status
475 self.status_poll = reactor.callLater(5, self.poll_for_status)
476 return succeed('Done')
477
478 def on_heatbeat_alarm(self, active):
479 if not active:
480 self.ready_network_access()
481
482 @inlineCallbacks
483 def _get_download_protocols(self):
484 if self._download_protocols is None:
485 try:
486 config = '<filter>' + \
487 '<file-servers-state xmlns="http://www.adtran.com/ns/yang/adtran-file-servers">' + \
488 '<profiles>' + \
489 '<supported-protocol/>' + \
490 '</profiles>' + \
491 '</file-servers-state>' + \
492 '</filter>'
493
494 results = yield self.netconf_client.get(config)
495
496 result_dict = xmltodict.parse(results.data_xml)
497 entries = result_dict['data']['file-servers-state']['profiles']['supported-protocol']
498 self._download_protocols = [entry['#text'].split(':')[-1] for entry in entries
499 if '#text' in entry]
500
501 except Exception as e:
502 self.log.exception('protocols', e=e)
503 self._download_protocols = None
504 self._download_deferred = reactor.callLater(10, self._get_download_protocols)
505
506 @inlineCallbacks
507 def ready_network_access(self):
508 # Check for port status
509 command = 'netstat -pan | grep -i 0.0.0.0:{} | wc -l'.format(self.pon_agent_port)
510 rcmd = RCmd(self.ip_address, self.netconf_username, self.netconf_password, command)
511
512 try:
513 self.log.debug('check-request', command=command)
514 results = yield rcmd.execute()
515 self.log.info('check-results', results=results, result_type=type(results))
516 create_it = int(results) != 1
517
518 except Exception as e:
519 self.log.exception('find', e=e)
520 create_it = True
521
522 if create_it:
523 def v1_method():
524 command = 'mkdir -p /etc/pon_agent; touch /etc/pon_agent/debug.conf; '
525 command += 'ps -ae | grep -i ngpon2_agent; '
526 command += 'service_supervisor stop ngpon2_agent; service_supervisor start ngpon2_agent; '
527 command += 'ps -ae | grep -i ngpon2_agent'
528
529 self.log.debug('create-request', command=command)
530 return RCmd(self.ip_address, self.netconf_username, self.netconf_password, command)
531
532 def v2_v3_method():
533 # Old V2 method
534 # For V2 images, want -> export ZMQ_LISTEN_ON_ANY_ADDRESS=1
535 # For V3+ images, want -> export AGENT_LISTEN_ON_ANY_ADDRESS=1
536
537 # V3 unifies listening port, compatible with v2
538 cmd = "sed --in-place '/add feature/aexport ZMQ_LISTEN_ON_ANY_ADDRESS=1' " \
539 "/etc/ngpon2_agent/ngpon2_agent_feature_flags; "
540 cmd += "sed --in-place '/add feature/aexport AGENT_LISTEN_ON_ANY_ADDRESS=1' " \
541 "/etc/ngpon2_agent/ngpon2_agent_feature_flags; "
542
543 # Note: 'ps' commands are to help decorate the logfile with useful info
544 cmd += 'ps -ae | grep -i ngpon2_agent; '
545 cmd += 'service_supervisor stop ngpon2_agent; service_supervisor start ngpon2_agent; '
546 cmd += 'ps -ae | grep -i ngpon2_agent'
547
548 self.log.debug('create-request', command=cmd)
549 return RCmd(self.ip_address, self.netconf_username, self.netconf_password, cmd)
550
551 # Look for version
552 next_run = 15
553 version = v2_v3_method # NOTE: Only v2 or later supported.
554
555 if version is not None:
556 try:
557 rcmd = version()
558 results = yield rcmd.execute()
559 self.log.info('create-results', results=results, result_type=type(results))
560
561 except Exception as e:
562 self.log.exception('mkdir-and-restart', e=e)
563 else:
564 next_run = 0
565
566 if next_run > 0:
567 self._ssh_deferred = reactor.callLater(next_run, self.ready_network_access)
568
569 returnValue('retrying' if next_run > 0 else 'ready')
570
571 def _zmq_startup(self):
572 # ZeroMQ clients
573 self._pon_agent = PonClient(self.ip_address,
574 port=self.pon_agent_port,
575 rx_callback=self.rx_pa_packet)
576
577 try:
578 self._pio_agent = PioClient(self.ip_address,
579 port=self.pio_port,
580 rx_callback=self.rx_pio_packet)
581 except Exception as e:
582 self._pio_agent = None
583 self.log.exception('pio-agent', e=e)
584
585 def _zmq_shutdown(self):
586 pon, self._pon_agent = self._pon_agent, None
587 pio, self._pio_agent = self._pio_agent, None
588
589 for c in [pon, pio]:
590 if c is not None:
591 try:
592 c.shutdown()
593 except:
594 pass
595
596 def _unregister_for_inter_adapter_messages(self):
597 try:
598 self.adapter_agent.unregister_for_inter_adapter_messages()
599 except:
600 pass
601
602 def disable(self):
603 self._cancel_deferred()
604
605 # Drop registration for adapter messages
606 self._unregister_for_inter_adapter_messages()
607 self._zmq_shutdown()
608 self._pio_exception_map = []
609
610 super(AdtranOltHandler, self).disable()
611
612 def reenable(self, done_deferred=None):
613 super(AdtranOltHandler, self).reenable(done_deferred=done_deferred)
614
615 # Only do the re-enable if we fully came up on the very first enable attempt.
616 # If we had not, the base class will have initiated the 'activate' for us
617
618 if self._initial_enable_complete:
619 self._zmq_startup()
620 self.adapter_agent.register_for_inter_adapter_messages()
621 self.status_poll = reactor.callLater(1, self.poll_for_status)
622
623 def reboot(self):
624 if not self._initial_enable_complete:
625 # Never contacted the device on the initial startup, do 'activate' steps instead
626 return
627
628 self._cancel_deferred()
629
630 # Drop registration for adapter messages
631 self._unregister_for_inter_adapter_messages()
632 self._zmq_shutdown()
633
634 # Download supported protocols may change (if new image gets activated)
635 self._download_protocols = None
636
637 super(AdtranOltHandler, self).reboot()
638
639 def _finish_reboot(self, timeout, previous_oper_status, previous_conn_status):
640 super(AdtranOltHandler, self)._finish_reboot(timeout, previous_oper_status, previous_conn_status)
641
642 self.ready_network_access()
643
644 # Download support
645 self._download_deferred = reactor.callLater(0, self._get_download_protocols)
646
647 # Register for adapter messages
648 self.adapter_agent.register_for_inter_adapter_messages()
649 self._zmq_startup()
650
651 self.status_poll = reactor.callLater(5, self.poll_for_status)
652
653 def delete(self):
654 self._cancel_deferred()
655
656 # Drop registration for adapter messages
657 self._unregister_for_inter_adapter_messages()
658 self._zmq_shutdown()
659
660 super(AdtranOltHandler, self).delete()
661
662 def rx_pa_packet(self, packets):
663 if self._pon_agent is not None:
664 for packet in packets:
665 try:
666 pon_id, onu_id, msg_bytes, is_omci = self._pon_agent.decode_packet(packet)
667
668 if is_omci:
669 proxy_address = self._pon_onu_id_to_proxy_address(pon_id, onu_id)
670
671 if proxy_address is not None:
672 self.adapter_agent.receive_proxied_message(proxy_address, msg_bytes)
673
674 except Exception as e:
675 self.log.exception('rx-pon-agent-packet', e=e)
676
677 def _compute_logical_port_no(self, port_no, evc_map, packet):
678 logical_port_no = None
679
680 # Upstream direction?
681 if self.is_pon_port(port_no):
682 #TODO: Validate the evc-map name
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500683 from pyvoltha.adapters.adtran_common.flow.evc_map import EVCMap
Chip Bolingf5af85d2019-02-12 15:36:17 -0600684 map_info = EVCMap.decode_evc_map_name(evc_map)
685 logical_port_no = int(map_info.get('ingress-port'))
686
687 if logical_port_no is None:
688 # Get PON
689 pon = self.get_southbound_port(port_no)
690
691 # Examine Packet and decode gvid
692 if packet is not None:
693 pass
694
695 elif self.is_nni_port(port_no):
696 nni = self.get_northbound_port(port_no)
697 logical_port = nni.get_logical_port() if nni is not None else None
698 logical_port_no = logical_port.ofp_port.port_no if logical_port is not None else None
699
700 # TODO: Need to decode base on port_no & evc_map
701 return logical_port_no
702
703 def rx_pio_packet(self, packets):
704 self.log.debug('rx-packet-in', type=type(packets), data=packets)
705 assert isinstance(packets, list), 'Expected a list of packets'
706
707 # TODO self._pio_agent.socket.socket.closed might be a good check here as well
708 if self.logical_device_id is not None and self._pio_agent is not None:
709 for packet in packets:
710 url_type = self._pio_agent.get_url_type(packet)
711 if url_type == PioClient.UrlType.EVCMAPS_RESPONSE:
712 exception_map = self._pio_agent.decode_query_response_packet(packet)
713 self.log.debug('rx-pio-packet', exception_map=exception_map)
714 # update latest pio exception map
715 self._pio_exception_map = exception_map
716
717 elif url_type == PioClient.UrlType.PACKET_IN:
718 try:
719 from scapy.layers.l2 import Ether, Dot1Q
720 ifindex, evc_map, packet = self._pio_agent.decode_packet(packet)
721
722 # convert ifindex to physical port number
723 # pon port numbers start at 60001 and end at 600016 (16 pons)
724 if ifindex > 60000 and ifindex < 60017:
725 port_no = (ifindex - 60000) + 4
726 # nni port numbers start at 1401 and end at 1404 (16 nnis)
727 elif ifindex > 1400 and ifindex < 1405:
728 port_no = ifindex - 1400
729 else:
730 raise ValueError('Unknown physical port. ifindex: {}'.format(ifindex))
731
732 logical_port_no = self._compute_logical_port_no(port_no, evc_map, packet)
733
734 if logical_port_no is not None:
735 if self.is_pon_port(port_no) and packet.haslayer(Dot1Q):
736 # Scrub g-vid
737 inner_pkt = packet.getlayer(Dot1Q)
738 assert inner_pkt.haslayer(Dot1Q), 'Expected a C-Tag'
739 packet = Ether(src=packet.src, dst=packet.dst, type=inner_pkt.type)\
740 / inner_pkt.payload
741
742 self.adapter_agent.send_packet_in(logical_device_id=self.logical_device_id,
743 logical_port_no=logical_port_no,
744 packet=str(packet))
745 else:
746 self.log.warn('logical-port-not-found', port_no=port_no, evc_map=evc_map)
747
748 except Exception as e:
749 self.log.exception('rx-pio-packet', e=e)
750
751 else:
752 self.log.warn('packet-in-unknown-url-type', url_type=url_type)
753
754 def packet_out(self, egress_port, msg):
755 """
756 Pass a packet_out message content to adapter so that it can forward it
757 out to the device. This is only called on root devices.
758
759 :param egress_port: egress logical port number
760 :param msg: actual message
761 :return: None """
762
763 if self.pio_port is not None:
764 from scapy.layers.l2 import Ether, Dot1Q
765 from scapy.layers.inet import UDP
766
767 self.log.debug('sending-packet-out', egress_port=egress_port,
768 msg=hexify(msg))
769 pkt = Ether(msg)
770
771 # Remove any extra tags
772 while pkt.type == 0x8100:
773 msg_hex = hexify(msg)
774 msg_hex = msg_hex[:24] + msg_hex[32:]
775 bytes = []
776 msg_hex = ''.join(msg_hex.split(" "))
777 for i in range(0, len(msg_hex), 2):
778 bytes.append(chr(int(msg_hex[i:i+2], 16)))
779
780 msg = ''.join(bytes)
781 pkt = Ether(msg)
782
783 if self._pio_agent is not None:
784 port, ctag, vlan_id, evcmapname = FlowEntry.get_packetout_info(self, egress_port)
785 exceptiontype = None
786 if pkt.type == FlowEntry.EtherType.EAPOL:
787 exceptiontype = 'eapol'
788 ctag = self.utility_vlan
789 elif pkt.type == 2:
790 exceptiontype = 'igmp'
791 elif pkt.type == FlowEntry.EtherType.IPv4:
792 if UDP in pkt and pkt[UDP].sport == 67 and pkt[UDP].dport == 68:
793 exceptiontype = 'dhcp'
794
795 if exceptiontype is None:
796 self.log.warn('packet-out-exceptiontype-unknown', eEtherType=pkt.type)
797
798 elif port is not None and ctag is not None and vlan_id is not None and \
799 evcmapname is not None and self.pio_exception_exists(evcmapname, exceptiontype):
800
801 self.log.debug('sending-pio-packet-out', port=port, ctag=ctag, vlan_id=vlan_id,
802 evcmapname=evcmapname, exceptiontype=exceptiontype)
803 out_pkt = (
804 Ether(src=pkt.src, dst=pkt.dst) /
805 Dot1Q(vlan=vlan_id) /
806 Dot1Q(vlan=ctag, type=pkt.type) /
807 pkt.payload
808 )
809 data = self._pio_agent.encode_packet(port, str(out_pkt), evcmapname, exceptiontype)
810 self.log.debug('pio-packet-out', message=data)
811 try:
812 self._pio_agent.send(data)
813
814 except Exception as e:
815 self.log.exception('pio-send', egress_port=egress_port, e=e)
816 else:
817 self.log.warn('packet-out-flow-not-found', egress_port=egress_port)
818
819 def pio_exception_exists(self, name, exp):
820 # verify exception is in the OLT's reported exception map for this evcmap name
821 if exp is None:
822 return False
823 entry = next((entry for entry in self._pio_exception_map if entry['evc-map-name'] == name), None)
824 if entry is None:
825 return False
826 if exp not in entry['exception-types']:
827 return False
828 return True
829
830 def send_packet_exceptions_request(self):
831 if self._pio_agent is not None:
832 request = self._pio_agent.query_request_packet()
833 try:
834 self._pio_agent.send(request)
835
836 except Exception as e:
837 self.log.exception('pio-send', e=e)
838
839 def poll_for_status(self):
840 self.log.debug('Initiating-status-poll')
841
842 device = self.adapter_agent.get_device(self.device_id)
843
844 if device.admin_state == AdminState.ENABLED and\
845 device.oper_status != OperStatus.ACTIVATING and\
846 self.rest_client is not None:
847 uri = AdtranOltHandler.GPON_OLT_HW_STATE_URI
848 name = 'pon-status-poll'
849 self.status_poll = self.rest_client.request('GET', uri, name=name)
850 self.status_poll.addBoth(self.status_poll_complete)
851 else:
852 self.status_poll = reactor.callLater(0, self.status_poll_complete, 'inactive')
853
854 def status_poll_complete(self, results):
855 """
856 Results of the status poll
857 :param results:
858 """
859 from pon_port import PonPort
860
861 if isinstance(results, dict) and 'pon' in results:
862 try:
863 self.log.debug('status-success')
864 for pon_id, pon in OltState(results).pons.iteritems():
865 pon_port = self.southbound_ports.get(pon_id, None)
866
867 if pon_port is not None and pon_port.state == PonPort.State.RUNNING:
868 pon_port.process_status_poll(pon)
869
870 except Exception as e:
871 self.log.exception('PON-status-poll', e=e)
872
873 # Reschedule
874
875 delay = self.status_poll_interval
876 delay += random.uniform(-delay / 10, delay / 10)
877
878 self.status_poll = reactor.callLater(delay, self.poll_for_status)
879
880 def _create_utility_flow(self):
881 nni_port = self.northbound_ports.get(1).port_no
882 pon_port = self.southbound_ports.get(0).port_no
883
884 return mk_flow_stat(
885 priority=200,
886 match_fields=[
887 in_port(nni_port),
888 vlan_vid(ofp.OFPVID_PRESENT + self.utility_vlan)
889 ],
890 actions=[output(pon_port)]
891 )
892
893 @inlineCallbacks
894 def update_flow_table(self, flows, device):
895 """
896 Update the flow table on the OLT. If an existing flow is not in the list, it needs
897 to be removed from the device.
898
899 :param flows: List of flows that should be installed upon completion of this function
900 :param device: A voltha.Device object, with possible device-type
901 specific extensions.
902 """
903 self.log.debug('bulk-flow-update', num_flows=len(flows),
904 device_id=device.id, flows=flows)
905
906 valid_flows = []
907
908 if flows:
909 # Special helper egress Packet In/Out flows
910 special_flow = self._create_utility_flow()
911 valid_flow, evc = FlowEntry.create(special_flow, self)
912
913 if valid_flow is not None:
914 valid_flows.append(valid_flow.flow_id)
915
916 if evc is not None:
917 try:
918 evc.schedule_install()
919 self.add_evc(evc)
920
921 except Exception as e:
922 evc.status = 'EVC Install Exception: {}'.format(e.message)
923 self.log.exception('EVC-install', e=e)
924
925 # verify exception flows were installed by OLT PET process
926 reactor.callLater(5, self.send_packet_exceptions_request)
927
928 # Now process bulk flows
929 for flow in flows:
930 try:
931 # Try to create an EVC.
932 #
933 # The first result is the flow entry that was created. This could be a match to an
934 # existing flow since it is a bulk update. None is returned only if no match to
935 # an existing entry is found and decode failed (unsupported field)
936 #
937 # The second result is the EVC this flow should be added to. This could be an
938 # existing flow (so your adding another EVC-MAP) or a brand new EVC (no existing
939 # EVC-MAPs). None is returned if there are not a valid EVC that can be created YET.
940
941 valid_flow, evc = FlowEntry.create(flow, self)
942
943 if valid_flow is not None:
944 valid_flows.append(valid_flow.flow_id)
945
946 if evc is not None:
947 try:
948 evc.schedule_install()
949 self.add_evc(evc)
950
951 except Exception as e:
952 evc.status = 'EVC Install Exception: {}'.format(e.message)
953 self.log.exception('EVC-install', e=e)
954
955 except Exception as e:
956 self.log.exception('bulk-flow-update-add', e=e)
957
958 # Now drop all flows from this device that were not in this bulk update
959 try:
960 yield FlowEntry.drop_missing_flows(self, valid_flows)
961
962 except Exception as e:
963 self.log.exception('bulk-flow-update-remove', e=e)
964
965 def remove_from_flow_table(self, _flows):
966 """
967 Remove flows from the device
968
969 :param _flows: (list) Flows
970 """
971 raise NotImplementedError
972
973 def add_to_flow_table(self, _flows):
974 """
975 Remove flows from the device
976
977 :param _flows: (list) Flows
978 """
979 raise NotImplementedError
980
981 def get_ofp_device_info(self, device):
982 """
983 Retrieve the OLT device info. This includes the ofp_desc and
984 ofp_switch_features. The existing ofp structures can be used,
985 or all the attributes get added to the Device definition or a new proto
986 definition gets created. This API will allow the Core to create a
987 LogicalDevice associated with this device (OLT only).
988 :param device: device
989 :return: Proto Message (TBD)
990 """
991 from pyvoltha.protos.inter_container_pb2 import SwitchCapability
992 version = device.images.image[0].version
993
994 return SwitchCapability(
995 desc=ofp_desc(mfr_desc='VOLTHA Project',
996 hw_desc=device.hardware_version,
997 sw_desc=version,
998 serial_num=device.serial_number,
999 dp_desc='n/a'),
1000 switch_features=ofp_switch_features(n_buffers=256, # TODO fake for now
1001 n_tables=2, # TODO ditto
1002 capabilities=( # TODO and ditto
1003 OFPC_FLOW_STATS |
1004 OFPC_TABLE_STATS |
1005 OFPC_PORT_STATS |
1006 OFPC_GROUP_STATS))
1007 )
1008
1009 def get_ofp_port_info(self, device, port_no):
1010 """
1011 Retrieve the port info. This includes the ofp_port. The existing ofp
1012 structure can be used, or all the attributes get added to the Port
1013 definitions or a new proto definition gets created. This API will allow
1014 the Core to create a LogicalPort associated with this device.
1015 :param device: device
1016 :param port_no: port number
1017 :return: Proto Message (TBD)
1018 """
1019 from pyvoltha.protos.inter_container_pb2 import PortCapability
1020 # Since the adapter created the device port then it has the reference of the port to
1021 # return the capability. TODO: Do a lookup on the NNI port number and return the
1022 # appropriate attributes
1023 self.log.info('get_ofp_port_info', port_no=port_no,
1024 info=self.ofp_port_no, device_id=device.id)
1025
1026 nni = self.get_northbound_port(port_no)
1027 if nni is not None:
1028 lp = nni.get_logical_port()
1029 if lp is not None:
1030 return PortCapability(port=lp)
1031
1032 # @inlineCallbacks
1033 def send_proxied_message(self, proxy_address, msg):
1034 self.log.debug('sending-proxied-message', msg=msg)
1035
1036 if isinstance(msg, Packet):
1037 msg = str(msg)
1038
1039 if self._pon_agent is not None:
1040 pon_id, onu_id = self._proxy_address_to_pon_onu_id(proxy_address)
1041
1042 pon = self.southbound_ports.get(pon_id)
1043
1044 if pon is not None and pon.enabled:
1045 onu = pon.onu(onu_id)
1046
1047 if onu is not None and onu.enabled:
1048 data = self._pon_agent.encode_omci_packet(msg, pon_id, onu_id)
1049 try:
1050 self._pon_agent.send(data)
1051
1052 except Exception as e:
1053 self.log.exception('pon-agent-send', pon_id=pon_id, onu_id=onu_id, e=e)
1054 else:
1055 self.log.debug('onu-invalid-or-disabled', pon_id=pon_id, onu_id=onu_id)
1056 else:
1057 self.log.debug('pon-invalid-or-disabled', pon_id=pon_id)
1058
1059 def _onu_offset(self, onu_id):
1060 # Start ONU's just past the southbound PON port numbers. Since ONU ID's start
1061 # at zero, add one
1062 # assert AdtranOltHandler.BASE_ONU_OFFSET > (self.num_northbound_ports + self.num_southbound_ports + 1)
1063 assert AdtranOltHandler.BASE_ONU_OFFSET > (4 + self.num_southbound_ports + 1) # Skip over uninitialized ports
1064 return AdtranOltHandler.BASE_ONU_OFFSET + onu_id
1065
1066 def _pon_onu_id_to_proxy_address(self, pon_id, onu_id):
1067 if pon_id in self.southbound_ports:
1068 pon = self.southbound_ports[pon_id]
1069 onu = pon.onu(onu_id)
1070 proxy_address = onu.proxy_address if onu is not None else None
1071
1072 else:
1073 proxy_address = None
1074
1075 return proxy_address
1076
1077 def _proxy_address_to_pon_onu_id(self, proxy_address):
1078 """
1079 Convert the proxy address to the PON-ID and ONU-ID
1080 :param proxy_address: (ProxyAddress)
1081 :return: (tuple) pon-id, onu-id
1082 """
1083 onu_id = proxy_address.onu_id
1084 pon_id = self._port_number_to_pon_id(proxy_address.channel_id)
1085
1086 return pon_id, onu_id
1087
1088 def pon_id_to_port_number(self, pon_id):
1089 return pon_id + 1 + 4 # Skip over uninitialized ports
1090
1091 def _port_number_to_pon_id(self, port):
1092 if self.is_uni_port(port):
1093 # Convert to OLT device port
1094 port = platform.intf_id_from_uni_port_num(port)
1095
1096 return port - 1 - 4 # Skip over uninitialized ports
1097
1098 def is_pon_port(self, port):
1099 return self._port_number_to_pon_id(port) in self.southbound_ports
1100
1101 def is_uni_port(self, port):
1102 return OFPP_MAX >= port >= (5 << 11)
1103
1104 def get_southbound_port(self, port):
1105 pon_id = self._port_number_to_pon_id(port)
1106 return self.southbound_ports.get(pon_id, None)
1107
1108 def get_northbound_port(self, port):
1109 return self.northbound_ports.get(port, None)
1110
1111 def get_port_name(self, port, logical_name=False):
1112 """
1113 Get the name for a port
1114
1115 Port names are used in various ways within and outside of VOLTHA.
1116 Typically, the physical port name will be used during device handler conversations
1117 with the hardware (REST, NETCONF, ...) while the logical port name is what the
1118 outside world (ONOS, SEBA, ...) uses.
1119
1120 All ports have a physical port name, but only ports exposed through VOLTHA
1121 as a logical port will have a logical port name
1122 """
1123 if self.is_nni_port(port):
1124 port = self.get_northbound_port(port)
1125 return port.logical_port_name if logical_name else port.physical_port_name
1126
1127 if self.is_pon_port(port):
1128 port = self.get_southbound_port(port)
1129 return port.logical_port_name if logical_name else port.physical_port_name
1130
1131 if self.is_uni_port(port):
1132 return 'uni-{}'.format(port)
1133
1134 if self.is_logical_port(port):
1135 raise NotImplemented('Logical OpenFlow ports are not supported')
1136
1137 def _update_download_status(self, request, download):
1138 if download is not None:
1139 request.state = download.download_state
1140 request.reason = download.failure_reason
1141 request.image_state = download.image_state
1142 request.additional_info = download.additional_info
1143 request.downloaded_bytes = download.downloaded_bytes
1144 else:
1145 request.state = ImageDownload.DOWNLOAD_UNKNOWN
1146 request.reason = ImageDownload.UNKNOWN_ERROR
1147 request.image_state = ImageDownload.IMAGE_UNKNOWN
1148 request.additional_info = "Download request '{}' not found".format(request.name)
1149 request.downloaded_bytes = 0
1150
1151 self.adapter_agent.update_image_download(request)
1152
Chip Bolingd2d7a4d2019-03-14 14:34:56 -05001153 @inlineCallbacks
Chip Bolingf5af85d2019-02-12 15:36:17 -06001154 def start_download(self, device, request, done):
1155 """
1156 This is called to request downloading a specified image into
1157 the standby partition of a device based on a NBI call.
1158
1159 :param device: A Voltha.Device object.
1160 :param request: A Voltha.ImageDownload object.
1161 :param done: (Deferred) Deferred to fire when done
1162 :return: (Deferred) Shall be fired to acknowledge the download.
1163 """
1164 log.info('image_download', request=request)
1165
1166 try:
1167 if not self._initial_enable_complete:
1168 # Never contacted the device on the initial startup, do 'activate' steps instead
1169 raise Exception('Device has not finished initial activation')
1170
1171 if request.name in self._downloads:
1172 raise Exception("Download request with name '{}' already exists".
1173 format(request.name))
1174 try:
1175 download = Download.create(self, request, self._download_protocols)
1176
1177 except Exception:
1178 request.additional_info = 'Download request creation failed due to exception'
1179 raise
1180
1181 try:
1182 self._downloads[download.name] = download
1183 self._update_download_status(request, download)
1184 done.callback('started')
Chip Bolingd2d7a4d2019-03-14 14:34:56 -05001185 returnValue(done)
Chip Bolingf5af85d2019-02-12 15:36:17 -06001186
1187 except Exception:
1188 request.additional_info = 'Download request startup failed due to exception'
1189 del self._downloads[download.name]
1190 download.cancel_download(request)
1191 raise
1192
1193 except Exception as e:
1194 self.log.exception('create', e=e)
1195
1196 request.reason = ImageDownload.UNKNOWN_ERROR if self._initial_enable_complete\
1197 else ImageDownload.DEVICE_BUSY
1198 request.state = ImageDownload.DOWNLOAD_FAILED
1199 if not request.additional_info:
1200 request.additional_info = e.message
1201
1202 self.adapter_agent.update_image_download(request)
1203
1204 # restore admin state to enabled
1205 device.admin_state = AdminState.ENABLED
Chip Bolingd2d7a4d2019-03-14 14:34:56 -05001206 yield self.adapter_agent.device_update(device)
Chip Bolingf5af85d2019-02-12 15:36:17 -06001207 raise
1208
Chip Bolingd2d7a4d2019-03-14 14:34:56 -05001209 @inlineCallbacks
Chip Bolingf5af85d2019-02-12 15:36:17 -06001210 def download_status(self, device, request, done):
1211 """
1212 This is called to inquire about a requested image download status based
1213 on a NBI call.
1214
1215 The adapter is expected to update the DownloadImage DB object with the
1216 query result
1217
1218 :param device: A Voltha.Device object.
1219 :param request: A Voltha.ImageDownload object.
1220 :param done: (Deferred) Deferred to fire when done
1221
1222 :return: (Deferred) Shall be fired to acknowledge
1223 """
1224 log.info('download_status', request=request)
1225 download = self._downloads.get(request.name)
1226
1227 self._update_download_status(request, download)
1228
1229 if request.state not in [ImageDownload.DOWNLOAD_STARTED,
1230 ImageDownload.DOWNLOAD_SUCCEEDED,
1231 ImageDownload.DOWNLOAD_FAILED]:
1232 # restore admin state to enabled
1233 device.admin_state = AdminState.ENABLED
Chip Bolingd2d7a4d2019-03-14 14:34:56 -05001234 yield self.adapter_agent.device_update(device)
Chip Bolingf5af85d2019-02-12 15:36:17 -06001235
1236 done.callback(request.state)
Chip Bolingd2d7a4d2019-03-14 14:34:56 -05001237 returnValue(done)
Chip Bolingf5af85d2019-02-12 15:36:17 -06001238
Chip Bolingd2d7a4d2019-03-14 14:34:56 -05001239 @inlineCallbacks
Chip Bolingf5af85d2019-02-12 15:36:17 -06001240 def cancel_download(self, device, request, done):
1241 """
1242 This is called to cancel a requested image download based on a NBI
1243 call. The admin state of the device will not change after the
1244 download.
1245
1246 :param device: A Voltha.Device object.
1247 :param request: A Voltha.ImageDownload object.
1248 :param done: (Deferred) Deferred to fire when done
1249
1250 :return: (Deferred) Shall be fired to acknowledge
1251 """
1252 log.info('cancel_download', request=request)
1253
1254 download = self._downloads.get(request.name)
1255
1256 if download is not None:
1257 del self._downloads[request.name]
1258 result = download.cancel_download(request)
1259 self._update_download_status(request, download)
1260 done.callback(result)
1261 else:
1262 self._update_download_status(request, download)
1263 done.errback(KeyError('Download request not found'))
1264
1265 if device.admin_state == AdminState.DOWNLOADING_IMAGE:
1266 device.admin_state = AdminState.ENABLED
Chip Bolingd2d7a4d2019-03-14 14:34:56 -05001267 yield self.adapter_agent.device_update(device)
Chip Bolingf5af85d2019-02-12 15:36:17 -06001268
Chip Bolingd2d7a4d2019-03-14 14:34:56 -05001269 returnValue(done)
Chip Bolingf5af85d2019-02-12 15:36:17 -06001270
Chip Bolingd2d7a4d2019-03-14 14:34:56 -05001271 @inlineCallbacks
Chip Bolingf5af85d2019-02-12 15:36:17 -06001272 def activate_image(self, device, request, done):
1273 """
1274 This is called to activate a downloaded image from a standby partition
1275 into active partition.
1276
1277 Depending on the device implementation, this call may or may not
1278 cause device reboot. If no reboot, then a reboot is required to make
1279 the activated image running on device
1280
1281 :param device: A Voltha.Device object.
1282 :param request: A Voltha.ImageDownload object.
1283 :param done: (Deferred) Deferred to fire when done
1284
1285 :return: (Deferred) OperationResponse object.
1286 """
1287 log.info('activate_image', request=request)
1288
1289 download = self._downloads.get(request.name)
1290 if download is not None:
1291 del self._downloads[request.name]
1292 result = download.activate_image()
1293 self._update_download_status(request, download)
1294 done.callback(result)
1295 else:
1296 self._update_download_status(request, download)
1297 done.errback(KeyError('Download request not found'))
1298
1299 # restore admin state to enabled
1300 device.admin_state = AdminState.ENABLED
Chip Bolingd2d7a4d2019-03-14 14:34:56 -05001301 yield self.adapter_agent.device_update(device)
1302 returnValue(done)
Chip Bolingf5af85d2019-02-12 15:36:17 -06001303
Chip Bolingd2d7a4d2019-03-14 14:34:56 -05001304 @inlineCallbacks
Chip Bolingf5af85d2019-02-12 15:36:17 -06001305 def revert_image(self, device, request, done):
1306 """
1307 This is called to deactivate the specified image at active partition,
1308 and revert to previous image at standby partition.
1309
1310 Depending on the device implementation, this call may or may not
1311 cause device reboot. If no reboot, then a reboot is required to
1312 make the previous image running on device
1313
1314 :param device: A Voltha.Device object.
1315 :param request: A Voltha.ImageDownload object.
1316 :param done: (Deferred) Deferred to fire when done
1317
1318 :return: (Deferred) OperationResponse object.
1319 """
1320 log.info('revert_image', request=request)
1321
1322 download = self._downloads.get(request.name)
1323 if download is not None:
1324 del self._downloads[request.name]
1325 result = download.revert_image()
1326 self._update_download_status(request, download)
1327 done.callback(result)
1328 else:
1329 self._update_download_status(request, download)
1330 done.errback(KeyError('Download request not found'))
1331
1332 # restore admin state to enabled
1333 device.admin_state = AdminState.ENABLED
Chip Bolingd2d7a4d2019-03-14 14:34:56 -05001334 yield self.adapter_agent.device_update(device)
1335 returnValue(done)
Chip Bolingf5af85d2019-02-12 15:36:17 -06001336
1337 def add_onu_device(self, pon_id, onu_id, serial_number):
1338 onu_device = self.adapter_agent.get_child_device(self.device_id,
1339 serial_number=serial_number)
1340 if onu_device is not None:
1341 return onu_device
1342
1343 try:
1344 # NOTE - channel_id of onu is set to pon_id
1345 pon_port = self.pon_id_to_port_number(pon_id)
1346 proxy_address = Device.ProxyAddress(device_id=self.device_id,
1347 channel_id=pon_port,
1348 onu_id=onu_id,
1349 onu_session_id=onu_id)
1350
1351 self.log.debug("added-onu", port_no=pon_id,
1352 onu_id=onu_id, serial_number=serial_number,
1353 proxy_address=proxy_address)
1354
1355 self.adapter_agent.add_onu_device(
1356 parent_device_id=self.device_id,
1357 parent_port_no=pon_port,
1358 vendor_id=serial_number[:4],
1359 proxy_address=proxy_address,
1360 root=True,
1361 serial_number=serial_number,
1362 admin_state=AdminState.ENABLED,
1363 )
1364
1365 except Exception as e:
1366 self.log.exception('onu-activation-failed', e=e)
1367 return None
1368
1369 def setup_onu_tech_profile(self, pon_id, onu_id, logical_port_number):
1370 # Send ONU Adapter related tech profile information.
1371 self.log.debug('add-tech-profile-info')
1372
1373 uni_id = self.platform.uni_id_from_uni_port(logical_port_number)
1374 parent_port_no = self.pon_id_to_port_number(pon_id)
1375 onu_device = self.adapter_agent.get_child_device(self.device_id,
1376 onu_id=onu_id,
1377 parent_port_no=parent_port_no)
1378
1379 ofp_port_name, ofp_port_no = self.get_ofp_port_name(pon_id, onu_id,
1380 logical_port_number)
1381 if ofp_port_name is None:
1382 self.log.error("port-name-not-found")
1383 return
1384
1385 tp_path = self.get_tp_path(pon_id, ofp_port_name)
1386
1387 self.log.debug('Load-tech-profile-request-to-onu-handler', tp_path=tp_path)
1388
1389 msg = {'proxy_address': onu_device.proxy_address, 'uni_id': uni_id,
1390 'event': 'download_tech_profile', 'event_data': tp_path}
1391
1392 # Send the event message to the ONU adapter
1393 self.adapter_agent.publish_inter_adapter_message(onu_device.id, msg)