]> git.apps.os.sepia.ceph.com Git - teuthology.git/commitdiff
Add teuthology.task.Task
authorZack Cerza <zack@redhat.com>
Tue, 21 Apr 2015 16:44:54 +0000 (10:44 -0600)
committerZack Cerza <zack@redhat.com>
Mon, 27 Apr 2015 19:30:09 +0000 (13:30 -0600)
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 <zack@redhat.com>
teuthology/task/__init__.py

index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..be462c29787fe0c49cac9be6167719e43a972d35 100644 (file)
@@ -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()