blob: 29ae93294e4fbd72c18dc00e9d8489586013a4ef [file] [log] [blame]
quilesj29114342019-10-29 09:30:44 +01001##
2# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
3# This file is part of OSM
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15# implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
19# For those usages not covered by the Apache License, Version 2.0 please
20# contact with: nfvlabs@tid.es
21##
22
23import asyncio
24import time
25
quilesj29114342019-10-29 09:30:44 +010026from juju.action import Action
beierlm32862bb2020-04-21 16:36:35 -040027from juju.application import Application
28from juju.machine import Machine
29from juju.model import ModelObserver, Model
quilesj29114342019-10-29 09:30:44 +010030
quilesj29114342019-10-29 09:30:44 +010031from n2vc.exceptions import N2VCTimeoutException
beierlm32862bb2020-04-21 16:36:35 -040032from n2vc.n2vc_conn import N2VCConnector, juju_status_2_osm_status
quilesj29114342019-10-29 09:30:44 +010033
34
35class _Entity:
36 def __init__(self, entity_id: str, entity_type: str, obj: object, db_dict: dict):
37 self.entity_id = entity_id
38 self.entity_type = entity_type
39 self.obj = obj
40 self.event = asyncio.Event()
41 self.db_dict = db_dict
42
43
44class JujuModelObserver(ModelObserver):
quilesj29114342019-10-29 09:30:44 +010045 def __init__(self, n2vc: N2VCConnector, model: Model):
46 self.n2vc = n2vc
47 self.model = model
48 model.add_observer(self)
49 self.machines = dict()
50 self.applications = dict()
51 self.actions = dict()
52
53 def register_machine(self, machine: Machine, db_dict: dict):
quilesjac4e0de2019-11-27 16:12:02 +000054 try:
55 entity_id = machine.entity_id
beierlm32862bb2020-04-21 16:36:35 -040056 except Exception:
quilesjac4e0de2019-11-27 16:12:02 +000057 # no entity_id aatribute, try machine attribute
58 entity_id = machine.machine
beierlm32862bb2020-04-21 16:36:35 -040059 # self.n2vc.debug(
60 # msg='Registering machine for change notifications: {}'.format(entity_id))
61 entity = _Entity(
62 entity_id=entity_id, entity_type="machine", obj=machine, db_dict=db_dict
63 )
quilesj29114342019-10-29 09:30:44 +010064 self.machines[entity_id] = entity
65
66 def unregister_machine(self, machine_id: str):
67 if machine_id in self.machines:
68 del self.machines[machine_id]
69
70 def is_machine_registered(self, machine_id: str):
71 return machine_id in self.machines
72
73 def register_application(self, application: Application, db_dict: dict):
74 entity_id = application.entity_id
beierlm32862bb2020-04-21 16:36:35 -040075 # self.n2vc.debug(
76 # msg='Registering application for change notifications: {}'.format(entity_id))
77 entity = _Entity(
78 entity_id=entity_id,
79 entity_type="application",
80 obj=application,
81 db_dict=db_dict,
82 )
quilesj29114342019-10-29 09:30:44 +010083 self.applications[entity_id] = entity
84
85 def unregister_application(self, application_id: str):
86 if application_id in self.applications:
87 del self.applications[application_id]
88
89 def is_application_registered(self, application_id: str):
90 return application_id in self.applications
91
92 def register_action(self, action: Action, db_dict: dict):
93 entity_id = action.entity_id
beierlm32862bb2020-04-21 16:36:35 -040094 # self.n2vc.debug(
95 # msg='Registering action for changes notifications: {}'.format(entity_id))
96 entity = _Entity(
97 entity_id=entity_id, entity_type="action", obj=action, db_dict=db_dict
98 )
quilesj29114342019-10-29 09:30:44 +010099 self.actions[entity_id] = entity
100
101 def unregister_action(self, action_id: str):
102 if action_id in self.actions:
103 del self.actions[action_id]
104
105 def is_action_registered(self, action_id: str):
106 return action_id in self.actions
107
108 async def wait_for_machine(
beierlm32862bb2020-04-21 16:36:35 -0400109 self,
110 machine_id: str,
111 progress_timeout: float = None,
112 total_timeout: float = None,
113 ) -> int:
quilesj29114342019-10-29 09:30:44 +0100114
115 if not self.is_machine_registered(machine_id):
116 return
117
beierlm32862bb2020-04-21 16:36:35 -0400118 self.n2vc.debug("Waiting for machine completed: {}".format(machine_id))
quilesj4074f8a2019-12-12 16:10:54 +0000119
quilesj29114342019-10-29 09:30:44 +0100120 # wait for a final state
121 entity = self.machines[machine_id]
122 return await self._wait_for_entity(
123 entity=entity,
beierlm32862bb2020-04-21 16:36:35 -0400124 field_to_check="agent_status",
125 final_states_list=["started"],
quilesj29114342019-10-29 09:30:44 +0100126 progress_timeout=progress_timeout,
beierlm32862bb2020-04-21 16:36:35 -0400127 total_timeout=total_timeout,
128 )
quilesj29114342019-10-29 09:30:44 +0100129
130 async def wait_for_application(
beierlm32862bb2020-04-21 16:36:35 -0400131 self,
132 application_id: str,
133 progress_timeout: float = None,
134 total_timeout: float = None,
135 ) -> int:
quilesj29114342019-10-29 09:30:44 +0100136
137 if not self.is_application_registered(application_id):
138 return
139
beierlm32862bb2020-04-21 16:36:35 -0400140 self.n2vc.debug("Waiting for application completed: {}".format(application_id))
quilesj4074f8a2019-12-12 16:10:54 +0000141
quilesj29114342019-10-29 09:30:44 +0100142 # application statuses: unknown, active, waiting
143 # wait for a final state
144 entity = self.applications[application_id]
145 return await self._wait_for_entity(
146 entity=entity,
beierlm32862bb2020-04-21 16:36:35 -0400147 field_to_check="status",
148 final_states_list=["active", "blocked"],
quilesj29114342019-10-29 09:30:44 +0100149 progress_timeout=progress_timeout,
beierlm32862bb2020-04-21 16:36:35 -0400150 total_timeout=total_timeout,
151 )
quilesj29114342019-10-29 09:30:44 +0100152
153 async def wait_for_action(
beierlm32862bb2020-04-21 16:36:35 -0400154 self,
155 action_id: str,
156 progress_timeout: float = None,
157 total_timeout: float = None,
158 ) -> int:
quilesj29114342019-10-29 09:30:44 +0100159
160 if not self.is_action_registered(action_id):
161 return
162
beierlm32862bb2020-04-21 16:36:35 -0400163 self.n2vc.debug("Waiting for action completed: {}".format(action_id))
quilesj4074f8a2019-12-12 16:10:54 +0000164
quilesj29114342019-10-29 09:30:44 +0100165 # action statuses: pending, running, completed, failed, cancelled
166 # wait for a final state
167 entity = self.actions[action_id]
168 return await self._wait_for_entity(
169 entity=entity,
beierlm32862bb2020-04-21 16:36:35 -0400170 field_to_check="status",
171 final_states_list=["completed", "failed", "cancelled"],
quilesj29114342019-10-29 09:30:44 +0100172 progress_timeout=progress_timeout,
beierlm32862bb2020-04-21 16:36:35 -0400173 total_timeout=total_timeout,
174 )
quilesj29114342019-10-29 09:30:44 +0100175
176 async def _wait_for_entity(
beierlm32862bb2020-04-21 16:36:35 -0400177 self,
178 entity: _Entity,
179 field_to_check: str,
180 final_states_list: list,
181 progress_timeout: float = None,
182 total_timeout: float = None,
183 ) -> int:
quilesj29114342019-10-29 09:30:44 +0100184
185 # default values for no timeout
186 if total_timeout is None:
beierlm03e807c2020-05-12 15:26:37 -0400187 total_timeout = 3600
quilesj29114342019-10-29 09:30:44 +0100188 if progress_timeout is None:
beierlm03e807c2020-05-12 15:26:37 -0400189 progress_timeout = 3600
quilesj29114342019-10-29 09:30:44 +0100190
191 # max end time
192 now = time.time()
193 total_end = now + total_timeout
194
195 if now >= total_end:
196 raise N2VCTimeoutException(
beierlm32862bb2020-04-21 16:36:35 -0400197 message="Total timeout {} seconds, {}: {}".format(
198 total_timeout, entity.entity_type, entity.entity_id
199 ),
200 timeout="total",
quilesj29114342019-10-29 09:30:44 +0100201 )
202
203 # update next progress timeout
204 progress_end = now + progress_timeout # type: float
205
206 # which is closest? progress or end timeout?
207 closest_end = min(total_end, progress_end)
208
209 next_timeout = closest_end - now
210
211 retries = 0
212
213 while entity.obj.__getattribute__(field_to_check) not in final_states_list:
214 retries += 1
215 if await _wait_for_event_or_timeout(entity.event, next_timeout):
216 entity.event.clear()
217 else:
beierlm03e807c2020-05-12 15:26:37 -0400218 message = "Progress timeout {} seconds, {}: {}".format(
beierlm32862bb2020-04-21 16:36:35 -0400219 progress_timeout, entity.entity_type, entity.entity_id
220 )
quilesj29114342019-10-29 09:30:44 +0100221 self.n2vc.debug(message)
beierlm32862bb2020-04-21 16:36:35 -0400222 raise N2VCTimeoutException(message=message, timeout="progress")
quilesj4074f8a2019-12-12 16:10:54 +0000223 # self.n2vc.debug('End of wait. Final state: {}, retries: {}'
224 # .format(entity.obj.__getattribute__(field_to_check), retries))
quilesj29114342019-10-29 09:30:44 +0100225 return retries
226
227 async def on_change(self, delta, old, new, model):
228
229 if new is None:
230 return
231
232 # log
quilesj4074f8a2019-12-12 16:10:54 +0000233 # self.n2vc.debug('on_change(): type: {}, entity: {}, id: {}'
234 # .format(delta.type, delta.entity, new.entity_id))
quilesj29114342019-10-29 09:30:44 +0100235
beierlm32862bb2020-04-21 16:36:35 -0400236 if delta.entity == "machine":
quilesj29114342019-10-29 09:30:44 +0100237
238 # check registered machine
239 if new.entity_id not in self.machines:
240 return
241
242 # write change in database
243 await self.n2vc.write_app_status_to_db(
244 db_dict=self.machines[new.entity_id].db_dict,
245 status=juju_status_2_osm_status(delta.entity, new.agent_status),
246 detailed_status=new.status_message,
247 vca_status=new.status,
beierlm32862bb2020-04-21 16:36:35 -0400248 entity_type="machine",
quilesj29114342019-10-29 09:30:44 +0100249 )
250
251 # set event for this machine
252 self.machines[new.entity_id].event.set()
253
beierlm32862bb2020-04-21 16:36:35 -0400254 elif delta.entity == "application":
quilesj29114342019-10-29 09:30:44 +0100255
256 # check registered application
257 if new.entity_id not in self.applications:
258 return
259
260 # write change in database
261 await self.n2vc.write_app_status_to_db(
262 db_dict=self.applications[new.entity_id].db_dict,
263 status=juju_status_2_osm_status(delta.entity, new.status),
264 detailed_status=new.status_message,
265 vca_status=new.status,
beierlm32862bb2020-04-21 16:36:35 -0400266 entity_type="application",
quilesj29114342019-10-29 09:30:44 +0100267 )
268
269 # set event for this application
270 self.applications[new.entity_id].event.set()
271
beierlm32862bb2020-04-21 16:36:35 -0400272 elif delta.entity == "unit":
quilesj29114342019-10-29 09:30:44 +0100273
274 # get the application for this unit
beierlm32862bb2020-04-21 16:36:35 -0400275 application_id = delta.data["application"]
quilesj29114342019-10-29 09:30:44 +0100276
277 # check registered application
278 if application_id not in self.applications:
279 return
280
281 # write change in database
David Garciaf9972972020-04-06 11:02:42 +0200282 if not new.dead:
283 await self.n2vc.write_app_status_to_db(
284 db_dict=self.applications[application_id].db_dict,
285 status=juju_status_2_osm_status(delta.entity, new.workload_status),
286 detailed_status=new.workload_status_message,
287 vca_status=new.workload_status,
beierlm32862bb2020-04-21 16:36:35 -0400288 entity_type="unit",
David Garciaf9972972020-04-06 11:02:42 +0200289 )
quilesj29114342019-10-29 09:30:44 +0100290
291 # set event for this application
292 self.applications[application_id].event.set()
293
beierlm32862bb2020-04-21 16:36:35 -0400294 elif delta.entity == "action":
quilesj29114342019-10-29 09:30:44 +0100295
296 # check registered action
297 if new.entity_id not in self.actions:
298 return
299
300 # write change in database
301 await self.n2vc.write_app_status_to_db(
302 db_dict=self.actions[new.entity_id].db_dict,
303 status=juju_status_2_osm_status(delta.entity, new.status),
304 detailed_status=new.status,
305 vca_status=new.status,
beierlm32862bb2020-04-21 16:36:35 -0400306 entity_type="action",
quilesj29114342019-10-29 09:30:44 +0100307 )
308
309 # set event for this application
310 self.actions[new.entity_id].event.set()
311
312
313async def _wait_for_event_or_timeout(event: asyncio.Event, timeout: float = None):
314 try:
315 await asyncio.wait_for(fut=event.wait(), timeout=timeout)
316 except asyncio.TimeoutError:
317 pass
318 return event.is_set()