TestCase to check set/get/unset flavor extraspecs

Adds functions in flavors_client.py to set/get/unset extra specs for
a flavor. Script by the name test_flavors_extra_specs.py is added so
as test the actions of set/get/unset extra specs for a flavor.

Change-Id: I1b57e8434fdef28676bdc4068760fe83054fb4f8
Implements: blueprint nova-grizzly1-blueprints-implementation
diff --git a/tempest/services/compute/json/flavors_client.py b/tempest/services/compute/json/flavors_client.py
index bd339b2..955068f 100644
--- a/tempest/services/compute/json/flavors_client.py
+++ b/tempest/services/compute/json/flavors_client.py
@@ -87,3 +87,22 @@
             if flavor['id'] == id:
                 return False
         return True
+
+    def set_flavor_extra_spec(self, flavor_id, specs):
+        """Sets extra Specs to the mentioned flavor."""
+        post_body = json.dumps({'extra_specs': specs})
+        resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
+                               post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['extra_specs']
+
+    def get_flavor_extra_spec(self, flavor_id):
+        """Gets extra Specs details of the mentioned flavor."""
+        resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id)
+        body = json.loads(body)
+        return resp, body['extra_specs']
+
+    def unset_flavor_extra_spec(self, flavor_id, key):
+        """Unsets extra Specs from the mentioned flavor."""
+        return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
+                           key))
diff --git a/tempest/services/compute/xml/flavors_client.py b/tempest/services/compute/xml/flavors_client.py
index c011fe4..ece362b 100644
--- a/tempest/services/compute/xml/flavors_client.py
+++ b/tempest/services/compute/xml/flavors_client.py
@@ -125,3 +125,25 @@
             if flavor['id'] == id:
                 return False
         return True
