Adding basic connectivity scenario to Neutron

A Basic scenario:
- Creates an internal network and a subnet
- Creating a key pair
- Creating a router, setting the gateway and adding an
  internal interface
- Lauching an instance with a Nic connected to the internal network
- Adding rules to the tenant's default security group to allow SSH
  and ICMP
- Creating and associating a Floaing IP to the instance
- Checking SSH connectivity to the instance Floating IP address

Change-Id: Ica6fef4763b6f98c7795629b99ab392e6f7b6e59
Co-Authored-By: John Schwarz <jschwarz@redhat.com>
This commit is contained in:
Itzik Brown 2016-05-15 05:34:41 +00:00 committed by Assaf Muller
parent 1ce8ce9546
commit 707363fea0
7 changed files with 278 additions and 15 deletions

View File

@ -58,7 +58,8 @@ that broad categorization, here are a few more characteristic:
such as an agent using no mocks.
* Integration tests - Run against a running cloud, often target the API level,
but also 'scenarios' or 'user stories'. You may find such tests under
tests/api, tests/fullstack and in the Tempest and Rally projects.
tests/tempest/api, tests/tempest/scenario, tests/fullstack, and in the
Tempest and Rally projects.
Tests in the Neutron tree are typically organized by the testing infrastructure
used, and not by the scope of the test. For example, many tests under the
@ -324,6 +325,17 @@ Tests for other resources should be contributed to the Neutron repository.
Scenario tests should be similarly split up between Tempest and Neutron
according to the API they're targeting.
Scenario Tests
~~~~~~~~~~~~~~
Scenario tests (neutron/tests/tempest/scenario), like API tests, use the
Tempest test infrastructure and have the same requirements. Guidelines for
writing a good scenario test may be found at the Tempest developer guide:
http://docs.openstack.org/developer/tempest/field_guide/scenario.html
Scenario tests, like API tests, are split between the Tempest and Neutron
repositories according to the Neutron API the test is targeting.
Development Process
-------------------
@ -510,10 +522,10 @@ Logging from the test infrastructure itself is placed in:
Fullstack test suite assumes 240.0.0.0/4 (Class E) range in root namespace of
the test machine is available for its usage.
API Tests
+++++++++
API & Scenario Tests
++++++++++++++++++++
To run the api tests, deploy Tempest and Neutron with DevStack and
To run the api or scenario tests, deploy Tempest and Neutron with DevStack and
then run the following command, from the tempest directory: ::
tox -e all-plugin

View File

@ -310,7 +310,7 @@ class SecurityGroupAgentRpc(object):
self.refresh_firewall(updated_devices)
# TODO(armax): for bw compat with external dependencies; to be dropped in M.
# TODO(armax): For bw compat with external dependencies; to be dropped in M.
SG_RPC_VERSION = (
securitygroups_rpc.SecurityGroupAgentRpcApiMixin.SG_RPC_VERSION
)

View File

