blob: e6d5884da623ac331faee02d8feb48f05d33f49e [file] [log] [blame]
Chip Boling32aab302019-01-23 10:50:18 -06001#
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#
16import os
17import structlog
18from datetime import datetime, timedelta
19from binascii import crc32, hexlify
20from transitions import Machine
21from transitions.extensions.nesting import HierarchicalMachine as HMachine
22from twisted.python import failure
23from twisted.internet import reactor
24from twisted.internet.defer import Deferred, CancelledError
25from common.event_bus import EventBusClient
26from voltha.protos.voltha_pb2 import ImageDownload
27from voltha.protos.omci_mib_db_pb2 import OpenOmciEventType
28from voltha.extensions.omci.omci_defs import EntityOperations, ReasonCodes, AttributeAccess, OmciSectionDataSize
29from voltha.extensions.omci.omci_entities import SoftwareImage
30from voltha.extensions.omci.omci_cc import DEFAULT_OMCI_TIMEOUT
31from voltha.extensions.omci.omci_messages import OmciEndSoftwareDownloadResponse, OmciActivateImageResponse
32
33###################################################################################
34## OLT out-of-band download image procedure
35###################################################################################
36
37class ImageDownloadeSTM(object):
38 DEFAULT_STATES = ['disabled', 'downloading', 'validating', 'done']
39 DEFAULT_TRANSITIONS = [
40 {'trigger': 'start', 'source': 'disabled', 'dest': 'downloading'},
41 {'trigger': 'stop', 'source': ['downloading', 'validating', 'done'], 'dest': 'disabled'},
42 {'trigger': 'dw_success', 'source': 'downloading', 'dest': 'validating'},
43 {'trigger': 'dw_fail', 'source': 'downloading', 'dest': 'done'},
44 {'trigger': 'validate_success', 'source': 'validating', 'dest': 'done'},
45 ]
46 DEFAULT_TIMEOUT_RETRY = 1000 # Seconds to delay after task failure/timeout
47
48 # def __init__(self, omci_agent, dev_id, local_name, local_dir, remote_url, download_task,
49 def __init__(self, omci_agent, image_download,
50 download_task_cls,
51 states=DEFAULT_STATES,
52 transitions=DEFAULT_TRANSITIONS,
53 initial_state='disabled',
54 timeout_delay=DEFAULT_TIMEOUT_RETRY,
55 advertise_events=True, clock=None):
56 """
57 :Param: omci_agent: (OpenOMCIAgent)
58 :Param: image_dnld: (ImageDownload)
59 ImageDownload.id : device id
60 ImageDownload.name: file name of the image
61 ImageDownload.url : URL to download the image from server
62 ImageDownload.local_dir: local directory of the image file
63 """
64 self.log = structlog.get_logger(device_id=image_download.id)
65 self._agent = omci_agent
66 # self._imgdw = ImageDownload()
67 # self._imgdw.name = local_name
68 # self._imgdw.id = dev_id
69 # self._imgdw.url = remote_url
70 # self._imgdw.local_dir = local_dir
71 self._imgdw = image_download
72 # self._imgdw.state = ImageDownload.DOWNLOAD_UNKNOWN # voltha_pb2
73
74 self._download_task_cls = download_task_cls
75 self._timeout_delay = timeout_delay
76
77 self._current_task = None
78 self._task_deferred = None
79 self._ret_deferred = None
80 self._timeout_dc = None # DelayedCall
81 self._advertise_events = advertise_events
82 self.reactor = clock if clock is not None else reactor
83
84 self.log.debug("ImageDownloadeSTM", image_download=self._imgdw)
85 self.machine = Machine(model=self, states=states,
86 transitions=transitions,
87 initial=initial_state,
88 queued=True,
89 name='{}-{}'.format(self.__class__.__name__, self._imgdw.id))
90 # @property
91 # def name(self):
92 # return self._imgdw.name
93
94 def _cancel_timeout(self):
95 d, self._timeout_dc = self._timeout_dc, None
96 if d is not None and not d.called:
97 d.cancel()
98
99 @property
100 def status(self):
101 return self._imgdw
102
103 @property
104 def deferred(self):
105 return self._ret_deferred
106
107 def advertise(self, event, info):
108 """Advertise an event on the OpenOMCI event bus"""
109 if self._advertise_events:
110 self._agent.advertise(event,
111 {
112 'state-machine': self.machine.name,
113 'info': info,
114 'time': str(datetime.utcnow())
115 })
116
117 # def reset(self):
118 # """
119 # Reset all the state machine to intial state
120 # It is used to clear failed result in last downloading
121 # """
122 # self.log.debug('reset download', image_download=self._imgdw)
123 # if self._current_task is not None:
124 # self._current_task.stop()
125
126 # self._cancel_deferred()
127
128 # if self._ret_deferred is not None:
129 # self._ret_deferred.cancel()
130 # self._ret_deferred = None
131
132 # self.stop()
133 # self._imgdw.state = ImageDownload.DOWNLOAD_UNKNOWN
134
135 def get_file(self):
136 """
137 return a Deferred object
138 Caller will register a callback to the Deferred to get notified once the image is available
139 """
140 # self.log.debug('get_file', image_download=self._imgdw)
141 if self._ret_deferred is None or self._ret_deferred.called:
142 self._ret_deferred = Deferred()
143
144 if self._imgdw.state == ImageDownload.DOWNLOAD_SUCCEEDED:
145 self.log.debug('Image Available')
146 self.reactor.callLater(0, self._ret_deferred.callback, self._imgdw)
147 elif self._imgdw.state == ImageDownload.DOWNLOAD_FAILED or self._imgdw.state == ImageDownload.DOWNLOAD_UNSUPPORTED:
148 self.log.debug('Image not exist')
149 self.reactor.callLater(0, self._ret_deferred.errback, failure.Failure(Exception('Image Download Failed ' + self._imgdw.name)))
150 elif self._imgdw.state == ImageDownload.DOWNLOAD_UNKNOWN or self._imgdw.state == ImageDownload.DOWNLOAD_REQUESTED:
151 self.log.debug('Start Image STM')
152 self._imgdw.state = ImageDownload.DOWNLOAD_STARTED
153 self.reactor.callLater(0, self.start)
154 else:
155 self.log.debug('NO action', state=self._imgdw.state)
156
157 return self._ret_deferred
158
159 def timeout(self):
160 self.log.debug('Image Download Timeout', download_task=self._current_task);
161 if self._current_task:
162 self.reactor.callLater(0, self._current_task.stop)
163 # if self._task_deferred is not None and not self._task_deferred.called:
164 # self._task_deferred.cancel()
165 self._current_task = None
166 # else:
167 # self.dw_fail()
168
169 def on_enter_downloading(self):
170 self.log.debug("on_enter_downloading")
171 self.advertise(OpenOmciEventType.state_change, self.state)
172 def success(results):
173 self.log.debug('image-download-success', results=results)
174 self._imgdw.state = ImageDownload.DOWNLOAD_SUCCEEDED
175 self._imgdw.reason = ImageDownload.NO_ERROR
176 self._current_task = None
177 self._task_deferred = None
178 self.dw_success()
179
180 def failure(reason):
181 self.log.info('image-download-failure', reason=reason)
182 if self._imgdw.state == ImageDownload.DOWNLOAD_STARTED:
183 self._imgdw.state = ImageDownload.DOWNLOAD_FAILED
184 if isinstance(reason, CancelledError):
185 self._imgdw.reason = ImageDownload.CANCELLED
186 self._current_task = None
187 self._task_deferred = None
188 self.dw_fail()
189
190 self._device = self._agent.get_device(self._imgdw.id)
191 self._current_task = self._download_task_cls(self._agent, self._imgdw, self.reactor)
192
193 self._task_deferred = self._device.task_runner.queue_task(self._current_task)
194 self._task_deferred.addCallbacks(success, failure)
195 self._imgdw.state = ImageDownload.DOWNLOAD_STARTED
196
197 if self._timeout_delay > 0:
198 self._timeout_dc = self.reactor.callLater(self._timeout_delay, self.timeout)
199
200 def on_enter_validating(self):
201 self.log.debug("on_enter_validating")
202 self.advertise(OpenOmciEventType.state_change, self.state)
203 self.validate_success()
204
205 def on_enter_done(self):
206 self.log.debug("on_enter_done")
207 self.advertise(OpenOmciEventType.state_change, self.state)
208 self._cancel_timeout()
209
210 d, self._ret_deferred = self._ret_deferred, None
211 if d is not None:
212 if self._imgdw.state == ImageDownload.DOWNLOAD_SUCCEEDED:
213 self.reactor.callLater(0, d.callback, self._imgdw)
214 else: # failed
215 if self._imgdw.reason == ImageDownload.CANCELLED:
216 self.reactor.callLater(0, d.cancel)
217 else:
218 self.reactor.callLater(0, d.errback, failure.Failure(Exception('Image Download Failed ' + self._imgdw.name)))
219
220 def on_enter_disabled(self):
221 self.log.debug("on_enter_disabled")
222 self.advertise(OpenOmciEventType.state_change, self.state)
223
224 self._cancel_timeout()
225 if self._current_task is not None:
226 self.reactor.callLater(0, self._current_task.stop)
227 self._current_task = None
228
229 if self._ret_deferred:
230 self.reactor.callLater(0, self._ret_deferred.cancel)
231 self._ret_deferred = None
232
233 # remove local file fragments if download failed
234 file_path = self._imgdw.local_dir + '/' + self._imgdw.name
235 if self._imgdw.state != ImageDownload.DOWNLOAD_SUCCEEDED and os.path.exists(file_path):
236 os.remove(file_path)
237 self._imgdw.state = ImageDownload.DOWNLOAD_UNKNOWN
238
239###################################################################################
240## OMCI Software Image Download Procedure
241###################################################################################
242
243class OmciSoftwareImageDownloadSTM(object):
244
245 OMCI_SWIMG_DOWNLOAD_TIMEOUT = 5400 # TODO: Seconds for the full downloading procedure to avoid errors that cause infinte downloading
246 OMCI_SWIMG_DOWNLOAD_WINDOW_SIZE = 32
247 OMCI_SWIMG_WINDOW_RETRY_MAX = 2
248 OMCI_SWIMG_ACTIVATE_RETRY_MAX = 2
249 OMCI_SWIMG_ACTIVATE_TRANSITIONS_TIMEOUT = 10 # Seconds to delay after task failure/timeout
250
251 # def __init__(self, omci_agent, dev_id, img_path,
252 def __init__(self, image_id, omci_agent, image_dnld,
253 window_size=OMCI_SWIMG_DOWNLOAD_WINDOW_SIZE,
254 timeout_delay=OMCI_SWIMG_DOWNLOAD_TIMEOUT,
255 advertise_events=True,
256 clock=None):
257 """
258 omci_agent: (OpenOMCIAgent)
259 image_dnld: (ImageDownload)
260 ImageDownload.id : device id
261 ImageDownload.name: file name of the image
262 ImageDownload.url : URL to download the image from server
263 ImageDownload.local_dir: local directory of the image file
264 window_size: window size of OMCI download procedure
265 """
266 self.log = structlog.get_logger(device_id=image_dnld.id)
267 self._omci_agent = omci_agent
268 self._image_download = image_dnld
269 self._timeout = timeout_delay
270 self._timeout_dc = None
271 self._window_size = window_size
272 self.reactor = clock if clock is not None else reactor
273 self._offset = 0
274 # self._win_section = 0
275 self._win_retry = 0
276 self._device_id = image_dnld.id
277 self._device = omci_agent.get_device(image_dnld.id)
278 self.__init_state_machine()
279 self._ret_deferred = None
280 self._image_id = image_id # Target software image entity ID
281 self._image_file = image_dnld.local_dir + '/' + image_dnld.name
282 self._image_obj = open(self._image_file, mode='rb')
283 self._image_size = os.path.getsize(self._image_file)
284 self._crc32 = 0
285 self._win_crc32 = 0
286 self._win_data = None
287 self._current_deferred = None
288 self._result = None # ReasonCodes
289 self.crctable = []
290 self._crctable_init = False
291 self._actimg_retry_max = OmciSoftwareImageDownloadSTM.OMCI_SWIMG_ACTIVATE_RETRY_MAX
292 self._actimg_retry = 0
293 self.log.debug("DownloadSTM", image=self._image_file, image_size=self._image_size)
294
295 def __init_state_machine(self):
296
297 #### Download Window Sub State Machine ####
298 OMCI_DOWNLOAD_WINDOW_STATE = ['init_window', 'sending_sections', 'window_success', 'window_failed']
299 OMCI_DOWNLOAD_WINDOW_TRANSITIONS = [
300 {'trigger': 'send_sections', 'source': 'init_window', 'dest': 'sending_sections'},
301 # {'trigger': 'send_section_last', 'source': 'start_section', 'dest': 'last_section' },
302 {'trigger': 'rx_ack_success', 'source': 'sending_sections', 'dest': 'window_success' },
303 {'trigger': 'rx_ack_failed', 'source': 'sending_sections', 'dest': 'window_failed' },
304 # {'trigger': 'retry_window', 'source': 'window_failed', 'dest': 'start_section' },
305 {'trigger': 'reset_window', 'source': '*', 'dest': 'init_window' }
306 ]
307 self.win_machine = HMachine(model=self,
308 states=OMCI_DOWNLOAD_WINDOW_STATE,
309 transitions=OMCI_DOWNLOAD_WINDOW_TRANSITIONS,
310 initial='init_window',
311 queued=True,
312 name='{}-window_section_machine'.format(self.__class__.__name__))
313
314 #### Software Activation Sub State Machine ####
315 OMCI_SWIMG_ACTIVATE_STATES = ['init_act', 'activating', 'busy', 'rebooting', 'committing', 'done', 'failed']
316 OMCI_SWIMG_ACTIVATE_TRANSITIONS = [
317 {'trigger': 'activate', 'source': ['init_act', 'busy'], 'dest': 'activating'},
318 {'trigger': 'onu_busy', 'source': 'activating', 'dest': 'busy'},
319 {'trigger': 'reboot', 'source': 'activating', 'dest': 'rebooting'},
320 {'trigger': 'do_commit', 'source': ['activating', 'rebooting'], 'dest': 'committing'},
321 # {'trigger': 'commit_ok', 'source': 'committing', 'dest': 'done'},
322 {'trigger': 'reset_actimg', 'source': ['activating', 'rebooting', 'committing', 'failed'], 'dest': 'init_act'},
323 # {'trigger': 'actimg_fail', 'source': ['init_act', 'activating', 'rebooting', 'committing'], 'dest': 'failed'}
324 ]
325
326 self.activate_machine = HMachine(model=self,
327 states=OMCI_SWIMG_ACTIVATE_STATES,
328 transitions=OMCI_SWIMG_ACTIVATE_TRANSITIONS,
329 initial='init_act',
330 queued=True,
331 name='{}-activate_machine'.format(self.__class__.__name__))
332
333 #### Main State Machine ####
334 OMCI_SWIMG_DOWNLOAD_STATES = [ 'init_image', 'starting_image', 'ending_image', 'endimg_busy', 'done_image',
335 {'name': 'dwin', 'children': self.win_machine},
336 {'name': 'actimg', 'children': self.activate_machine}
337 ]
338 OMCI_SWIMG_DOWNLOAD_TRANSITIONS = [
339 {'trigger': 'start_image', 'source': 'init_image', 'dest': 'starting_image' },
340 {'trigger': 'download_window', 'source': 'starting_image', 'dest': 'dwin_init_window' },
341 {'trigger': 'download_success', 'source': 'dwin', 'dest': 'ending_image' },
342 {'trigger': 'onu_busy', 'source': 'ending_image', 'dest': 'endimg_busy' },
343 {'trigger': 'retry_endimg', 'source': 'endimg_busy', 'dest': 'ending_image' },
344 {'trigger': 'end_img_success', 'source': 'ending_image', 'dest': 'actimg_init_act' },
345 {'trigger': 'activate_done', 'source': 'actimg', 'dest': 'done_image' },
346 {'trigger': 'download_fail', 'source': '*', 'dest': 'done_image' },
347 {'trigger': 'reset_image', 'source': '*', 'dest': 'init_image' },
348 ]
349
350 self.img_machine = HMachine(model=self,
351 states=OMCI_SWIMG_DOWNLOAD_STATES,
352 transitions=OMCI_SWIMG_DOWNLOAD_TRANSITIONS,
353 initial='init_image',
354 queued=True,
355 name='{}-image_download_machine'.format(self.__class__.__name__))
356
357 # @property
358 # def image_filename(self):
359 # return self._image_file
360
361 # @image_filename.setter
362 # def image_filename(self, value):
363 # if self._image_fd is not None:
364 # self._image_fd.close()
365 # self._image_filename = value
366 # self._image_fd = open(self._image_filename, mode='rb')
367 # self._image_size = os.path.getsize(self._image_filename)
368 # print("Set image file: " + self._image_filename + " size: " + str(self._image_size))
369
370 def __omci_start_download_resp_success(self, rx_frame):
371 self.log.debug("__omci_download_resp_success")
372 self.download_window()
373 return rx_frame
374
375 def __omci_start_download_resp_fail(self, fail):
376 self.log.debug("__omci_download_resp_fail", failure=fail)
377 self._result = ReasonCodes.ProcessingError
378 self.download_fail()
379
380 def __omci_end_download_resp_success(self, rx_frame):
381 self.log.debug("__omci_end_download_resp_success")
382 if rx_frame.fields['message_type'] == OmciEndSoftwareDownloadResponse.message_id: # 0x35
383 omci_data = rx_frame.fields['omci_message']
384 if omci_data.fields['result'] == 0:
385 self.log.debug('OMCI End Image OK')
386 self._result = ReasonCodes.Success
387 self.end_img_success()
388 elif omci_data.fields['result'] == 6: # Device Busy
389 self.log.debug('OMCI End Image Busy')
390 self.onu_busy()
391 else:
392 self.log.debug('OMCI End Image Failed', reason=omci_data.fields['result'])
393 else:
394 self.log.debug('Receive Unexpected OMCI', message_type=rx_frame.fields['message_type'])
395
396 def __omci_end_download_resp_fail(self, fail):
397 self.log.debug("__omci_end_download_resp_fail", failure=fail)
398 self._result = ReasonCodes.ProcessingError
399 self.download_fail()
400
401 def __omci_send_window_resp_success(self, rx_frame, cur_state, datasize):
402 # self.log.debug("__omci_send_window_resp_success", current_state=cur_state)
403 self._offset += datasize
404 self._image_download.downloaded_bytes += datasize
405 self.rx_ack_success()
406
407 def __omci_send_window_resp_fail(self, fail, cur_state):
408 self.log.debug("__omci_send_window_resp_fail", current_state=cur_state)
409 self.rx_ack_failed()
410
411 def __activate_resp_success(self, rx_frame):
412 self._current_deferred = None
413 if rx_frame.fields['message_type'] == OmciActivateImageResponse.message_id: # 0x36
414 omci_data = rx_frame.fields['omci_message']
415 if omci_data.fields['result'] == 0:
416 self.log.debug("Activate software image success, rebooting ONU ...", device_id=self._device.device_id,
417 state=self._image_download.image_state)
418 standby_image_id = 0 if self._image_id else 1
419 self._omci_agent.database.set(self._device.device_id, SoftwareImage.class_id, self._image_id, {"is_active": 1})
420 self._omci_agent.database.set(self._device.device_id, SoftwareImage.class_id, standby_image_id, {"is_active": 0})
421 self.reboot()
422 elif omci_data.fields['result'] == 6: # Device Busy
423 self.log.debug('OMCI Activate Image Busy')
424 self.onu_busy()
425 else:
426 self.log.debug('OMCI Activate Image Failed', reason=omci_data['result'])
427 else:
428 self.log.debug('Receive Unexpected OMCI', message_type=rx_frame['message_type'])
429
430 def __activate_fail(self, fail):
431 self.log.debug("Activate software image failed", faile=fail)
432 self._current_deferred = None
433 self._result = ReasonCodes.ProcessingError
434 self.activate_done()
435
436 def __commit_success(self, rx_frame):
437 self.log.debug("Commit software success", device_id=self._device_id)
438 self._current_deferred = None
439 standby_image_id = 0 if self._image_id else 1
440 self._omci_agent.database.set(self._device_id, SoftwareImage.class_id, self._image_id, {"is_committed": 1})
441 self._omci_agent.database.set(self._device_id, SoftwareImage.class_id, standby_image_id, {"is_committed": 0})
442 self._image_download.image_state = ImageDownload.IMAGE_ACTIVE
443 self._result = ReasonCodes.Success
444 self.activate_done()
445
446 def __commit_fail(self, fail):
447 self.log.debug("Commit software image failed", faile=fail)
448 self._current_deferred = None
449 self._result = ReasonCodes.ProcessingError
450 self._image_download.image_state = ImageDownload.IMAGE_REVERT
451 self.activate_done()
452
453# @property
454# def image_id(self):
455# return self._image_id
456
457# @image_id.setter
458# def image_id(self, value):
459# self._image_id = value
460
461 @property
462 def status(self):
463 return self._image_download
464
465 def start(self):
466 self.log.debug("OmciSoftwareImageDownloadSTM.start", current_state=self.state)
467 if self._ret_deferred is None:
468 self._ret_deferred = Deferred()
469 if self.state == 'init_image':
470 self.reactor.callLater(0, self.start_image)
471 return self._ret_deferred
472
473 def stop(self):
474 self.log.debug("OmciSoftwareImageDownloadSTM.stop", current_state=self.state)
475 self._result = ReasonCodes.OperationCancelled
476 self.download_fail()
477
478 def on_enter_init_image(self):
479 self.log.debug("on_enter_init_image")
480 self._image_obj.seek(0)
481 self._offset = 0
482 # self._win_section = 0
483 self._win_retry = 0
484
485 def on_enter_starting_image(self):
486 self.log.debug("on_enter_starting_image")
487 self._image_download.downloaded_bytes = 0
488 self._current_deferred = self._device.omci_cc.send_start_software_download(self._image_id, self._image_size, self._window_size)
489 self._current_deferred.addCallbacks(self.__omci_start_download_resp_success, self.__omci_start_download_resp_fail)
490 # callbackArgs=(self.state,), errbackArgs=(self.state,))
491
492 def on_enter_dwin_init_window(self):
493 # self.log.debug("on_enter_dwin_init_window", offset=self._offset, image_size=self._image_size)
494 if self._offset < self._image_size:
495 self.send_sections()
496
497 def on_enter_dwin_sending_sections(self):
498 # self.log.debug("on_enter_dwin_sending_sections", offset=self._offset)
499
500 if (self._offset + self._window_size * OmciSectionDataSize) <= self._image_size:
501 sections = self._window_size
502 mod = 0
503 datasize = self._window_size * OmciSectionDataSize
504 else:
505 datasize = self._image_size - self._offset
506 sections = datasize / OmciSectionDataSize
507 mod = datasize % OmciSectionDataSize
508 sections = sections + 1 if mod > 0 else sections
509
510 # self.log.debug("on_enter_dwin_sending_sections", offset=self._offset, datasize=datasize, sections=sections)
511 if self._win_retry == 0:
512 self._win_data = self._image_obj.read(datasize)
513 self._win_crc32 = self.crc32(self._crc32, self._win_data)
514 # self.log.debug("CRC32", crc32=self._win_crc32, offset=self._offset)
515 else:
516 self.log.debug("Retry download window with crc32", offset=self._offset)
517
518 sent = 0
519 for i in range(0, sections):
520 if i < sections - 1:
521 # self.log.debug("section data", data=hexlify(data[(self._offset+sent):(self._offset+sent+OmciSectionDataSize)]))
522 self._device.omci_cc.send_download_section(self._image_id, i,
523 self._win_data[sent:sent+OmciSectionDataSize])
524 sent += OmciSectionDataSize
525 else:
526 last_size = OmciSectionDataSize if mod == 0 else mod
527 self._current_deferred = self._device.omci_cc.send_download_section(self._image_id, i,
528 self._win_data[sent:sent+last_size],
529 timeout=DEFAULT_OMCI_TIMEOUT)
530 self._current_deferred.addCallbacks(self.__omci_send_window_resp_success, self.__omci_send_window_resp_fail,
531 callbackArgs=(self.state, datasize), errbackArgs=(self.state,))
532 sent += last_size
533 assert sent==datasize
534
535 # def on_enter_dwin_last_section(self):
536 # self._current_deferred = self._device.omci_cc.send_download_section, self._instance_id, self._win_section, data)
537 # self._current_deferred.addCallbacks(self.__omci_resp_success, self.__omci_resp_fail,
538 # callbackArgs=(self.state,), errbackArgs=(self.state,))
539
540 def on_enter_dwin_window_success(self):
541 # self.log.debug("on_enter_dwin_window_success")
542 self._crc32 = self._win_crc32 if self._win_crc32 != 0 else self._crc32
543 self._win_crc32 = 0
544 self._win_retry = 0
545 if self._offset < self._image_size:
546 self.reset_window()
547 else:
548 self.download_success()
549
550 def on_enter_dwin_window_failed(self):
551 self.log.debug("on_enter_dwin_window_fail: ", retry=self._win_retry)
552 if self._win_retry < self.OMCI_SWIMG_WINDOW_RETRY_MAX:
553 self._win_retry += 1
554 self.reset_window()
555 else:
556 self._result = ReasonCodes.ProcessingError
557 self.download_fail()
558
559 def on_enter_ending_image(self):
560 self.log.debug("on_enter_ending_image", crc32=self._crc32)
561 self._current_deferred = self._device.omci_cc.send_end_software_download(self._image_id, self._crc32,
562 self._image_size, timeout=18)
563 self._current_deferred.addCallbacks(self.__omci_end_download_resp_success, self.__omci_end_download_resp_fail)
564 # callbackArgs=(self.state,), errbackArgs=(self.state,))
565
566 def on_enter_endimg_busy(self):
567 self.log.debug("on_enter_endimg_busy")
568 self.reactor.callLater(3, self.retry_endimg)
569
570 def on_enter_actimg_init_act(self):
571 self.log.debug("on_enter_actimg_init_act", retry=self._actimg_retry, max_retry=self._actimg_retry_max)
572 # self._images[0] = self._omci_agent.database.query(self._device_id, SoftwareImage.class_id, 0, ["is_active", "is_committed", "is_valid"])
573 # self._images[1] = self._omci_agent.database.query(self._device_id, SoftwareImage.class_id, 1, ["is_active", "is_committed", "is_valid"])
574 # if (self._images[self._to_image]["is_active"] != 1 and self._images[self._to_image]["is_valid"] == 1):
575 if self._actimg_retry > self._actimg_retry_max:
576 self.log.debug("activate image failed: retry max", retries=self._actimg_retry)
577 self._result = ReasonCodes.ProcessingError
578 self.activate_done()
579 else:
580 self._image_download.image_state = ImageDownload.IMAGE_ACTIVATE
581 self.activate()
582
583 def on_enter_actimg_activating(self):
584 self.log.debug("on_enter_actimg_activating")
585 img = self._omci_agent.database.query(self._device_id, SoftwareImage.class_id,
586 self._image_id, ["is_active", "is_committed", "is_valid"])
587
588 self.log.debug("on_enter_actimg_activating", instance=self._image_id, state=img)
589 if img["is_active"] == 0:
590 #if img["is_valid"] == 1:
591 self._current_deferred = self._device.omci_cc.send_active_image(self._image_id)
592 self._current_deferred.addCallbacks(self.__activate_resp_success, self.__activate_fail)
593 #else:
594 # self.fail()
595 else:
596 self.do_commit()
597
598 def on_enter_actimg_busy(self):
599 self.log.debug("on_enter_actimg_busy")
600 self.reactor.callLater(3, self.activate)
601
602 def __on_reboot_timeout(self):
603 self.log.debug("on_reboot_timeout")
604 self._timeout_dc = None
605 self._result = ReasonCodes.ProcessingError
606 self.activate_done()
607
608 def on_enter_actimg_rebooting(self):
609 self.log.debug("on_enter_actimg_rebooting")
610 if self._timeout_dc == None:
611 self._timeout_dc = self.reactor.callLater(self._timeout, self.__on_reboot_timeout)
612
613 def on_exit_actimg_rebooting(self):
614 self.log.debug("on_exit_actimg_rebooting", timeout=self._timeout_dc)
615 if self._timeout_dc and self._timeout_dc.active:
616 self._timeout_dc.cancel()
617 self._timeout_dc = None
618
619 def on_enter_actimg_committing(self):
620 # self.log.debug("on_enter_committing")
621 img = self._omci_agent.database.query(self._device_id, SoftwareImage.class_id,
622 self._image_id, ["is_active", "is_committed", "is_valid"])
623 self.log.debug("on_enter_actimg_committing", instance=self._image_id, state=img)
624 if (img['is_active'] == 0):
625 self._actimg_retry += 1
626 self.log.debug("do retry", retry=self._actimg_retry)
627 self.reset_actimg()
628 else:
629 self._actimg_retry = 0
630 self._current_deferred = self._device.omci_cc.send_commit_image(self._image_id)
631 self._current_deferred.addCallbacks(self.__commit_success, self.__commit_fail)
632
633 def on_enter_done_image(self):
634 self.log.debug("on_enter_done_image", result=self._result)
635 if self._result == ReasonCodes.Success:
636 self.reactor.callLater(0, self._ret_deferred.callback, self._image_download) # (str(self._instance_id))
637 else:
638 self._ret_deferred.errback(failure.Failure(Exception('ONU Software Download Failed, instance ' + str(self._image_id))))
639
640 def __crc_GenTable32(self):
641 if self._crctable_init:
642 return
643
644 # x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x + 1
645 pn32 = [0, 1, 2, 4, 5, 7, 8, 10, 11, 12, 16, 22, 23, 26]
646 poly = 0
647 for i in pn32:
648 poly |= (1 << i)
649
650 for i in range(0, 256):
651 _accum = (i << 24) & 0xFFFFFFFF
652 for j in range(0, 8):
653 if _accum & (1 << 31):
654 _accum = (_accum << 1) ^ poly
655 else:
656 _accum = (_accum << 1) & 0xFFFFFFFF
657 # self.crctable[i] = accum
658 self.crctable.append(_accum)
659 self._crctable_init = True
660
661 def crc32(self, accum, data):
662 self.__crc_GenTable32()
663 _accum = ~accum & 0xFFFFFFFF
664 num = len(data)
665 for i in range(0, num):
666 _accum = self.crctable[((_accum >> 24) ^ ord(data[i])) & 0xFF] ^ ((_accum << 8) & 0xFFFFFFFF)
667
668 return ~_accum & 0xFFFFFFFF
669
670###################################################################################
671## OMCI Software Image Activation/Committing Procedure
672###################################################################################
673'''
674class OmciSoftwareImageActivateSTM(object):
675 OMCI_SWIMG_ACTIVATE_STATES = ['starting', 'activating', 'busy', 'rebooting', 'committing', 'done', 'failed']
676 OMCI_SWIMG_ACTIVATE_TRANSITIONS = [
677 {'trigger': 'activate', 'source': ['starting', 'busy'], 'dest': 'activating'},
678 {'trigger': 'onu_busy', 'source': 'activating', 'dest': 'busy'},
679 {'trigger': 'reboot', 'source': 'activating', 'dest': 'rebooting'},
680 {'trigger': 'do_commit', 'source': ['activating', 'rebooting'], 'dest': 'committing'},
681 {'trigger': 'commit_ok', 'source': 'committing', 'dest': 'done'},
682 {'trigger': 'reset', 'source': ['activating', 'rebooting', 'committing', 'failed'], 'dest': 'starting'},
683 {'trigger': 'fail', 'source': ['starting', 'activating', 'rebooting', 'committing'], 'dest': 'failed'}
684 ]
685 OMCI_SWIMG_ACTIVATE_TRANSITIONS_TIMEOUT = 10 # Seconds to delay after task failure/timeout
686 OMCI_SWIMG_ACTIVATE_RETRY_MAX = 2
687 def __init__(self, omci_agent, dev_id, target_img_entity_id, image_download,
688 states=OMCI_SWIMG_ACTIVATE_STATES,
689 transitions=OMCI_SWIMG_ACTIVATE_TRANSITIONS,
690 initial_state='disabled',
691 timeout_delay=OMCI_SWIMG_ACTIVATE_TRANSITIONS_TIMEOUT,
692 advertise_events=True,
693 clock=None):
694 self.log = structlog.get_logger(device_id=dev_id)
695 self._omci_agent = omci_agent
696 self._device_id = dev_id
697 self._device = omci_agent.get_device(dev_id)
698 self._to_image = target_img_entity_id
699 self._from_image = 0 if self._to_image == 1 else 1
700 self._image_download = image_download
701 # self._images = dict()
702 self._timeout = timeout_delay
703 self._timeout_dc = None
704 self.reactor = clock if clock is not None else reactor
705 self._retry_max = OmciSoftwareImageActivateSTM.OMCI_SWIMG_ACTIVATE_RETRY_MAX
706 self._retry = 0
707 self._deferred = None
708 self.ret_deferred = None
709 self.machine = Machine(model=self,
710 states=states,
711 transitions=transitions,
712 initial='starting',
713 queued=True,
714 name='{}-image_activate_machine'.format(self.__class__.__name__))
715 self.log.debug("OmciSoftwareImageActivateSTM", target=self._to_image)
716
717 def __activate_resp_success(self, rx_frame):
718 if rx_frame.fields['message_type'] == 0x36: # (OmciActivateImageResponse)
719 omci_data = rx_frame.fields['omci_message']
720 if omci_data.fields['result'] == 0:
721 self.log.debug("Activate software image success, rebooting ONU ...", device_id=self._device_id)
722 self._omci_agent.database.set(self._device_id, SoftwareImage.class_id, self._to_image, {"is_active": 1})
723 self._omci_agent.database.set(self._device_id, SoftwareImage.class_id, self._from_image, {"is_active": 0})
724 self.reboot()
725 elif omci_data.fields['result'] == 6: # Device Busy
726 self.log.debug('OMCI Activate Image Busy')
727 self.onu_busy()
728 else:
729 self.log.debug('OMCI Activate Image Failed', reason=omci_data['result'])
730 else:
731 self.log.debug('Receive Unexpected OMCI', message_type=rx_frame['message_type'])
732
733 def __activate_fail(self, fail):
734 self.log.debug("Activate software image failed", faile=fail)
735
736 def __commit_success(self, rx_frame):
737 self.log.debug("Commit software success", device_id=self._device_id)
738 self._omci_agent.database.set(self._device_id, SoftwareImage.class_id, self._to_image, {"is_committed": 1})
739 self._omci_agent.database.set(self._device_id, SoftwareImage.class_id, self._from_image, {"is_committed": 0})
740 self.commit_ok()
741
742 def __commit_fail(self, fail):
743 self.log.debug("Commit software image failed", faile=fail)
744
745 @property
746 def status(self):
747 return self._image_download
748
749 def start(self):
750 self.log.debug("Start switch software image", target=self._to_image)
751 # self._images[0] = self._omci_agent.database.query(self._device_id, SoftwareImage.class_id, 0, ["is_active", "is_committed", "is_valid"])
752 # self._images[1] = self._omci_agent.database.query(self._device_id, SoftwareImage.class_id, 1, ["is_active", "is_committed", "is_valid"])
753 # if (self._images[self._to_image]["is_active"] == 0 and self._images[self._to_image]["is_valid"] == 1):
754 self.ret_deferred = Deferred()
755 self._image_download.image_state = ImageDownload.IMAGE_ACTIVATE
756 self.reactor.callLater(0, self.activate)
757 return self.ret_deferred
758
759 def on_enter_starting(self):
760 # self.log.debug("on_enter_starting")
761 # self._images[0] = self._omci_agent.database.query(self._device_id, SoftwareImage.class_id, 0, ["is_active", "is_committed", "is_valid"])
762 # self._images[1] = self._omci_agent.database.query(self._device_id, SoftwareImage.class_id, 1, ["is_active", "is_committed", "is_valid"])
763 # if (self._images[self._to_image]["is_active"] != 1 and self._images[self._to_image]["is_valid"] == 1):
764 if self._retry > self._retry_max:
765 self.log.debug("failed: retry max", retries=self._retry)
766 self.fail()
767 else:
768 self.activate()
769
770 def on_enter_activating(self):
771 img = self._omci_agent.database.query(self._device_id, SoftwareImage.class_id,
772 self._to_image, ["is_active", "is_committed", "is_valid"])
773
774 self.log.debug("on_enter_activating", instance=self._to_image, state=img)
775 if img["is_active"] == 0:
776 #if img["is_valid"] == 1:
777 self._deferred = self._device.omci_cc.send_active_image(self._to_image)
778 self._deferred.addCallbacks(self.__activate_resp_success, self.__activate_fail)
779 #else:
780 # self.fail()
781 else:
782 self.do_commit()
783
784 def on_enter_busy(self):
785 self.log.debug("on_enter_busy")
786 self.reactor.callLater(3, self.activate)
787
788 def on_enter_rebooting(self):
789 self.log.debug("on_enter_rebooting")
790 if self._timeout_dc == None:
791 self._timeout_dc = self.reactor.callLater(self._timeout, self.fail)
792
793 def on_exit_rebooting(self):
794 self.log.debug("on_exit_rebooting")
795 if self._timeout_dc and self._timeout_dc.active:
796 self._timeout_dc.cancel()
797 self._timeout_dc = None
798
799 def on_enter_committing(self):
800 # self.log.debug("on_enter_committing")
801 img = self._omci_agent.database.query(self._device_id, SoftwareImage.class_id,
802 self._to_image, ["is_active", "is_committed", "is_valid"])
803 self.log.debug("on_enter_committing", instance=self._to_image, state=img)
804 if (img['is_active'] == 0):
805 self._retry += 1
806 self.log.debug("do retry", retry=self._retry)
807 self.reset()
808 else:
809 self._retry = 0
810 self._deferred = self._device.omci_cc.send_commit_image(self._to_image)
811 self._deferred.addCallbacks(self.__commit_success, self.__commit_fail)
812
813 def on_enter_done(self):
814 self.log.debug("on_enter_done")
815 self._image_download.image_state = ImageDownload.IMAGE_ACTIVE
816 self.ret_deferred.callback(self._to_image)
817
818 def on_enter_failed(self):
819 self.log.debug("on_enter_failed")
820 self._image_download.image_state = ImageDownload.IMAGE_REVERT
821 self.ret_deferred.errback(failure.Failure(Exception('ONU Software Activating Failed, instance ' + str(self._to_image))))
822'''
823
824###################################################################################
825## Image Agent for OLT/ONT software image handling
826###################################################################################
827class ImageAgent(object):
828 """
829 Image Agent supports multiple state machines running at the same time:
830 """
831
832 DEFAULT_LOCAL_ROOT = "/"
833
834 # def __init__(self, omci_agent, dev_id, stm_cls, img_tasks, advertise_events=True):
835 def __init__(self, omci_agent, dev_id,
836 dwld_stm_cls, dwld_img_tasks,
837 upgrade_onu_stm_cls, upgrade_onu_tasks,
838 # image_activate_stm_cls,
839 advertise_events=True, local_dir=None, clock=None):
840 """
841 Class initialization
842
843 :param omci_agent: (OpenOmciAgent) Agent
844 :param dev_id : (str) ONU Device ID
845 :param dwld_stm_cls : (ImageDownloadeSTM) Image download state machine class
846 :param dwld_img_tasks : (FileDownloadTask) file download task
847 :param upgrade_onu_stm_cls : (OmciSoftwareImageDownloadSTM) ONU Image upgrade state machine class
848 :param upgrade_onu_tasks : ({OmciSwImageUpgradeTask})
849 # :param image_activate_stm_cls: (OmciSoftwareImageActivateSTM)
850 """
851
852 self.log = structlog.get_logger(device_id=dev_id)
853
854 self._omci_agent = omci_agent
855 self._device_id = dev_id
856 self._dwld_stm_cls = dwld_stm_cls
857 # self._image_download_sm = None
858 self._images = dict()
859 self._download_task_cls = dwld_img_tasks['download-file']
860
861 self._omci_upgrade_sm_cls = upgrade_onu_stm_cls
862 self._omci_upgrade_task_cls = upgrade_onu_tasks['omci_upgrade_task']
863 self._omci_upgrade_task = None
864 self._omci_upgrade_deferred = None
865
866 # self._omci_activate_img_sm_cls = image_activate_stm_cls
867 # self._omci_activate_img_sm = None
868 self.reactor = clock if clock is not None else reactor
869
870 self._advertise_events = advertise_events
871 # self._local_dir = None
872
873 self._device = None
874 # onu_dev = self._omci_agent.get_device(self._device_id)
875 # assert device
876
877 # self._local_dir = DEFAULT_LOCAL_ROOT + onu_dev.adapter_agent.name
878 # self.log.debug("ImageAgent", local_dir=self._local_dir)
879
880
881 def __get_standby_image_instance(self):
882 instance_id = None
883 instance_0 = self._omci_agent.database.query(self._device_id, SoftwareImage.class_id, 0, ["is_active", "is_committed"])
884 if instance_0['is_active'] == 1:
885 instance_id = 1
886 else:
887 instance_1 = self._omci_agent.database.query(self._device_id, SoftwareImage.class_id, 1, ["is_active", "is_committed"])
888 if instance_1['is_active'] == 1:
889 instance_id = 0
890 return instance_id
891
892 def __clear_task(self, arg):
893 self.__omci_upgrade_task = None
894
895 # def get_image(self, name, local_dir, remote_url, timeout_delay=ImageDownloadeSTM.DEFAULT_TIMEOUT_RETRY):
896 def get_image(self, image_download, timeout_delay=ImageDownloadeSTM.DEFAULT_TIMEOUT_RETRY):
897
898 """
899 Get named image from servers
900 :param image_download: (voltha_pb2.ImageDownload)
901 :param timeout_delay : (number) timeout for download task
902 :
903 :Return a Deferred that will be triggered if the file is locally availabe or downloaded sucessfully
904 : Caller will register callback and errback to the returned defer to get notified
905 """
906 self.log.debug("get_image", download=image_download)
907
908 # if self._local_dir is None:
909 # onu_dev = self._omci_agent.get_device(self._device_id)
910 # assert onu_dev
911 # if image_download.local_dir is None:
912 # self._local_dir = ImageAgent.DEFAULT_LOCAL_ROOT + onu_dev.adapter_agent.name
913 # else:
914 # self._local_dir = image_download.local_dir + '/' + onu_dev.adapter_agent.name
915
916 # self.log.debug("ImageAgent", local_dir=self._local_dir)
917 # image_download.local_dir = self._local_dir
918
919 # if os.path.isfile(self._local_dir + '/' + image_download.name): # image file exists
920 # d = Deferred()
921 # self.reactor.callLater(0, d.callback, image_download)
922 # self.log.debug("Image file exists")
923 # return d
924
925 img_dnld_sm = self._images.get(image_download.name)
926 if img_dnld_sm is None:
927 img_dnld_sm = self._dwld_stm_cls(self._omci_agent, # self._device_id, name, local_dir, remote_url,
928 image_download,
929 self._download_task_cls,
930 timeout_delay=timeout_delay,
931 clock=self.reactor
932 )
933 self._images[image_download.name] = img_dnld_sm
934
935 # if self._image_download_sm is None:
936 # self._image_download_sm = self._dwld_stm_cls(self._omci_agent, # self._device_id, name, local_dir, remote_url,
937 # image_download,
938 # self._download_task_cls,
939 # timeout_delay=timeout_delay,
940 # clock=self.reactor
941 # )
942 # else:
943 # if self._image_download_sm.download_status.state != ImageDownload.DOWNLOAD_SUCCEEDED:
944 # self._image_download_sm.reset()
945
946 d = img_dnld_sm.get_file()
947 return d
948
949 def cancel_download_image(self, name):
950 img_dnld_sm = self._images.pop(name, None)
951 if img_dnld_sm is not None:
952 img_dnld_sm.stop()
953
954
955 def onu_omci_download(self, image_dnld_name):
956 """
957 Start upgrading ONU.
958 image_dnld: (ImageDownload)
959 : Return Defer instance to get called after upgrading success or failed.
960 : Or return None if image does not exist
961 """
962 self.log.debug("onu_omci_download", image=image_dnld_name)
963
964 image_dnld_sm = self._images.get(image_dnld_name)
965 if image_dnld_sm is None:
966 return None
967
968 self._device = self._omci_agent.get_device(image_dnld_sm.status.id) if self._device is None else self._device
969
970 # if restart:
971 # self.cancel_upgrade_onu()
972
973 if self._omci_upgrade_task is None:
974 img_id = self.__get_standby_image_instance()
975 self.log.debug("start task", image_Id=img_id, task=self._omci_upgrade_sm_cls)
976 self._omci_upgrade_task = self._omci_upgrade_task_cls(img_id,
977 self._omci_upgrade_sm_cls,
978 self._omci_agent,
979 image_dnld_sm.status, clock=self.reactor)
980 self.log.debug("task created but not started")
981 # self._device.task_runner.start()
982 self._omci_upgrade_deferred = self._device.task_runner.queue_task(self._omci_upgrade_task)
983 self._omci_upgrade_deferred.addBoth(self.__clear_task)
984 return self._omci_upgrade_deferred
985
986
987 def cancel_upgrade_onu(self):
988 self.log.debug("cancel_upgrade_onu")
989 if self._omci_upgrade_task is not None:
990 self.log.debug("cancel_upgrade_onu", running=self._omci_upgrade_task.running)
991 # if self._omci_upgrade_task.running:
992 self._omci_upgrade_task.stop()
993 self._omci_upgrade_task = None
994 if self._omci_upgrade_deferred is not None:
995 self.reactor.callLater(0, self._omci_upgrade_deferred.cancel)
996 self._omci_upgrade_deferred = None
997
998
999 # def activate_onu_image(self, image_name):
1000 # self.log.debug("activate_onu_image", image=image_name)
1001 # img_dnld = self.get_image_status(image_name)
1002 # if img_dnld is None:
1003 # return None
1004
1005 # img_dnld.image_state = ImageDownload.IMAGE_INACTIVE
1006 # if self._omci_activate_img_sm is None:
1007 # self._omci_activate_img_sm = self._omci_activate_img_sm_cls(self._omci_agent, self._device_id,
1008 # self.__get_standby_image_instance(),
1009 # img_dnld, clock=self.reactor)
1010 # return self._omci_activate_img_sm.start()
1011 # else:
1012 # return None
1013
1014 def onu_bootup(self):
1015 if self._omci_upgrade_task is not None:
1016 self._omci_upgrade_task.onu_bootup()
1017
1018 def get_image_status(self, image_name):
1019 """
1020 Return (ImageDownload)
1021 """
1022 sm = self._images.get(image_name)
1023 return sm.status if sm is not None else None
1024