blob: c325278a56aa5c39f10c58520a2a547de84d2ea6 [file] [log] [blame]
Chip Boling32aab302019-01-23 10:50:18 -06001#
2# Copyright 2018 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#
16from task import Task
17from twisted.internet import reactor
18from twisted.internet.defer import failure, inlineCallbacks, TimeoutError, returnValue
19from voltha.extensions.omci.omci_defs import ReasonCodes, EntityOperations
20from voltha.extensions.omci.omci_me import MEFrame
21from voltha.extensions.omci.omci_frame import OmciFrame
22from voltha.extensions.omci.omci_cc import DEFAULT_OMCI_TIMEOUT
23from voltha.extensions.omci.omci_messages import OmciGet
24from voltha.extensions.omci.omci_fields import OmciTableField
25
26RC = ReasonCodes
27OP = EntityOperations
28
29
30class GetException(Exception):
31 pass
32
33
34class OmciGetRequest(Task):
35 """
36 OpenOMCI Get an OMCI ME Instance Attributes
37
38 Upon completion, the Task deferred callback is invoked with a reference of
39 this Task object.
40
41 The Task has an initializer option (allow_failure) that will retry all
42 requested attributes if the original request fails with a status code of
43 9 (Attributes failed or unknown). This result means that an attribute
44 is not supported by the ONU or that a mandatory/optional attribute could
45 not be executed by the ONU, even if it is supported, for example,
46 because of a range or type violation.
47 """
48 task_priority = 128
49 name = "ONU OMCI Get Task"
50
51 def __init__(self, omci_agent, device_id, entity_class, entity_id, attributes,
52 exclusive=False, allow_failure=False):
53 """
54 Class initialization
55
56 :param omci_agent: (OmciAdapterAgent) OMCI Adapter agent
57 :param device_id: (str) ONU Device ID
58 :param entity_class: (EntityClass) ME Class to retrieve
59 :param entity_id: (int) ME Class instance ID to retrieve
60 :param attributes: (list or set) Name of attributes to retrieve
61 :param exclusive: (bool) True if this GET request Task exclusively own the
62 OMCI-CC while running. Default: False
63 :param allow_failure: (bool) If true, attempt to get all valid attributes
64 if the original request receives an error
65 code of 9 (Attributes failed or unknown).
66 """
67 super(OmciGetRequest, self).__init__(OmciGetRequest.name,
68 omci_agent,
69 device_id,
70 priority=OmciGetRequest.task_priority,
71 exclusive=exclusive)
72 self._device = omci_agent.get_device(device_id)
73 self._entity_class = entity_class
74 self._entity_id = entity_id
75 self._attributes = attributes
76 self._allow_failure = allow_failure
77 self._failed_or_unknown_attributes = set()
78 self._results = None
79 self._local_deferred = None
80
81 def cancel_deferred(self):
82 super(OmciGetRequest, self).cancel_deferred()
83
84 d, self._local_deferred = self._local_deferred, None
85 try:
86 if d is not None and not d.called:
87 d.cancel()
88 except:
89 pass
90
91 @property
92 def me_class(self):
93 """The OMCI Managed Entity Class associated with this request"""
94 return self._entity_class
95
96 @property
97 def entity_id(self):
98 """The ME Entity ID associated with this request"""
99 return self._entity_id
100
101 @property
102 def attributes(self):
103 """
104 Return a dictionary of attributes for the request if the Get was
105 successfully completed. None otherwise
106 """
107 if self._results is None:
108 return None
109
110 omci_msg = self._results.fields['omci_message'].fields
111 return omci_msg['data'] if 'data' in omci_msg else None
112
113 @property
114 def success_code(self):
115 """
116 Return the OMCI success/reason code for the Get Response.
117 """
118 if self._results is None:
119 return None
120
121 return self._results.fields['omci_message'].fields['success_code']
122
123 @property
124 def raw_results(self):
125 """
126 Return the raw Get Response OMCIFrame
127 """
128 return self._results
129
130 def start(self):
131 """
132 Start MIB Capabilities task
133 """
134 super(OmciGetRequest, self).start()
135 self._local_deferred = reactor.callLater(0, self.perform_get_omci)
136
137 @property
138 def failed_or_unknown_attributes(self):
139 """
140 Returns a set attributes that failed or unknown in the original get
141 request that resulted in an initial status code of 9 (Attributes
142 failed or unknown).
143
144 :return: (set of str) attributes
145 """
146 return self._failed_or_unknown_attributes
147
148 @inlineCallbacks
149 def perform_get_omci(self):
150 """
151 Perform the initial get request
152 """
153 self.log.info('perform-get', entity_class=self._entity_class,
154 entity_id=self._entity_id, attributes=self._attributes)
155 try:
156 # If one or more attributes is a table attribute, get it separately
157 def is_table_attr(attr):
158 index = self._entity_class.attribute_name_to_index_map[attr]
159 attr_def = self._entity_class.attributes[index]
160 return isinstance(attr_def.field, OmciTableField)
161
162 first_attributes = {attr for attr in self._attributes if not is_table_attr(attr)}
163 table_attributes = {attr for attr in self._attributes if is_table_attr(attr)}
164
165 frame = MEFrame(self._entity_class, self._entity_id, first_attributes).get()
166 self.strobe_watchdog()
167 results = yield self._device.omci_cc.send(frame)
168
169 status = results.fields['omci_message'].fields['success_code']
170 self.log.debug('perform-get-status', status=status)
171
172 # Success?
173 if status == RC.Success.value:
174 self._results = results
175 results_omci = results.fields['omci_message'].fields
176
177 # Were all attributes fetched?
178 missing_attr = frame.fields['omci_message'].fields['attributes_mask'] ^ \
179 results_omci['attributes_mask']
180
181 if missing_attr > 0 or len(table_attributes) > 0:
182 self.log.info('perform-get-missing', num_missing=missing_attr,
183 table_attr=table_attributes)
184 self.strobe_watchdog()
185 self._local_deferred = reactor.callLater(0,
186 self.perform_get_missing_attributes,
187 missing_attr,
188 table_attributes)
189 returnValue(self._local_deferred)
190
191 elif status == RC.AttributeFailure.value:
192 # What failed? Note if only one attribute was attempted, then
193 # that is an overall failure
194
195 if not self._allow_failure or len(self._attributes) <= 1:
196 raise GetException('Get failed with status code: {}'.
197 format(RC.AttributeFailure.value))
198
199 self.strobe_watchdog()
200 self._local_deferred = reactor.callLater(0,
201 self.perform_get_failed_attributes,
202 results,
203 self._attributes)
204 returnValue(self._local_deferred)
205
206 else:
207 raise GetException('Get failed with status code: {}'.format(status))
208
209 self.log.debug('get-completed')
210 self.deferred.callback(self)
211
212 except TimeoutError as e:
213 self.deferred.errback(failure.Failure(e))
214
215 except Exception as e:
216 self.log.exception('perform-get', e=e, class_id=self._entity_class,
217 entity_id=self._entity_id, attributes=self._attributes)
218 self.deferred.errback(failure.Failure(e))
219
220 @inlineCallbacks
221 def perform_get_missing_attributes(self, missing_attr, table_attributes):
222 """
223 This method is called when the original Get requests completes with success
224 but not all attributes were returned. This can happen if one or more of the
225 attributes would have exceeded the space available in the OMCI frame.
226
227 This routine iterates through the missing attributes and attempts to retrieve
228 the ones that were missing.
229
230 :param missing_attr: (int) Missing attributes bitmask
231 :param table_attributes: (set) Attributes that need table get/get-next support
232 """
233 self.log.debug('perform-get-missing', attrs=missing_attr, tbl=table_attributes)
234
235 # Retrieve missing attributes first (if any)
236 results_omci = self._results.fields['omci_message'].fields
237
238 for index in xrange(16):
239 attr_mask = 1 << index
240
241 if attr_mask & missing_attr:
242 # Get this attribute
243 frame = OmciFrame(
244 transaction_id=None, # OMCI-CC will set
245 message_type=OmciGet.message_id,
246 omci_message=OmciGet(
247 entity_class=self._entity_class.class_id,
248 entity_id=self._entity_id,
249 attributes_mask=attr_mask
250 )
251 )
252 try:
253 self.strobe_watchdog()
254 get_results = yield self._device.omci_cc.send(frame)
255
256 get_omci = get_results.fields['omci_message'].fields
257 if get_omci['success_code'] != RC.Success.value:
258 continue
259
260 assert attr_mask == get_omci['attributes_mask'], 'wrong attribute'
261 results_omci['attributes_mask'] |= attr_mask
262
263 if results_omci.get('data') is None:
264 results_omci['data'] = dict()
265
266 results_omci['data'].update(get_omci['data'])
267
268 except TimeoutError:
269 self.log.debug('missing-timeout')
270
271 except Exception as e:
272 self.log.exception('missing-failure', e=e)
273
274 # Now any table attributes. OMCI_CC handles background get/get-next sequencing
275 for tbl_attr in table_attributes:
276 attr_mask = self._entity_class.mask_for(tbl_attr)
277 frame = OmciFrame(
278 transaction_id=None, # OMCI-CC will set
279 message_type=OmciGet.message_id,
280 omci_message=OmciGet(
281 entity_class=self._entity_class.class_id,
282 entity_id=self._entity_id,
283 attributes_mask=attr_mask
284 )
285 )
286 try:
287 timeout = 2 * DEFAULT_OMCI_TIMEOUT # Multiple frames expected
288 self.strobe_watchdog()
289 get_results = yield self._device.omci_cc.send(frame,
290 timeout=timeout)
291 self.strobe_watchdog()
292 get_omci = get_results.fields['omci_message'].fields
293 if get_omci['success_code'] != RC.Success.value:
294 continue
295
296 if results_omci.get('data') is None:
297 results_omci['data'] = dict()
298
299 results_omci['data'].update(get_omci['data'])
300
301 except TimeoutError:
302 self.log.debug('tbl-attr-timeout')
303
304 except Exception as e:
305 self.log.exception('tbl-attr-timeout', e=e)
306
307 self.deferred.callback(self)
308
309 @inlineCallbacks
310 def perform_get_failed_attributes(self, tmp_results, attributes):
311 """
312
313 :param tmp_results:
314 :param attributes:
315 :return:
316 """
317 self.log.debug('perform-get-failed', attrs=attributes)
318
319 for attr in attributes:
320 try:
321 frame = MEFrame(self._entity_class, self._entity_id, {attr}).get()
322
323 self.strobe_watchdog()
324 results = yield self._device.omci_cc.send(frame)
325
326 status = results.fields['omci_message'].fields['success_code']
327
328 if status == RC.AttributeFailure.value:
329 self.log.debug('unknown-or-invalid-attribute', attr=attr, status=status)
330 self._failed_or_unknown_attributes.add(attr)
331
332 elif status != RC.Success.value:
333 self.log.warn('invalid-get', class_id=self._entity_class,
334 attribute=attr, status=status)
335 self._failed_or_unknown_attributes.add(attr)
336
337 else:
338 # Add to partial results and correct the status
339 tmp_results.fields['omci_message'].fields['success_code'] = status
340 tmp_results.fields['omci_message'].fields['attributes_mask'] |= \
341 results.fields['omci_message'].fields['attributes_mask']
342
343 if tmp_results.fields['omci_message'].fields.get('data') is None:
344 tmp_results.fields['omci_message'].fields['data'] = dict()
345
346 tmp_results.fields['omci_message'].fields['data'][attr] = \
347 results.fields['omci_message'].fields['data'][attr]
348
349 except TimeoutError as e:
350 self.log.debug('attr-timeout')
351
352 except Exception as e:
353 self.log.exception('attr-failure', e=e)
354
355 self._results = tmp_results
356 self.deferred.callback(self)