blob: 3c2e267d7e6acd741aa1069a9fec1e891316f528 [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
26from voltha.protos.common_pb2 import AdminState, OperStatus
27from voltha.registry import registry
28
Zsolt Haraszti66862032016-11-28 14:28:39 -080029
30class InvalidStateTransition(Exception): pass
31
32
33class DeviceAgent(object):
34
35 def __init__(self, core, initial_data):
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080036
Zsolt Haraszti66862032016-11-28 14:28:39 -080037 self.core = core
38 self._tmp_initial_data = initial_data
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080039 self.last_data = None
Sergio Slobodrian2db4c102017-03-09 22:29:23 -050040 self.calback_data = None
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080041
Zsolt Haraszti66862032016-11-28 14:28:39 -080042 self.proxy = core.get_proxy('/devices/{}'.format(initial_data.id))
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080043 self.flows_proxy = core.get_proxy(
44 '/devices/{}/flows'.format(initial_data.id))
45 self.groups_proxy = core.get_proxy(
46 '/devices/{}/flow_groups'.format(initial_data.id))
47
Sergio Slobodrian2db4c102017-03-09 22:29:23 -050048 self.pm_config_proxy = core.get_proxy(
49 '/devices/{}/pm_configs'.format(initial_data.id))
50
Zsolt Haraszti66862032016-11-28 14:28:39 -080051 self.proxy.register_callback(
52 CallbackType.PRE_UPDATE, self._validate_update)
53 self.proxy.register_callback(
54 CallbackType.POST_UPDATE, self._process_update)
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080055
56 self.flows_proxy.register_callback(
57 CallbackType.POST_UPDATE, self._flow_table_updated)
58 self.groups_proxy.register_callback(
59 CallbackType.POST_UPDATE, self._group_table_updated)
60
Sergio Slobodrian2db4c102017-03-09 22:29:23 -050061 self.pm_config_proxy.register_callback(
62 CallbackType.POST_UPDATE, self._pm_config_updated)
63
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080064 # to know device capabilities
65 self.device_type = core.get_proxy(
66 '/device_types/{}'.format(initial_data.type)).get()
67
Zsolt Haraszti66862032016-11-28 14:28:39 -080068 self.adapter_agent = None
Zsolt Haraszti89a27302016-12-08 16:53:06 -080069 self.log = structlog.get_logger(device_id=initial_data.id)
Zsolt Haraszti66862032016-11-28 14:28:39 -080070
71 @inlineCallbacks
72 def start(self):
Zsolt Haraszti89a27302016-12-08 16:53:06 -080073 self.log.debug('starting')
Zsolt Haraszti66862032016-11-28 14:28:39 -080074 self._set_adapter_agent()
75 yield self._process_update(self._tmp_initial_data)
76 del self._tmp_initial_data
Zsolt Haraszti89a27302016-12-08 16:53:06 -080077 self.log.info('started')
Zsolt Haraszti66862032016-11-28 14:28:39 -080078 returnValue(self)
79
Khen Nursimulud068d812017-03-06 11:44:18 -050080 @inlineCallbacks
81 def stop(self, device):
82 self.log.debug('stopping', device=device)
83
84 # First, propagate this request to the device agents
85 yield self._delete_device(device)
86
Zsolt Haraszti66862032016-11-28 14:28:39 -080087 self.proxy.unregister_callback(
88 CallbackType.PRE_UPDATE, self._validate_update)
89 self.proxy.unregister_callback(
90 CallbackType.POST_UPDATE, self._process_update)
Zsolt Haraszti89a27302016-12-08 16:53:06 -080091 self.log.info('stopped')
Zsolt Haraszti66862032016-11-28 14:28:39 -080092
Khen Nursimulud068d812017-03-06 11:44:18 -050093 @inlineCallbacks
94 def reboot_device(self, device, dry_run=False):
95 self.log.info('reboot-device', device=device, dry_run=dry_run)
96 if not dry_run:
97 yield self.adapter_agent.reboot_device(device)
98
99 @inlineCallbacks
100 def get_device_details(self, device, dry_run=False):
101 self.log.info('get-device-details', device=device, dry_run=dry_run)
102 if not dry_run:
103 yield self.adapter_agent.get_device_details(device)
104
Zsolt Haraszti66862032016-11-28 14:28:39 -0800105 def _set_adapter_agent(self):
106 adapter_name = self._tmp_initial_data.adapter
107 if adapter_name == '':
108 proxy = self.core.get_proxy('/')
109 known_device_types = dict(
110 (dt.id, dt) for dt in proxy.get('/device_types'))
111 device_type = known_device_types[self._tmp_initial_data.type]
112 adapter_name = device_type.adapter
113 assert adapter_name != ''
114 self.adapter_agent = registry('adapter_loader').get_agent(adapter_name)
115
116 @inlineCallbacks
117 def _validate_update(self, device):
118 """
119 Called before each update, it allows the blocking of the update
120 (by raising an exception), or even the augmentation of the incoming
121 data.
122 """
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800123 self.log.debug('device-pre-update', device=device)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800124 yield self._process_state_transitions(device, dry_run=True)
125 returnValue(device)
126
127 @inlineCallbacks
128 def _process_update(self, device):
129 """
130 Called after the device object was updated (individually or part of
131 a transaction), and it is used to propagate the change down to the
132 adapter
133 """
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800134 self.log.debug('device-post-update', device=device)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800135
136 # first, process any potential state transition
137 yield self._process_state_transitions(device)
138
139 # finally, store this data as last data so we can see what changed
140 self.last_data = device
141
142 @inlineCallbacks
143 def _process_state_transitions(self, device, dry_run=False):
144
145 old_admin_state = getattr(self.last_data, 'admin_state',
146 AdminState.UNKNOWN)
147 new_admin_state = device.admin_state
148 transition_handler = self.admin_state_fsm.get(
149 (old_admin_state, new_admin_state), None)
150 if transition_handler is None:
151 pass # no-op
152 elif transition_handler is False:
153 raise InvalidStateTransition('{} -> {}'.format(
154 old_admin_state, new_admin_state))
155 else:
156 assert callable(transition_handler)
157 yield transition_handler(self, device, dry_run)
158
159 @inlineCallbacks
160 def _activate_device(self, device, dry_run=False):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800161 self.log.info('activate-device', device=device, dry_run=dry_run)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800162 if not dry_run:
Zsolt Haraszti66862032016-11-28 14:28:39 -0800163 device.oper_status = OperStatus.ACTIVATING
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800164 self.update_device(device)
165 yield self.adapter_agent.adopt_device(device)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800166
167 def update_device(self, device):
168 self.last_data = device # so that we don't propagate back
169 self.proxy.update('/', device)
170
Sergio Slobodrian2db4c102017-03-09 22:29:23 -0500171 def update_device_pm_config(self, device_pm_config, init=False):
172 self.callback_data = init# so that we don't push init data
173 self.pm_config_proxy.update('/', device_pm_config)
174
Zsolt Haraszti66862032016-11-28 14:28:39 -0800175 def _propagate_change(self, device, dry_run=False):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800176 self.log.info('propagate-change', device=device, dry_run=dry_run)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800177 if device != self.last_data:
178 raise NotImplementedError()
179 else:
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800180 self.log.debug('no-op')
Zsolt Haraszti66862032016-11-28 14:28:39 -0800181
182 def _abandon_device(self, device, dry_run=False):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800183 self.log.info('abandon-device', device=device, dry_run=dry_run)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800184 raise NotImplementedError()
185
Khen Nursimulud068d812017-03-06 11:44:18 -0500186 @inlineCallbacks
Zsolt Haraszti66862032016-11-28 14:28:39 -0800187 def _disable_device(self, device, dry_run=False):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800188 self.log.info('disable-device', device=device, dry_run=dry_run)
Khen Nursimulud068d812017-03-06 11:44:18 -0500189 if not dry_run:
190 yield self.adapter_agent.disable_device(device)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800191
Khen Nursimulud068d812017-03-06 11:44:18 -0500192 @inlineCallbacks
Zsolt Haraszti66862032016-11-28 14:28:39 -0800193 def _reenable_device(self, device, dry_run=False):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800194 self.log.info('reenable-device', device=device, dry_run=dry_run)
Khen Nursimulud068d812017-03-06 11:44:18 -0500195 if not dry_run:
196 yield self.adapter_agent.reenable_device(device)
197
198 @inlineCallbacks
199 def _delete_device(self, device, dry_run=False):
200 self.log.info('delete-device', device=device, dry_run=dry_run)
201 if not dry_run:
202 yield self.adapter_agent.delete_device(device)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800203
204 admin_state_fsm = {
205
206 # Missing entries yield no-op
207 # False means invalid state change
208
209 (AdminState.UNKNOWN, AdminState.ENABLED): _activate_device,
210
211 (AdminState.PREPROVISIONED, AdminState.UNKNOWN): False,
212 (AdminState.PREPROVISIONED, AdminState.ENABLED): _activate_device,
213
214 (AdminState.ENABLED, AdminState.UNKNOWN): False,
215 (AdminState.ENABLED, AdminState.ENABLED): _propagate_change,
216 (AdminState.ENABLED, AdminState.DISABLED): _disable_device,
217 (AdminState.ENABLED, AdminState.PREPROVISIONED): _abandon_device,
218
219 (AdminState.DISABLED, AdminState.UNKNOWN): False,
220 (AdminState.DISABLED, AdminState.PREPROVISIONED): _abandon_device,
221 (AdminState.DISABLED, AdminState.ENABLED): _reenable_device
222
223 }
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800224
Sergio Slobodrian2db4c102017-03-09 22:29:23 -0500225 ## <======================= PM CONFIG UPDATE HANDLING ====================
226
227 #@inlineCallbacks
228 def _pm_config_updated(self, pm_configs):
229 self.log.debug('pm-config-updated', pm_configs=pm_configs,
230 callback_data=self.callback_data)
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400231 device_id = self.proxy.get('/').id
Sergio Slobodrian2db4c102017-03-09 22:29:23 -0500232 if not self.callback_data:
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400233 self.adapter_agent.update_adapter_pm_config(device_id, pm_configs)
Sergio Slobodrian2db4c102017-03-09 22:29:23 -0500234 self.callback_data = None
235
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800236 ## <======================= FLOW TABLE UPDATE HANDLING ====================
237
238 @inlineCallbacks
239 def _flow_table_updated(self, flows):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800240 self.log.debug('flow-table-updated',
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800241 logical_device_id=self.last_data.id, flows=flows)
242
243 # if device accepts bulk flow update, lets just call that
244 if self.device_type.accepts_bulk_flow_update:
245 groups = self.groups_proxy.get('/') # gather flow groups
246 yield self.adapter_agent.update_flows_bulk(
247 device=self.last_data,
248 flows=flows,
249 groups=groups)
alshabibb5d77812017-02-01 20:21:49 -0800250 # add ability to notify called when an flow update completes
251 # see https://jira.opencord.org/browse/CORD-839
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800252
Khen Nursimulud068d812017-03-06 11:44:18 -0500253 elif self.device_type.accepts_add_remove_flow_updates:
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800254 raise NotImplementedError()
255
256 else:
257 raise NotImplementedError()
258
259 ## <======================= GROUP TABLE UPDATE HANDLING ===================
260
261 @inlineCallbacks
262 def _group_table_updated(self, groups):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800263 self.log.debug('group-table-updated',
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800264 logical_device_id=self.last_data.id,
265 flow_groups=groups)
266
267 # if device accepts bulk flow update, lets just call that
268 if self.device_type.accepts_bulk_flow_update:
269 flows = self.flows_proxy.get('/') # gather flows
270 yield self.adapter_agent.update_flows_bulk(
271 device=self.last_data,
272 flows=flows,
273 groups=groups)
alshabibb5d77812017-02-01 20:21:49 -0800274 # add ability to notify called when an group update completes
275 # see https://jira.opencord.org/browse/CORD-839
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800276
Khen Nursimulud068d812017-03-06 11:44:18 -0500277 elif self.device_type.accepts_add_remove_flow_updates:
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800278 raise NotImplementedError()
279
280 else:
281 raise NotImplementedError()
282