blob: 0fc49dcf07ea7212d59a74d66dcda9fbc6a074b7 [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 structlog
16from enum import Enum
17from twisted.internet import reactor
18from twisted.internet.defer import inlineCallbacks, returnValue, succeed
19
20from pyvoltha.protos.common_pb2 import OperStatus, AdminState
21
22
23class 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