+
+    def set_flavor_extra_spec(self, flavor_id, specs):
+        """Sets extra Specs to the mentioned flavor."""
+        extra_specs = Element("extra_specs")
+        for key in specs.keys():
+            extra_specs.add_attr(key, specs[key])
+        resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
+                               str(Document(extra_specs)), self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def get_flavor_extra_spec(self, flavor_id):
+        """Gets extra Specs of the mentioned flavor."""
+        resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id,
+                              self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def unset_flavor_extra_spec(self, flavor_id, key):
+        """Unsets an extra spec based on the mentioned flavor and key."""
+        return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
+                           key))
diff --git a/tempest/tests/compute/admin/test_flavors_extra_specs.py b/tempest/tests/compute/admin/test_flavors_extra_specs.py
new file mode 100644
index 0000000..2645153
--- /dev/null
+++ b/tempest/tests/compute/admin/test_flavors_extra_specs.py
@@ -0,0 +1,158 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import nose
+from nose.plugins.attrib import attr
+
+from tempest import exceptions
+from tempest.tests import compute
+from tempest.tests.compute import base
+import unittest2 as unittest
+
+
+class FlavorsExtraSpecsTestBase(object):
+
+    """
+    Tests Flavor Extra Spec API extension.
+    SET, UNSET Flavor Extra specs require admin privileges.
+    GET Flavor Extra specs can be performed even by without admin privileges.
+    """
+
+    @classmethod
+    def setUpClass(self, cls):
+        if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+            msg = "FlavorExtraData extension not enabled."
+            raise nose.SkipTest(msg)
+
+        cls.client = cls.os.flavors_client
+        flavor_name = 'test_flavor2'
+        ram = 512
+        vcpus = 1
+        disk = 10
+        ephemeral = 10
+        cls.new_flavor_id = 12345
+        swap = 1024
+        rxtx = 1
+        #Create a flavor so as to set/get/unset extra specs
+        resp, cls.flavor = cls.client.create_flavor(flavor_name,
+                                                    ram, vcpus,
+                                                    disk,
+                                                    cls.new_flavor_id,
+                                                    ephemeral=ephemeral,
+                                                    swap=swap, rxtx=rxtx)
+
+    @classmethod
+    def tearDownClass(self, cls):
+        resp, body = cls.client.delete_flavor(cls.flavor['id'])
+
+    def test_flavor_set_get_unset_keys(self):
+        #Test to SET, GET UNSET flavor extra spec as a user
+        #with admin privileges.
+        #Assigning extra specs values that are to be set
+        specs = {"key1": "value1", "key2": "value2"}
+        #SET extra specs to the flavor created in setUp
+        set_resp, set_body = \
+            self.client.set_flavor_extra_spec(self.flavor['id'], specs)
+        self.assertEqual(set_resp.status, 200)
+        self.assertEqual(set_body, specs)
+        #GET extra specs and verify
+        get_resp, get_body = \
+            self.client.get_flavor_extra_spec(self.flavor['id'])
+        self.assertEqual(get_resp.status, 200)
+        self.assertEqual(get_body, specs)
+        #UNSET extra specs that were set in this test
+        unset_resp, _ = \
+            self.client.unset_flavor_extra_spec(self.flavor['id'], "key1")
+        self.assertEqual(unset_resp.status, 200)
+
+    @unittest.skip('Until bug 1094142 is resolved.')
+    def test_flavor_non_admin_set_get_unset_keys(self):
+        #Test to SET, GET UNSET flavor extra spec as a user
+        #with out admin privileges.
+        self.nonadmin_client = self.flavors_client
+        #Assigning extra specs values that are to be set
+        specs = {"key1": "value1", "key2": "value2"}
+        msg = None
+
+        #Verify if able to SET flavor extraspec with non-admin user
+        try:
+            set_resp, set_body = \
+                self.nonadmin_client.set_flavor_extra_spec(
+                    self.flavor['id'], specs)
+        except exceptions.Unauthorized:
+            pass
+        else:
+            msg = "Flavor extra specs is being SET"
+            msg += " by unauthorized non-admin user.\n"
+        #SET flavor extra specs with admin user
+        #so as to check GET/UNSET flavor extra specs with non-admin
+        set_resp, set_body =\
+            self.client.set_flavor_extra_spec(self.flavor['id'], specs)
+        #Verify if able to GET flavor extraspec with non-admin user
+        try:
+            get_resp, get_body = \
+                self.nonadmin_client.get_flavor_extra_spec('')
+            self.assertEqual(get_resp.status, 200)
+        except Exception as e:
+            msg += "Got an exception when GET Flavor extra specs"
+            msg += " by non-admin user. Exception is: %s\n" % e
+        #Verify if able to UNSET flavor extraspec with non-admin user
+        try:
+            unset_resp, _ = \
+                self.nonadmin_client.unset_flavor_extra_spec(self.flavor['id'],
+                                                             "key1")
+        except exceptions.Unauthorized:
+            pass
+        else:
+            msg += "Flavor extra specs is being UNSET"
+            msg += " by unauthorized non-admin user.\n"
+        #Verification to check if actions failed.
+        #msg variable  would contain the message according to the failures.
+        if msg is not None:
+            self.fail(msg)
+
+
+class FlavorsExtraSpecsTestXML(base.BaseComputeAdminTestXML,
+                               base.BaseComputeTestXML,
+                               FlavorsExtraSpecsTestBase):
+
+    @classmethod
+    def setUpClass(cls):
+        super(FlavorsExtraSpecsTestXML, cls).setUpClass()
+        base.BaseComputeTestXML.setUpClass()
+        FlavorsExtraSpecsTestBase.setUpClass(cls)
+
+    @classmethod
+    def tearDownClass(cls):
+        FlavorsExtraSpecsTestBase.tearDownClass(cls)
+        super(FlavorsExtraSpecsTestXML, cls).tearDownClass()
+
+
+class FlavorsExtraSpecsTestJSON(base.BaseComputeAdminTestJSON,
+                                base.BaseComputeTestJSON,
+                                FlavorsExtraSpecsTestBase):
+
+    @classmethod
+    def setUpClass(cls):
+        super(FlavorsExtraSpecsTestJSON, cls).setUpClass()
+        base.BaseComputeTestJSON.setUpClass()
+        FlavorsExtraSpecsTestBase.setUpClass(cls)
+
+    @classmethod
+    def tearDownClass(cls):
+        FlavorsExtraSpecsTestBase.tearDownClass(cls)
+        super(FlavorsExtraSpecsTestJSON, cls).tearDownClass()