blob: 7ad358f35185769f899957c6f16732e2b6a43f3e [file] [log] [blame]
Zsolt Haraszti66862032016-11-28 14:28:39 -08001#
Zsolt Haraszti3eb27a52017-01-03 21:56:48 -08002# Copyright 2017 the original author or authors.
Zsolt Haraszti66862032016-11-28 14:28:39 -08003#
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
17"""
18A device agent is instantiated for each Device and plays an important role
19between the Device object and its adapter.
20"""
21import structlog
22from twisted.internet import reactor
23from twisted.internet.defer import inlineCallbacks, returnValue
24
25from voltha.core.config.config_proxy import CallbackType
Lydia Fang01f2e852017-06-28 17:24:58 -070026from voltha.protos.common_pb2 import AdminState, OperStatus, ConnectStatus, \
27 OperationResp
28from voltha.protos.device_pb2 import ImageDownload
Zsolt Haraszti66862032016-11-28 14:28:39 -080029from voltha.registry import registry
Khen Nursimulu49792142017-03-17 12:34:05 -040030from voltha.protos.openflow_13_pb2 import Flows, FlowGroups
Zsolt Haraszti66862032016-11-28 14:28:39 -080031
32class InvalidStateTransition(Exception): pass
33
34
35class DeviceAgent(object):
36
37 def __init__(self, core, initial_data):
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080038
Zsolt Haraszti66862032016-11-28 14:28:39 -080039 self.core = core
40 self._tmp_initial_data = initial_data
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080041 self.last_data = None
Sergio Slobodrian2db4c102017-03-09 22:29:23 -050042 self.calback_data = None
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080043
Zsolt Haraszti66862032016-11-28 14:28:39 -080044 self.proxy = core.get_proxy('/devices/{}'.format(initial_data.id))
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080045 self.flows_proxy = core.get_proxy(
46 '/devices/{}/flows'.format(initial_data.id))
47 self.groups_proxy = core.get_proxy(
48 '/devices/{}/flow_groups'.format(initial_data.id))
49
Sergio Slobodrian2db4c102017-03-09 22:29:23 -050050 self.pm_config_proxy = core.get_proxy(
51 '/devices/{}/pm_configs'.format(initial_data.id))
52
Lydia Fang01f2e852017-06-28 17:24:58 -070053 self.img_dnld_proxies = {}
54
Zsolt Haraszti66862032016-11-28 14:28:39 -080055 self.proxy.register_callback(
56 CallbackType.PRE_UPDATE, self._validate_update)
57 self.proxy.register_callback(
58 CallbackType.POST_UPDATE, self._process_update)
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080059
60 self.flows_proxy.register_callback(
61 CallbackType.POST_UPDATE, self._flow_table_updated)
62 self.groups_proxy.register_callback(
63 CallbackType.POST_UPDATE, self._group_table_updated)
64
Sergio Slobodrian2db4c102017-03-09 22:29:23 -050065 self.pm_config_proxy.register_callback(
66 CallbackType.POST_UPDATE, self._pm_config_updated)
67
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080068 # to know device capabilities
69 self.device_type = core.get_proxy(
70 '/device_types/{}'.format(initial_data.type)).get()
71
Zsolt Haraszti66862032016-11-28 14:28:39 -080072 self.adapter_agent = None
Zsolt Haraszti89a27302016-12-08 16:53:06 -080073 self.log = structlog.get_logger(device_id=initial_data.id)
Zsolt Haraszti66862032016-11-28 14:28:39 -080074
75 @inlineCallbacks
khenaidoo032d3302017-06-09 14:50:04 -040076 def start(self, device=None, reconcile=False):
77 self.log.info('starting', device=device)
Zsolt Haraszti66862032016-11-28 14:28:39 -080078 self._set_adapter_agent()
khenaidoo032d3302017-06-09 14:50:04 -040079 if device:
80 # Starting from an existing data, so set the last_data
81 self.last_data = device
82 if reconcile:
83 self.reconcile_existing_device(device)
84 else:
85 yield self._process_update(self._tmp_initial_data)
86
Zsolt Haraszti66862032016-11-28 14:28:39 -080087 del self._tmp_initial_data
khenaidoo032d3302017-06-09 14:50:04 -040088
Zsolt Haraszti89a27302016-12-08 16:53:06 -080089 self.log.info('started')
Zsolt Haraszti66862032016-11-28 14:28:39 -080090 returnValue(self)
91
Khen Nursimulud068d812017-03-06 11:44:18 -050092 @inlineCallbacks
93 def stop(self, device):
94 self.log.debug('stopping', device=device)
95
96 # First, propagate this request to the device agents
97 yield self._delete_device(device)
98
Zsolt Haraszti66862032016-11-28 14:28:39 -080099 self.proxy.unregister_callback(
100 CallbackType.PRE_UPDATE, self._validate_update)
101 self.proxy.unregister_callback(
102 CallbackType.POST_UPDATE, self._process_update)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800103 self.log.info('stopped')
Zsolt Haraszti66862032016-11-28 14:28:39 -0800104
Khen Nursimulud068d812017-03-06 11:44:18 -0500105 @inlineCallbacks
106 def reboot_device(self, device, dry_run=False):
khenaidoo032d3302017-06-09 14:50:04 -0400107 self.log.debug('reboot-device', device=device, dry_run=dry_run)
Khen Nursimulud068d812017-03-06 11:44:18 -0500108 if not dry_run:
109 yield self.adapter_agent.reboot_device(device)
110
Lydia Fang01f2e852017-06-28 17:24:58 -0700111 def register_image_download(self, request):
112 try:
113 self.log.debug('register-image-download', request=request)
114 path = '/devices/{}/image_downloads/{}'.format(request.id, request.name)
115 self.img_dnld_proxies[request.name] = self.core.get_proxy(path)
116 self.img_dnld_proxies[request.name].register_callback(
117 CallbackType.POST_UPDATE, self._update_image)
118 # trigger update callback
119 request.state = ImageDownload.DOWNLOAD_REQUESTED
120 self.img_dnld_proxies[request.name].update('/', request)
121 except Exception as e:
122 self.log.exception(e.message)
123
124 def activate_image_update(self, request):
125 try:
126 self.log.debug('activate-image-download', request=request)
127 request.image_state = ImageDownload.IMAGE_ACTIVATE
128 self.img_dnld_proxies[request.name].update('/', request)
129 except Exception as e:
130 self.log.exception(e.message)
131
132 def revert_image_update(self, request):
133 try:
134 self.log.debug('revert-image-download', request=request)
135 request.image_state = ImageDownload.IMAGE_REVERT
136 self.img_dnld_proxies[request.name].update('/', request)
137 except Exception as e:
138 self.log.exception(e.message)
139
140 @inlineCallbacks
141 def _download_image(self, device, img_dnld):
142 try:
143 self.log.debug('download-image', img_dnld=img_dnld)
144 yield self.adapter_agent.download_image(device, img_dnld)
145 except Exception as e:
146 self.log.exception(e.message)
147
148 def get_image_download_status(self, request):
149 try:
150 self.log.debug('get-image-download-status',
151 request=request)
152 device = self.proxy.get('/')
153 self.adapter_agent.get_image_download_status(device, request)
154 except Exception as e:
155 self.log.exception(e.message)
156
157 def cancel_image_download(self, img_dnld):
158 try:
159 self.log.debug('cancel-image-download',
160 img_dnld=img_dnld)
161 device = self.proxy.get('/')
162 self.adapter_agent.cancel_image_download(device, img_dnld)
163 except Exception as e:
164 self.log.exception(e.message)
165
166 def update_device_image_download(self, img_dnld):
167 try:
168 self.log.debug('update-device-image-download',
169 img_dnld=img_dnld)
170 self.proxy.update('/image_downloads/{}'\
171 .format(img_dnld.name), img_dnld)
172 except Exception as e:
173 self.log.exception(e.message)
174
175 def unregister_device_image_download(self, name):
176 try:
177 self.log.debug('unregister-device-image-download',
178 name=name)
179 self.self_proxies[name].unregister_callback(
180 CallbackType.POST_ADD, self._download_image)
181 self.self_proxies[name].unregister_callback(
182 CallbackType.POST_UPDATE, self._process_image)
183 except Exception as e:
184 self.log.exception(e.message)
185
186 @inlineCallbacks
187 def _update_image(self, img_dnld):
188 try:
189 self.log.debug('update-image', img_dnld=img_dnld)
190 # handle download
191 if img_dnld.state == ImageDownload.DOWNLOAD_REQUESTED:
192 device = self.proxy.get('/')
193 yield self._download_image(device, img_dnld)
194 if img_dnld.image_state == ImageDownload.IMAGE_ACTIVATE:
195 device = self.proxy.get('/')
196 yield self.adapter_agent.activate_image_update(device, img_dnld)
197 elif img_dnld.image_state == ImageDownload.IMAGE_REVERT:
198 device = self.proxy.get('/')
199 yield self.adapter_agent.revert_image_update(device, img_dnld)
200 except Exception as e:
201 self.log.exception(e.message)
202
Khen Nursimulud068d812017-03-06 11:44:18 -0500203 @inlineCallbacks
sathishg5ae86222017-06-28 15:16:29 +0530204 def self_test(self, device, dry_run=False):
205 self.log.debug('self-test-device', device=device, dry_run=dry_run)
206 if not dry_run:
207 result = yield self.adapter_agent.self_test(device)
208 returnValue(result)
209
210 @inlineCallbacks
Khen Nursimulud068d812017-03-06 11:44:18 -0500211 def get_device_details(self, device, dry_run=False):
khenaidoo032d3302017-06-09 14:50:04 -0400212 self.log.debug('get-device-details', device=device, dry_run=dry_run)
Khen Nursimulud068d812017-03-06 11:44:18 -0500213 if not dry_run:
214 yield self.adapter_agent.get_device_details(device)
215
Stephane Barbarie980a0912017-05-11 11:27:06 -0400216 @inlineCallbacks
217 def suppress_alarm(self, filter):
218 self.log.debug('suppress-alarms')
219 try:
220 yield self.adapter_agent.suppress_alarm(filter)
221 except Exception as e:
222 self.log.exception(e.message)
223
224 @inlineCallbacks
225 def unsuppress_alarm(self, filter):
226 self.log.debug('unsuppress-alarms')
227 try:
228 yield self.adapter_agent.unsuppress_alarm(filter)
229 except Exception as e:
230 self.log.exception(e.message)
231
khenaidoo032d3302017-06-09 14:50:04 -0400232 @inlineCallbacks
233 def reconcile_existing_device(self, device, dry_run=False):
234 self.log.debug('reconcile-existing-device',
235 device=device,
236 dry_run=False)
237 if not dry_run:
238 yield self.adapter_agent.reconcile_device(device)
239
Zsolt Haraszti66862032016-11-28 14:28:39 -0800240 def _set_adapter_agent(self):
241 adapter_name = self._tmp_initial_data.adapter
242 if adapter_name == '':
243 proxy = self.core.get_proxy('/')
244 known_device_types = dict(
245 (dt.id, dt) for dt in proxy.get('/device_types'))
246 device_type = known_device_types[self._tmp_initial_data.type]
247 adapter_name = device_type.adapter
248 assert adapter_name != ''
249 self.adapter_agent = registry('adapter_loader').get_agent(adapter_name)
250
251 @inlineCallbacks
252 def _validate_update(self, device):
253 """
254 Called before each update, it allows the blocking of the update
255 (by raising an exception), or even the augmentation of the incoming
256 data.
257 """
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800258 self.log.debug('device-pre-update', device=device)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800259 yield self._process_state_transitions(device, dry_run=True)
260 returnValue(device)
261
262 @inlineCallbacks
263 def _process_update(self, device):
264 """
265 Called after the device object was updated (individually or part of
266 a transaction), and it is used to propagate the change down to the
267 adapter
268 """
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800269 self.log.debug('device-post-update', device=device)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800270
271 # first, process any potential state transition
272 yield self._process_state_transitions(device)
273
274 # finally, store this data as last data so we can see what changed
275 self.last_data = device
276
277 @inlineCallbacks
278 def _process_state_transitions(self, device, dry_run=False):
279
280 old_admin_state = getattr(self.last_data, 'admin_state',
281 AdminState.UNKNOWN)
282 new_admin_state = device.admin_state
khenaidoo032d3302017-06-09 14:50:04 -0400283 self.log.debug('device-admin-states', old_state=old_admin_state,
rshettyc26a3c32017-07-27 11:06:38 +0530284 new_state=new_admin_state, dry_run=dry_run)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800285 transition_handler = self.admin_state_fsm.get(
286 (old_admin_state, new_admin_state), None)
287 if transition_handler is None:
rshettyc26a3c32017-07-27 11:06:38 +0530288 self.log.debug('No Operation', old_state=old_admin_state,
289 new_state=new_admin_state, dry_run=dry_run)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800290 pass # no-op
291 elif transition_handler is False:
292 raise InvalidStateTransition('{} -> {}'.format(
293 old_admin_state, new_admin_state))
294 else:
295 assert callable(transition_handler)
296 yield transition_handler(self, device, dry_run)
297
298 @inlineCallbacks
299 def _activate_device(self, device, dry_run=False):
khenaidoo032d3302017-06-09 14:50:04 -0400300 self.log.debug('activate-device', device=device, dry_run=dry_run)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800301 if not dry_run:
Zsolt Haraszti66862032016-11-28 14:28:39 -0800302 device.oper_status = OperStatus.ACTIVATING
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800303 self.update_device(device)
304 yield self.adapter_agent.adopt_device(device)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800305
Girish61687212018-01-08 12:48:58 +0530306 @inlineCallbacks
Zsolt Haraszti66862032016-11-28 14:28:39 -0800307 def update_device(self, device):
308 self.last_data = device # so that we don't propagate back
309 self.proxy.update('/', device)
ggowdru64d738a2018-05-10 07:08:06 -0700310 if device.admin_state == AdminState.ENABLED and \
311 device.oper_status == OperStatus.ACTIVE and \
312 device.connect_status == ConnectStatus.REACHABLE:
Nikolay Titov89004ec2017-06-19 18:22:42 -0400313 self.log.info('replay-create-interfaces ', device=device.id)
314 self.core.xpon_agent.replay_interface(device.id)
Girish61687212018-01-08 12:48:58 +0530315 # if device accepts bulk flow update, lets just call that
316 if self.device_type.accepts_bulk_flow_update:
317 flows = self.flows_proxy.get('/') # gather flows
318 groups = self.groups_proxy.get('/') # gather flow groups
319 self.log.info('replay-flows ', device=device.id)
320 yield self.adapter_agent.update_flows_bulk(
321 device=device,
322 flows=flows,
323 groups=groups)
324
Sergio Slobodrian2db4c102017-03-09 22:29:23 -0500325 def update_device_pm_config(self, device_pm_config, init=False):
326 self.callback_data = init# so that we don't push init data
327 self.pm_config_proxy.update('/', device_pm_config)
328
Zsolt Haraszti66862032016-11-28 14:28:39 -0800329 def _propagate_change(self, device, dry_run=False):
khenaidoo032d3302017-06-09 14:50:04 -0400330 self.log.debug('propagate-change', device=device, dry_run=dry_run)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800331 if device != self.last_data:
nick369a5062018-05-29 17:11:06 -0400332 self.log.warn('Not-implemented-default-to-noop')
333 #raise NotImplementedError()
Zsolt Haraszti66862032016-11-28 14:28:39 -0800334 else:
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800335 self.log.debug('no-op')
Zsolt Haraszti66862032016-11-28 14:28:39 -0800336
337 def _abandon_device(self, device, dry_run=False):
khenaidoo032d3302017-06-09 14:50:04 -0400338 self.log.debug('abandon-device', device=device, dry_run=dry_run)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800339 raise NotImplementedError()
340
Khen Nursimulu49792142017-03-17 12:34:05 -0400341 def _delete_all_flows(self):
342 """ Delete all flows on the device """
343 try:
344 self.flows_proxy.update('/', Flows(items=[]))
345 self.groups_proxy.update('/', FlowGroups(items=[]))
346 except Exception, e:
347 self.exception('flow-delete-exception', e=e)
348
Khen Nursimulud068d812017-03-06 11:44:18 -0500349 @inlineCallbacks
Zsolt Haraszti66862032016-11-28 14:28:39 -0800350 def _disable_device(self, device, dry_run=False):
khenaidoo032d3302017-06-09 14:50:04 -0400351 try:
352 self.log.debug('disable-device', device=device, dry_run=dry_run)
353 if not dry_run:
354 # Remove all flows before disabling device
355 self._delete_all_flows()
356 yield self.adapter_agent.disable_device(device)
357 except Exception, e:
358 self.log.exception('error', e=e)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800359
Khen Nursimulud068d812017-03-06 11:44:18 -0500360 @inlineCallbacks
Zsolt Haraszti66862032016-11-28 14:28:39 -0800361 def _reenable_device(self, device, dry_run=False):
khenaidoo032d3302017-06-09 14:50:04 -0400362 self.log.debug('reenable-device', device=device, dry_run=dry_run)
Khen Nursimulud068d812017-03-06 11:44:18 -0500363 if not dry_run:
364 yield self.adapter_agent.reenable_device(device)
365
366 @inlineCallbacks
367 def _delete_device(self, device, dry_run=False):
khenaidoo032d3302017-06-09 14:50:04 -0400368 self.log.debug('delete-device', device=device, dry_run=dry_run)
Khen Nursimulud068d812017-03-06 11:44:18 -0500369 if not dry_run:
370 yield self.adapter_agent.delete_device(device)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800371
372 admin_state_fsm = {
373
374 # Missing entries yield no-op
375 # False means invalid state change
376
377 (AdminState.UNKNOWN, AdminState.ENABLED): _activate_device,
378
379 (AdminState.PREPROVISIONED, AdminState.UNKNOWN): False,
380 (AdminState.PREPROVISIONED, AdminState.ENABLED): _activate_device,
Lydia Fang01f2e852017-06-28 17:24:58 -0700381 (AdminState.PREPROVISIONED, AdminState.DOWNLOADING_IMAGE): False,
Zsolt Haraszti66862032016-11-28 14:28:39 -0800382
383 (AdminState.ENABLED, AdminState.UNKNOWN): False,
384 (AdminState.ENABLED, AdminState.ENABLED): _propagate_change,
385 (AdminState.ENABLED, AdminState.DISABLED): _disable_device,
386 (AdminState.ENABLED, AdminState.PREPROVISIONED): _abandon_device,
387
388 (AdminState.DISABLED, AdminState.UNKNOWN): False,
389 (AdminState.DISABLED, AdminState.PREPROVISIONED): _abandon_device,
Lydia Fang01f2e852017-06-28 17:24:58 -0700390 (AdminState.DISABLED, AdminState.ENABLED): _reenable_device,
391 (AdminState.DISABLED, AdminState.DOWNLOADING_IMAGE): False,
392
393 (AdminState.DOWNLOADING_IMAGE, AdminState.DISABLED): False
Zsolt Haraszti66862032016-11-28 14:28:39 -0800394
395 }
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800396
Sergio Slobodrian2db4c102017-03-09 22:29:23 -0500397 ## <======================= PM CONFIG UPDATE HANDLING ====================
398
399 #@inlineCallbacks
400 def _pm_config_updated(self, pm_configs):
401 self.log.debug('pm-config-updated', pm_configs=pm_configs,
402 callback_data=self.callback_data)
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400403 device_id = self.proxy.get('/').id
Sergio Slobodrian2db4c102017-03-09 22:29:23 -0500404 if not self.callback_data:
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400405 self.adapter_agent.update_adapter_pm_config(device_id, pm_configs)
Sergio Slobodrian2db4c102017-03-09 22:29:23 -0500406 self.callback_data = None
407
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800408 ## <======================= FLOW TABLE UPDATE HANDLING ====================
409
410 @inlineCallbacks
411 def _flow_table_updated(self, flows):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800412 self.log.debug('flow-table-updated',
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800413 logical_device_id=self.last_data.id, flows=flows)
414
415 # if device accepts bulk flow update, lets just call that
416 if self.device_type.accepts_bulk_flow_update:
417 groups = self.groups_proxy.get('/') # gather flow groups
418 yield self.adapter_agent.update_flows_bulk(
419 device=self.last_data,
420 flows=flows,
421 groups=groups)
alshabibb5d77812017-02-01 20:21:49 -0800422 # add ability to notify called when an flow update completes
423 # see https://jira.opencord.org/browse/CORD-839
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800424
Khen Nursimulud068d812017-03-06 11:44:18 -0500425 elif self.device_type.accepts_add_remove_flow_updates:
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800426 raise NotImplementedError()
427
428 else:
429 raise NotImplementedError()
430
431 ## <======================= GROUP TABLE UPDATE HANDLING ===================
432
433 @inlineCallbacks
434 def _group_table_updated(self, groups):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800435 self.log.debug('group-table-updated',
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800436 logical_device_id=self.last_data.id,
437 flow_groups=groups)
438
439 # if device accepts bulk flow update, lets just call that
440 if self.device_type.accepts_bulk_flow_update:
441 flows = self.flows_proxy.get('/') # gather flows
442 yield self.adapter_agent.update_flows_bulk(
443 device=self.last_data,
444 flows=flows,
445 groups=groups)
alshabibb5d77812017-02-01 20:21:49 -0800446 # add ability to notify called when an group update completes
447 # see https://jira.opencord.org/browse/CORD-839
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800448
Khen Nursimulud068d812017-03-06 11:44:18 -0500449 elif self.device_type.accepts_add_remove_flow_updates:
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800450 raise NotImplementedError()
451
452 else:
453 raise NotImplementedError()