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:
Matt Riedemann 2016-07-27 14:41:32 -04:00
parent 22afc4b8ec
commit 3e4a46aa69
5 changed files with 259 additions and 1 deletions

View File

@ -221,3 +221,7 @@ Microversion tests implemented in Tempest
* `2.25`_
.. _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

View 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)

View 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)

View File

@ -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_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_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_9 import servers as schemav29
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.9', 'max': '2.15', 'schema': schemav29},
{'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,
enable_instance_password=True, **kwargs):