Merge "Add "Nova V3 API test for add-fixed-IP/remove-fixed-IP""
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 95a4884..ee2da40 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -359,6 +359,10 @@
 # iSCSI volumes (boolean value)
 #block_migrate_cinder_iscsi=false
 
+# Enable VNC console. This configuration value should be same
+# as [nova.vnc]->vnc_enabled in nova.conf (boolean value)
+#vnc_console=false
+
 
 [dashboard]
 
@@ -635,6 +639,9 @@
 # creating containers (string value)
 #operator_role=Member
 
+# User role that has reseller admin (string value)
+#reseller_admin_role=ResellerAdmin
+
 
 [object-storage-feature-enabled]
 
diff --git a/requirements.txt b/requirements.txt
index a08a437..0bddca3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-pbr>=0.5.21,<1.0
+pbr>=0.6,<1.0
 anyjson>=0.3.3
 httplib2>=0.7.5
 jsonschema>=2.0.0,<3.0.0
@@ -8,18 +8,18 @@
 paramiko>=1.9.0
 netaddr>=0.7.6
 python-glanceclient>=0.9.0
-python-keystoneclient>=0.4.2
+python-keystoneclient>=0.6.0
 python-novaclient>=2.15.0
-python-neutronclient>=2.3.3,<3
+python-neutronclient>=2.3.4,<3
 python-cinderclient>=1.0.6
 python-heatclient>=0.2.3
-python-savannaclient>=0.4.1
-python-swiftclient>=1.5
+python-savannaclient>=0.5.0
+python-swiftclient>=1.6
 testresources>=0.2.4
 keyring>=1.6.1,<2.0,>=2.1
-testrepository>=0.0.17
+testrepository>=0.0.18
 oslo.config>=1.2.0
-six>=1.4.1
+six>=1.5.2
 iso8601>=0.1.8
 fixtures>=0.3.14
 testscenarios>=0.4
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index e642715..406c45a 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -410,3 +410,16 @@
         resp, server = self.servers_client.start(self.server_id)
         self.assertEqual(202, resp.status)
         self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+    @testtools.skipUnless(CONF.compute_feature_enabled.vnc_console,
+                          'VNC Console feature is disabled')
+    @test.attr(type='gate')
+    def test_get_vnc_console(self):
+        # Get the VNC console
+        console_types = ['novnc', 'xvpvnc']
+        for console_type in console_types:
+            resp, body = self.servers_client.get_vnc_console(self.server_id,
+                                                             console_type)
+            self.assertEqual(200, resp.status)
+            self.assertEqual(console_type, body['type'])
+            self.assertNotEqual('', body['url'])
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 2a5401f..759c4a9 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -70,7 +70,7 @@
 
     @attr(type='gate')
     def test_delete_image(self):
-        # Deletes a image by image_id
+        # Deletes an image by image_id
 
         # Create image
         image_name = data_utils.rand_name('image')
@@ -90,6 +90,43 @@
         self.assertEqual(resp.status, 200)
         self.assertNotIn(image_id, images)
 
