Allow decorators.attr to be conditional

There are cases where we want to conditionally apply an
attribute to a test function, for example, if SSH validation
is enabled then a test may run much slower than if it is not.

This adds a 'condition' kwarg to the attr() decorator which
behaves similarly to the 'condition' kwarg on the skip_because()
decorator.

Change-Id: I83233854a217b6961e7614d7d9df1b4fc8d5a640
diff --git a/releasenotes/notes/conditional-attr-a8564ec5a70ec840.yaml b/releasenotes/notes/conditional-attr-a8564ec5a70ec840.yaml
new file mode 100644
index 0000000..c707f14
--- /dev/null
+++ b/releasenotes/notes/conditional-attr-a8564ec5a70ec840.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    The ``tempest.lib.decorators.attr`` decorator now supports a ``condition``
+    kwarg which can be used to conditionally apply the attr to the test
+    function if the condition evaluates to True.
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index b399aa0..62a5d67 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -136,9 +136,16 @@
 
     This decorator applies the testtools.testcase.attr if it is in the list of
     attributes to testtools we want to apply.
+
+    :param condition: Optional condition which if true will apply the attr. If
+        a condition is specified which is false the attr will not be applied to
+        the test function. If not specified, the attr is always applied.
     """
 
     def decorator(f):
+        # Check to see if the attr should be conditional applied.
+        if 'condition' in kwargs and not kwargs.get('condition'):
+            return f
         if 'type' in kwargs and isinstance(kwargs['type'], str):
             f = testtools.testcase.attr(kwargs['type'])(f)
         elif 'type' in kwargs and isinstance(kwargs['type'], list):
diff --git a/tempest/tests/lib/test_decorators.py b/tempest/tests/lib/test_decorators.py
index 0b1a599..3e6160e 100644
--- a/tempest/tests/lib/test_decorators.py
+++ b/tempest/tests/lib/test_decorators.py
@@ -32,9 +32,17 @@
         # By our decorators.attr decorator the attribute __testtools_attrs
         # will be set only for 'type' argument, so we test it first.
         if 'type' in decorator_args:
-            # this is what testtools sets
-            self.assertEqual(getattr(foo, '__testtools_attrs'),
-                             set(expected_attrs))
+            if 'condition' in decorator_args:
+                if decorator_args['condition']:
+                    # The expected attrs should be in the function.
+                    self.assertEqual(set(expected_attrs),
+                                     getattr(foo, '__testtools_attrs'))
+                else:
+                    # The expected attrs should not be in the function.
+                    self.assertNotIn('__testtools_attrs', foo)
+            else:
+                self.assertEqual(set(expected_attrs),
+                                 getattr(foo, '__testtools_attrs'))
 
     def test_attr_without_type(self):
         self._test_attr_helper(expected_attrs='baz', bar='baz')
@@ -50,6 +58,13 @@
     def test_attr_decorator_with_duplicated_type(self):
         self._test_attr_helper(expected_attrs=['foo'], type=['foo', 'foo'])
 
+    def test_attr_decorator_condition_false(self):
+        self._test_attr_helper(None, type='slow', condition=False)
+
+    def test_attr_decorator_condition_true(self):
+        self._test_attr_helper(expected_attrs=['slow'], type='slow',
+                               condition=True)
+
 
 class TestSkipBecauseDecorator(base.TestCase):
     def _test_skip_because_helper(self, expected_to_skip=True,