Merge "Include timezone in timestamp fields"
diff --git a/neutron/tests/tempest/api/admin/test_quotas_negative.py b/neutron/tests/tempest/api/admin/test_quotas_negative.py
index 12ae0be..3ff49c6 100644
--- a/neutron/tests/tempest/api/admin/test_quotas_negative.py
+++ b/neutron/tests/tempest/api/admin/test_quotas_negative.py
@@ -71,6 +71,7 @@
 
         subnet_args = {'tenant_id': tenant_id,
                        'network_id': net['id'],
+                       'enable_dhcp': False,
                        'cidr': '10.0.0.0/24',
                        'ip_version': '4'}
         subnet = self.admin_client.create_subnet(**subnet_args)['subnet']
diff --git a/neutron/tests/tempest/api/admin/test_routers_flavors.py b/neutron/tests/tempest/api/admin/test_routers_flavors.py
new file mode 100644
index 0000000..300c956
--- /dev/null
+++ b/neutron/tests/tempest/api/admin/test_routers_flavors.py
@@ -0,0 +1,97 @@
+#
+#    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.
+
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+import testtools
+
+from neutron.tests.tempest.api import base_routers as base
+
+
+class RoutersFlavorTestCase(base.BaseRouterTest):
+
+    @classmethod
+    @test.requires_ext(extension="router", service="network")
+    @test.requires_ext(extension="flavors", service="network")
+    def skip_checks(cls):
+        super(RoutersFlavorTestCase, cls).skip_checks()
+
+    @classmethod
+    def resource_setup(cls):
+        super(RoutersFlavorTestCase, cls).resource_setup()
+        cls.service_profiles = []
+        cls.flavor_service_profiles = []
+        # make a flavor based on legacy router for regular tenant to use
+        driver = ('neutron.services.l3_router.service_providers.'
+                  'single_node.SingleNodeDriver')
+        sp = cls.admin_client.create_service_profile(driver=driver)
+        cls.service_profiles.append(sp['service_profile'])
+        cls.flavor = cls.create_flavor(
+                name='special_flavor',
+                description='econonomy class',
+                service_type='L3_ROUTER_NAT')
+        cls.admin_client.create_flavor_service_profile(
+            cls.flavor['id'], sp['service_profile']['id'])
+        cls.flavor_service_profiles.append((cls.flavor['id'],
+                                            sp['service_profile']['id']))
+        # make another with a different driver
+        driver = ('neutron.services.l3_router.service_providers.'
+                  'dvr.DvrDriver')
+        sp = cls.admin_client.create_service_profile(driver=driver)
+        cls.service_profiles.append(sp['service_profile'])
+        cls.prem_flavor = cls.create_flavor(
+                name='better_special_flavor',
+                description='econonomy comfort',
+                service_type='L3_ROUTER_NAT')
+        cls.admin_client.create_flavor_service_profile(
+            cls.prem_flavor['id'], sp['service_profile']['id'])
+        cls.flavor_service_profiles.append((cls.prem_flavor['id'],
+                                            sp['service_profile']['id']))
+
+    @classmethod
+    def resource_cleanup(cls):
+        for flavor_id, service_profile_id in cls.flavor_service_profiles:
+            cls.admin_client.delete_flavor_service_profile(flavor_id,
+                                                           service_profile_id)
+        for service_profile in cls.service_profiles:
+            cls.admin_client.delete_service_profile(
+                service_profile['id'])
+        super(RoutersFlavorTestCase, cls).resource_cleanup()
+
+    @test.idempotent_id('a4d01977-e968-4983-b4d9-824ea6c33f4b')
+    def test_create_router_with_flavor(self):
+        # ensure regular client can see flavor
+        flavors = self.client.list_flavors(id=self.flavor['id'])
+        flavor = flavors['flavors'][0]
+        self.assertEqual('special_flavor', flavor['name'])
+        flavors = self.client.list_flavors(id=self.prem_flavor['id'])
+        prem_flavor = flavors['flavors'][0]
+        self.assertEqual('better_special_flavor', prem_flavor['name'])
+
+        # ensure client can create router with both flavors
+        router = self.create_router('name', flavor_id=flavor['id'])
+        self.assertEqual(flavor['id'], router['flavor_id'])
+        router = self.create_router('name', flavor_id=prem_flavor['id'])
+        self.assertEqual(prem_flavor['id'], router['flavor_id'])
+
+    @test.idempotent_id('30e73858-a0fc-409c-a2e0-e9cd2826f6a2')
+    def test_delete_router_flavor_in_use(self):
+        self.create_router('name', flavor_id=self.flavor['id'])
+        with testtools.ExpectedException(lib_exc.Conflict):
+            self.admin_client.delete_flavor(self.flavor['id'])
+
+    @test.idempotent_id('83939cf7-5070-41bc-9a3e-cd9f22df2186')
+    def test_badrequest_on_requesting_flags_and_flavor(self):
+        with testtools.ExpectedException(lib_exc.BadRequest):
+            self.admin_client.create_router(
+                'name', flavor_id=self.flavor['id'], distributed=True)
diff --git a/neutron/tests/tempest/api/base.py b/neutron/tests/tempest/api/base.py
index 045cca2..ffb2dfb 100644
--- a/neutron/tests/tempest/api/base.py
+++ b/neutron/tests/tempest/api/base.py
@@ -728,3 +728,12 @@
             # marker
             expected_resources[:-1],
             self._extract_resources(body))
