blob: 150a9fcee66e3c0b2f8352e47bc8d96c2955a440 [file] [log] [blame]
Zsolt Haraszti66862032016-11-28 14:28:39 -08001#
2# Copyright 2016 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#
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
29log = structlog.get_logger()
30
31
32class InvalidStateTransition(Exception): pass
33
34
35class DeviceAgent(object):
36
37 def __init__(self, core, initial_data):
38 self.core = core
39 self._tmp_initial_data = initial_data
40 self.proxy = core.get_proxy('/devices/{}'.format(initial_data.id))
41 self.proxy.register_callback(
42 CallbackType.PRE_UPDATE, self._validate_update)
43 self.proxy.register_callback(
44 CallbackType.POST_UPDATE, self._process_update)
45 self.last_data = None
46 self.adapter_agent = None
47
48 @inlineCallbacks
49 def start(self):
50 log.debug('starting')
51 self._set_adapter_agent()
52 yield self._process_update(self._tmp_initial_data)
53 del self._tmp_initial_data
54 log.info('started')
55 returnValue(self)
56
57 def stop(self):
58 log.debug('stopping')
59 self.proxy.unregister_callback(
60 CallbackType.PRE_UPDATE, self._validate_update)
61 self.proxy.unregister_callback(
62 CallbackType.POST_UPDATE, self._process_update)
63 log.info('stopped')
64
65 def _set_adapter_agent(self):
66 adapter_name = self._tmp_initial_data.adapter
67 if adapter_name == '':
68 proxy = self.core.get_proxy('/')
69 known_device_types = dict(
70 (dt.id, dt) for dt in proxy.get('/device_types'))
71 device_type = known_device_types[self._tmp_initial_data.type]
72 adapter_name = device_type.adapter
73 assert adapter_name != ''
74 self.adapter_agent = registry('adapter_loader').get_agent(adapter_name)
75
76 @inlineCallbacks
77 def _validate_update(self, device):
78 """
79 Called before each update, it allows the blocking of the update
80 (by raising an exception), or even the augmentation of the incoming
81 data.
82 """
83 log.debug('device-pre-update', device=device)
84 yield self._process_state_transitions(device, dry_run=True)
85 returnValue(device)
86
87 @inlineCallbacks
88 def _process_update(self, device):
89 """
90 Called after the device object was updated (individually or part of
91 a transaction), and it is used to propagate the change down to the
92 adapter
93 """
94 log.debug('device-post-update', device=device)
95
96 # first, process any potential state transition
97 yield self._process_state_transitions(device)
98
99 # finally, store this data as last data so we can see what changed
100 self.last_data = device
101
102 @inlineCallbacks
103 def _process_state_transitions(self, device, dry_run=False):
104
105 old_admin_state = getattr(self.last_data, 'admin_state',
106 AdminState.UNKNOWN)
107 new_admin_state = device.admin_state
108 transition_handler = self.admin_state_fsm.get(
109 (old_admin_state, new_admin_state), None)
110 if transition_handler is None:
111 pass # no-op
112 elif transition_handler is False:
113 raise InvalidStateTransition('{} -> {}'.format(
114 old_admin_state, new_admin_state))
115 else:
116 assert callable(transition_handler)
117 yield transition_handler(self, device, dry_run)
118
119 @inlineCallbacks
120 def _activate_device(self, device, dry_run=False):
121 log.info('activate-device', device=device, dry_run=dry_run)
122 if not dry_run:
123 device = yield self.adapter_agent.adopt_device(device)
124 device.oper_status = OperStatus.ACTIVATING
125 # successful return from this may also populated the device
126 # data, so we need to write it back
127 reactor.callLater(0, self.update_device, device)
128
129 def update_device(self, device):
130 self.last_data = device # so that we don't propagate back
131 self.proxy.update('/', device)
132
133 def remove_device(self, device_id):
134 raise NotImplementedError()
135
136 def _propagate_change(self, device, dry_run=False):
137 log.info('propagate-change', device=device, dry_run=dry_run)
138 if device != self.last_data:
139 raise NotImplementedError()
140 else:
141 log.debug('no-op')
142
143 def _abandon_device(self, device, dry_run=False):
144 log.info('abandon-device', device=device, dry_run=dry_run)
145 raise NotImplementedError()
146
147 def _disable_device(self, device, dry_run=False):
148 log.info('disable-device', device=device, dry_run=dry_run)
149 raise NotImplementedError()
150
151 def _reenable_device(self, device, dry_run=False):
152 log.info('reenable-device', device=device, dry_run=dry_run)
153 raise NotImplementedError()
154
155 admin_state_fsm = {
156
157 # Missing entries yield no-op
158 # False means invalid state change
159
160 (AdminState.UNKNOWN, AdminState.ENABLED): _activate_device,
161
162 (AdminState.PREPROVISIONED, AdminState.UNKNOWN): False,
163 (AdminState.PREPROVISIONED, AdminState.ENABLED): _activate_device,
164
165 (AdminState.ENABLED, AdminState.UNKNOWN): False,
166 (AdminState.ENABLED, AdminState.ENABLED): _propagate_change,
167 (AdminState.ENABLED, AdminState.DISABLED): _disable_device,
168 (AdminState.ENABLED, AdminState.PREPROVISIONED): _abandon_device,
169
170 (AdminState.DISABLED, AdminState.UNKNOWN): False,
171 (AdminState.DISABLED, AdminState.PREPROVISIONED): _abandon_device,
172 (AdminState.DISABLED, AdminState.ENABLED): _reenable_device
173
174 }