Chip Boling | f5af85d | 2019-02-12 15:36:17 -0600 | [diff] [blame^] | 1 | # 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 | |
| 15 | import structlog |
| 16 | from enum import Enum |
| 17 | from twisted.internet import reactor |
| 18 | from twisted.internet.defer import inlineCallbacks, returnValue, succeed |
| 19 | |
| 20 | from pyvoltha.protos.common_pb2 import OperStatus, AdminState |
| 21 | |
| 22 | |
| 23 | class AdtnPort(object): |
| 24 | """ |
| 25 | A class similar to the 'Port' class in the VOLTHA |
| 26 | """ |
| 27 | class State(Enum): |
| 28 | INITIAL = 0 # Created and initialization in progress |
| 29 | RUNNING = 1 # PON port contacted, ONU discovery active |
| 30 | STOPPED = 2 # Disabled |
| 31 | DELETING = 3 # Cleanup |
| 32 | |
| 33 | def __init__(self, parent, **kwargs): |
| 34 | assert parent, 'parent is None' |
| 35 | assert 'port_no' in kwargs, 'Port number not found' |
| 36 | |
| 37 | self.log = structlog.get_logger(device_id=parent.device_id) |
| 38 | |
| 39 | self._parent = parent |
| 40 | self._port_no = kwargs.get('port_no') |
| 41 | |
| 42 | # Set the following in your derived class. These names are used in |
| 43 | # various ways. Typically, the physical port name will be used during |
| 44 | # device handler conversations with the hardware (REST, NETCONF, ...) |
| 45 | # while the logical port name is what the outside world (ONOS, SEBA, ...) |
| 46 | # uses. All ports have a physical port name, but only ports exposed through |
| 47 | # VOLTHA as a logical port will have a logical port name |
| 48 | |
| 49 | self._physical_port_name = None |
| 50 | self._logical_port_name = None |
| 51 | self._label = None |
| 52 | self._port = None |
| 53 | |
| 54 | self.sync_tick = 20.0 |
| 55 | self.sync_deferred = None # For sync of PON config to hardware |
| 56 | |
| 57 | # TODO: Deprecate 'enabled' and use admin_state instead may want initial to always be |
| 58 | # disabled and then in derived classes, set it in the 'reset' method called on startup. |
| 59 | self._enabled = True |
| 60 | self._admin_state = AdminState.ENABLED |
| 61 | |
| 62 | self._oper_status = OperStatus.DISCOVERED |
| 63 | self._state = AdtnPort.State.INITIAL |
| 64 | |
| 65 | self.deferred = None # General purpose |
| 66 | |
| 67 | # Statistics |
| 68 | self.rx_packets = 0 |
| 69 | self.rx_bytes = 0 |
| 70 | self.tx_packets = 0 |
| 71 | self.tx_bytes = 0 |
| 72 | self.timestamp = 0 # UTC when KPI items last updated |
| 73 | |
| 74 | def __del__(self): |
| 75 | self.stop() |
| 76 | |
| 77 | def get_port(self): |
| 78 | """ |
| 79 | Get the VOLTHA PORT object for this port |
| 80 | :return: VOLTHA Port object |
| 81 | """ |
| 82 | raise NotImplementedError('Add to your derived class') |
| 83 | |
| 84 | @property |
| 85 | def port_no(self): |
| 86 | return self._port_no |
| 87 | |
| 88 | @property |
| 89 | def intf_id(self): |
| 90 | return self.port_no |
| 91 | |
| 92 | @property |
| 93 | def physical_port_name(self): |
| 94 | return self._physical_port_name |
| 95 | |
| 96 | @property |
| 97 | def logical_port_name(self): |
| 98 | return self._logical_port_name |
| 99 | |
| 100 | @property # For backwards compatibility |
| 101 | def name(self): |
| 102 | return self._logical_port_name |
| 103 | |
| 104 | @property |
| 105 | def state(self): |
| 106 | return self._state |
| 107 | |
| 108 | @state.setter |
| 109 | def state(self, value): |
| 110 | self._state = value |
| 111 | |
| 112 | @property |
| 113 | def olt(self): |
| 114 | return self._parent |
| 115 | |
| 116 | @property |
| 117 | def admin_state(self): |
| 118 | return self._admin_state |
| 119 | |
| 120 | @admin_state.setter |
| 121 | def admin_state(self, value): |
| 122 | if self._admin_state != value: |
| 123 | self._admin_state = value |
| 124 | if self._admin_state == AdminState.ENABLED: |
| 125 | self.start() |
| 126 | else: |
| 127 | self.stop() |
| 128 | @property |
| 129 | def enabled(self): |
| 130 | return self._admin_state == AdminState.ENABLED |
| 131 | |
| 132 | @enabled.setter |
| 133 | def enabled(self, value): |
| 134 | assert isinstance(value, bool), 'enabled is a boolean' |
| 135 | self.admin_state = AdminState.ENABLED if value else AdminState.DISABLED |
| 136 | |
| 137 | @property |
| 138 | def oper_status(self): |
| 139 | return self._oper_status |
| 140 | |
| 141 | @property |
| 142 | def adapter_agent(self): |
| 143 | return self.olt.adapter_agent |
| 144 | |
| 145 | def get_logical_port(self): |
| 146 | """ |
| 147 | Get the VOLTHA logical port for this port. For PON ports, a logical port |
| 148 | is not currently created, so always return None |
| 149 | |
| 150 | :return: VOLTHA logical port or None if not supported |
| 151 | """ |
| 152 | return None |
| 153 | |
| 154 | def cancel_deferred(self): |
| 155 | d1, self.deferred = self.deferred, None |
| 156 | d2, self.sync_deferred = self.sync_deferred, None |
| 157 | |
| 158 | for d in [d1, d2]: |
| 159 | try: |
| 160 | if d is not None and not d.called: |
| 161 | d.cancel() |
| 162 | except Exception: |
| 163 | pass |
| 164 | |
| 165 | def _update_adapter_agent(self): |
| 166 | raise NotImplementedError('Add to your derived class') |
| 167 | |
| 168 | def start(self): |
| 169 | """ |
| 170 | Start/enable this PON and start ONU discover |
| 171 | """ |
| 172 | if self.state == AdtnPort.State.RUNNING: |
| 173 | return succeed('Running') |
| 174 | |
| 175 | self.log.info('start-port') |
| 176 | |
| 177 | self.cancel_deferred() |
| 178 | self.state = AdtnPort.State.INITIAL |
| 179 | self._oper_status = OperStatus.ACTIVATING |
| 180 | self._enabled = True |
| 181 | |
| 182 | # Do the rest of the startup in an async method |
| 183 | self.deferred = reactor.callLater(0.5, self.finish_startup) |
| 184 | self._update_adapter_agent() |
| 185 | |
| 186 | return succeed('Scheduled') |
| 187 | |
| 188 | def finish_startup(self): |
| 189 | if self.state == AdtnPort.State.INITIAL: |
| 190 | self.log.debug('final-startup') |
| 191 | |
| 192 | # If here, initial settings were successfully written to hardware |
| 193 | |
| 194 | self._enabled = True |
| 195 | self._admin_state = AdminState.ENABLED |
| 196 | self._oper_status = OperStatus.ACTIVE # TODO: is this correct, how do we tell GRPC |
| 197 | self.state = AdtnPort.State.RUNNING |
| 198 | |
| 199 | self.sync_deferred = reactor.callLater(self.sync_tick, |
| 200 | self.sync_hardware) |
| 201 | self._update_adapter_agent() |
| 202 | |
| 203 | @inlineCallbacks |
| 204 | def stop(self): |
| 205 | if self.state == AdtnPort.State.STOPPED: |
| 206 | self.log.debug('already stopped') |
| 207 | returnValue('Stopped') |
| 208 | |
| 209 | self.log.info('stopping') |
| 210 | try: |
| 211 | self.cancel_deferred() |
| 212 | self._enabled = False |
| 213 | self._admin_state = AdminState.DISABLED |
| 214 | self._oper_status = OperStatus.UNKNOWN |
| 215 | self._update_adapter_agent() |
| 216 | |
| 217 | self.state = AdtnPort.State.STOPPED |
| 218 | |
| 219 | self.deferred = self.finish_stop() |
| 220 | yield self.deferred |
| 221 | |
| 222 | except Exception as e: |
| 223 | self.log.exception('stop-failed', e=e) |
| 224 | |
| 225 | returnValue('Stopped') |
| 226 | |
| 227 | @inlineCallbacks |
| 228 | def finish_stop(self): |
| 229 | pass # Add to your derived class if needed |
| 230 | returnValue(None) |
| 231 | |
| 232 | def restart(self): |
| 233 | if self.state == AdtnPort.State.RUNNING or self.state == AdtnPort.State.STOPPED: |
| 234 | start_it = (self.state == AdtnPort.State.RUNNING) |
| 235 | self.state = AdtnPort.State.INITIAL |
| 236 | return self.start() if start_it else self.stop() |
| 237 | return succeed('nop') |
| 238 | |
| 239 | def delete(self): |
| 240 | """ |
| 241 | Parent device is being deleted. Do not change any config but |
| 242 | stop all polling |
| 243 | """ |
| 244 | self.log.info('Deleting') |
| 245 | self.state = AdtnPort.State.DELETING |
| 246 | self.cancel_deferred() |
| 247 | |
| 248 | def sync_hardware(self): |
| 249 | raise NotImplementedError('Add to your derived class') |
| 250 | |
| 251 | # TODO: Continue to consolidate port functionality |