+
+    def _test_list_validation_filters(self):
+        validation_args = {
+            'unknown_filter': 'value',
+        }
+        body = self.list_method(**validation_args)
+        resources = self._extract_resources(body)
+        for resource in resources:
+            self.assertIn(resource['name'], self.resource_names)
diff --git a/neutron/tests/tempest/api/test_extensions.py b/neutron/tests/tempest/api/test_extensions.py
index 28029d8..b1b09e8 100644
--- a/neutron/tests/tempest/api/test_extensions.py
+++ b/neutron/tests/tempest/api/test_extensions.py
@@ -34,3 +34,7 @@
     @test.idempotent_id('19db409e-a23f-445d-8bc8-ca3d64c84706')
     def test_list_extensions_pagination(self):
         self._test_list_extensions_includes('pagination')
+
+    @test.idempotent_id('155b7bc2-e358-4dd8-bf3e-1774c084567f')
+    def test_list_extensions_project_id(self):
+        self._test_list_extensions_includes('project-id')
diff --git a/neutron/tests/tempest/api/test_floating_ips.py b/neutron/tests/tempest/api/test_floating_ips.py
index 8ccdd44..bafa54c 100644
--- a/neutron/tests/tempest/api/test_floating_ips.py
+++ b/neutron/tests/tempest/api/test_floating_ips.py
@@ -71,3 +71,33 @@
         self.assertEqual('d2', body['floatingip']['description'])
         body = self.client.show_floatingip(body['floatingip']['id'])
         self.assertEqual('d2', body['floatingip']['description'])
+        # disassociate
+        body = self.client.update_floatingip(body['floatingip']['id'],
+                                             port_id=None)
+        self.assertEqual('d2', body['floatingip']['description'])
+
+    @test.idempotent_id('fd7161e1-2167-4686-a6ff-0f3df08001bb')
+    @test.requires_ext(extension="standard-attr-description",
+                       service="network")
+    def test_floatingip_update_extra_attributes_port_id_not_changed(self):
+        port_id = self.ports[1]['id']
+        body = self.client.create_floatingip(
+            floating_network_id=self.ext_net_id,
+            port_id=port_id,
+            description='d1'
+        )['floatingip']
+        self.assertEqual('d1', body['description'])
+        body = self.client.show_floatingip(body['id'])['floatingip']
+        self.assertEqual(port_id, body['port_id'])
+        # Update description
+        body = self.client.update_floatingip(body['id'], description='d2')
+        self.assertEqual('d2', body['floatingip']['description'])
+        # Floating IP association is not changed.
+        self.assertEqual(port_id, body['floatingip']['port_id'])
+        body = self.client.show_floatingip(body['floatingip']['id'])
+        self.assertEqual('d2', body['floatingip']['description'])
+        self.assertEqual(port_id, body['floatingip']['port_id'])
+        # disassociate
+        body = self.client.update_floatingip(body['floatingip']['id'],
+                                             port_id=None)
+        self.assertEqual(None, body['floatingip']['port_id'])
diff --git a/neutron/tests/tempest/api/test_networks.py b/neutron/tests/tempest/api/test_networks.py
index 2c2991a..439d7a9 100644
--- a/neutron/tests/tempest/api/test_networks.py
+++ b/neutron/tests/tempest/api/test_networks.py
@@ -46,6 +46,10 @@
             fields.append('mtu')
         for key in fields:
             self.assertEqual(network[key], self.network[key])
