Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 1 | # |
| 2 | # Copyright 2017 the original author or authors. |
| 3 | # |
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | # you may not use this file except in compliance with the License. |
| 6 | # You may obtain a copy of the License at |
| 7 | # |
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | # |
| 10 | # Unless required by applicable law or agreed to in writing, software |
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | # See the License for the specific language governing permissions and |
| 14 | # limitations under the License. |
| 15 | # |
| 16 | import structlog |
| 17 | import ast |
| 18 | from pon_port import PonPort |
| 19 | from uni_port import UniPort |
| 20 | from heartbeat import HeartBeat |
| 21 | from omci.omci import OMCI |
| 22 | from onu_traffic_descriptor import OnuTrafficDescriptor |
| 23 | from onu_tcont import OnuTCont |
| 24 | from onu_gem_port import OnuGemPort |
| 25 | |
| 26 | from pyvoltha.adapters.extensions.alarms.adapter_alarms import AdapterAlarms |
| 27 | from pyvoltha.adapters.extensions.kpi.onu.onu_pm_metrics import OnuPmMetrics |
| 28 | from pyvoltha.adapters.extensions.kpi.onu.onu_omci_pm import OnuOmciPmMetrics |
| 29 | |
| 30 | from twisted.internet import reactor |
| 31 | from twisted.internet.defer import DeferredQueue, inlineCallbacks |
| 32 | from twisted.internet.defer import returnValue |
| 33 | |
| 34 | from pyvoltha.common.utils.registry import registry |
| 35 | from pyvoltha.protos import third_party |
| 36 | from pyvoltha.protos.common_pb2 import OperStatus, ConnectStatus |
| 37 | from pyvoltha.common.tech_profile.tech_profile import TechProfile |
| 38 | from pyvoltha.adapters.common.kvstore.consul_client import Consul |
| 39 | from pyvoltha.adapters.common.kvstore.etcd_client import EtcdClient |
| 40 | |
| 41 | import adtran_olt.resources.adtranolt_platform as platform |
| 42 | from adapters.adtran_common.flow.flow_entry import FlowEntry |
| 43 | from omci.adtn_install_flow import AdtnInstallFlowTask |
| 44 | from omci.adtn_remove_flow import AdtnRemoveFlowTask |
| 45 | from omci.adtn_tp_service_specific_task import AdtnTpServiceSpecificTask |
| 46 | from pyvoltha.common.tech_profile.tech_profile import DEFAULT_TECH_PROFILE_TABLE_ID |
| 47 | |
| 48 | _ = third_party |
| 49 | _MAXIMUM_PORT = 17 # Only one PON and UNI port at this time |
| 50 | _ONU_REBOOT_MIN = 90 # IBONT 602 takes about 3 minutes |
| 51 | _ONU_REBOOT_RETRY = 10 |
| 52 | _STARTUP_RETRY_WAIT = 20 |
| 53 | |
| 54 | |
| 55 | class AdtranOnuHandler(object): |
| 56 | def __init__(self, adapter, device_id): |
| 57 | self.adapter = adapter |
| 58 | self.adapter_agent = adapter.adapter_agent |
| 59 | self.device_id = device_id |
| 60 | self.log = structlog.get_logger(device_id=device_id) |
| 61 | self.logical_device_id = None |
| 62 | self.proxy_address = None |
| 63 | self._enabled = False |
| 64 | self.pm_metrics = None |
| 65 | self.alarms = None |
| 66 | |
| 67 | self._openomci = OMCI(self, adapter.omci_agent) |
| 68 | self._in_sync_subscription = None |
| 69 | |
| 70 | self._pon_port_number = 1 |
| 71 | |
| 72 | self._unis = dict() # Port # -> UniPort |
| 73 | self._pon = PonPort.create(self, self._pon_port_number) |
| 74 | self._heartbeat = HeartBeat.create(self, device_id) |
| 75 | self._deferred = None |
| 76 | |
| 77 | # Flow entries |
| 78 | self._flows = dict() |
| 79 | |
| 80 | # OMCI resources # TODO: Some of these could be dynamically chosen |
| 81 | self.vlan_tcis_1 = 0x900 |
| 82 | self.mac_bridge_service_profile_entity_id = self.vlan_tcis_1 |
| 83 | self.gal_enet_profile_entity_id = 0 |
| 84 | |
| 85 | # Technology profile related values |
| 86 | self.incoming_messages = DeferredQueue() |
| 87 | self.event_messages = DeferredQueue() |
| 88 | self._tp_service_specific_task = dict() |
| 89 | self._tech_profile_download_done = dict() |
| 90 | |
| 91 | # Initialize KV store client |
| 92 | self.args = registry('main').get_args() |
| 93 | if self.args.backend == 'etcd': |
| 94 | host, port = self.args.etcd.split(':', 1) |
| 95 | self.kv_client = EtcdClient(host, port, |
| 96 | TechProfile.KV_STORE_TECH_PROFILE_PATH_PREFIX) |
| 97 | elif self.args.backend == 'consul': |
| 98 | host, port = self.args.consul.split(':', 1) |
| 99 | self.kv_client = Consul(host, port, |
| 100 | TechProfile.KV_STORE_TECH_PROFILE_PATH_PREFIX) |
| 101 | else: |
| 102 | self.log.error('Invalid-backend') |
| 103 | raise Exception("Invalid-backend-for-kv-store") |
| 104 | |
| 105 | # Handle received ONU event messages |
| 106 | reactor.callLater(0, self.handle_onu_events) |
| 107 | |
| 108 | def __str__(self): |
| 109 | return "AdtranOnuHandler: {}".format(self.device_id) |
| 110 | |
| 111 | def _cancel_deferred(self): |
| 112 | d, self._deferred = self._deferred, None |
| 113 | try: |
| 114 | if d is not None and not d.called: |
| 115 | d.cancel() |
| 116 | except: |
| 117 | pass |
| 118 | |
| 119 | @property |
| 120 | def enabled(self): |
| 121 | return self._enabled |
| 122 | |
| 123 | @enabled.setter |
| 124 | def enabled(self, value): |
| 125 | assert isinstance(value, bool), 'enabled is a boolean' |
| 126 | if self._enabled != value: |
| 127 | self._enabled = value |
| 128 | if self._enabled: |
| 129 | self.start() |
| 130 | else: |
| 131 | self.stop() |
| 132 | |
| 133 | @property |
| 134 | def openomci(self): |
| 135 | return self._openomci |
| 136 | |
| 137 | @property |
| 138 | def heartbeat(self): |
| 139 | return self._heartbeat |
| 140 | |
| 141 | @property |
| 142 | def uni_ports(self): |
| 143 | return self._unis.values() |
| 144 | |
| 145 | def uni_port(self, port_no_or_name): |
| 146 | if isinstance(port_no_or_name, (str, unicode)): |
| 147 | return next((uni for uni in self.uni_ports |
| 148 | if uni.name == port_no_or_name), None) |
| 149 | |
| 150 | assert isinstance(port_no_or_name, int), 'Invalid parameter type' |
| 151 | return self._unis.get(port_no_or_name) |
| 152 | |
| 153 | def pon_port(self, port_no=None): |
| 154 | return self._pon if port_no is None or port_no == self._pon.port_number else None |
| 155 | |
| 156 | @property |
| 157 | def pon_ports(self): |
| 158 | return [self._pon] |
| 159 | |
| 160 | def start(self): |
| 161 | assert self._enabled, 'Start should only be called if enabled' |
| 162 | self._cancel_deferred() |
| 163 | |
| 164 | # Register for adapter messages |
| 165 | self.adapter_agent.register_for_inter_adapter_messages() |
| 166 | |
| 167 | # OpenOMCI Startup |
| 168 | self._subscribe_to_events() |
| 169 | self._openomci.enabled = True |
| 170 | |
| 171 | # Port startup |
| 172 | if self._pon is not None: |
| 173 | self._pon.enabled = True |
| 174 | |
| 175 | for port in self.uni_ports: |
| 176 | port.enabled = True |
| 177 | |
| 178 | # Heartbeat |
| 179 | self._heartbeat.enabled = True |
| 180 | |
| 181 | def stop(self): |
| 182 | assert not self._enabled, 'Stop should only be called if disabled' |
| 183 | self._cancel_deferred() |
| 184 | |
| 185 | # Drop registration for adapter messages |
| 186 | self.adapter_agent.unregister_for_inter_adapter_messages() |
| 187 | |
| 188 | # Heartbeat |
| 189 | self._heartbeat.enabled = False |
| 190 | |
| 191 | # OMCI Communications |
| 192 | self._unsubscribe_to_events() |
| 193 | |
| 194 | # Port shutdown |
| 195 | for port in self.uni_ports: |
| 196 | port.enabled = False |
| 197 | |
| 198 | if self._pon is not None: |
| 199 | self._pon.enabled = False |
| 200 | self._openomci.enabled = False |
| 201 | |
| 202 | def receive_message(self, msg): |
| 203 | if self.enabled: |
| 204 | # TODO: Have OpenOMCI actually receive the messages |
| 205 | self.openomci.receive_message(msg) |
| 206 | |
| 207 | def activate(self, device): |
| 208 | self.log.info('activating') |
| 209 | |
| 210 | try: |
| 211 | # first we verify that we got parent reference and proxy info |
| 212 | assert device.parent_id, 'Invalid Parent ID' |
| 213 | assert device.proxy_address.device_id, 'Invalid Device ID' |
| 214 | |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 215 | # Cache our proxy address |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 216 | self.proxy_address = device.proxy_address |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 217 | |
| 218 | # initialize device info |
| 219 | device.root = False |
| 220 | device.vendor = 'Adtran Inc.' |
| 221 | device.model = 'n/a' |
| 222 | device.hardware_version = 'n/a' |
| 223 | device.firmware_version = 'n/a' |
| 224 | device.reason = '' |
| 225 | device.connect_status = ConnectStatus.UNKNOWN |
| 226 | |
| 227 | # Register physical ports. Should have at least one of each |
| 228 | self.adapter_agent.add_port(device.id, self._pon.get_port()) |
| 229 | |
| 230 | def xpon_not_found(): |
| 231 | self.enabled = True |
| 232 | |
| 233 | # Schedule xPON 'not found' startup for 10 seconds from now. We will |
| 234 | # easily get a vONT-ANI create within that time if xPON is being used |
| 235 | # as this is how we are initially launched and activated in the first |
| 236 | # place if xPON is in use. |
| 237 | reactor.callLater(10, xpon_not_found) # TODO: Clean up old xPON delay |
| 238 | |
| 239 | # reference of uni_port is required when re-enabling the device if |
| 240 | # it was disabled previously |
| 241 | # Need to query ONU for number of supported uni ports |
| 242 | # For now, temporarily set number of ports to 1 - port #2 |
| 243 | parent_device = self.adapter_agent.get_device(device.parent_id) |
| 244 | |
| 245 | self.logical_device_id = parent_device.parent_id |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 246 | yield self.adapter_agent.device_update(device) |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 247 | |
| 248 | ############################################################################ |
| 249 | # Setup PM configuration for this device |
| 250 | # Pass in ONU specific options |
| 251 | kwargs = { |
| 252 | OnuPmMetrics.DEFAULT_FREQUENCY_KEY: OnuPmMetrics.DEFAULT_ONU_COLLECTION_FREQUENCY, |
| 253 | 'heartbeat': self.heartbeat, |
| 254 | OnuOmciPmMetrics.OMCI_DEV_KEY: self.openomci.onu_omci_device |
| 255 | } |
| 256 | self.pm_metrics = OnuPmMetrics(self.adapter_agent, self.device_id, |
| 257 | self.logical_device_id, grouped=True, |
| 258 | freq_override=False, **kwargs) |
| 259 | pm_config = self.pm_metrics.make_proto() |
| 260 | self.openomci.set_pm_config(self.pm_metrics.omci_pm.openomci_interval_pm) |
| 261 | self.log.info("initial-pm-config", pm_config=pm_config) |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 262 | yield self.adapter_agent.device_pm_config_update(pm_config, init=True) |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 263 | |
| 264 | ############################################################################ |
| 265 | # Setup Alarm handler |
| 266 | self.alarms = AdapterAlarms(self.adapter_agent, device.id, self.logical_device_id) |
| 267 | self.openomci.onu_omci_device.alarm_synchronizer.set_alarm_params(mgr=self.alarms, |
| 268 | ani_ports=[self._pon]) |
| 269 | ############################################################################ |
| 270 | # Start collecting stats from the device after a brief pause |
| 271 | reactor.callLater(30, self.pm_metrics.start_collector) |
| 272 | |
| 273 | except Exception as e: |
| 274 | self.log.exception('activate-failure', e=e) |
| 275 | device.reason = 'Failed to activate: {}'.format(e.message) |
| 276 | device.connect_status = ConnectStatus.UNREACHABLE |
| 277 | device.oper_status = OperStatus.FAILED |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 278 | self.adapter_agent.device_update(device) |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 279 | |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 280 | @inlineCallbacks |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 281 | def reconcile(self, device): |
| 282 | self.log.info('reconciling-ONU-device-starts') |
| 283 | |
| 284 | # first we verify that we got parent reference and proxy info |
| 285 | assert device.parent_id |
| 286 | assert device.proxy_address.device_id |
| 287 | # assert device.proxy_address.channel_id |
| 288 | self._cancel_deferred() |
| 289 | |
| 290 | # register for proxied messages right away |
| 291 | self.proxy_address = device.proxy_address |
| 292 | self.adapter_agent.register_for_proxied_messages(device.proxy_address) |
| 293 | |
| 294 | # Register for adapter messages |
| 295 | self.adapter_agent.register_for_inter_adapter_messages() |
| 296 | |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 297 | # TODO: Verify that the uni, pon and logical ports exists |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 298 | self.enabled = True |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 299 | |
| 300 | # Mark the device as REACHABLE and ACTIVE |
| 301 | device = self.adapter_agent.get_device(device.id) |
| 302 | device.connect_status = ConnectStatus.REACHABLE |
| 303 | device.oper_status = OperStatus.ACTIVE |
| 304 | device.reason = '' |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 305 | yield self.adapter_agent.device_update(device) |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 306 | |
| 307 | self.log.info('reconciling-ONU-device-ends') |
| 308 | |
| 309 | @inlineCallbacks |
| 310 | def handle_onu_events(self): |
| 311 | # TODO: Add 'shutdown' message to exit loop |
| 312 | event_msg = yield self.event_messages.get() |
| 313 | try: |
| 314 | if event_msg['event'] == 'download_tech_profile': |
| 315 | tp_path = event_msg['event_data'] |
| 316 | uni_id = event_msg['uni_id'] |
| 317 | self.load_and_configure_tech_profile(uni_id, tp_path) |
| 318 | |
| 319 | except Exception as e: |
| 320 | self.log.error("exception-handling-onu-event", e=e) |
| 321 | |
| 322 | # Handle next event |
| 323 | reactor.callLater(0, self.handle_onu_events) |
| 324 | |
| 325 | def _tp_path_to_tp_id(self, tp_path): |
| 326 | parts = tp_path.split('/') |
| 327 | if len(parts) > 2: |
| 328 | try: |
| 329 | return int(tp_path[1]) |
| 330 | except ValueError: |
| 331 | return DEFAULT_TECH_PROFILE_TABLE_ID |
| 332 | |
| 333 | def _create_tcont(self, uni_id, us_scheduler, tech_profile_id): |
| 334 | """ |
| 335 | Decode Upstream Scheduler and create appropriate TCONT structures |
| 336 | |
| 337 | :param uni_id: (int) UNI ID on the PON |
| 338 | :param us_scheduler: (Scheduler) Upstream Scheduler with TCONT information |
| 339 | :param tech_profile_id: (int) Tech Profile ID |
| 340 | |
| 341 | :return (OnuTCont) Created TCONT |
| 342 | """ |
| 343 | self.log.debug('create-tcont', us_scheduler=us_scheduler, profile_id=tech_profile_id) |
| 344 | |
| 345 | q_sched_policy = { |
| 346 | 'strictpriority': 1, # Per TCONT (ME #262) values |
| 347 | 'wrr': 2 |
| 348 | }.get(us_scheduler.get('q_sched_policy', 'none').lower(), 0) |
| 349 | |
| 350 | tcont_data = { |
| 351 | 'tech-profile-id': tech_profile_id, |
| 352 | 'uni-id': uni_id, |
| 353 | 'alloc-id': us_scheduler['alloc_id'], |
| 354 | 'q-sched-policy': q_sched_policy |
| 355 | } |
| 356 | # TODO: Support TD if shaping on ONU is to be performed |
| 357 | td = OnuTrafficDescriptor(0, 0, 0) |
| 358 | tcont = OnuTCont.create(self, tcont_data, td) |
| 359 | self._pon.add_tcont(tcont) |
| 360 | return tcont |
| 361 | |
| 362 | # Called when there is an olt up indication, providing the gem port id chosen by the olt handler |
| 363 | def _create_gemports(self, upstream_ports, downstream_ports, tcont, uni_id, tech_profile_id): |
| 364 | """ |
| 365 | Create GEM Ports for a specifc tech profile |
| 366 | |
| 367 | The routine will attempt to combine upstream and downstream GEM Ports into bidirectional |
| 368 | ports where possible |
| 369 | |
| 370 | :param upstream_ports: (list of IGemPortAttribute) Upstream GEM Port attributes |
| 371 | :param downstream_ports: (list of IGemPortAttribute) Downstream GEM Port attributes |
| 372 | :param tcont: (OnuTCont) Associated TCONT |
| 373 | :param uni_id: (int) UNI Instance ID |
| 374 | :param tech_profile_id: (int) Tech Profile ID |
| 375 | """ |
| 376 | self.log.debug('create-gemports', upstream=upstream_ports, |
| 377 | downstream_ports=downstream_ports, |
| 378 | tcont=tcont, tech_id=tech_profile_id) |
| 379 | # Convert GEM Port lists to dicts with GEM ID as they key |
| 380 | upstream = {gem['gemport_id']: gem for gem in upstream_ports} |
| 381 | downstream = {gem['gemport_id']: gem for gem in downstream_ports} |
| 382 | |
| 383 | upstream_ids = set(upstream.keys()) |
| 384 | downstream_ids = set(downstream.keys()) |
| 385 | bidirectional_ids = upstream_ids & downstream_ids |
| 386 | |
| 387 | gem_port_types = { # Keys are the 'direction' attribute value, value is list of GEM attributes |
| 388 | OnuGemPort.UPSTREAM: [upstream[gid] for gid in upstream_ids - bidirectional_ids], |
| 389 | OnuGemPort.DOWNSTREAM: [downstream[gid] for gid in downstream_ids - bidirectional_ids], |
| 390 | OnuGemPort.BIDIRECTIONAL: [upstream[gid] for gid in bidirectional_ids] |
| 391 | } |
| 392 | for direction, gem_list in gem_port_types.items(): |
| 393 | for gem in gem_list: |
| 394 | gem_data = { |
| 395 | 'gemport-id': gem['gemport_id'], |
| 396 | 'direction': direction, |
| 397 | 'encryption': gem['aes_encryption'].lower() == 'true', |
| 398 | 'discard-policy': gem['discard_policy'], |
| 399 | 'max-q-size': gem['max_q_size'], |
| 400 | 'pbit-map': gem['pbit_map'], |
| 401 | 'priority-q': gem['priority_q'], |
| 402 | 'scheduling-policy': gem['scheduling_policy'], |
| 403 | 'weight': gem['weight'], |
| 404 | 'uni-id': uni_id, |
| 405 | 'discard-config': { |
| 406 | 'max-probability': gem['discard_config']['max_probability'], |
| 407 | 'max-threshold': gem['discard_config']['max_threshold'], |
| 408 | 'min-threshold': gem['discard_config']['min_threshold'], |
| 409 | }, |
| 410 | } |
| 411 | gem_port = OnuGemPort.create(self, gem_data, tcont.alloc_id, |
| 412 | tech_profile_id, uni_id, |
| 413 | self._pon.next_gem_entity_id) |
| 414 | self._pon.add_gem_port(gem_port) |
| 415 | |
| 416 | def _do_tech_profile_configuration(self, uni_id, tp, tech_profile_id): |
| 417 | us_scheduler = tp['us_scheduler'] |
| 418 | tcont = self._create_tcont(uni_id, us_scheduler, tech_profile_id) |
| 419 | |
| 420 | upstream = tp['upstream_gem_port_attribute_list'] |
| 421 | downstream = tp['downstream_gem_port_attribute_list'] |
| 422 | self._create_gemports(upstream, downstream, tcont, uni_id, tech_profile_id) |
| 423 | |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 424 | @inlineCallbacks |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 425 | def load_and_configure_tech_profile(self, uni_id, tp_path): |
| 426 | self.log.debug("loading-tech-profile-configuration", uni_id=uni_id, tp_path=tp_path) |
| 427 | |
| 428 | if uni_id not in self._tp_service_specific_task: |
| 429 | self._tp_service_specific_task[uni_id] = dict() |
| 430 | |
| 431 | if uni_id not in self._tech_profile_download_done: |
| 432 | self._tech_profile_download_done[uni_id] = dict() |
| 433 | |
| 434 | if tp_path not in self._tech_profile_download_done[uni_id]: |
| 435 | self._tech_profile_download_done[uni_id][tp_path] = False |
| 436 | |
| 437 | if not self._tech_profile_download_done[uni_id][tp_path]: |
| 438 | try: |
| 439 | if tp_path in self._tp_service_specific_task[uni_id]: |
| 440 | self.log.info("tech-profile-config-already-in-progress", |
| 441 | tp_path=tp_path) |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 442 | returnValue('already-in-progress') |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 443 | |
| 444 | tp = self.kv_client[tp_path] |
| 445 | tp = ast.literal_eval(tp) |
| 446 | self.log.debug("tp-instance", tp=tp) |
| 447 | |
| 448 | tech_profile_id = self._tp_path_to_tp_id(tp_path) |
| 449 | self._do_tech_profile_configuration(uni_id, tp, tech_profile_id) |
| 450 | |
| 451 | def success(_results): |
| 452 | self.log.info("tech-profile-config-done-successfully") |
| 453 | device = self.adapter_agent.get_device(self.device_id) |
| 454 | device.reason = '' |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 455 | yield self.adapter_agent.device_update(device) |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 456 | |
| 457 | if tp_path in self._tp_service_specific_task[uni_id]: |
| 458 | del self._tp_service_specific_task[uni_id][tp_path] |
| 459 | |
| 460 | self._tech_profile_download_done[uni_id][tp_path] = True |
| 461 | |
| 462 | def failure(_reason): |
| 463 | self.log.warn('tech-profile-config-failure-retrying', reason=_reason) |
| 464 | device = self.adapter_agent.get_device(self.device_id) |
| 465 | device.reason = 'Tech Profile config failed-retrying' |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 466 | yield self.adapter_agent.device_update(device) |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 467 | |
| 468 | if tp_path in self._tp_service_specific_task[uni_id]: |
| 469 | del self._tp_service_specific_task[uni_id][tp_path] |
| 470 | |
| 471 | self._deferred = reactor.callLater(_STARTUP_RETRY_WAIT, |
| 472 | self.load_and_configure_tech_profile, |
| 473 | uni_id, tp_path) |
| 474 | |
| 475 | self.log.info('downloading-tech-profile-configuration') |
| 476 | tp_task = AdtnTpServiceSpecificTask(self.openomci.omci_agent, self, uni_id) |
| 477 | |
| 478 | self._tp_service_specific_task[uni_id][tp_path] = tp_task |
| 479 | self._deferred = self.openomci.onu_omci_device.task_runner.queue_task(tp_task) |
| 480 | self._deferred.addCallbacks(success, failure) |
| 481 | |
| 482 | except Exception as e: |
| 483 | self.log.exception("error-loading-tech-profile", e=e) |
| 484 | else: |
| 485 | self.log.info("tech-profile-config-already-done") |
| 486 | |
| 487 | def update_pm_config(self, _device, pm_config): |
| 488 | # TODO: This has not been tested |
| 489 | self.log.info('update_pm_config', pm_config=pm_config) |
| 490 | self.pm_metrics.update(pm_config) |
| 491 | |
| 492 | @inlineCallbacks |
| 493 | def update_flow_table(self, flows): |
| 494 | if len(flows) == 0: |
| 495 | returnValue('nop') # TODO: Do we need to delete all flows if empty? |
| 496 | |
| 497 | self.log.debug('bulk-flow-update', flows=flows) |
| 498 | valid_flows = set() |
| 499 | |
| 500 | for flow in flows: |
| 501 | # Decode it |
| 502 | flow_entry = FlowEntry.create(flow, self) |
| 503 | |
| 504 | # Already handled? |
| 505 | if flow_entry.flow_id in self._flows: |
| 506 | valid_flows.add(flow_entry.flow_id) |
| 507 | |
| 508 | if flow_entry is None or flow_entry.flow_direction not in \ |
| 509 | FlowEntry.upstream_flow_types | FlowEntry.downstream_flow_types: |
| 510 | continue |
| 511 | |
| 512 | is_upstream = flow_entry.flow_direction in FlowEntry.upstream_flow_types |
| 513 | |
| 514 | # Ignore untagged upstream etherType flows. These are trapped at the |
| 515 | # OLT and the default flows during initial OMCI service download will |
| 516 | # send them to the Default VLAN (4091) port for us |
| 517 | if is_upstream and flow_entry.vlan_vid is None and flow_entry.etype is not None: |
| 518 | continue |
| 519 | |
| 520 | # Also ignore upstream untagged/priority tag that sets priority tag |
| 521 | # since that is already installed and any user-data flows for upstream |
| 522 | # priority tag data will be at a higher level. Also should ignore the |
| 523 | # corresponding priority-tagged to priority-tagged flow as well. |
| 524 | if (flow_entry.vlan_vid == 0 and flow_entry.set_vlan_vid == 0) or \ |
| 525 | (flow_entry.vlan_vid is None and flow_entry.set_vlan_vid == 0 |
| 526 | and not is_upstream): |
| 527 | continue |
| 528 | |
| 529 | # Add it to hardware |
| 530 | try: |
| 531 | def failed(_reason, fid): |
| 532 | del self._flows[fid] |
| 533 | |
| 534 | task = AdtnInstallFlowTask(self.openomci.omci_agent, self, flow_entry) |
| 535 | d = self.openomci.onu_omci_device.task_runner.queue_task(task) |
| 536 | d.addErrback(failed, flow_entry.flow_id) |
| 537 | |
| 538 | valid_flows.add(flow_entry.flow_id) |
| 539 | self._flows[flow_entry.flow_id] = flow_entry |
| 540 | |
| 541 | except Exception as e: |
| 542 | self.log.exception('flow-add', e=e, flow=flow_entry) |
| 543 | |
| 544 | # Now check for flows that were missing in the bulk update |
| 545 | deleted_flows = set(self._flows.keys()) - valid_flows |
| 546 | |
| 547 | for flow_id in deleted_flows: |
| 548 | try: |
| 549 | del_flow = self._flows[flow_id] |
| 550 | |
| 551 | task = AdtnRemoveFlowTask(self.openomci.omci_agent, self, del_flow) |
| 552 | self.openomci.onu_omci_device.task_runner.queue_task(task) |
| 553 | # TODO: Change to success/failure callback checks later |
| 554 | # d.addCallback(success, flow_entry.flow_id) |
| 555 | del self._flows[flow_id] |
| 556 | |
| 557 | except Exception as e: |
| 558 | self.log.exception('flow-remove', e=e, flow=self._flows[flow_id]) |
| 559 | |
| 560 | |
| 561 | def remove_from_flow_table(self, _flows): |
| 562 | """ |
| 563 | Remove flows from the device |
| 564 | |
| 565 | :param _flows: (list) Flows |
| 566 | """ |
| 567 | raise NotImplementedError |
| 568 | |
| 569 | def add_to_flow_table(self, _flows): |
| 570 | """ |
| 571 | Remove flows from the device |
| 572 | |
| 573 | :param _flows: (list) Flows |
| 574 | """ |
| 575 | raise NotImplementedError |
| 576 | |
| 577 | @inlineCallbacks |
| 578 | def reboot(self): |
| 579 | self.log.info('rebooting', device_id=self.device_id) |
| 580 | self._cancel_deferred() |
| 581 | |
| 582 | reregister = False |
| 583 | try: |
| 584 | # Drop registration for adapter messages |
| 585 | reregister = True |
| 586 | self.adapter_agent.unregister_for_inter_adapter_messages() |
| 587 | |
| 588 | except KeyError: |
| 589 | reregister = False |
| 590 | |
| 591 | # Update the operational status to ACTIVATING and connect status to |
| 592 | # UNREACHABLE |
| 593 | device = self.adapter_agent.get_device(self.device_id) |
| 594 | |
| 595 | previous_oper_status = device.oper_status |
| 596 | previous_conn_status = device.connect_status |
| 597 | |
| 598 | device.oper_status = OperStatus.ACTIVATING |
| 599 | device.connect_status = ConnectStatus.UNREACHABLE |
| 600 | device.reason = 'Attempting reboot' |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 601 | yield self.adapter_agent.device_update(device) |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 602 | |
| 603 | # TODO: send alert and clear alert after the reboot |
| 604 | try: |
| 605 | ###################################################### |
| 606 | # MIB Reset |
| 607 | yield self.openomci.onu_omci_device.reboot(timeout=1) |
| 608 | |
| 609 | except Exception as e: |
| 610 | self.log.exception('send-reboot', e=e) |
| 611 | raise |
| 612 | |
| 613 | # Reboot in progress. A reboot may take up to 3 min 30 seconds |
| 614 | # Go ahead and pause less than that and start to look |
| 615 | # for it being alive |
| 616 | device.reason = 'reboot in progress' |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 617 | yield self.adapter_agent.device_update(device) |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 618 | |
| 619 | # Disable OpenOMCI |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 620 | self.openomci.enabled = False |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 621 | self._deferred = reactor.callLater(_ONU_REBOOT_MIN, |
| 622 | self._finish_reboot, |
| 623 | previous_oper_status, |
| 624 | previous_conn_status, |
| 625 | reregister) |
| 626 | |
| 627 | @inlineCallbacks |
| 628 | def _finish_reboot(self, previous_oper_status, previous_conn_status, |
| 629 | reregister): |
| 630 | # Restart OpenOMCI |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 631 | self.openomci.enabled = True |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 632 | |
| 633 | device = self.adapter_agent.get_device(self.device_id) |
| 634 | device.oper_status = previous_oper_status |
| 635 | device.connect_status = previous_conn_status |
| 636 | device.reason = '' |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 637 | yield self.adapter_agent.device_update(device) |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 638 | |
| 639 | if reregister: |
| 640 | self.adapter_agent.register_for_inter_adapter_messages() |
| 641 | |
| 642 | self.log.info('reboot-complete', device_id=self.device_id) |
| 643 | |
| 644 | def self_test_device(self, device): |
| 645 | """ |
| 646 | This is called to Self a device based on a NBI call. |
| 647 | :param device: A Voltha.Device object. |
| 648 | :return: Will return result of self test |
| 649 | """ |
| 650 | from pyvoltha.protos.voltha_pb2 import SelfTestResponse |
| 651 | self.log.info('self-test-device', device=device.id) |
| 652 | # TODO: Support self test? |
| 653 | return SelfTestResponse(result=SelfTestResponse.NOT_SUPPORTED) |
| 654 | |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 655 | @inlineCallbacks |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 656 | def disable(self): |
| 657 | self.log.info('disabling', device_id=self.device_id) |
| 658 | try: |
| 659 | # Get the latest device reference |
| 660 | device = self.adapter_agent.get_device(self.device_id) |
| 661 | |
| 662 | # Disable all ports on that device |
| 663 | self.adapter_agent.disable_all_ports(self.device_id) |
| 664 | |
| 665 | # Update the device operational status to UNKNOWN |
| 666 | device.oper_status = OperStatus.UNKNOWN |
| 667 | device.connect_status = ConnectStatus.UNREACHABLE |
| 668 | device.reason = 'Disabled' |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 669 | yield self.adapter_agent.device_update(device) |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 670 | |
| 671 | # Remove the uni logical port from the OLT, if still present |
| 672 | parent_device = self.adapter_agent.get_device(device.parent_id) |
| 673 | assert parent_device |
| 674 | |
| 675 | for uni in self.uni_ports: |
| 676 | # port_id = 'uni-{}'.format(uni.port_number) |
| 677 | port_id = uni.port_id_name() |
| 678 | try: |
| 679 | logical_device_id = parent_device.parent_id |
| 680 | assert logical_device_id |
| 681 | port = self.adapter_agent.get_logical_port(logical_device_id,port_id) |
| 682 | self.adapter_agent.delete_logical_port(logical_device_id, port) |
| 683 | except KeyError: |
| 684 | self.log.info('logical-port-not-found', device_id=self.device_id, |
| 685 | portid=port_id) |
| 686 | |
| 687 | # Remove pon port from parent and disable |
| 688 | if self._pon is not None: |
| 689 | self.adapter_agent.delete_port_reference_from_parent(self.device_id, |
| 690 | self._pon.get_port()) |
| 691 | self._pon.enabled = False |
| 692 | |
| 693 | # Unregister for proxied message |
| 694 | self.adapter_agent.unregister_for_proxied_messages(device.proxy_address) |
| 695 | |
| 696 | except Exception as _e: |
| 697 | pass # This is expected if OLT has deleted the ONU device handler |
| 698 | |
| 699 | # And disable OMCI as well |
| 700 | self.enabled = False |
| 701 | self.log.info('disabled') |
| 702 | |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 703 | @inlineCallbacks |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 704 | def reenable(self): |
| 705 | self.log.info('re-enabling', device_id=self.device_id) |
| 706 | try: |
| 707 | # Get the latest device reference |
| 708 | device = self.adapter_agent.get_device(self.device_id) |
| 709 | self._cancel_deferred() |
| 710 | |
| 711 | # First we verify that we got parent reference and proxy info |
| 712 | assert device.parent_id |
| 713 | assert device.proxy_address.device_id |
| 714 | # assert device.proxy_address.channel_id |
| 715 | |
| 716 | # Re-register for proxied messages right away |
| 717 | self.proxy_address = device.proxy_address |
| 718 | self.adapter_agent.register_for_proxied_messages( |
| 719 | device.proxy_address) |
| 720 | |
| 721 | # Re-enable the ports on that device |
| 722 | self.adapter_agent.enable_all_ports(self.device_id) |
| 723 | |
| 724 | # Add the pon port reference to the parent |
| 725 | if self._pon is not None: |
| 726 | self._pon.enabled = True |
| 727 | self.adapter_agent.add_port_reference_to_parent(device.id, |
| 728 | self._pon.get_port()) |
| 729 | # Update the connect status to REACHABLE |
| 730 | device.connect_status = ConnectStatus.REACHABLE |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 731 | yield self.adapter_agent.device_update(device) |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 732 | |
| 733 | # re-add uni port to logical device |
| 734 | parent_device = self.adapter_agent.get_device(device.parent_id) |
| 735 | self.logical_device_id = parent_device.parent_id |
| 736 | assert self.logical_device_id, 'Invalid logical device ID' |
| 737 | |
| 738 | # reestablish logical ports for each UNI |
| 739 | multi_uni = len(self.uni_ports) > 1 |
| 740 | for uni in self.uni_ports: |
| 741 | self.adapter_agent.add_port(device.id, uni.get_port()) |
| 742 | uni.add_logical_port(uni.logical_port_number, multi_uni) |
| 743 | |
| 744 | device = self.adapter_agent.get_device(device.id) |
| 745 | device.oper_status = OperStatus.ACTIVE |
| 746 | device.connect_status = ConnectStatus.REACHABLE |
| 747 | device.reason = '' |
| 748 | |
| 749 | self.enabled = True |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 750 | yield self.adapter_agent.device_update(device) |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 751 | |
| 752 | self.log.info('re-enabled') |
| 753 | |
| 754 | except Exception, e: |
| 755 | self.log.exception('error-re-enabling', e=e) |
| 756 | |
| 757 | def delete(self): |
| 758 | self.log.info('deleting', device_id=self.device_id) |
| 759 | |
| 760 | try: |
| 761 | for uni in self._unis.values(): |
| 762 | uni.stop() |
| 763 | uni.delete() |
| 764 | |
| 765 | self._pon.stop() |
| 766 | self._pon.delete() |
| 767 | |
| 768 | except Exception as _e: |
| 769 | pass # Expected if the OLT deleted us from the device handler |
| 770 | |
| 771 | # OpenOMCI cleanup |
| 772 | omci, self._openomci = self._openomci, None |
| 773 | omci.delete() |
| 774 | |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 775 | @inlineCallbacks |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 776 | def add_uni_ports(self): |
| 777 | """ Called after in-sync achieved and not in xPON mode""" |
| 778 | # TODO: We have to methods adding UNI ports. Go to one |
| 779 | # TODO: Should this be moved to the omci.py module for this ONU? |
| 780 | |
| 781 | # This is only for working WITHOUT xPON |
| 782 | pptp_entities = self.openomci.onu_omci_device.configuration.pptp_entities |
| 783 | device = self.adapter_agent.get_device(self.device_id) |
| 784 | |
| 785 | multi_uni = len(pptp_entities) > 1 |
| 786 | uni_id = 0 |
| 787 | |
| 788 | for entity_id, pptp in pptp_entities.items(): |
| 789 | intf_id = self.proxy_address.channel_id |
| 790 | onu_id = self.proxy_address.onu_id |
| 791 | uni_no = platform.mk_uni_port_num(intf_id, onu_id, uni_id=uni_id) |
| 792 | uni_name = "uni-{}".format(uni_no) |
| 793 | mac_bridge_port_num = uni_id + 1 |
| 794 | |
| 795 | uni_port = UniPort.create(self, uni_name, uni_no, uni_name) |
| 796 | uni_port.entity_id = entity_id |
| 797 | uni_port.enabled = True |
| 798 | uni_port.mac_bridge_port_num = mac_bridge_port_num |
| 799 | uni_port.add_logical_port(uni_port.port_number, multi_uni) |
| 800 | self.log.debug("created-uni-port", uni=uni_port) |
| 801 | |
| 802 | self.adapter_agent.add_port(device.id, uni_port.get_port()) |
| 803 | parent_device = self.adapter_agent.get_device(device.parent_id) |
| 804 | |
| 805 | parent_adapter_agent = registry('adapter_loader').get_agent(parent_device.adapter) |
| 806 | if parent_adapter_agent is None: |
| 807 | self.log.error('olt-adapter-agent-could-not-be-retrieved') |
| 808 | |
| 809 | parent_adapter_agent.add_port(device.parent_id, uni_port.get_port()) |
| 810 | |
| 811 | self._unis[uni_port.port_number] = uni_port |
| 812 | self.openomci.onu_omci_device.alarm_synchronizer.set_alarm_params(onu_id=self.proxy_address.onu_id, |
| 813 | uni_ports=self._unis.values()) |
| 814 | # TODO: this should be in the PonPort class |
| 815 | pon_port = self._pon.get_port() |
| 816 | self.adapter_agent.delete_port_reference_from_parent(self.device_id, |
| 817 | pon_port) |
| 818 | # Find index where this ONU peer is (should almost always be zero) |
| 819 | d = [i for i, e in enumerate(pon_port.peers) if |
| 820 | e.port_no == intf_id and e.device_id == device.parent_id] |
| 821 | |
| 822 | if len(d) > 0: |
| 823 | pon_port.peers[d[0]].port_no = uni_port.port_number |
| 824 | self.adapter_agent.add_port_reference_to_parent(self.device_id, |
| 825 | pon_port) |
Chip Boling | d2d7a4d | 2019-03-14 14:34:56 -0500 | [diff] [blame] | 826 | yield self.adapter_agent.device_update(device) |
Chip Boling | 8e042f6 | 2019-02-12 16:14:34 -0600 | [diff] [blame] | 827 | uni_port.enabled = True |
| 828 | uni_id += 1 |
| 829 | |
| 830 | def rx_inter_adapter_message(self, msg): |
| 831 | raise NotImplemented('Not currently supported') |
| 832 | |
| 833 | def _subscribe_to_events(self): |
| 834 | from pyvoltha.adapters.extensions.omci.onu_device_entry import OnuDeviceEvents, \ |
| 835 | OnuDeviceEntry |
| 836 | |
| 837 | # OMCI MIB Database sync status |
| 838 | bus = self.openomci.onu_omci_device.event_bus |
| 839 | topic = OnuDeviceEntry.event_bus_topic(self.device_id, |
| 840 | OnuDeviceEvents.MibDatabaseSyncEvent) |
| 841 | self._in_sync_subscription = bus.subscribe(topic, self.in_sync_handler) |
| 842 | |
| 843 | def _unsubscribe_to_events(self): |
| 844 | insync, self._in_sync_subscription = self._in_sync_subscription, None |
| 845 | |
| 846 | if insync is not None: |
| 847 | bus = self.openomci.onu_omci_device.event_bus |
| 848 | bus.unsubscribe(insync) |
| 849 | |
| 850 | def in_sync_handler(self, _topic, msg): |
| 851 | # Create UNI Ports on first In-Sync event |
| 852 | if self._in_sync_subscription is not None: |
| 853 | try: |
| 854 | from pyvoltha.adapters.extensions.omci.onu_device_entry import IN_SYNC_KEY |
| 855 | |
| 856 | if msg[IN_SYNC_KEY]: |
| 857 | # Do not proceed if we have not got our vENET information yet. |
| 858 | |
| 859 | if len(self.uni_ports) == 0: |
| 860 | # Drop subscription.... |
| 861 | insync, self._in_sync_subscription = self._in_sync_subscription, None |
| 862 | |
| 863 | if insync is not None: |
| 864 | bus = self.openomci.onu_omci_device.event_bus |
| 865 | bus.unsubscribe(insync) |
| 866 | |
| 867 | # Set up UNI Ports. The UNI ports are currently created when the xPON |
| 868 | # vENET information is created. Once xPON is removed, we need to create |
| 869 | # them from the information provided from the MIB upload UNI-G and other |
| 870 | # UNI related MEs. |
| 871 | self.add_uni_ports() |
| 872 | |
| 873 | except Exception as e: |
| 874 | self.log.exception('in-sync', e=e) |