From: Zack Cerza Date: Tue, 21 Apr 2015 16:44:54 +0000 (-0600) Subject: Add teuthology.task.Task X-Git-Tag: 1.1.0~959^2~1 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=28efbde725e326028fa24077652b16127c27e0f1;p=teuthology.git Add teuthology.task.Task This is intended to be a base class providing a more modern, featured, tested foundation for tasks. It has built-in overrides, host selection, and optional skipping of the teardown stage. Signed-off-by: Zack Cerza --- diff --git a/teuthology/task/__init__.py b/teuthology/task/__init__.py index e69de29bb2..be462c2978 100644 --- a/teuthology/task/__init__.py +++ b/teuthology/task/__init__.py @@ -0,0 +1,118 @@ +import logging + +from teuthology.misc import deep_merge +from teuthology.orchestra.cluster import Cluster + +log = logging.getLogger(__name__) + + +class Task(object): + """ + A base-class for "new-style" teuthology tasks. + + Can be used as a drop-in replacement for the old-style task functions with + @contextmanager decorators. + """ + def __init__(self, ctx=None, config=None): + self.log = log + self.name = self.__class__.__name__.lower() + self.ctx = ctx + self.config = config or dict() + if not isinstance(self.config, dict): + raise TypeError("config must be a dict") + self.apply_overrides() + self.filter_hosts() + + def apply_overrides(self): + """ + Look for an 'overrides' dict in self.ctx.config; look inside that for a + dict with the same name as this task. Override any settings in + self.config with those overrides + """ + all_overrides = self.ctx.config.get('overrides', dict()) + if not all_overrides: + return + task_overrides = all_overrides.get(self.name) + if task_overrides: + self.log.debug( + "Applying overrides for task {name}: {overrides}".format( + name=self.name, overrides=task_overrides) + ) + deep_merge(self.config, task_overrides) + + def filter_hosts(self): + """ + Look for a 'hosts' list in self.config. Each item in the list may + either be a role or a hostname. The task will then be run against only + those hosts which match one (or more) of the roles or hostnames + specified. + """ + if not hasattr(self.ctx, 'cluster'): + return + host_specs = self.config.get('hosts', list()) + cluster = Cluster() + for host_spec in host_specs: + role_matches = self.ctx.cluster.only(host_spec) + if len(role_matches.remotes) > 0: + for (remote, roles) in role_matches.remotes.iteritems(): + cluster.add(remote, roles) + elif isinstance(host_spec, basestring): + for (remote, roles) in self.ctx.cluster.remotes.iteritems(): + if remote.name.split('@')[-1] == host_spec or \ + remote.shortname == host_spec: + cluster.add(remote, roles) + if not cluster.remotes: + raise RuntimeError("All target hosts were excluded!") + self.cluster = cluster + hostnames = [h.shortname for h in self.cluster.remotes.keys()] + self.log.debug("Restricting task {name} to hosts: {hosts}".format( + name=self.name, hosts=' '.join(hostnames)) + ) + return self.cluster + + def setup(self): + """ + Perform any setup that is needed by the task before it executes + """ + pass + + def begin(self): + """ + Execute the main functionality of the task + """ + pass + + def end(self): + """ + Perform any work needed to stop processes started in begin() + """ + pass + + def teardown(self): + """ + Perform any work needed to restore configuration to a previous state. + + Can be skipped by setting 'skip_teardown' to True in self.config + """ + pass + + def __enter__(self): + """ + When using an instance of the class as a context manager, this method + calls self.setup(), then calls self.begin() and returns self. + """ + self.setup() + self.begin() + return self + + def __exit__(self, type_, value, traceback): + """ + When using an instance of the class as a context manager, this method + calls self.end() and self.teardown() - unless + self.config['skip_teardown'] is True + """ + self.end() + if self.config.get('skip_teardown', False): + self.log.info("Skipping teardown") + else: + self.teardown()