+        project_id = self.client.tenant_id
+        self.assertEqual(project_id, network['tenant_id'])
+        if test.is_extension_enabled('project-id', 'network'):
+            self.assertEqual(project_id, network['project_id'])
 
     @test.idempotent_id('867819bb-c4b6-45f7-acf9-90edcf70aa5e')
     def test_show_network_fields(self):
@@ -59,6 +63,27 @@
         self.assertEqual(sorted(network.keys()), sorted(fields))
         for field_name in fields:
             self.assertEqual(network[field_name], self.network[field_name])
+        self.assertNotIn('tenant_id', network)
+        self.assertNotIn('project_id', network)
+
+    @test.idempotent_id('26f2b7a5-2cd1-4f3a-b11f-ad259b099b11')
+    @test.requires_ext(extension="project-id", service="network")
+    def test_show_network_fields_keystone_v3(self):
+
+        def _check_show_network_fields(fields, expect_project_id,
+                                       expect_tenant_id):
+            params = {}
+            if fields:
+                params['fields'] = fields
+            body = self.client.show_network(self.network['id'], **params)
+            network = body['network']
+            self.assertEqual(expect_project_id, 'project_id' in network)
+            self.assertEqual(expect_tenant_id, 'tenant_id' in network)
+
+        _check_show_network_fields(None, True, True)
+        _check_show_network_fields(['tenant_id'], False, True)
+        _check_show_network_fields(['project_id'], True, False)
+        _check_show_network_fields(['project_id', 'tenant_id'], True, True)
 
     @test.idempotent_id('c72c1c0c-2193-4aca-ccc4-b1442640bbbb')
     @test.requires_ext(extension="standard-attr-description",
@@ -75,6 +100,28 @@
         body = self.client.list_networks(id=net_id)['networks'][0]
         self.assertEqual('d2', body['description'])
 
+    @test.idempotent_id('0cc0552f-afaf-4231-b7a7-c2a1774616da')
+    @test.requires_ext(extension="project-id", service="network")
+    def test_create_network_keystone_v3(self):
+        project_id = self.client.tenant_id
+
+        name = 'created-with-project_id'
+        body = self.client.create_network_keystone_v3(name, project_id)
+        new_net = body['network']
+        self.assertEqual(name, new_net['name'])
+        self.assertEqual(project_id, new_net['project_id'])
+        self.assertEqual(project_id, new_net['tenant_id'])
+
+        body = self.client.list_networks(id=new_net['id'])['networks'][0]
+        self.assertEqual(name, body['name'])
+
+        new_name = 'create-with-project_id-2'
+        body = self.client.update_network(new_net['id'], name=new_name)
+        new_net = body['network']
+        self.assertEqual(new_name, new_net['name'])
+        self.assertEqual(project_id, new_net['project_id'])
+        self.assertEqual(project_id, new_net['tenant_id'])
+
     @test.idempotent_id('6ae6d24f-9194-4869-9c85-c313cb20e080')
     def test_list_networks_fields(self):
         # Verify specific fields of the networks
@@ -87,6 +134,26 @@
         for network in networks:
             self.assertEqual(sorted(network.keys()), sorted(fields))
 
+    @test.idempotent_id('a23186b9-aa6f-4b08-b877-35ca3b9cd54c')
+    @test.requires_ext(extension="project-id", service="network")
+    def test_list_networks_fields_keystone_v3(self):
+        def _check_list_networks_fields(fields, expect_project_id,
+                                        expect_tenant_id):
+            params = {}
+            if fields:
+                params['fields'] = fields
+            body = self.client.list_networks(**params)
+            networks = body['networks']
+            self.assertNotEmpty(networks, "Network list returned is empty")
+            for network in networks:
+                self.assertEqual(expect_project_id, 'project_id' in network)
+                self.assertEqual(expect_tenant_id, 'tenant_id' in network)
+
+        _check_list_networks_fields(None, True, True)
+        _check_list_networks_fields(['tenant_id'], False, True)
+        _check_list_networks_fields(['project_id'], True, False)
+        _check_list_networks_fields(['project_id', 'tenant_id'], True, True)
+
 
 class NetworksSearchCriteriaTest(base.BaseSearchCriteriaTest):
 
@@ -135,3 +202,7 @@
     @test.idempotent_id('f1867fc5-e1d6-431f-bc9f-8b882e43a7f9')
     def test_list_no_pagination_limit_0(self):
         self._test_list_no_pagination_limit_0()
+
+    @test.idempotent_id('3574ec9b-a8b8-43e3-9c11-98f5875df6a9')
+    def test_list_validation_filters(self):
+        self._test_list_validation_filters()
diff --git a/neutron/tests/tempest/api/test_ports.py b/neutron/tests/tempest/api/test_ports.py
index 916f549..093de07 100644
--- a/neutron/tests/tempest/api/test_ports.py
+++ b/neutron/tests/tempest/api/test_ports.py
@@ -47,6 +47,18 @@
         self.client.update_subnet(s['id'], enable_dhcp=True)
         self.create_port(self.network)
 
+    @test.idempotent_id('1d6d8683-8691-43c6-a7ba-c69723258726')
+    def test_add_ips_to_port(self):
+        s = self.create_subnet(self.network)
+        port = self.create_port(self.network)
+        # request another IP on the same subnet
+        port['fixed_ips'].append({'subnet_id': s['id']})
+        updated = self.client.update_port(port['id'],
+                                          fixed_ips=port['fixed_ips'])
+        subnets = [ip['subnet_id'] for ip in updated['port']['fixed_ips']]
+        expected = [s['id'], s['id']]
+        self.assertEqual(expected, subnets)
+
 
 class PortsSearchCriteriaTest(base.BaseSearchCriteriaTest):
 
diff --git a/neutron/tests/tempest/api/test_qos.py b/neutron/tests/tempest/api/test_qos.py
index a2a9941..b9572ca 100644
--- a/neutron/tests/tempest/api/test_qos.py
+++ b/neutron/tests/tempest/api/test_qos.py
@@ -554,6 +554,7 @@
         self.client2.show_qos_policy(qos_pol['id'])
         rbac_pol = {'target_tenant': '*',
                     'tenant_id': self.admin_client.tenant_id,
+                    'project_id': self.admin_client.tenant_id,
                     'object_type': 'qos_policy',
                     'object_id': qos_pol['id'],
                     'action': 'access_as_shared'}
diff --git a/neutron/tests/tempest/api/test_revisions.py b/neutron/tests/tempest/api/test_revisions.py
index 0ecb7b8..10438b7 100644
--- a/neutron/tests/tempest/api/test_revisions.py
+++ b/neutron/tests/tempest/api/test_revisions.py
@@ -153,3 +153,5 @@
         b2 = self.client.update_floatingip(body['id'], description='d2')
         self.assertGreater(b2['floatingip']['revision_number'],
                            body['revision_number'])
+        # disassociate
+        self.client.update_floatingip(b2['floatingip']['id'], port_id=None)
diff --git a/neutron/tests/tempest/api/test_subnetpools.py b/neutron/tests/tempest/api/test_subnetpools.py
index 5bd222f..e8ec954 100644
--- a/neutron/tests/tempest/api/test_subnetpools.py
+++ b/neutron/tests/tempest/api/test_subnetpools.py
@@ -110,13 +110,15 @@
         body = self._create_subnetpool(description='d1')
         self.assertEqual('d1', body['description'])
         sub_id = body['id']
-        body = filter(lambda x: x['id'] == sub_id,
-                      self.client.list_subnetpools()['subnetpools'])[0]
+        subnet_pools = [x for x in
+            self.client.list_subnetpools()['subnetpools'] if x['id'] == sub_id]
+        body = subnet_pools[0]
         self.assertEqual('d1', body['description'])
         body = self.client.update_subnetpool(sub_id, description='d2')
         self.assertEqual('d2', body['subnetpool']['description'])
-        body = filter(lambda x: x['id'] == sub_id,
-                      self.client.list_subnetpools()['subnetpools'])[0]
+        subnet_pools = [x for x in
+            self.client.list_subnetpools()['subnetpools'] if x['id'] == sub_id]
+        body = subnet_pools[0]
         self.assertEqual('d2', body['description'])
 
     @test.idempotent_id('741d08c2-1e3f-42be-99c7-0ea93c5b728c')
@@ -390,3 +392,7 @@
     @test.idempotent_id('82a13efc-c18f-4249-b8ec-cec7cf26fbd6')
     def test_list_no_pagination_limit_0(self):
         self._test_list_no_pagination_limit_0()
+
+    @test.idempotent_id('27feb3f8-40f4-4e50-8cd2-7d0096a98682')
+    def test_list_validation_filters(self):
+        self._test_list_validation_filters()
diff --git a/neutron/tests/tempest/api/test_subnets.py b/neutron/tests/tempest/api/test_subnets.py
index a3a2e00..4e63a3a 100644
--- a/neutron/tests/tempest/api/test_subnets.py
+++ b/neutron/tests/tempest/api/test_subnets.py
@@ -63,3 +63,7 @@
     @test.idempotent_id('d851937c-9821-4b46-9d18-43e9077ecac0')
     def test_list_no_pagination_limit_0(self):
         self._test_list_no_pagination_limit_0()
+
+    @test.idempotent_id('c0f9280b-9d81-4728-a967-6be22659d4c8')
+    def test_list_validation_filters(self):
+        self._test_list_validation_filters()
diff --git a/neutron/tests/tempest/api/test_trunk.py b/neutron/tests/tempest/api/test_trunk.py
index bf15d9a..d5aa7db 100644
--- a/neutron/tests/tempest/api/test_trunk.py
+++ b/neutron/tests/tempest/api/test_trunk.py
@@ -58,33 +58,18 @@
         trunks_cleanup(cls.client, cls.trunks)
         super(TrunkTestJSONBase, cls).resource_cleanup()
 
-    def _remove_timestamps(self, trunk):
-        # Let's make sure these fields exist, but let's not
-        # use them in the comparison, in case skews or
-        # roundups get in the way.
-        created_at = trunk.pop('created_at')
-        updated_at = trunk.pop('updated_at')
-        self.assertIsNotNone(created_at)
-        self.assertIsNotNone(updated_at)
-
     def _create_trunk_with_network_and_parent(self, subports, **kwargs):
         network = self.create_network()
         parent_port = self.create_port(network)
         trunk = self.client.create_trunk(parent_port['id'], subports, **kwargs)
         self.trunks.append(trunk['trunk'])
-        self._remove_timestamps(trunk['trunk'])
         return trunk
 
     def _show_trunk(self, trunk_id):
-        trunk = self.client.show_trunk(trunk_id)
-        self._remove_timestamps(trunk['trunk'])
-        return trunk
+        return self.client.show_trunk(trunk_id)
 
     def _list_trunks(self):
-        trunks = self.client.list_trunks()
-        for t in trunks['trunks']:
-            self._remove_timestamps(t)
-        return trunks
+        return self.client.list_trunks()
 
 
 class TrunkTestJSON(TrunkTestJSONBase):
diff --git a/neutron/tests/tempest/services/network/json/network_client.py b/neutron/tests/tempest/services/network/json/network_client.py
index d788687..4828059 100644
--- a/neutron/tests/tempest/services/network/json/network_client.py
+++ b/neutron/tests/tempest/services/network/json/network_client.py
@@ -795,6 +795,29 @@
         self.expected_success(204, resp.status)
         return service_client.ResponseBody(resp, body)
 
+    def create_flavor_service_profile(self, flavor_id, service_profile_id):
+        body = jsonutils.dumps({'service_profile': {'id': service_profile_id}})
+        uri = '%s/flavors/%s/service_profiles' % (self.uri_prefix, flavor_id)
+        resp, body = self.post(uri, body)
+        self.expected_success(201, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def list_flavor_service_profiles(self, flavor_id):
+        uri = '%s/flavors/%s/service_profiles' % (self.uri_prefix, flavor_id)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def delete_flavor_service_profile(self, flavor_id, service_profile_id):
+        uri = '%s/flavors/%s/service_profiles/%s' % (self.uri_prefix,
+                                                     flavor_id,
+                                                     service_profile_id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return service_client.ResponseBody(resp, body)
+
     def create_security_group_rule(self, direction, security_group_id,
                                    **kwargs):
         post_body = {'security_group_rule': kwargs}
@@ -849,6 +872,19 @@
         body = jsonutils.loads(body)
         return service_client.ResponseBody(resp, body)
 
+    def create_network_keystone_v3(self, name, project_id):
+        uri = '%s/networks' % self.uri_prefix
+        post_data = {
+            'network': {
+                'name': name,
+                'project_id': project_id
+            }
+        }
+        resp, body = self.post(uri, self.serialize(post_data))
+        body = self.deserialize_single(body)
+        self.expected_success(201, resp.status)
+        return service_client.ResponseBody(resp, body)
+
     def list_extensions(self, **filters):
         uri = self.get_uri("extensions")
         if filters: