Skip to content

Commit

Permalink
ovirtlago: adding waiters.py
Browse files Browse the repository at this point in the history
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
nvgoldin committed Mar 20, 2017
1 parent 4cf0197 commit 5326d7c
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 0 deletions.
45 changes: 45 additions & 0 deletions ovirtlago/data/waiters.yaml
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"]
100 changes: 100 additions & 0 deletions ovirtlago/waiters.py
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

0 comments on commit 5326d7c

Please sign in to comment.