Add compute API tests for 'get-me-a-network'
This adds the tests for the 2.37 microversion in nova that handles automatic allocation of network resources for a server via neutron's auto-allocated-topology extension when there are no networks already available to the tenant. This also adds the 2.26 microversion json response schema since that's what's coming back for GET and LIST operations on servers when testing the 2.37 microversion. Tests for nova blueprint get-me-a-network Change-Id: Ia8538e20ba52c85cfabbf9e44a959b2821995eab
This commit is contained in:
parent
22afc4b8ec
commit
3e4a46aa69
@ -221,3 +221,7 @@ Microversion tests implemented in Tempest
|
|||||||
* `2.25`_
|
* `2.25`_
|
||||||
|
|
||||||
.. _2.25: http://docs.openstack.org/developer/nova/api_microversion_history.html#maximum-in-mitaka
|
.. _2.25: http://docs.openstack.org/developer/nova/api_microversion_history.html#maximum-in-mitaka
|
||||||
|
|
||||||
|
* `2.37`_
|
||||||
|
|
||||||
|
.. _2.37: http://docs.openstack.org/developer/nova/api_microversion_history.html#id34
|
||||||
|
205
tempest/api/compute/admin/test_auto_allocate_network.py
Normal file
205
tempest/api/compute/admin/test_auto_allocate_network.py
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
# Copyright 2016 IBM Corp.
|
||||||
|
#
|
||||||
|
# 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 oslo_log import log
|
||||||
|
|
||||||
|
from tempest.api.compute import base
|
||||||
|
from tempest.common import compute
|
||||||
|
from tempest.common import waiters
|
||||||
|
from tempest import config
|
||||||
|
from tempest import exceptions
|
||||||
|
from tempest.lib.common.utils import test_utils
|
||||||
|
from tempest import test
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(mriedem): This is in the admin directory only because it requires
|
||||||
|
# force_tenant_isolation=True, but doesn't extend BaseV2ComputeAdminTest
|
||||||
|
# because it doesn't actually use any admin credentials in the tests.
|
||||||
|
class AutoAllocateNetworkTest(base.BaseV2ComputeTest):
|
||||||
|
"""Tests auto-allocating networks with the v2.37 microversion.
|
||||||
|
|
||||||
|
These tests rely on Neutron being enabled. Also, the tenant must not have
|
||||||
|
any network resources available to it so we can make sure that Nova
|
||||||
|
calls to Neutron to automatically allocate the network topology.
|
||||||
|
"""
|
||||||
|
|
||||||
|
force_tenant_isolation = True
|
||||||
|
|
||||||
|
min_microversion = '2.37'
|
||||||
|
max_microversion = 'latest'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def skip_checks(cls):
|
||||||
|
super(AutoAllocateNetworkTest, cls).skip_checks()
|
||||||
|
if not CONF.service_available.neutron:
|
||||||
|
raise cls.skipException('Neutron is required')
|
||||||
|
if not test.is_extension_enabled('auto-allocated-topology', 'network'):
|
||||||
|
raise cls.skipException(
|
||||||
|
'auto-allocated-topology extension is not available')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setup_credentials(cls):
|
||||||
|
# Do not create network resources for these tests.
|
||||||
|
cls.set_network_resources()
|
||||||
|
super(AutoAllocateNetworkTest, cls).setup_credentials()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setup_clients(cls):
|
||||||
|
super(AutoAllocateNetworkTest, cls).setup_clients()
|
||||||
|
cls.servers_client = cls.servers_client
|
||||||
|
cls.networks_client = cls.os.networks_client
|
||||||
|
cls.routers_client = cls.os.routers_client
|
||||||
|
cls.subnets_client = cls.os.subnets_client
|
||||||
|
cls.ports_client = cls.os.ports_client
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(AutoAllocateNetworkTest, cls).resource_setup()
|
||||||
|
# Sanity check that there are no networks available to the tenant.
|
||||||
|
# This is essentially what Nova does for getting available networks.
|
||||||
|
tenant_id = cls.networks_client.tenant_id
|
||||||
|
# (1) Retrieve non-public network list owned by the tenant.
|
||||||
|
search_opts = {'tenant_id': tenant_id, 'shared': False}
|
||||||
|
nets = cls.networks_client.list_networks(
|
||||||
|
**search_opts).get('networks', [])
|
||||||
|
if nets:
|
||||||
|
raise exceptions.TempestException(
|
||||||
|
'Found tenant networks: %s' % nets)
|
||||||
|
# (2) Retrieve shared network list.
|
||||||
|
search_opts = {'shared': True}
|
||||||
|
nets = cls.networks_client.list_networks(
|
||||||
|
**search_opts).get('networks', [])
|
||||||
|
if nets:
|
||||||
|
raise exceptions.TempestException(
|
||||||
|
'Found shared networks: %s' % nets)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_cleanup(cls):
|
||||||
|
"""Deletes any auto_allocated_network and it's associated resources."""
|
||||||
|
|
||||||
|
# Find the auto-allocated router for the tenant.
|
||||||
|
# This is a bit hacky since we don't have a great way to find the
|
||||||
|
# auto-allocated router given the private tenant network we have.
|
||||||
|
routers = cls.routers_client.list_routers().get('routers', [])
|
||||||
|
if len(routers) > 1:
|
||||||
|
# This indicates a race where nova is concurrently calling the
|
||||||
|
# neutron auto-allocated-topology API for multiple server builds
|
||||||
|
# at the same time (it's called from nova-compute when setting up
|
||||||
|
# networking for a server). Neutron will detect duplicates and
|
||||||
|
# automatically clean them up, but there is a window where the API
|
||||||
|
# can return multiple and we don't have a good way to filter those
|
||||||
|
# out right now, so we'll just handle them.
|
||||||
|
LOG.info('(%s) Found more than one router for tenant.',
|
||||||
|
test_utils.find_test_caller())
|
||||||
|
|
||||||
|
# Let's just blindly remove any networks, duplicate or otherwise, that
|
||||||
|
# the test might have created even though Neutron will cleanup
|
||||||
|
# duplicate resources automatically (so ignore 404s).
|
||||||
|
networks = cls.networks_client.list_networks().get('networks', [])
|
||||||
|
|
||||||
|
for router in routers:
|
||||||
|
# Disassociate the subnets from the router. Because of the race
|
||||||
|
# mentioned above the subnets might not be associated with the
|
||||||
|
# router so ignore any 404.
|
||||||
|
for network in networks:
|
||||||
|
for subnet_id in network['subnets']:
|
||||||
|
test_utils.call_and_ignore_notfound_exc(
|
||||||
|
cls.routers_client.remove_router_interface,
|
||||||
|
router['id'], subnet_id=subnet_id)
|
||||||
|
|
||||||
|
# Delete the router.
|
||||||
|
cls.routers_client.delete_router(router['id'])
|
||||||
|
|
||||||
|
for network in networks:
|
||||||
|
# Get and delete the ports for the given network.
|
||||||
|
ports = cls.ports_client.list_ports(
|
||||||
|
network_id=network['id']).get('ports', [])
|
||||||
|
for port in ports:
|
||||||
|
test_utils.call_and_ignore_notfound_exc(
|
||||||
|
cls.ports_client.delete_port, port['id'])
|
||||||
|
|
||||||
|
# Delete the subnets.
|
||||||
|
for subnet_id in network['subnets']:
|
||||||
|
test_utils.call_and_ignore_notfound_exc(
|
||||||
|
cls.subnets_client.delete_subnet, subnet_id)
|
||||||
|
|
||||||
|
# Delete the network.
|
||||||
|
test_utils.call_and_ignore_notfound_exc(
|
||||||
|
cls.networks_client.delete_network, network['id'])
|
||||||
|
|
||||||
|
@test.idempotent_id('5eb7b8fa-9c23-47a2-9d7d-02ed5809dd34')
|
||||||
|
def test_server_create_no_allocate(self):
|
||||||
|
"""Tests that no networking is allocated for the server."""
|
||||||
|
# create the server with no networking
|
||||||
|
server, _ = compute.create_test_server(
|
||||||
|
self.os, networks='none', wait_until='ACTIVE')
|
||||||
|
self.addCleanup(waiters.wait_for_server_termination,
|
||||||
|
self.servers_client, server['id'])
|
||||||
|
self.addCleanup(self.servers_client.delete_server, server['id'])
|
||||||
|
# get the server ips
|
||||||
|
addresses = self.servers_client.list_addresses(
|
||||||
|
server['id'])['addresses']
|
||||||
|
# assert that there is no networking
|
||||||
|
self.assertEqual({}, addresses)
|
||||||
|
|
||||||
|
@test.idempotent_id('2e6cf129-9e28-4e8a-aaaa-045ea826b2a6')
|
||||||
|
def test_server_multi_create_auto_allocate(self):
|
||||||
|
"""Tests that networking is auto-allocated for multiple servers."""
|
||||||
|
|
||||||
|
# Create multiple servers with auto networking to make sure the
|
||||||
|
# automatic network allocation is atomic. Using a minimum of three
|
||||||
|
# servers is essential for this scenario because:
|
||||||
|
#
|
||||||
|
# - First request sees no networks for the tenant so it auto-allocates
|
||||||
|
# one from Neutron, let's call that net1.
|
||||||
|
# - Second request sees no networks for the tenant so it auto-allocates
|
||||||
|
# one from Neutron. Neutron creates net2 but sees it's a duplicate
|
||||||
|
# so it queues net2 for deletion and returns net1 from the API and
|
||||||
|
# Nova uses that for the second server request.
|
||||||
|
# - Third request sees net1 and net2 for the tenant and fails with a
|
||||||
|
# NetworkAmbiguous 400 error.
|
||||||
|
_, servers = compute.create_test_server(
|
||||||
|
self.os, networks='auto', wait_until='ACTIVE',
|
||||||
|
min_count=3)
|
||||||
|
server_nets = set()
|
||||||
|
for server in servers:
|
||||||
|
self.addCleanup(waiters.wait_for_server_termination,
|
||||||
|
self.servers_client, server['id'])
|
||||||
|
self.addCleanup(self.servers_client.delete_server, server['id'])
|
||||||
|
# get the server ips
|
||||||
|
addresses = self.servers_client.list_addresses(
|
||||||
|
server['id'])['addresses']
|
||||||
|
# assert that there is networking (should only be one)
|
||||||
|
self.assertEqual(1, len(addresses))
|
||||||
|
server_nets.add(list(addresses.keys())[0])
|
||||||
|
# all servers should be on the same network
|
||||||
|
self.assertEqual(1, len(server_nets))
|
||||||
|
|
||||||
|
# List the networks for the tenant; we filter on admin_state_up=True
|
||||||
|
# because the auto-allocated-topology code in Neutron won't set that
|
||||||
|
# to True until the network is ready and is returned from the API.
|
||||||
|
# Duplicate networks created from a race should have
|
||||||
|
# admin_state_up=False.
|
||||||
|
search_opts = {'tenant_id': self.networks_client.tenant_id,
|
||||||
|
'shared': False,
|
||||||
|
'admin_state_up': True}
|
||||||
|
nets = self.networks_client.list_networks(
|
||||||
|
**search_opts).get('networks', [])
|
||||||
|
self.assertEqual(1, len(nets))
|
||||||
|
# verify the single private tenant network is the one that the servers
|
||||||
|
# are using also
|
||||||
|
self.assertIn(nets[0]['name'], server_nets)
|
47
tempest/lib/api_schema/response/compute/v2_26/servers.py
Normal file
47
tempest/lib/api_schema/response/compute/v2_26/servers.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Copyright 2016 IBM Corp.
|
||||||
|
#
|
||||||
|
# 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 copy
|
||||||
|
|
||||||
|
from tempest.lib.api_schema.response.compute.v2_1 import servers as servers21
|
||||||
|
from tempest.lib.api_schema.response.compute.v2_19 import servers as servers219
|
||||||
|
|
||||||
|
# The 2.26 microversion changes the server GET and (detailed) LIST responses to
|
||||||
|
# include the server 'tags' which is just a list of strings.
|
||||||
|
|
||||||
|
tag_items = {
|
||||||
|
'type': 'array',
|
||||||
|
'maxItems': 50,
|
||||||
|
'items': {
|
||||||
|
'type': 'string',
|
||||||
|
'pattern': '^[^,/]*$',
|
||||||
|
'maxLength': 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_server = copy.deepcopy(servers219.get_server)
|
||||||
|
get_server['response_body']['properties']['server'][
|
||||||
|
'properties'].update({'tags': tag_items})
|
||||||
|
get_server['response_body']['properties']['server'][
|
||||||
|
'required'].append('tags')
|
||||||
|
|
||||||
|
list_servers_detail = copy.deepcopy(servers219.list_servers_detail)
|
||||||
|
list_servers_detail['response_body']['properties']['servers']['items'][
|
||||||
|
'properties'].update({'tags': tag_items})
|
||||||
|
list_servers_detail['response_body']['properties']['servers']['items'][
|
||||||
|
'required'].append('tags')
|
||||||
|
|
||||||
|
# list response schema wasn't changed for v2.26 so use v2.1
|
||||||
|
|
||||||
|
list_servers = copy.deepcopy(servers21.list_servers)
|
@ -22,6 +22,7 @@ from six.moves.urllib import parse as urllib
|
|||||||
from tempest.lib.api_schema.response.compute.v2_1 import servers as schema
|
from tempest.lib.api_schema.response.compute.v2_1 import servers as schema
|
||||||
from tempest.lib.api_schema.response.compute.v2_16 import servers as schemav216
|
from tempest.lib.api_schema.response.compute.v2_16 import servers as schemav216
|
||||||
from tempest.lib.api_schema.response.compute.v2_19 import servers as schemav219
|
from tempest.lib.api_schema.response.compute.v2_19 import servers as schemav219
|
||||||
|
from tempest.lib.api_schema.response.compute.v2_26 import servers as schemav226
|
||||||
from tempest.lib.api_schema.response.compute.v2_3 import servers as schemav23
|
from tempest.lib.api_schema.response.compute.v2_3 import servers as schemav23
|
||||||
from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29
|
from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29
|
||||||
from tempest.lib.common import rest_client
|
from tempest.lib.common import rest_client
|
||||||
@ -34,7 +35,8 @@ class ServersClient(base_compute_client.BaseComputeClient):
|
|||||||
{'min': '2.3', 'max': '2.8', 'schema': schemav23},
|
{'min': '2.3', 'max': '2.8', 'schema': schemav23},
|
||||||
{'min': '2.9', 'max': '2.15', 'schema': schemav29},
|
{'min': '2.9', 'max': '2.15', 'schema': schemav29},
|
||||||
{'min': '2.16', 'max': '2.18', 'schema': schemav216},
|
{'min': '2.16', 'max': '2.18', 'schema': schemav216},
|
||||||
{'min': '2.19', 'max': None, 'schema': schemav219}]
|
{'min': '2.19', 'max': '2.25', 'schema': schemav219},
|
||||||
|
{'min': '2.26', 'max': None, 'schema': schemav226}]
|
||||||
|
|
||||||
def __init__(self, auth_provider, service, region,
|
def __init__(self, auth_provider, service, region,
|
||||||
enable_instance_password=True, **kwargs):
|
enable_instance_password=True, **kwargs):
|
||||||
|
Loading…
Reference in New Issue
Block a user