blob: 48f026031b39c731fd1272cee33c8c24edba5406 [file] [log] [blame]
Chip Boling252c7772017-08-16 10:13:17 -05001# Copyright 2017-present Open Networking Foundation
Chip Boling3e3b1a92017-05-16 11:51:18 -05002#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
Chip Boling252c7772017-08-16 10:13:17 -05007# http://www.apache.org/licenses/LICENSE-2.0
Chip Boling3e3b1a92017-05-16 11:51:18 -05008#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
Chip Boling252c7772017-08-16 10:13:17 -050014
Chip Boling3e3b1a92017-05-16 11:51:18 -050015import json
16import pprint
17import random
18
Chip Boling3e3b1a92017-05-16 11:51:18 -050019import structlog
Chip Boling7294b252017-06-15 16:16:55 -050020from enum import Enum
Chip Boling3e3b1a92017-05-16 11:51:18 -050021from twisted.internet import reactor
Chip Boling7294b252017-06-15 16:16:55 -050022from twisted.internet.defer import inlineCallbacks, returnValue, succeed
Chip Boling3e3b1a92017-05-16 11:51:18 -050023
24from adtran_olt_handler import AdtranOltHandler
25from codec.olt_config import OltConfig
26from onu import Onu
27from voltha.protos.common_pb2 import OperStatus, AdminState
28from voltha.protos.device_pb2 import Device
29from voltha.protos.device_pb2 import Port
30
Chip Boling3e3b1a92017-05-16 11:51:18 -050031
32class PonPort(object):
33 """
34 A class similar to the 'Port' class in the VOLTHA
Chip Boling252c7772017-08-16 10:13:17 -050035
Chip Boling3e3b1a92017-05-16 11:51:18 -050036 TODO: Merge this with the Port class or cleanup where possible
37 so we do not duplicate fields/properties/methods
38 """
39 MAX_ONUS_SUPPORTED = 256
40 DEFAULT_ENABLED = False
41
Chip Boling7294b252017-06-15 16:16:55 -050042 class State(Enum):
Chip Boling69fba862017-08-18 15:11:32 -050043 INITIAL = 0 # Created and initialization in progress
44 RUNNING = 1 # PON port contacted, ONU discovery active
45 STOPPED = 2 # Disabled
Chip Boling7294b252017-06-15 16:16:55 -050046 DELETING = 3 # Cleanup
47
Chip Boling252c7772017-08-16 10:13:17 -050048 _SUPPORTED_ACTIVATION_METHODS = ['autodiscovery', 'autoactivate']
49 _SUPPORTED_AUTHENTICATION_METHODS = ['serial-number']
50
Chip Boling3e3b1a92017-05-16 11:51:18 -050051 def __init__(self, pon_index, port_no, parent, admin_state=AdminState.UNKNOWN, label=None):
Chip Boling7294b252017-06-15 16:16:55 -050052 # TODO: Weed out those properties supported by common 'Port' object (future)
Chip Boling3e3b1a92017-05-16 11:51:18 -050053 assert admin_state != AdminState.UNKNOWN
Chip Boling7294b252017-06-15 16:16:55 -050054
Chip Boling252c7772017-08-16 10:13:17 -050055 self.log = structlog.get_logger(device_id=parent.device_id, pon_id=pon_index)
Chip Boling7294b252017-06-15 16:16:55 -050056
57 self._parent = parent
58 self._pon_id = pon_index
Chip Boling3e3b1a92017-05-16 11:51:18 -050059 self._port_no = port_no
Chip Boling252c7772017-08-16 10:13:17 -050060 self._name = 'xpon 0/{}'.format(pon_index+1)
Chip Boling7294b252017-06-15 16:16:55 -050061 self._label = label or 'PON-{}'.format(pon_index)
62 self._port = None
63 self._no_onu_discover_tick = 5.0 # TODO: Decrease to 1 or 2 later
64 self._discovery_tick = 20.0
65 self._discovered_onus = [] # List of serial numbers
Chip Boling252c7772017-08-16 10:13:17 -050066 self._onus = {} # serial_number-base64 -> ONU (allowed list)
67 self._onu_by_id = {} # onu-id -> ONU
Chip Boling7294b252017-06-15 16:16:55 -050068 self._next_onu_id = Onu.MIN_ONU_ID
69
Chip Boling252c7772017-08-16 10:13:17 -050070 self._admin_state = AdminState.DISABLED
71 self._oper_status = OperStatus.DISCOVERED
72 self._deferred = None # General purpose
73 self._discovery_deferred = None # Specifically for ONU discovery
Chip Boling7294b252017-06-15 16:16:55 -050074 self._state = PonPort.State.INITIAL
75
76 # Local cache of PON configuration
77
Chip Boling252c7772017-08-16 10:13:17 -050078 self._xpon_name = None
Chip Boling7294b252017-06-15 16:16:55 -050079 self._enabled = None
80 self._downstream_fec_enable = None
81 self._upstream_fec_enable = None
Chip Boling252c7772017-08-16 10:13:17 -050082 self._authentication_method = 'serial-number'
83 self._activation_method = 'autoactivate' if self.olt.autoactivate else 'autodiscovery'
Chip Boling3e3b1a92017-05-16 11:51:18 -050084
85 def __del__(self):
Chip Boling7294b252017-06-15 16:16:55 -050086 self.stop()
Chip Boling3e3b1a92017-05-16 11:51:18 -050087
88 def __str__(self):
Chip Boling252c7772017-08-16 10:13:17 -050089 return "PonPort-{}: Admin: {}, Oper: {}, OLT: {}".format(self._label,
90 self._admin_state,
91 self._oper_status,
92 self.olt)
Chip Boling3e3b1a92017-05-16 11:51:18 -050093
94 def get_port(self):
95 """
96 Get the VOLTHA PORT object for this port
97 :return: VOLTHA Port object
98 """
Chip Boling7294b252017-06-15 16:16:55 -050099 if self._port is None:
100 self._port = Port(port_no=self._port_no,
101 label=self._label,
102 type=Port.PON_OLT,
Chip Boling252c7772017-08-16 10:13:17 -0500103 admin_state=AdminState.ENABLED,
104 oper_status=OperStatus.ACTIVE)
105 # TODO: For now, no way to report the proper ADMIN or OPER status
106 # admin_state=self._admin_state,
107 # oper_status=self._oper_status)
Chip Boling7294b252017-06-15 16:16:55 -0500108 return self._port
Chip Boling3e3b1a92017-05-16 11:51:18 -0500109
110 @property
111 def port_number(self):
112 return self._port_no
113
114 @property
Chip Boling5561d552017-07-07 15:11:26 -0500115 def name(self):
116 return self._name
117
118 @property
Chip Boling252c7772017-08-16 10:13:17 -0500119 def xpon_name(self):
120 return self._xpon_name
121
122 @xpon_name.setter
123 def xpon_name(self, value):
124 self._xpon_name = value
125
126 @property
Chip Boling3e3b1a92017-05-16 11:51:18 -0500127 def pon_id(self):
Chip Boling7294b252017-06-15 16:16:55 -0500128 return self._pon_id
129
130 @property
131 def olt(self):
132 return self._parent
133
134 @property
135 def state(self):
136 return self._state
137
138 @property
139 def adapter_agent(self):
140 return self.olt.adapter_agent
Chip Boling3e3b1a92017-05-16 11:51:18 -0500141
Chip Boling252c7772017-08-16 10:13:17 -0500142 @property
143 def discovery_tick(self):
144 return self._discovery_tick * 10
145
146 @discovery_tick.setter
147 def discovery_tick(self, value):
148 if value < 0:
149 raise ValueError("Polling interval must be >= 0")
150
151 if self.discovery_tick != value:
152 self._discovery_tick = value / 10
153
154 if self._discovery_deferred is not None:
155 self._discovery_deferred.cancel()
156 self._discovery_deferred = None
157
158 if self._discovery_tick > 0:
159 self._discovery_deferred = reactor.callLater(self._discovery_tick,
160 self._discover_onus)
161
162 @property
163 def activation_method(self):
164 return self._activation_method
165
166 @activation_method.setter
167 def activation_method(self, value):
168 value = value.lower()
169 if value not in PonPort._SUPPORTED_ACTIVATION_METHODS:
170 raise ValueError('Invalid ONU activation method')
171 self._activation_method = value
172
173 @property
174 def authentication_method(self):
175 return self._authentication_method
176
177 @authentication_method.setter
178 def authentication_method(self, value):
179 value = value.lower()
180 if value not in PonPort._SUPPORTED_AUTHENTICATION_METHODS:
181 raise ValueError('Invalid ONU authentication method')
182 self._authentication_method = value
183
Chip Boling3e3b1a92017-05-16 11:51:18 -0500184 def get_logical_port(self):
185 """
Chip Boling7294b252017-06-15 16:16:55 -0500186 Get the VOLTHA logical port for this port. For PON ports, a logical port
187 is not currently created, so always return None
188
Chip Boling3e3b1a92017-05-16 11:51:18 -0500189 :return: VOLTHA logical port or None if not supported
190 """
191 return None
192
Chip Boling7294b252017-06-15 16:16:55 -0500193 def _cancel_deferred(self):
Chip Boling252c7772017-08-16 10:13:17 -0500194 d1, self._deferred = self._deferred, None
195 d2, self._discovery_deferred = self._discovery_deferred, None
196
197 if d1 is not None:
198 d1.cancel()
199 if d2 is not None:
200 d2.cancel()
Chip Boling7294b252017-06-15 16:16:55 -0500201
202 def _update_adapter_agent(self):
203 # TODO: Currently the adapter_agent does not allow 'update' of port status
204 # self.adapter_agent.update_port(self.olt.device_id, self.get_port())
205 pass
206
Chip Boling3e3b1a92017-05-16 11:51:18 -0500207 def start(self):
208 """
209 Start/enable this PON and start ONU discover
210 :return: (deferred)
211 """
Chip Boling7294b252017-06-15 16:16:55 -0500212 if self._state == PonPort.State.RUNNING:
213 return succeed('Running')
Chip Boling3e3b1a92017-05-16 11:51:18 -0500214
Chip Boling252c7772017-08-16 10:13:17 -0500215 self.log.info('start')
Chip Boling7294b252017-06-15 16:16:55 -0500216
217 self._cancel_deferred()
218 self._state = PonPort.State.INITIAL
Chip Boling252c7772017-08-16 10:13:17 -0500219 self._oper_status = OperStatus.ACTIVATING
Chip Boling7294b252017-06-15 16:16:55 -0500220
221 # Do the rest of the startup in an async method
Chip Boling5561d552017-07-07 15:11:26 -0500222 self._deferred = reactor.callLater(0.5, self._finish_startup)
Chip Boling252c7772017-08-16 10:13:17 -0500223 self._update_adapter_agent()
224
Chip Boling7294b252017-06-15 16:16:55 -0500225 return self._deferred
226
227 @inlineCallbacks
228 def _finish_startup(self):
Chip Boling3e3b1a92017-05-16 11:51:18 -0500229 """
Chip Boling7294b252017-06-15 16:16:55 -0500230 Do all startup offline since REST may fail
Chip Boling3e3b1a92017-05-16 11:51:18 -0500231 """
Chip Boling7294b252017-06-15 16:16:55 -0500232 if self._state != PonPort.State.INITIAL:
233 returnValue('Done')
Chip Boling3e3b1a92017-05-16 11:51:18 -0500234
Chip Boling252c7772017-08-16 10:13:17 -0500235 self.log.debug('final-startup')
Chip Boling5561d552017-07-07 15:11:26 -0500236
Chip Boling7294b252017-06-15 16:16:55 -0500237 if self._enabled is None or self._downstream_fec_enable is None or self._upstream_fec_enable is None:
238 try:
239 self._deferred = self.get_pon_config()
240 results = yield self._deferred
Chip Boling3e3b1a92017-05-16 11:51:18 -0500241
Chip Boling7294b252017-06-15 16:16:55 -0500242 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500243 self.log.exception('initial-GET', e=e)
Chip Boling5561d552017-07-07 15:11:26 -0500244 self._deferred = reactor.callLater(5, self._finish_startup)
Chip Boling7294b252017-06-15 16:16:55 -0500245 returnValue(self._deferred)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500246
Chip Boling7294b252017-06-15 16:16:55 -0500247 # Load cache
Chip Boling3e3b1a92017-05-16 11:51:18 -0500248
Chip Boling7294b252017-06-15 16:16:55 -0500249 self._enabled = results.get('enabled', False)
250 self._downstream_fec_enable = results.get('downstream-fec-enable', False)
251 self._upstream_fec_enable = results.get('upstream-fec-enable', False)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500252
Chip Boling7294b252017-06-15 16:16:55 -0500253 if not self._enabled:
254 try:
255 self._deferred = self.set_pon_config("enabled", True)
256 results = yield self._deferred
257 self._enabled = True
Chip Boling3e3b1a92017-05-16 11:51:18 -0500258
Chip Boling7294b252017-06-15 16:16:55 -0500259 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500260 self.log.exception('final-startup-enable', e=e)
Chip Boling7294b252017-06-15 16:16:55 -0500261 self._deferred = reactor.callLater(3, self._finish_startup)
262 returnValue(self._deferred)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500263
Chip Boling7294b252017-06-15 16:16:55 -0500264 if not self._downstream_fec_enable:
265 try:
266 self._deferred = self.set_pon_config("downstream-fec-enable", True)
267 results = yield self._deferred
268 self._downstream_fec_enable = True
Chip Boling3e3b1a92017-05-16 11:51:18 -0500269
Chip Boling7294b252017-06-15 16:16:55 -0500270 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500271 self.log.exception('final-startup-downstream-FEC', e=e)
Chip Boling5561d552017-07-07 15:11:26 -0500272 self._deferred = reactor.callLater(5, self._finish_startup)
Chip Boling7294b252017-06-15 16:16:55 -0500273 returnValue(self._deferred)
274
275 if not self._upstream_fec_enable:
276 try:
277 self._deferred = self.set_pon_config("upstream-fec-enable", True)
278 results = yield self._deferred
279 self._upstream_fec_enable = True
280
281 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500282 self.log.exception('final-startup-upstream-FEC', e=e)
Chip Boling5561d552017-07-07 15:11:26 -0500283 self._deferred = reactor.callLater(5, self._finish_startup)
Chip Boling7294b252017-06-15 16:16:55 -0500284 returnValue(self._deferred)
285
Chip Boling252c7772017-08-16 10:13:17 -0500286 self.log.debug('startup-complete', results=pprint.PrettyPrinter().pformat(results))
Chip Boling7294b252017-06-15 16:16:55 -0500287
288 if self._enabled:
289 self._admin_state = AdminState.ENABLED
290 self._oper_status = OperStatus.ACTIVE # TODO: is this correct, how do we tell GRPC
291 self._state = PonPort.State.RUNNING
292
Chip Boling252c7772017-08-16 10:13:17 -0500293 # Begin to ONU discovery
294
295 self._discovery_deferred = reactor.callLater(5, self._discover_onus)
Chip Boling7294b252017-06-15 16:16:55 -0500296
297 self._update_adapter_agent()
298 returnValue('Enabled')
Chip Boling3e3b1a92017-05-16 11:51:18 -0500299
300 else:
301 # Startup failed. Could be due to object creation with an invalid initial admin_status
302 # state. May want to schedule a start to occur again if this happens
Chip Boling7294b252017-06-15 16:16:55 -0500303 self._admin_state = AdminState.DISABLED
Chip Boling252c7772017-08-16 10:13:17 -0500304 self._oper_status = OperStatus.FAILED
Chip Boling7294b252017-06-15 16:16:55 -0500305 self._state = PonPort.State.STOPPED
Chip Boling3e3b1a92017-05-16 11:51:18 -0500306
Chip Boling7294b252017-06-15 16:16:55 -0500307 self._update_adapter_agent()
308 returnValue('Disabled')
309
Chip Boling3e3b1a92017-05-16 11:51:18 -0500310 def stop(self):
Chip Boling7294b252017-06-15 16:16:55 -0500311 if self._state == PonPort.State.STOPPED:
312 return succeed('Stopped')
Chip Boling3e3b1a92017-05-16 11:51:18 -0500313
Chip Boling252c7772017-08-16 10:13:17 -0500314 self.log.info('stopping')
Chip Boling3e3b1a92017-05-16 11:51:18 -0500315
Chip Boling7294b252017-06-15 16:16:55 -0500316 self._cancel_deferred()
317 self._deferred = self.set_pon_config("enabled", False)
318
319 # Flush config cache
320 self._enabled = None
321 self._downstream_fec_enable = None
322 self._upstream_fec_enable = None
323
324 self._admin_state = AdminState.DISABLED
325 self._oper_status = OperStatus.UNKNOWN
326 self._update_adapter_agent()
327
328 self._state = PonPort.State.STOPPED
329 return self._deferred
Chip Boling3e3b1a92017-05-16 11:51:18 -0500330
331 @inlineCallbacks
332 def reset(self):
Chip Boling7294b252017-06-15 16:16:55 -0500333 """
334 Set the PON Port to a known good state on initial port startup. Actual
335 PON 'Start' is done elsewhere
336 """
337 if self._state != PonPort.State.INITIAL:
Chip Boling252c7772017-08-16 10:13:17 -0500338 self.log.error('reset-ignored', state=self._state)
Chip Boling7294b252017-06-15 16:16:55 -0500339 returnValue('Ignored')
Chip Boling3e3b1a92017-05-16 11:51:18 -0500340
Chip Boling252c7772017-08-16 10:13:17 -0500341 self.log.info('reset')
Chip Boling7294b252017-06-15 16:16:55 -0500342
Chip Boling252c7772017-08-16 10:13:17 -0500343 try:
344 self._deferred = self.get_pon_config()
345 results = yield self._deferred
346
347 # Load cache
348 self._enabled = results.get('enabled', False)
349
350 except Exception as e:
351 self._enabled = None
352 self.log.exception('GET-failed', e=e)
353
354 initial_port_state = AdminState.ENABLED if self.olt.autoactivate else AdminState.DISABLED
355
356 if self._admin_state != initial_port_state:
Chip Boling3e3b1a92017-05-16 11:51:18 -0500357 try:
Chip Boling252c7772017-08-16 10:13:17 -0500358 enable = initial_port_state == AdminState.ENABLED
359 if self._enabled is None or self._enabled != enable:
360 yield self.set_pon_config("enabled", enable)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500361
362 # TODO: Move to 'set_pon_config' method and also make sure GRPC/Port is ok
Chip Boling252c7772017-08-16 10:13:17 -0500363 self._admin_state = AdminState.ENABLED if enable else AdminState.DISABLED
Chip Boling3e3b1a92017-05-16 11:51:18 -0500364
365 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500366 self.log.exception('reset', e=e)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500367 raise
368
Chip Boling252c7772017-08-16 10:13:17 -0500369 # Walk the provisioned ONU list and disable any exiting ONUs
Chip Boling3e3b1a92017-05-16 11:51:18 -0500370
Chip Boling252c7772017-08-16 10:13:17 -0500371 try:
372 results = yield self.get_onu_config()
Chip Boling3e3b1a92017-05-16 11:51:18 -0500373
Chip Boling252c7772017-08-16 10:13:17 -0500374 if isinstance(results, list) and len(results) > 0:
375 onu_configs = OltConfig.Pon.Onu.decode(results)
376 for onu_id in onu_configs.iterkeys():
377 try:
378 yield self.delete_onu(onu_id)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500379
Chip Boling252c7772017-08-16 10:13:17 -0500380 except Exception as e:
381 self.log.exception('rest-ONU-delete', onu_id=onu_id, e=e)
382 pass # Non-fatal
383
384 except Exception as e:
385 self.log.exception('onu-delete', e=e)
386 raise
Chip Boling3e3b1a92017-05-16 11:51:18 -0500387
Chip Boling5561d552017-07-07 15:11:26 -0500388 returnValue('Reset complete')
389
Chip Boling69fba862017-08-18 15:11:32 -0500390 def restart(self):
391 if self._state == PonPort.State.RUNNING or self._state == PonPort.State.STOPPED:
392 start_it = (self._state == PonPort.State.RUNNING)
393 self._state = PonPort.State.INITIAL
394 return self.start() if start_it else self.stop()
395 return succeed('nop')
396
Chip Boling7294b252017-06-15 16:16:55 -0500397 def delete(self):
398 """
399 Parent device is being deleted. Do not change any config but
400 stop all polling
401 """
Chip Boling252c7772017-08-16 10:13:17 -0500402 self.log.info('Deleting')
Chip Boling7294b252017-06-15 16:16:55 -0500403 self._state = PonPort.State.DELETING
404 self._cancel_deferred()
405
Chip Boling252c7772017-08-16 10:13:17 -0500406 # @property
Chip Boling69fba862017-08-18 15:11:32 -0500407 def gem_ids(self, onu_vid, exception_gems):
Chip Boling252c7772017-08-16 10:13:17 -0500408 """
409 Get all GEM Port IDs used on a given PON
410
Chip Boling69fba862017-08-18 15:11:32 -0500411 :param onu_vid: (int) ONU VLAN ID if customer ONU specific. None if for all ONUs
412 on PON
413 :param exception_gems: (boolean) Select from special purpose ACL GEM-Portas
Chip Boling252c7772017-08-16 10:13:17 -0500414 :return: (dict) key -> onu-id, value -> frozenset of GEM Port IDs
415 """
416 gem_ids = {}
417 for onu_id, onu in self._onu_by_id.iteritems():
Chip Boling69fba862017-08-18 15:11:32 -0500418 if onu_vid is None or onu_vid == onu.onu_vid:
419 gem_ids[onu_id] = onu.gem_ids(exception_gems)
Chip Boling252c7772017-08-16 10:13:17 -0500420 return gem_ids
421
Chip Boling3e3b1a92017-05-16 11:51:18 -0500422 def get_pon_config(self):
Chip Boling7294b252017-06-15 16:16:55 -0500423 uri = AdtranOltHandler.GPON_PON_CONFIG_URI.format(self._pon_id)
424 name = 'pon-get-config-{}'.format(self._pon_id)
425 return self._parent.rest_client.request('GET', uri, name=name)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500426
427 def get_onu_config(self, onu_id=None):
Chip Boling252c7772017-08-16 10:13:17 -0500428 if onu_id is None:
429 uri = AdtranOltHandler.GPON_ONU_CONFIG_LIST_URI.format(self._pon_id)
430 else:
431 uri = AdtranOltHandler.GPON_ONU_CONFIG_URI.format(self._pon_id, onu_id)
432
Chip Boling7294b252017-06-15 16:16:55 -0500433 name = 'pon-get-onu_config-{}-{}'.format(self._pon_id, onu_id)
434 return self._parent.rest_client.request('GET', uri, name=name)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500435
436 def set_pon_config(self, leaf, value):
437 data = json.dumps({leaf: value})
Chip Boling7294b252017-06-15 16:16:55 -0500438 uri = AdtranOltHandler.GPON_PON_CONFIG_URI.format(self._pon_id)
439 name = 'pon-set-config-{}-{}-{}'.format(self._pon_id, leaf, str(value))
440 return self._parent.rest_client.request('PATCH', uri, data=data, name=name)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500441
Chip Boling252c7772017-08-16 10:13:17 -0500442 def _discover_onus(self):
443 self.log.debug('discovery')
Chip Boling3e3b1a92017-05-16 11:51:18 -0500444
Chip Boling7294b252017-06-15 16:16:55 -0500445 if self._admin_state == AdminState.ENABLED:
446 data = json.dumps({'pon-id': self._pon_id})
Chip Boling3e3b1a92017-05-16 11:51:18 -0500447 uri = AdtranOltHandler.GPON_PON_DISCOVER_ONU
Chip Boling7294b252017-06-15 16:16:55 -0500448 name = 'pon-discover-onu-{}'.format(self._pon_id)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500449
Chip Boling252c7772017-08-16 10:13:17 -0500450 self._discovery_deferred = self._parent.rest_client.request('POST', uri, data, name=name)
451 self._discovery_deferred.addBoth(self._onu_discovery_init_complete)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500452
Chip Boling252c7772017-08-16 10:13:17 -0500453 def _onu_discovery_init_complete(self, _):
Chip Boling3e3b1a92017-05-16 11:51:18 -0500454 """
455 This method is called after the REST POST to request ONU discovery is
456 completed. The results (body) of the post is always empty / 204 NO CONTENT
457 """
Chip Boling3e3b1a92017-05-16 11:51:18 -0500458 # Reschedule
459
Chip Boling7294b252017-06-15 16:16:55 -0500460 delay = self._no_onu_discover_tick if len(self._onus) == 0 else self._discovery_tick
Chip Boling3e3b1a92017-05-16 11:51:18 -0500461 delay += random.uniform(-delay / 10, delay / 10)
462
Chip Boling252c7772017-08-16 10:13:17 -0500463 self._discovery_deferred = reactor.callLater(delay, self._discover_onus)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500464
465 def process_status_poll(self, status):
466 """
467 Process PON status poll request
468
469 :param status: (OltState.Pon object) results from RESTCONF GET
470 """
Chip Boling252c7772017-08-16 10:13:17 -0500471 self.log.debug('process-status-poll', status=status)
Chip Boling7294b252017-06-15 16:16:55 -0500472
473 if self._admin_state != AdminState.ENABLED:
Chip Boling3e3b1a92017-05-16 11:51:18 -0500474 return
475
476 # Process the ONU list in for this PON, may have previously provisioned ones there
477 # were discovered on an earlier boot
478
479 new = self._process_status_onu_list(status.onus)
480
481 for onu_id in new:
482 # self.add_new_onu(serial_number, status)
Chip Boling252c7772017-08-16 10:13:17 -0500483 self.log.info('found-ONU', onu_id=onu_id)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500484 raise NotImplementedError('TODO: Adding ONUs from existing ONU (status list) not supported')
485
486 # Get new/missing from the discovered ONU leaf
487
488 new, missing = self._process_status_onu_discovered_list(status.discovered_onu)
489
Chip Boling5561d552017-07-07 15:11:26 -0500490 # TODO: Do something useful (Does the discovery list clear out activated ONU's?)
491 # if len(missing):
Chip Boling252c7772017-08-16 10:13:17 -0500492 # self.log.info('missing-ONUs', missing=missing)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500493
494 for serial_number in new:
495 reactor.callLater(0, self.add_onu, serial_number, status)
496
497 # Process discovered ONU list
498
499 # TODO: Process LOS list
500 # TODO: Process status
501 pass
502
503 def _process_status_onu_list(self, onus):
504 """
505 Look for new or missing ONUs
506
507 :param onus: (dict) Set of known ONUs
508 """
Chip Boling252c7772017-08-16 10:13:17 -0500509 self.log.debug('ONU-list', onus=onus)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500510
Chip Boling7294b252017-06-15 16:16:55 -0500511 my_onu_ids = frozenset([o.onu_id for o in self._onus.itervalues()])
Chip Boling3e3b1a92017-05-16 11:51:18 -0500512 discovered_onus = frozenset(onus.keys())
513
514 new_onus_ids = discovered_onus - my_onu_ids
515 missing_onus_ids = my_onu_ids - discovered_onus
516
517 new = {o: v for o, v in onus.iteritems() if o in new_onus_ids}
518 missing_onus = {o: v for o, v in onus.iteritems() if o in missing_onus_ids}
519
520 return new # , missing_onus # TODO: Support ONU removal
521
522 def _process_status_onu_discovered_list(self, discovered_onus):
523 """
524 Look for new or missing ONUs
525
526 :param discovered_onus: (frozenset) Set of ONUs currently discovered
527 """
Chip Boling252c7772017-08-16 10:13:17 -0500528 self.log.debug('discovered-ONUs', list=discovered_onus)
529
530 # Only request discovery if activation is auto-discovery or auto-activate
531 continue_discovery = ['autodiscovery', 'autoactivate']
532
533 if self._activation_method not in continue_discovery:
534 return set(), set()
Chip Boling3e3b1a92017-05-16 11:51:18 -0500535
Chip Boling7294b252017-06-15 16:16:55 -0500536 my_onus = frozenset(self._onus.keys())
Chip Boling3e3b1a92017-05-16 11:51:18 -0500537
538 new_onus = discovered_onus - my_onus
539 missing_onus = my_onus - discovered_onus
540
541 return new_onus, missing_onus
542
Chip Boling252c7772017-08-16 10:13:17 -0500543 def _get_onu_info(self, serial_number):
544 """
545 Parse through available xPON information for ONU configuration settings
546 :param serial_number: (string) Decoded (not base64) serial number string
547 :return: (dict) onu config data or None on lookup failure
548 """
549 try:
550 from flow.demo_data import get_tconts, get_gem_ports
551
552 if self.activation_method == "autoactivate":
553 onu_id = self.get_next_onu_id()
554 enabled = True
555 channel_speed = 0
556
557 elif self.activation_method == "autodiscovery":
558 if self.authentication_method == 'serial-number':
559 gpon_info = self.olt.get_xpon_info(self.pon_id)
560
561 try:
562 vont_info = next(info for _, info in gpon_info['v_ont_anis'].items()
563 if info.get('expected-serial-number') == serial_number)
564
565 onu_id = vont_info['onu-id']
566 enabled = vont_info['enabled']
567 channel_speed = vont_info['upstream-channel-speed']
568
569 except StopIteration:
570 return None
571 else:
572 return None
573 else:
574 return None
575
576 onu_info = {
577 'serial-number': serial_number,
578 'xpon-name': None,
579 'pon': self,
580 'onu-id': onu_id,
581 'enabled': enabled,
582 'upstream-channel-speed': channel_speed,
583 'password': Onu.DEFAULT_PASSWORD,
584 't-conts': get_tconts(self.pon_id, serial_number, onu_id),
585 'gem-ports': get_gem_ports(self.pon_id, serial_number, onu_id),
Chip Boling69fba862017-08-18 15:11:32 -0500586 'onu-vid': self.olt.get_channel_id(self._pon_id, onu_id)
Chip Boling252c7772017-08-16 10:13:17 -0500587 }
588 return onu_info
589
590 except Exception as e:
591 self.log.exception('get-onu-info', e=e)
592 return None
593
Chip Boling3e3b1a92017-05-16 11:51:18 -0500594 @inlineCallbacks
595 def add_onu(self, serial_number, status):
Chip Boling252c7772017-08-16 10:13:17 -0500596 self.log.info('add-ONU', serial_number=serial_number)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500597
598 if serial_number not in status.onus:
599 # Newly found and not enabled ONU, enable it now if not at max
600
Chip Boling252c7772017-08-16 10:13:17 -0500601 if len(self._onus) >= self.MAX_ONUS_SUPPORTED:
602 self.log.warning('max-onus-provisioned')
603 else:
604 onu_info = self._get_onu_info(Onu.serial_number_to_string(serial_number))
Chip Boling3e3b1a92017-05-16 11:51:18 -0500605
Chip Boling252c7772017-08-16 10:13:17 -0500606 if onu_info is None:
607 self.log.info('lookup-failure', serial_number=serial_number)
608
609 elif serial_number in self._onus or onu_info['onu-id'] in self._onu_by_id:
610 self.log.warning('onu-already-added', serial_number=serial_number)
611
612 else:
613 # TODO: Make use of upstream_channel_speed variable
614 onu = Onu(onu_info)
615 self._onus[serial_number] = onu
616 self._onu_by_id[onu.onu_id] = onu
617
Chip Boling3e3b1a92017-05-16 11:51:18 -0500618 try:
Chip Boling252c7772017-08-16 10:13:17 -0500619 yield onu.create(onu_info)
620 self.activate_onu(onu)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500621
622 except Exception as e:
Chip Boling252c7772017-08-16 10:13:17 -0500623 del self._onus[serial_number]
624 del self._onu_by_id[onu.onu_id]
625 self.log.exception('add_onu', serial_number=serial_number, e=e)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500626
Chip Boling252c7772017-08-16 10:13:17 -0500627 def activate_onu(self, onu):
Chip Boling3e3b1a92017-05-16 11:51:18 -0500628 """
629 Called when a new ONU is discovered and VOLTHA device adapter needs to be informed
630 :param onu:
631 :return:
632 """
Chip Boling7294b252017-06-15 16:16:55 -0500633 olt = self.olt
634 adapter = self.adapter_agent
Chip Boling69fba862017-08-18 15:11:32 -0500635 channel_id = onu.onu_vid
Chip Boling3e3b1a92017-05-16 11:51:18 -0500636
Chip Boling5561d552017-07-07 15:11:26 -0500637 proxy = Device.ProxyAddress(device_id=olt.device_id, channel_id=channel_id)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500638
639 adapter.child_device_detected(parent_device_id=olt.device_id,
Chip Boling7294b252017-06-15 16:16:55 -0500640 parent_port_no=self._port_no,
Chip Boling252c7772017-08-16 10:13:17 -0500641 child_device_type=onu.vendor_id,
Nikolay Titov89004ec2017-06-19 18:22:42 -0400642 proxy_address=proxy,
Chip Boling5561d552017-07-07 15:11:26 -0500643 admin_state=AdminState.ENABLED,
644 vlan=channel_id)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500645
646 def get_next_onu_id(self):
Chip Boling5561d552017-07-07 15:11:26 -0500647 used_ids = [onu.onu_id for onu in self._onus.itervalues()]
Chip Boling3e3b1a92017-05-16 11:51:18 -0500648
649 while True:
Chip Boling5561d552017-07-07 15:11:26 -0500650 onu_id = self._next_onu_id
651 self._next_onu_id += 1
Chip Boling3e3b1a92017-05-16 11:51:18 -0500652
Chip Boling5561d552017-07-07 15:11:26 -0500653 if self._next_onu_id > Onu.MAX_ONU_ID:
654 self._next_onu_id = Onu.MIN_ONU_ID
Chip Boling3e3b1a92017-05-16 11:51:18 -0500655
656 if onu_id not in used_ids:
657 return onu_id
658
659 def delete_onu(self, onu_id):
Chip Boling252c7772017-08-16 10:13:17 -0500660 uri = AdtranOltHandler.GPON_ONU_CONFIG_URI.format(self._pon_id, onu_id)
Chip Boling7294b252017-06-15 16:16:55 -0500661 name = 'pon-delete-onu-{}-{}'.format(self._pon_id, onu_id)
Chip Boling3e3b1a92017-05-16 11:51:18 -0500662
Chip Boling252c7772017-08-16 10:13:17 -0500663 # Remove from any local dictionary
664 if onu_id in self._onu_by_id:
665 del self._onu_by_id[onu_id]
666 for sn in [onu.serial_numbers for onu in self._onus.itervalues() if onu.onu_id == onu_id]:
667 del self._onus[sn]
668
Chip Boling3e3b1a92017-05-16 11:51:18 -0500669 # TODO: Need removal from VOLTHA child_device method
670
Chip Boling7294b252017-06-15 16:16:55 -0500671 return self._parent.rest_client.request('DELETE', uri, name=name)
Chip Boling252c7772017-08-16 10:13:17 -0500672
673 @inlineCallbacks
674 def channel_partition(self, name, partition=0, xpon_system=0, operation=None):
675 """
676 Delete/enable/disable a specified channel partition on this PON.
677
678 When creating a new Channel Partition, create it disabled, then define any associated
679 Channel Pairs. Then enable the Channel Partition.
680
681 :param name: (string) Name of the channel partition
682 :param partition: (int: 0..15) An index of the operator-specified channel subset
683 in a NG-PON2 system. For XGS-PON, this is typically 0
684 :param xpon_system: (int: 0..1048575) Identifies a specific xPON system
685 :param operation: (string) 'delete', 'enable', or 'disable'
686 """
687 if operation.lower() not in ['delete', 'enable', 'disable']:
688 raise ValueError('Unsupported operation: {}'.format(operation))
689
690 try:
691 xml = 'interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"'
692
693 if operation.lower() is 'delete':
694 xml += '<interface operation="delete">'
695 else:
696 xml += '<interface>'
697 xml += '<type xmlns:adtn-xp="http://www.adtran.com/ns/yang/adtran-xpon">' +\
698 'adtn-xp:xpon-channel-partition</type>'
699 xml += '<adtn-xp:channel-partition xmlns:adtn-xp="http://www.adtran.com/ns/yang/adtran-xpon">'
700 xml += ' <adtn-xp:partition-id>{}</adtn-xp:partition-id>'.format(partition)
701 xml += ' <adtn-xp:xpon-system>{}</adtn-xp:xpon-system>'.format(xpon_system)
702 xml += '</adtn-xp:channel-partition>'
703 xml += '<enabled>{}</enabled>'.format('true' if operation.lower() == 'enable' else 'false')
704
705 xml += '<name>{}</name>'.format(name)
706 xml += '</interface></interfaces>'
707
708 results = yield self.olt.netconf_client.edit_config(xml)
709 returnValue(results)
710
711 except Exception as e:
712 self.log.exception('channel_partition')
713 raise
714
715 @inlineCallbacks
716 def channel_pair(self, name, partition, operation=None, **kwargs):
717 """
718 Create/delete a channel pair on a specific channel_partition for a PON
719
720 :param name: (string) Name of the channel pair
721 :param partition: (string) Name of the channel partition
722 :param operation: (string) 'delete', 'enable', or 'disable'
723 :param kwargs: (dict) Additional leaf settings if desired
724 """
725 if operation.lower() not in ['delete', 'enable', 'disable']:
726 raise ValueError('Unsupported operation: {}'.format(operation))
727
728 try:
729 xml = 'interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"'
730
731 if operation.lower() is 'delete':
732 xml += '<interface operation="delete">'
733 else:
734 xml += '<interface>'
735 xml += '<type xmlns:adtn-xp="http://www.adtran.com/ns/yang/adtran-xpon">' +\
736 'adtn-xp:xpon-channel-pair</type>'
737 xml += '<adtn-xp:channel-pair xmlns:adtn-xp="http://www.adtran.com/ns/yang/adtran-xpon">'
738 xml += ' <adtn-xp:channel-partition>{}</adtn-xp:channel-partition>'.format(partition)
739 xml += ' <adtn-xp:channel-termination>channel-termination {}</adtn-xp:channel-termination>'.\
740 format(self.pon_id)
741 xml += ' <adtn-xp:upstream-admin-label>{}</adtn-xp:upstream-admin-label>'.\
742 format(kwargs.get('upstream-admin-label', 1))
743 xml += ' <adtn-xp:downstream-admin-label>{}</adtn-xp:downstream-admin-label>'.\
744 format(kwargs.get('downstream-admin-label', 1))
745 xml += ' <adtn-xp:upstream-channel-id>{}</adtn-xp:upstream-channel-id>'.\
746 format(kwargs.get('upstream-channel-id', 15))
747 xml += ' <adtn-xp:downstream-channel-id>{}</adtn-xp:downstream-channel-id>'.\
748 format(kwargs.get('downstream-channel-id', 15))
749 xml += ' <adtn-xp:downstream-channel-fec-enable>{}</adtn-xp:downstream-channel-fec-enable>'. \
750 format('true' if kwargs.get('downstream-channel-fec-enable', True) else 'false')
751 xml += ' <adtn-xp:upstream-channel-fec-enable>{}</adtn-xp:upstream-channel-fec-enable>'. \
752 format('true' if kwargs.get('upstream-channel-fec-enable', True) else 'false')
753 xml += '</adtn-xp:channel-pair>'
754 # TODO: Add support for upstream/downstream FEC-enable coming from here and not hard-coded
755
756 xml += '<name>{}</name>'.format(name)
757 xml += '</interface></interfaces>'
758
759 results = yield self.olt.netconf_client.edit_config(xml)
760 returnValue(results)
761
762 except Exception as e:
763 self.log.exception('channel_pair')
764 raise