+    @attr(type='gate')
+    def test_update_image(self):
+        # Updates an image by image_id
+
+        # Create image
+        image_name = data_utils.rand_name('image')
+        resp, body = self.client.create_image(name=image_name,
+                                              container_format='bare',
+                                              disk_format='iso',
+                                              visibility='public')
+        self.assertEqual(201, resp.status)
+        self.assertEqual('queued', body['status'])
+        image_id = body['id']
+
+        # Now try uploading an image file
+        file_content = '*' * 1024
+        image_file = StringIO.StringIO(file_content)
+        resp, body = self.client.store_image(image_id, image_file)
+        self.assertEqual(204, resp.status)
+
+        # Update Image
+        new_image_name = data_utils.rand_name('new-image')
+        new_visibility = 'private'
+        resp, body = self.client.update_image(image_id, [
+            dict(replace='/name', value=new_image_name),
+            dict(replace='/visibility', value=new_visibility)])
+
+        self.assertEqual(200, resp.status)
+
+        # Verifying updating
+
+        resp, body = self.client.get_image(image_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(image_id, body['id'])
+        self.assertEqual(new_image_name, body['name'])
+        self.assertEqual(new_visibility, body['visibility'])
+
 
 class ListImagesTest(base.BaseV2ImageTest):
     """
diff --git a/tempest/api/image/v2/test_images_member.py b/tempest/api/image/v2/test_images_member.py
index 41fc49d..530262f 100644
--- a/tempest/api/image/v2/test_images_member.py
+++ b/tempest/api/image/v2/test_images_member.py
@@ -22,6 +22,7 @@
         image_id = self._create_image()
         resp, member = self.os_img_client.add_member(image_id,
                                                      self.alt_tenant_id)
+        self.assertEqual(200, resp.status)
         self.assertEqual(member['member_id'], self.alt_tenant_id)
         self.assertEqual(member['image_id'], image_id)
         self.assertEqual(member['status'], 'pending')
@@ -30,7 +31,8 @@
                                                  self.alt_tenant_id,
                                                  'accepted')
         self.assertIn(image_id, self._list_image_ids_as_alt())
-        _, body = self.os_img_client.get_image_membership(image_id)
+        resp, body = self.os_img_client.get_image_membership(image_id)
+        self.assertEqual(200, resp.status)
         members = body['members']
         member = members[0]
         self.assertEqual(len(members), 1, str(members))
@@ -43,11 +45,44 @@
         image_id = self._create_image()
         resp, member = self.os_img_client.add_member(image_id,
                                                      self.alt_tenant_id)
+        self.assertEqual(200, resp.status)
         self.assertEqual(member['member_id'], self.alt_tenant_id)
         self.assertEqual(member['image_id'], image_id)
         self.assertEqual(member['status'], 'pending')
         self.assertNotIn(image_id, self._list_image_ids_as_alt())
+        resp, _ = self.alt_img_client.update_member_status(image_id,
+                                                           self.alt_tenant_id,
+                                                           'rejected')
+        self.assertEqual(200, resp.status)
+        self.assertNotIn(image_id, self._list_image_ids_as_alt())
+
+    @attr(type='gate')
+    def test_get_image_member(self):
+        image_id = self._create_image()
+        self.os_img_client.add_member(image_id,
+                                      self.alt_tenant_id)
         self.alt_img_client.update_member_status(image_id,
                                                  self.alt_tenant_id,
-                                                 'rejected')
+                                                 'accepted')
+
+        self.assertIn(image_id, self._list_image_ids_as_alt())
+        resp, member = self.os_img_client.get_member(image_id,
+                                                     self.alt_tenant_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(self.alt_tenant_id, member['member_id'])
+        self.assertEqual(image_id, member['image_id'])
+        self.assertEqual('accepted', member['status'])
+
+    @attr(type='gate')
+    def test_remove_image_member(self):
+        image_id = self._create_image()
+        self.os_img_client.add_member(image_id,
+                                      self.alt_tenant_id)
+        self.alt_img_client.update_member_status(image_id,
+                                                 self.alt_tenant_id,
+                                                 'accepted')
+
+        self.assertIn(image_id, self._list_image_ids_as_alt())
+        resp = self.os_img_client.remove_member(image_id, self.alt_tenant_id)
+        self.assertEqual(204, resp.status)
         self.assertNotIn(image_id, self._list_image_ids_as_alt())
diff --git a/tempest/api/object_storage/test_account_quotas.py b/tempest/api/object_storage/test_account_quotas.py
index 788292d..b14adc0 100644
--- a/tempest/api/object_storage/test_account_quotas.py
+++ b/tempest/api/object_storage/test_account_quotas.py
@@ -17,9 +17,12 @@
 from tempest.api.object_storage import base
 from tempest import clients
 from tempest.common.utils import data_utils
+from tempest import config
 from tempest import exceptions
 from tempest import test
 
+CONF = config.CONF
+
 
 class AccountQuotasTest(base.BaseObjectTest):
 
@@ -41,7 +44,7 @@
         try:
             _, roles = cls.os_admin.identity_client.list_roles()
             reseller_role_id = next(r['id'] for r in roles if r['name']
-                                    == 'ResellerAdmin')
+                                    == CONF.object_storage.reseller_admin_role)
         except StopIteration:
             msg = "No ResellerAdmin role found"
             raise exceptions.NotFound(msg)
diff --git a/tempest/api/object_storage/test_account_quotas_negative.py b/tempest/api/object_storage/test_account_quotas_negative.py
index cab307d..402cd90 100644
--- a/tempest/api/object_storage/test_account_quotas_negative.py
+++ b/tempest/api/object_storage/test_account_quotas_negative.py
@@ -17,9 +17,12 @@
 from tempest.api.object_storage import base
 from tempest import clients
 from tempest.common.utils import data_utils
+from tempest import config
 from tempest import exceptions
 from tempest import test
 
+CONF = config.CONF
+
 
 class AccountQuotasNegativeTest(base.BaseObjectTest):
 
@@ -41,7 +44,7 @@
         try:
             _, roles = cls.os_admin.identity_client.list_roles()
             reseller_role_id = next(r['id'] for r in roles if r['name']
-                                    == 'ResellerAdmin')
+                                    == CONF.object_storage.reseller_admin_role)
         except StopIteration:
             msg = "No ResellerAdmin role found"
             raise exceptions.NotFound(msg)
diff --git a/tempest/api/orchestration/stacks/test_neutron_resources.py b/tempest/api/orchestration/stacks/test_neutron_resources.py
index 3e621f4..291f0d1 100644
--- a/tempest/api/orchestration/stacks/test_neutron_resources.py
+++ b/tempest/api/orchestration/stacks/test_neutron_resources.py
@@ -135,7 +135,7 @@
             # the cause of the server not signalling the waitcondition
             # to heat.
             resp, body = cls.client.get_resource(cls.stack_identifier,
-                                                 'SmokeServerNeutron')
+                                                 'Server')
             server_id = body['physical_resource_id']
             LOG.debug('Console output for %s', server_id)
             resp, output = cls.servers_client.get_console_output(
diff --git a/tempest/config.py b/tempest/config.py
index 05a493c..887bac3 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -263,7 +263,11 @@
     cfg.BoolOpt('block_migrate_cinder_iscsi',
                 default=False,
                 help="Does the test environment block migration support "
-                     "cinder iSCSI volumes")
+                     "cinder iSCSI volumes"),
+    cfg.BoolOpt('vnc_console',
+                default=False,
+                help='Enable VNC console. This configuration value should '
+                     'be same as [nova.vnc]->vnc_enabled in nova.conf')
 ]
 
 
@@ -472,6 +476,9 @@
                default='Member',
                help="Role to add to users created for swift tests to "
                     "enable creating containers"),
+    cfg.StrOpt('reseller_admin_role',
+               default='ResellerAdmin',
+               help="User role that has reseller admin"),
 ]
 
 object_storage_feature_group = cfg.OptGroup(
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index 840e914..819e366 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -423,3 +423,15 @@
     def restore_soft_deleted_server(self, server_id, **kwargs):
         """Restore a soft-deleted server."""
         return self.action(server_id, 'restore', None, **kwargs)
+
+    def get_vnc_console(self, server_id, type):
+        """Get URL of VNC console."""
+        post_body = json.dumps({
+            "get_vnc_console": {
+                "type": type
+            }
+        })
+        resp, body = self.post('servers/%s/action' % str(server_id),
+                               post_body)
+        body = json.loads(body)
+        return resp, body['console']
diff --git a/tempest/services/image/v2/json/image_client.py b/tempest/services/image/v2/json/image_client.py
index 58819d0..30c6ffc 100644
--- a/tempest/services/image/v2/json/image_client.py
+++ b/tempest/services/image/v2/json/image_client.py
@@ -68,6 +68,15 @@
                 self._http = self._get_http()
         return self._http
 
+    def update_image(self, image_id, patch):
+        data = json.dumps(patch)
+        self._validate_schema(data)
+
+        headers = {"Content-Type": "application/openstack-images-v2.0"
+                                   "-json-patch"}
+        resp, body = self.patch('v2/images/%s' % image_id, data, headers)
+        return resp, self._parse_resp(body)
+
     def create_image(self, name, container_format, disk_format, **kwargs):
         params = {
             "name": name,
@@ -163,3 +172,15 @@
         body = json.loads(body)
         self.expected_success(200, resp)
         return resp, body
+
+    def get_member(self, image_id, member_id):
+        url = 'v2/images/%s/members/%s' % (image_id, member_id)
+        resp, body = self.get(url)
+        self.expected_success(200, resp)
+        return resp, json.loads(body)
+
+    def remove_member(self, image_id, member_id):
+        url = 'v2/images/%s/members/%s' % (image_id, member_id)
+        resp, _ = self.delete(url)
+        self.expected_success(204, resp)
+        return resp