@ -13,24 +13,21 @@
# License for the specific language governing permissions and limitations
# under the License.
from tempest.lib.services.compute import keypairs_client
from tempest.lib.services.compute import servers_client
from tempest import manager
from tempest.services.identity.v2.json.tenants_client import \
TenantsClient
from tempest.services.identity.v2.json import tenants_client
from neutron.tests.tempest import config
from neutron.tests.tempest.services.network.json.network_client import \
NetworkClientJSON
from neutron.tests.tempest.services.network.json import network_client
CONF = config.CONF
class Manager(manager.Manager):
"""
Top level manager for OpenStack tempest clients
"""
default_params = {
'disable_ssl_certificate_validation':
CONF.identity.disable_ssl_certificate_validation,
@ -51,7 +48,7 @@ class Manager(manager.Manager):
self._set_identity_clients()
self.network_client = NetworkClientJSON(
self.network_client = network_client.NetworkClientJSON(
self.auth_provider,
CONF.network.catalog_type,
CONF.network.region or CONF.identity.region,
@ -60,6 +57,23 @@ class Manager(manager.Manager):
build_timeout=CONF.network.build_timeout,
**self.default_params)
params = {
'service': CONF.compute.catalog_type,
'region': CONF.compute.region or CONF.identity.region,
'endpoint_type': CONF.compute.endpoint_type,
'build_interval': CONF.compute.build_interval,
'build_timeout': CONF.compute.build_timeout
}
params.update(self.default_params)
self.servers_client = servers_client.ServersClient(
self.auth_provider,
enable_instance_password=CONF.compute_feature_enabled
.enable_instance_password,
**params)
self.keypairs_client = keypairs_client.KeyPairsClient(
self.auth_provider, **params)
def _set_identity_clients(self):
params = {
'service': CONF.identity.catalog_type,
@ -69,5 +83,5 @@ class Manager(manager.Manager):
params_v2_admin = params.copy()
params_v2_admin['endpoint_type'] = CONF.identity.v2_admin_endpoint_type
# Client uses admin endpoint type of Keystone API v2
self.tenants_client = TenantsClient(self.auth_provider,
**params_v2_admin)
self.tenants_client = tenants_client.TenantsClient(self.auth_provider,
**params_v2_admin)

View File

@ -0,0 +1,113 @@
# Copyright 2016 Red Hat, Inc.
# 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.
from oslo_log import log as logging
from tempest.common import waiters
from tempest.lib.common import ssh
from tempest.lib.common.utils import data_utils
from neutron.tests.tempest.api import base as base_api
from neutron.tests.tempest import config
from neutron.tests.tempest.scenario import constants
CONF = config.CONF
LOG = logging.getLogger(__name__)
class BaseTempestTestCase(base_api.BaseNetworkTest):
@classmethod
def resource_setup(cls):
super(BaseTempestTestCase, cls).resource_setup()
cls.servers = []
cls.keypairs = []
@classmethod
def resource_cleanup(cls):
for server in cls.servers:
cls.manager.servers_client.delete_server(server)
waiters.wait_for_server_termination(cls.manager.servers_client,
server)
for keypair in cls.keypairs:
cls.manager.keypairs_client.delete_keypair(
keypair_name=keypair['name'])
super(BaseTempestTestCase, cls).resource_cleanup()
@classmethod
def create_server(cls, flavor_ref, image_ref, key_name, networks,
name=None):
name = name or data_utils.rand_name('server-test')
server = cls.manager.servers_client.create_server(
name=name, flavorRef=flavor_ref,
imageRef=image_ref,
key_name=key_name,
networks=networks)
cls.servers.append(server['server']['id'])
return server
@classmethod
def create_keypair(cls, client=None):
client = client or cls.manager.keypairs_client
name = data_utils.rand_name('keypair-test')
body = client.create_keypair(name=name)
cls.keypairs.append(body['keypair'])
return body['keypair']
@classmethod
def create_loginable_secgroup_rule(cls, secgroup_id=None):
client = cls.manager.network_client
if not secgroup_id:
sgs = client.list_security_groups()['security_groups']
for sg in sgs:
if sg['name'] == constants.DEFAULT_SECURITY_GROUP:
secgroup_id = sg['id']
break
# This rule is intended to permit inbound ssh
# traffic from all sources, so no group_id is provided.
# Setting a group_id would only permit traffic from ports
# belonging to the same security group.
ruleset = {'protocol': 'tcp',
'port_range_min': 22,
'port_range_max': 22,
'remote_ip_prefix': '0.0.0.0/0'}
rules = [client.create_security_group_rule(
direction='ingress', security_group_id=secgroup_id,
**ruleset)['security_group_rule']]
return rules
@classmethod
def create_router_and_interface(cls, subnet_id):
router = cls.create_router(
data_utils.rand_name('router'), admin_state_up=True,
external_network_id=CONF.network.public_network_id)
cls.create_router_interface(router['id'], subnet_id)
cls.routers.append(router)
return router
@classmethod
def create_and_associate_floatingip(cls, port_id):
fip = cls.manager.network_client.create_floatingip(
CONF.network.public_network_id,
port_id=port_id)['floatingip']
cls.floating_ips.append(fip)
return fip
@classmethod
def check_connectivity(cls, host, ssh_user, ssh_key=None):
ssh_client = ssh.Client(host, ssh_user, pkey=ssh_key)
ssh_client.test_connection_auth()

View File

@ -0,0 +1,16 @@
# Copyright 2016 Red Hat, Inc.
#
# 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.
SERVER_STATUS_ACTIVE = 'ACTIVE'
DEFAULT_SECURITY_GROUP = 'default'

View File

@ -0,0 +1,54 @@
# Copyright 2016 Red Hat, Inc.
# 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.
from oslo_log import log as logging
from tempest.common import waiters
from neutron.tests.tempest import config
from neutron.tests.tempest.scenario import base
from neutron.tests.tempest.scenario import constants
CONF = config.CONF
LOG = logging.getLogger(__name__)
class NetworkBasicTest(base.BaseTempestTestCase):
credentials = ['primary']
force_tenant_isolation = False
# Default to ipv4.
_ip_version = 4
def test_basic_instance(self):
network = self.create_network()
subnet = self.create_subnet(network)
self.create_router_and_interface(subnet['id'])
keypair = self.create_keypair()
self.create_loginable_secgroup_rule()
server = self.create_server(
flavor_ref=CONF.compute.flavor_ref,
image_ref=CONF.compute.image_ref,
key_name=keypair['name'],
networks=[{'uuid': network['id']}])
waiters.wait_for_server_status(self.manager.servers_client,
server['server']['id'],
constants.SERVER_STATUS_ACTIVE)
port = self.client.list_ports(network_id=network['id'],
device_id=server[
'server']['id'])['ports'][0]
fip = self.create_and_associate_floatingip(port['id'])
self.check_connectivity(fip['floating_ip_address'],
CONF.validation.image_ssh_user,
keypair['private_key'])

View File

@ -639,3 +639,57 @@ class NetworkClientJSON(service_client.RestClient):
self.expected_success(200, resp.status)
body = jsonutils.loads(body)
return service_client.ResponseBody(resp, body)
def create_security_group_rule(self, direction, security_group_id,
**kwargs):
post_body = {'security_group_rule': kwargs}
post_body['security_group_rule']['direction'] = direction
post_body['security_group_rule'][
'security_group_id'] = security_group_id
body = jsonutils.dumps(post_body)
uri = '%s/security-group-rules' % self.uri_prefix
resp, body = self.post(uri, body)
self.expected_success(201, resp.status)
body = jsonutils.loads(body)
return service_client.ResponseBody(resp, body)
def list_security_groups(self, **kwargs):
post_body = {'security_groups': kwargs}
body = jsonutils.dumps(post_body)
uri = '%s/security-groups' % self.uri_prefix
if kwargs:
uri += '?' + urlparse.urlencode(kwargs, doseq=1)
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = jsonutils.loads(body)
return service_client.ResponseBody(resp, body)
def delete_security_group(self, security_group_id):
uri = '%s/security-groups/%s' % (
self.uri_prefix, security_group_id)
resp, body = self.delete(uri)
self.expected_success(204, resp.status)
return service_client.ResponseBody(resp, body)
def list_ports(self, **kwargs):
post_body = {'ports': kwargs}
body = jsonutils.dumps(post_body)
uri = '%s/ports' % self.uri_prefix
if kwargs:
uri += '?' + urlparse.urlencode(kwargs, doseq=1)
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = jsonutils.loads(body)
return service_client.ResponseBody(resp, body)
def create_floatingip(self, floating_network_id, **kwargs):
post_body = {'floatingip': {
'floating_network_id': floating_network_id}}
if kwargs:
post_body['floatingip'].update(kwargs)
body = jsonutils.dumps(post_body)
uri = '%s/floatingips' % self.uri_prefix
resp, body = self.post(uri, body)
self.expected_success(201, resp.status)
body = jsonutils.loads(body)
return service_client.ResponseBody(resp, body)