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