-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This new class creates a common infrastructure for tasks that require polling results periodically from the SDK. The methods in the 'waiters' class are generated from the data/waiters.yaml file. Each method defined in the YAML is in charge of polling the Engine for a status of a request. In this commit only 3 methods were configured in the YAML: waiters.vm_up(api, id) waiters.vm_down(api, id) waiters.host_up(api, id) Invoking each of these methods will check if the VM with the given id reached an 'accepting' state as defined in the YAML 'acceptors' section. It will run for 'max_attempts', with a 'delay' between and block until. The polling might end earlier, if the VM reached any of the 'rejectors' states as defined in the YAML, and then a 'WaiterError' exception will be thrown. Adding more 'waiters' requires only updating the YAML. Signed-off-by: Nadav Goldin <[email protected]>
- Loading branch information
Showing
2 changed files
with
145 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
common: &common | ||
max_attempts: 20 | ||
delay: 10 | ||
|
||
waiters: | ||
- waiter_name: 'host_up' | ||
<<: *common | ||
service: hosts_service | ||
target: | ||
name: 'host_service' | ||
path: 'get' | ||
acceptors: | ||
- name: 'HostStatus' | ||
type: status | ||
paths: ['UP'] | ||
rejectors: | ||
- name: 'HostStatus' | ||
type: status | ||
paths: ["NON_OPERATIONAL", "INSTALL_FAILED", "NON_RESPONSIVE"] | ||
|
||
- waiter_name: 'vm_up' | ||
<<: *common | ||
service: vms_service | ||
target: | ||
name: 'vm_service' | ||
path: 'get' | ||
acceptors: | ||
- name: 'VmStatus' | ||
type: status | ||
paths: ["UP"] | ||
rejectors: | ||
- name: 'VmStatus' | ||
type: status | ||
paths: ["NOT_RESPONDING", "SUSPENDED"] | ||
|
||
- waiter_name: 'vm_down' | ||
<<: *common | ||
service: vms_service | ||
target: | ||
name: 'vm_service' | ||
path: 'get' | ||
acceptors: | ||
- name: 'VmStatus' | ||
type: status | ||
paths: ["DOWN"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import time | ||
|
||
import pkg_resources | ||
import yaml | ||
|
||
from ovirtsdk4 import types as ovirt_types | ||
|
||
|
||
class WaiterError(BaseException): | ||
pass | ||
|
||
|
||
class Waiter(object): | ||
def __init__(self, config): | ||
self.config = config | ||
self.state = 'unknown' | ||
self.name = config['waiter_name'] | ||
self.rejectors = self._build_matchers(config.get('rejectors', [])) | ||
self.acceptors = self._build_matchers(config['acceptors']) | ||
self.target = self.config['target']['name'] | ||
self.target_path = self.config['target']['path'] | ||
self.operation = self._build_operation() | ||
|
||
# TO-DO: create a matcher class which holds more details about | ||
# the result. | ||
|
||
def _build_matchers(self, matchers): | ||
callables = [] | ||
for matcher in matchers: | ||
if matcher['type'] == 'status': | ||
callables.append(self._build_status_matcher(matcher)) | ||
return callables | ||
|
||
def _build_status_matcher(self, matcher): | ||
def exact_match(result): | ||
base = ( | ||
getattr(getattr(ovirt_types, matcher['name']), path) | ||
for path in matcher['paths'] | ||
) | ||
return result.status in base | ||
|
||
return exact_match | ||
|
||
def _build_operation(self): | ||
def func(api, *args, **kwargs): | ||
system_service = getattr(api, 'system_service')() | ||
service = getattr(system_service, self.config['service'])() | ||
target = getattr(service, self.target)(*args, **kwargs) | ||
return getattr(target, self.target_path)() | ||
|
||
return func | ||
|
||
def wait(self, api, *args, **kwargs): | ||
max_attempts = kwargs.pop('max_attempts', | ||
False) or self.config['max_attempts'] | ||
delay = kwargs.pop('delay', False) or self.config['delay'] | ||
attempts = 0 | ||
max_attempts = int(max_attempts) | ||
delay = int(delay) | ||
|
||
while True: | ||
res = self.operation(api, *args, **kwargs) | ||
attempts = attempts + 1 | ||
for acceptor in self.acceptors: | ||
if acceptor(res): | ||
self.state = 'success' | ||
break | ||
|
||
for rejector in self.rejectors: | ||
if rejector(res): | ||
self.state = 'failed' | ||
raise WaiterError('rejected state') | ||
|
||
if self.state == 'success': | ||
return | ||
|
||
if attempts >= max_attempts: | ||
raise WaiterError('Maximum number of attempts exceeded') | ||
time.sleep(delay) | ||
|
||
|
||
class _waiters_meta(type): | ||
def __init__(self, name, bases, d): | ||
type.__init__(self, name, bases, d) | ||
config = pkg_resources.resource_filename( | ||
__name__, '/'.join(['data', 'waiters.yaml']) | ||
) | ||
with open(config, 'r') as waiters_fd: | ||
waiters_cfg = yaml.load(waiters_fd)['waiters'] | ||
|
||
for waiter_cfg in waiters_cfg: | ||
waiter = Waiter(config=waiter_cfg) | ||
# TO-DO: attach docstring for the wait method | ||
# according to the waiter name | ||
setattr(self, waiter.name, waiter.wait) | ||
|
||
|
||
class waiters: | ||
__metaclass__ = _waiters_meta | ||
pass |