Split cases for nova and neutron networks.

Added simlest network tests.

Change-Id: Ifc3f951a29d85fad61869376bcbe602ae209f6c6
This commit is contained in:
alexey-mr 2015-11-05 19:17:16 +03:00
parent 3ecaef77b7
commit 93aabba2ff
21 changed files with 505 additions and 262 deletions

View File

@ -60,9 +60,9 @@ now, 8-byte hashes are generated and returned for any ID to report.
Solution: Nova GCE API just uses first key.
* Default Openstack flavors are available as machine types. GCE doesn't allow symbol '.' in machine type names,
that's why GCE API plugin converts symbols '.' into '-' in 'get' requests (e.g. request of machine types converts
the name 'm1.tiny' into m1-tiny) and vise versa in 'put/post/delete' requests (e.g. instance creation converts
the name 'n1-standard-1' to 'n1.standard.1').
that's why GCE API plugin converts symbols '.' into '-' in 'get' requests (e.g. request of machine types converts
the name 'm1.tiny' into m1-tiny) and vise versa in 'put/post/delete' requests (e.g. instance creation converts
the name 'n1-standard-1' to 'n1.standard.1').
Authentication specifics
========================

View File

@ -207,6 +207,14 @@ class API(object):
self._delete_db_item(context, item)
return only_os_items
@staticmethod
def _from_gce(name):
return name.replace("-", ".")
@staticmethod
def _to_gce(name):
return name.replace(".", "-")
class _CallbackReasons(object):
check_delete = 1

View File

@ -119,7 +119,7 @@ def keystone(context):
# Ver2 doesn't create session and performs
# authentication automatically, but Ver3 does create session
# if it's not provided and doesn't perform authentication.
# TODO(use sessions)
# TODO(alexey-mr): use sessions
c.authenticate()
return c

View File

@ -51,7 +51,7 @@ class Controller(object):
# Ver2 doesn't create session and performs
# authentication automatically, but Ver3 does create session
# if it's not provided and doesn't perform authentication.
# TODO(use sessions)
# TODO(alexey-mr): use sessions
keystone.authenticate()
catalog = keystone.service_catalog.get_data()
public_url = clients.get_url_from_catalog(catalog, "gceapi")

View File

@ -60,9 +60,3 @@ class API(base_api.API):
def _prepare_item(self, item):
item["name"] = self._to_gce(item["name"])
return item
def _from_gce(self, name):
return name.replace("-", ".")
def _to_gce(self, name):
return name.replace(".", "-")

View File

@ -154,7 +154,7 @@ class Controller(object):
# Ver2 doesn't create session and performs
# authentication automatically, but Ver3 does create session
# if it's not provided and doesn't perform authentication.
# TODO(use sessions)
# TODO(alexy-mr): use sessions
keystone.authenticate()
client.auth_token = keystone.auth_token
s = keystone.auth_ref.issued
@ -223,7 +223,7 @@ class AuthProtocol(object):
# Ver2 doesn't create session and performs
# authentication automatically, but Ver3 does create session
# if it's not provided and doesn't perform authentication.
# TODO(use sessions)
# TODO(alexey-mr): use sessions
keystone.authenticate()
scoped_token = keystone.auth_token
env["HTTP_X_AUTH_TOKEN"] = scoped_token

View File

@ -20,6 +20,19 @@ from gceapi import exception
CONF = cfg.CONF
# OS usual region names are in PascalCase - e.g. RegionOne,
# GCE region name should matche the regexp [a-z](?:[-a-z0-9]{0,61}[a-z0-9])?
_OS_GCE_MAP = {
'RegionOne': 'region-one',
'RegionTwo': 'region-two',
'RegionThree': 'region-three',
'RegionFour': 'region-four',
}
def _map_region_name(name):
return _OS_GCE_MAP.get(name, name)
class API(base_api.API):
"""GCE Regions API
@ -33,7 +46,7 @@ class API(base_api.API):
def __init__(self, *args, **kwargs):
super(API, self).__init__(*args, **kwargs)
self._REGIONS = [CONF.get("region").strip()]
self._REGIONS = [_map_region_name(CONF.get("region").strip())]
def _get_type(self):
return self.KIND

View File

@ -58,11 +58,13 @@ if [[ ! -f $TEST_CONFIG_DIR/$TEST_CONFIG ]]; then
[[ "$?" -eq 0 ]] || { echo "Failed to prepare flavor"; exit 1; }
fi
# create network
# create default network
if [[ -n $(openstack service list | grep neutron) ]]; then
net_id=$(neutron net-create --tenant-id $project_id "private" | grep ' id ' | awk '{print $4}')
# neutron networking
networking="neutron"
net_id=$(neutron net-create --tenant-id $project_id "default" | grep ' id ' | awk '{print $4}')
[[ -n "$net_id" ]] || { echo "net-create failed"; exit 1; }
subnet_id=$(neutron subnet-create --tenant-id $project_id --ip_version 4 --gateway 10.0.0.1 --name "private_subnet" $net_id 10.0.0.0/24 | grep ' id ' | awk '{print $4}')
subnet_id=$(neutron subnet-create --tenant-id $project_id --ip_version 4 --gateway 10.240.0.1 --name "private_subnet" $net_id 10.240.0.0/24 | grep ' id ' | awk '{print $4}')
[[ -n "$subnet_id" ]] || { echo "subnet-create failed"; exit 1; }
router_id=$(neutron router-create --tenant-id $project_id "private_router" | grep ' id ' | awk '{print $4}')
[[ -n "$router_id" ]] || { echo "router-create failed"; exit 1; }
@ -72,6 +74,10 @@ if [[ ! -f $TEST_CONFIG_DIR/$TEST_CONFIG ]]; then
[[ -n "$public_net_id" ]] || { echo "can't find public network"; exit 1; }
neutron router-gateway-set $router_id $public_net_id
[[ "$?" -eq 0 ]] || { echo "router-gateway-set failed"; exit 1; }
else
# nova networking
networking="nova-network"
nova network-create "default" --fixed-range-v4 10.240.0.0/24 --gateway 10.240.0.1
fi
#create image in raw format
@ -117,7 +123,8 @@ discovery_url=${GCE_DISCOVERY_URL:-'/discovery/v1/apis/{api}/{apiVersion}/rest'}
# GCE resource IDs for testing
project_id=${OS_PROJECT_NAME}
zone=${ZONE:-'nova'}
region=${REGION:-'RegionOne'}
networking=${networking}
region=${REGION:-'region-one'}
# convert flavor name: becase GCE dowsn't allows '.' and converts '-' into '.'
machine_type=${flavor_name//\./-}
image=${os_image_name}

View File

@ -0,0 +1,129 @@
# Copyright 2015 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# 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 gceapi.tests.functional import test_base
CREATE_ADDRESS_TEMPLATE = {
"name": "${name}",
}
def _prepare_address_create_parameters(**kwargs):
return test_base.insert_json_parameters(CREATE_ADDRESS_TEMPLATE, **kwargs)
class TestAddressesBase(test_base.GCETestCase):
@property
def addresses(self):
res = self.api.compute.addresses()
self.assertIsNotNone(
res,
'Null addresses object, api is not built properly')
return res
def _create_address(self, options):
self._add_cleanup(self._delete_address, options['name'])
cfg = self.cfg
project_id = cfg.project_id
region = cfg.region
config = _prepare_address_create_parameters(**options)
self.trace('Crete address with options {}'.format(config))
request = self.addresses.insert(
project=project_id,
region=region,
body=config)
self._execute_async_request(request, project_id, region=region)
def _delete_address(self, name):
cfg = self.cfg
project_id = cfg.project_id
region = cfg.region
self.trace('Delete address: project_id={} region={} name={}'.
format(project_id, region, name))
request = self.addresses.delete(
project=project_id,
region=region,
address=name)
self._remove_cleanup(self._delete_address, name)
self._execute_async_request(request, project_id, region=region)
def _list_addresses(self):
cfg = self.cfg
project_id = cfg.project_id
region = cfg.region
self.trace('List addresses: project_id={} region={}'.
format(project_id, region))
request = self.addresses.list(
project=project_id,
region=region)
result = request.execute()
self.trace('Addresses: {}'.format(result))
self.api.validate_schema(value=result, schema_name='AddressList')
return result
def _get_address(self, name):
cfg = self.cfg
project_id = cfg.project_id
region = cfg.region
self.trace('Get address: project_id={} region={} name={}'.
format(project_id, region, name))
request = self.addresses.get(
project=project_id,
region=region,
address=name)
result = request.execute()
self.trace('Addresses: {}'.format(result))
self.api.validate_schema(value=result, schema_name='Address')
return result
class TestAddressesCRUD(TestAddressesBase):
@property
def addresses(self):
res = self.api.compute.addresses()
self.assertIsNotNone(
res,
'Null addresses object, api is not built properly')
return res
def setUp(self):
super(TestAddressesCRUD, self).setUp()
self._address_name = self._rand_name('testaddr')
def _create(self):
options = {
'name': self._address_name
}
self._create_address(options)
def _read(self):
result = self._get_address(self._address_name)
self.assertEqual(self._address_name, result['name'])
result = self._list_addresses()
self.assertFind(self._address_name, result)
def _update(self):
pass
def _delete(self):
self._delete_address(self._address_name)
def test_crud(self):
self._create()
self._read()
self._update()
self._delete()

View File

@ -15,17 +15,11 @@
# under the License.
from string import Template
from json import dumps
from json import loads
from gceapi.tests.functional import test_base
BASE_COMPUTE_URL = '{address}/compute/v1'
CREATE_INSTANCE_TEMPLATE = {
"name": "${instance}",
"name": "${name}",
"description": "Testing instance",
"machineType": "zones/${zone}/machineTypes/${machine_type}",
"disks": [
@ -66,30 +60,13 @@ CREATE_INSTANCE_TEMPLATE = {
}
]
}
CREATE_NETWORK_TEMPLATE = {
"name": "${name}",
"IPv4Range": "10.240.0.0/16",
"description": "testing network ${name}",
"gatewayIPv4": "10.240.0.1"
}
def _insert_json_parameters(obj, **kwargs):
s = dumps(obj)
t = Template(s)
s = t.substitute(**kwargs)
return loads(s)
def _prepare_instance_insert_parameters(**kwargs):
return test_base.insert_json_parameters(CREATE_INSTANCE_TEMPLATE, **kwargs)
def _prepare_instace_insert_parameters(**kwargs):
return _insert_json_parameters(CREATE_INSTANCE_TEMPLATE, **kwargs)
def _prepare_network_create_parameters(**kwargs):
return _insert_json_parameters(CREATE_NETWORK_TEMPLATE, **kwargs)
class TestIntancesBase(test_base.GCETestCase):
class TestInstancesBase(test_base.GCETestCase):
@property
def instances(self):
res = self.api.compute.instances()
@ -98,132 +75,87 @@ class TestIntancesBase(test_base.GCETestCase):
'Null instances object, api is not built properly')
return res
@property
def networks(self):
res = self.api.compute.networks()
self.assertIsNotNone(
res,
'Null networks object, api is not built properly')
return res
def setUp(self):
super(TestIntancesBase, self).setUp()
self._instance_name = self.getUniqueString('testinst')
self._network_name = self.getUniqueString('testnet')
def _create_network(self):
cfg = self.cfg
project_id = cfg.project_id
network = self._network_name
kw = {
'name': network,
}
config = _prepare_network_create_parameters(**kw)
self.trace('Crete network with options {}'.format(config))
request = self.networks.insert(
project=project_id,
body=config)
result = self._execute_async_request(request, project_id)
self.api.validate_schema(value=result, schema_name='Operation')
return result
def _delete_network(self):
cfg = self.cfg
project_id = cfg.project_id
network = self._network_name
self.trace(
'Delete network: project_id={} network={}'.
format(project_id, network))
request = self.networks.delete(
project=project_id,
network=network)
result = self._execute_async_request(request, project_id)
self.api.validate_schema(value=result, schema_name='Operation')
return result
def _create_instance(self):
def _create_instance(self, options):
self._add_cleanup(self._delete_instance, options['name'])
cfg = self.cfg
project_id = cfg.project_id
zone = cfg.zone
kw = {
'zone': zone,
'instance': self._instance_name,
'machine_type': cfg.machine_type,
'image': cfg.image,
'network': self._network_name,
}
config = _prepare_instace_insert_parameters(**kw)
config = _prepare_instance_insert_parameters(**options)
self.trace('Crete instance with options {}'.format(config))
request = self.instances.insert(
project=project_id,
zone=zone,
body=config)
result = self._execute_async_request(request, project_id, zone=zone)
self.api.validate_schema(value=result, schema_name='Operation')
return result
self._execute_async_request(request, project_id, zone=zone)
def _delete_instance(self):
def _delete_instance(self, name):
cfg = self.cfg
project_id = cfg.project_id
zone = cfg.zone
instance = self._instance_name
self.trace(
'Delete instance: project_id={} zone={} instance {}'.
format(project_id, zone, instance))
self.trace('Delete instance: project_id={} zone={} instance {}'.
format(project_id, zone, name))
request = self.instances.delete(
project=project_id,
zone=zone,
instance=instance)
result = self._execute_async_request(request, project_id, zone=zone)
self.api.validate_schema(value=result, schema_name='Operation')
return result
instance=name)
self._remove_cleanup(self._delete_instance, name)
self._execute_async_request(request, project_id, zone=zone)
def _list(self):
def _list_instances(self):
project_id = self.cfg.project_id
zone = self.cfg.zone
self.trace(
'List instances: project_id={} zone={}'.format(project_id, zone))
self.trace('List instances: project_id={} zone={}'.
format(project_id, zone))
request = self.instances.list(project=project_id, zone=zone)
self._trace_request(request)
self.trace_request(request)
result = request.execute()
self.trace('Instances: {}'.format(result))
self.api.validate_schema(value=result, schema_name='InstanceList')
self.assertFind(self._instance_name, result)
return result
def _get(self):
def _get_instance(self, name):
project_id = self.cfg.project_id
zone = self.cfg.zone
instance = self._instance_name
self.trace(
'Get instance: project_id={} zone={} instance={}'.
format(project_id, zone, instance))
self.trace('Get instance: project_id={} zone={} instance={}'.
format(project_id, zone, name))
request = self.instances.get(
project=project_id,
zone=zone,
instance=instance)
instance=name)
result = request.execute()
self.trace('Instance: {}'.format(result))
self.api.validate_schema(value=result, schema_name='Instance')
return result
class TestIntancesCRUD(TestIntancesBase):
class TestInstancesCRUD(TestInstancesBase):
def setUp(self):
super(TestInstancesCRUD, self).setUp()
self._instance_name = self._rand_name('testinst')
def _create(self):
self._create_network()
self._create_instance()
cfg = self.cfg
options = {
'zone': cfg.zone,
'name': self._instance_name,
'machine_type': cfg.machine_type,
'image': cfg.image,
'network': 'default',
}
self._create_instance(options)
def _read(self):
self._get()
self._list()
result = self._get_instance(self._instance_name)
self.assertEqual(self._instance_name, result['name'])
result = self._list_instances()
self.assertFind(self._instance_name, result)
def _update(self):
#TODO(to impl simple update cases)
# TODO(alexey-mr): to impl simple update cases
pass
def _delete(self):
self._delete_instance()
self._delete_network()
self._delete_instance(self._instance_name)
def test_crud(self):
self._create()

View File

@ -0,0 +1,132 @@
# Copyright 2015 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# 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 gceapi.tests.functional import test_base
CREATE_NETWORK_TEMPLATE = {
"name": "${name}",
"IPv4Range": "${ip_range}",
"description": "testing network ${name}",
"gatewayIPv4": "${gateway}"
}
def _prepare_network_create_parameters(**kwargs):
return test_base.insert_json_parameters(CREATE_NETWORK_TEMPLATE, **kwargs)
class TestNetworksBase(test_base.GCETestCase):
@property
def networks(self):
res = self.api.compute.networks()
self.assertIsNotNone(
res,
'Null networks object, api is not built properly')
return res
def _create_network(self, options):
self._add_cleanup(self._delete_network, options['name'])
project_id = self.cfg.project_id
config = _prepare_network_create_parameters(**options)
self.trace('Crete network with options {}'.format(config))
request = self.networks.insert(
project=project_id,
body=config)
self._execute_async_request(request, project_id)
def _delete_network(self, name):
cfg = self.cfg
project_id = cfg.project_id
self.trace('Delete network: project_id={} network={}'.
format(project_id, name))
request = self.networks.delete(
project=project_id,
network=name)
self._remove_cleanup(self._delete_network, name)
self._execute_async_request(request, project_id)
def _list_networks(self):
project_id = self.cfg.project_id
self.trace('List networks: project_id={}'.format(project_id))
request = self.networks.list(project=project_id)
self.trace_request(request)
result = request.execute()
self.trace('Networks: {}'.format(result))
self.api.validate_schema(value=result, schema_name='NetworkList')
return result
def _get_network(self, name):
project_id = self.cfg.project_id
self.trace('Get network: project_id={} network={}'.
format(project_id, name))
request = self.networks.get(
project=project_id,
network=name)
result = request.execute()
self.trace('Network: {}'.format(result))
self.api.validate_schema(value=result, schema_name='Network')
return result
class TestReadDefaultNetwork(TestNetworksBase):
def setUp(self):
super(TestReadDefaultNetwork, self).setUp()
self._network_name = 'default'
def test_get(self):
self._get_network(self._network_name)
def test_list(self):
result = self._list_networks()
self.assertFind(self._network_name, result)
class TestNetworksCRUD(TestNetworksBase):
def setUp(self):
if self.cfg.networking == 'nova-network':
self.skipTest('Skip network because of nova-network')
return
super(TestNetworksCRUD, self).setUp()
self._network_name = self._rand_name('network')
def _create(self):
options = {
'name': self._network_name,
'ip_range': '10.240.0.0/16',
'gateway': '10.240.0.1'
}
# TODO(alexey-mr): gateway is optional, so add case with absent one
self._create_network(options)
def _read(self):
result = self._get_network(self._network_name)
self.assertEqual(self._network_name, result['name'])
result = self._list_networks()
self.assertFind(self._network_name, result)
def _update(self):
# TODO(alexey-mr): to be implemented
pass
def _delete(self):
self._delete_network(self._network_name)
def test_crud(self):
self._create()
self._read()
self._update()
self._delete()

View File

@ -14,8 +14,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import unittest
from gceapi.tests.functional import test_base
@ -34,8 +32,6 @@ class TestRegions(test_base.GCETestCase):
'Null regions object, api is not built properly')
return res
# TODO(alexey-mr): Google allows [a-z](?:[-a-z0-9]{0,61}[a-z0-9])?
@unittest.skip("Skip test for now: google dosnt't allow name RegionOne")
def test_describe(self):
project_id = self.cfg.project_id
region = self.cfg.region

View File

@ -31,13 +31,14 @@ import tempest.test
CONF = config.CONF
LOG = logging.getLogger("tempest.thirdparty.gce")
REGION_NAME = 'region-one'
class GCEConnection(rest_client.RestClient):
def __init__(self, auth_provider):
super(GCEConnection, self).__init__(auth_provider,
"gceapi", "RegionOne")
"gceapi", REGION_NAME)
self.service = CONF.gceapi.catalog_type
def set_zone(self, zone):

View File

@ -79,8 +79,11 @@ OPTIONS = [
default='nova',
help='GCE Zone for testing'),
cfg.StrOpt('region',
default='RegionOne',
default='us-central1',
help='GCE Region for testing'),
cfg.StrOpt('networking',
default='neutron',
help='Types of OS networking: neutron or nova-network'),
cfg.StrOpt('machine_type',
default='n1-standard-1',

View File

@ -15,39 +15,34 @@
# under the License.
from keystoneclient.client import Client as KeystoneClient
from oauth2client.client import AccessTokenCredentials
from oauth2client.client import GoogleCredentials
from keystoneclient import client as keystone_client
from oauth2client import client as oauth_client
class CredentialsProvider(object):
def __init__(self, supp):
self._supp = supp
def __init__(self, cfg):
self.cfg = cfg
def _trace(self, msg):
self._supp.trace(msg)
@staticmethod
def _get_app_credentials():
return oauth_client.GoogleCredentials.get_application_default()
def _get_app_credentials(self):
self._trace('Create GoogleCredentials from default app file')
return GoogleCredentials.get_application_default()
def _get_token_crenetials(self):
def _get_token_credentials(self):
client = self._create_keystone_client()
token = client.auth_token
self._trace('Created token {}'.format(token))
return AccessTokenCredentials(access_token=token,
user_agent='GCE test')
return oauth_client.AccessTokenCredentials(
access_token=token,
user_agent='GCE test')
def _create_keystone_client(self):
cfg = self._supp.cfg
cfg = self.cfg
auth_data = {
'username': cfg.username,
'password': cfg.password,
'tenant_name': cfg.project_id,
'auth_url': cfg.auth_url
}
self._trace('Create keystone client, auth_data={}'.format(auth_data))
client = KeystoneClient(**auth_data)
client = keystone_client.Client(**auth_data)
if not client.authenticate():
raise Exception('Failed to authenticate user')
return client
@ -58,9 +53,9 @@ class CredentialsProvider(object):
@property
def credentials(self):
cred_type = self._supp.cfg.cred_type
cred_type = self.cfg.cred_type
if cred_type == 'os_token':
return self._get_token_crenetials()
return self._get_token_credentials()
elif cred_type == 'gcloud_auth':
return self._get_app_credentials()
else:

View File

@ -14,33 +14,43 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
import string
import time
import traceback
from googleapiclient.discovery import build
from googleapiclient.schema import Schemas
from jsonschema import RefResolver
from jsonschema import validate
from googleapiclient import discovery
from googleapiclient import schema
import jsonschema
from oslo_log import log as logging
from tempest_lib import base
from tempest_lib.common.utils import data_utils
from gceapi.tests.functional import config
from gceapi.tests.functional.credentials import CredentialsProvider
from gceapi.tests.functional import credentials
class TestSupp(object):
def __init__(self, *args, **kwargs):
self._cfg = config.CONF.gce
from oslo_log import log as logging
self._log = logging.getLogger("gceapi")
@property
def cfg(self):
return self._cfg
def trace(self, *args, **kwargs):
self._log.debug(*args, **kwargs)
CONF = config.CONF.gce
LOG = logging.getLogger("gceapi")
class LocalRefResolver(RefResolver):
def trace(msg):
LOG.debug(msg)
def safe_call(method):
def wrapper(self, *args, **kwargs):
try:
return method(self, *args, **kwargs)
except Exception as err:
trace('Exception {}'.format(err))
bt = traceback.format_exc()
trace('Exception back trace {}'.format(bt))
return None
return wrapper
class LocalRefResolver(jsonschema.RefResolver):
def __init__(
self,
base_uri,
@ -66,15 +76,14 @@ class LocalRefResolver(RefResolver):
class GCEApi(object):
def __init__(self, supp, cred_provider):
def __init__(self, cred_provider):
self._compute = None
self._cred_provider = cred_provider
self._schema = None
self._scheme_ref_resolver = 0
self._supp = supp
def init(self):
self._schema = Schemas(self._supp.cfg.schema)
self._schema = schema.Schemas(CONF.schema)
self._scheme_ref_resolver = LocalRefResolver.from_schema(
self._schema.schemas)
self._build_api()
@ -82,9 +91,8 @@ class GCEApi(object):
def _build_api(self):
credentials = self._cred_provider.credentials
url = self._discovery_url
self._trace(
'Build Google compute api with discovery url {}'.format(url))
self._compute = build(
trace('Build Google compute api with discovery url {}'.format(url))
self._compute = discovery.build(
'compute', 'v1',
credentials=credentials,
discoveryServiceUrl=url
@ -92,7 +100,7 @@ class GCEApi(object):
@property
def _discovery_url(self):
cfg = self._supp.cfg
cfg = CONF
return '{}://{}:{}{}'.format(
cfg.protocol,
cfg.host,
@ -100,9 +108,6 @@ class GCEApi(object):
cfg.discovery_url
)
def _trace(self, msg):
self._supp.trace(msg)
@property
def compute(self):
assert(self._compute is not None)
@ -110,7 +115,7 @@ class GCEApi(object):
@property
def base_url(self):
cfg = self._supp.cfg
cfg = CONF
return '{}://{}:{}'.format(
cfg.protocol,
cfg.host,
@ -119,27 +124,31 @@ class GCEApi(object):
def validate_schema(self, value, schema_name):
schema = self._schema.get(schema_name)
validate(value, schema, resolver=self._scheme_ref_resolver)
jsonschema.validate(value, schema, resolver=self._scheme_ref_resolver)
class GCETestCase(base.BaseTestCase):
@property
def cfg(self):
assert(self._supp.cfg is not None)
return self._supp.cfg
@property
def api(self):
assert(self._api is not None)
return self._api
def trace(self, *args, **kwargs):
self._supp.trace(*args, **kwargs)
@property
def cfg(self):
return CONF
@staticmethod
def trace(msg):
trace(msg)
@staticmethod
def trace_request(request):
trace('Request: {}'.format(request.to_json()))
@classmethod
def setUpClass(cls):
cls._supp = TestSupp()
cls._api = GCEApi(cls._supp, CredentialsProvider(cls._supp))
cp = credentials.CredentialsProvider(CONF)
cls._api = GCEApi(cp)
cls._api.init()
super(GCETestCase, cls).setUpClass()
@ -154,49 +163,60 @@ class GCETestCase(base.BaseTestCase):
self.fail(
'There is no required item {} in the list {}'.format(item, items))
def _trace_request(self, r):
self.trace('Request: {}'.format(r.to_json()))
def _get_operations_request(self, name, project, zone):
def _get_operations_request(self, name, project, zone, region):
if zone is not None:
return self.api.compute.zoneOperations().get(
project=project,
zone=zone,
operation=name)
if region is not None:
return self.api.compute.regionOperations().get(
project=project,
region=region,
operation=name)
return self.api.compute.globalOperations().get(
project=project,
operation=name)
def _execute_async_request(self, request, project, zone=None):
self._trace_request(request)
@staticmethod
def _rand_name(prefix='n-'):
return data_utils.rand_name(prefix)
def _add_cleanup(self, method, *args, **kwargs):
self.addCleanup(method, *args, **kwargs)
@safe_call
def _remove_cleanup(self, method, *args, **kwargs):
v = (method, args, kwargs)
self._cleanups.remove(v)
def _execute_async_request(self, request, project, zone=None, region=None):
self.trace_request(request)
operation = request.execute()
name = operation['name']
self.trace('Waiting for operation {} to finish...'.format(name))
begin = time.time()
timeout = self._supp.cfg.build_timeout
timeout = self.cfg.build_timeout
result = None
while time.time() - begin < timeout:
result = self._get_operations_request(
name, project, zone).execute()
name, project, zone, region).execute()
self.api.validate_schema(value=result, schema_name='Operation')
if result['status'] == 'DONE':
if 'error' in result:
self.fail('Request {} failed with error {}'. format(
name, result['error']))
else:
self.trace("Request {} done successfully".format(name))
return result
return
time.sleep(1)
self.fail('Request {} failed with timeout {}'.format(name, timeout))
self.fail('Request {} failed with timeout {},'
' latest operation status {}'.format(name, timeout, result))
def safe_call(method):
def wrapper(self, *args, **kwargs):
try:
return method(self, *args, **kwargs)
except Exception as err:
self.trace('Exception {}'.format(err))
import traceback
bt = traceback.format_exc()
self.trace('Exception back trace {}'.format(bt))
return None
return wrapper
def insert_json_parameters(obj, **kwargs):
s = json.dumps(obj)
t = string.Template(s)
s = t.substitute(**kwargs)
return json.loads(s)

View File

@ -58,13 +58,14 @@ COMMON_PENDING_OPERATION = {
}
COMMON_PENDING_OPERATION.update(COMMON_OPERATION)
REGION = fake_request.REGION
REGION_OPERATION_SPECIFIC = {
u'id': u'6294142421306477203',
u'id': u'5036531165588500177',
u'selfLink': u'http://localhost/compute/v1beta15/projects/'
'fake_project/regions/RegionOne/operations/'
'operation-735d48a5-284e-4fb4-a10c-a465ac0b8888',
'fake_project/regions/%s/operations/'
'operation-735d48a5-284e-4fb4-a10c-a465ac0b8888' % REGION,
u'region': u'http://localhost/compute/v1beta15/projects/'
'fake_project/regions/RegionOne',
'fake_project/regions/%s' % REGION,
}
COMMON_REGION_FINISHED_OPERATION = copy.copy(COMMON_FINISHED_OPERATION)

View File

@ -17,12 +17,12 @@ from gceapi import wsgi_ext as os_wsgi
PROJECT_ID = "4a5cc7d8893544a9babb3b890227d75e"
REGION = u'region-one'
FAKE_SERVICE_CATALOG = [{
u'endpoints': [{
u'adminURL': u'http://192.168.137.21:8774/v2/' + PROJECT_ID,
u'region': u'RegionOne',
u'region': REGION,
u'id': u'81a8b36abc5f4945bbd1269be0423012',
u'internalURL': u'http://192.168.137.21:8774/v2/' + PROJECT_ID,
u'publicURL': u'http://192.168.137.21:8774/v2/' + PROJECT_ID}],
@ -32,7 +32,7 @@ FAKE_SERVICE_CATALOG = [{
}, {
u'endpoints': [{
u'adminURL': u'http://192.168.137.21:9696/',
u'region': u'RegionOne',
u'region': REGION,
u'id': u'10a0fc598a5741c390f0d6560a89fced',
u'internalURL': u'http://192.168.137.21:9696/',
u'publicURL': u'http://192.168.137.21:9696/'}],
@ -42,7 +42,7 @@ FAKE_SERVICE_CATALOG = [{
}, {
u'endpoints': [{
u'adminURL': u'http://192.168.137.21:9292',
u'region': u'RegionOne',
u'region': REGION,
u'id': u'39643060448c4c089535fce07f2d2aa4',
u'internalURL': u'http://192.168.137.21:9292',
u'publicURL': u'http://192.168.137.21:9292'}],
@ -52,7 +52,7 @@ FAKE_SERVICE_CATALOG = [{
}, {
u'endpoints': [{
u'adminURL': u'http://192.168.137.21:8776/v1/' + PROJECT_ID,
u'region': u'RegionOne',
u'region': REGION,
u'id': u'494bd5333aed467092316e03b1163139',
u'internalURL': u'http://192.168.137.21:8776/v1/' + PROJECT_ID,
u'publicURL': u'http://192.168.137.21:8776/v1/' + PROJECT_ID}],

View File

@ -13,22 +13,27 @@
# limitations under the License.
from gceapi.api import addresses
from gceapi.tests.unit.api import common
from gceapi.tests.unit.api import common
from gceapi.tests.unit.api import fake_request
REGION = fake_request.REGION
EXPECTED_ADDRESSES = [{
"kind": "compute#address",
"id": "4065623605586261056",
"id": "1870839154859306350",
"creationTimestamp": "",
"status": "IN USE",
"region": "http://localhost/compute/v1beta15/projects/"
"fake_project/regions/RegionOne",
"fake_project/regions/%s" % REGION,
"name": "address-172-24-4-227",
"description": "",
"address": "172.24.4.227",
"selfLink": "http://localhost/compute/v1beta15/projects/"
"fake_project/regions/RegionOne/addresses/address-172-24-4-227",
"fake_project/regions/%s/"
"addresses/address-172-24-4-227" % REGION,
"users": ["http://localhost/compute/v1beta15/projects/"
"fake_project/zones/nova/instances/i1"]
"fake_project/zones/nova/instances/i1"]
}]
@ -40,37 +45,38 @@ class AddressesTest(common.GCEControllerTest):
def test_get_address_by_invalid_name(self):
response = self.request_gce("/fake_project/regions/"
"RegionOne/addresses/fake")
"%s/addresses/fake" % REGION)
self.assertEqual(404, response.status_int)
def test_get_address_by_name(self):
response = self.request_gce("/fake_project/regions/"
"RegionOne/addresses/address-172-24-4-227")
response = self.request_gce(
"/fake_project/regions/%s/addresses/address-172-24-4-227" % REGION)
self.assertEqual(200, response.status_int)
self.assertEqual(response.json_body, EXPECTED_ADDRESSES[0])
def test_get_address_list_filtered(self):
response = self.request_gce("/fake_project/regions/RegionOne/addresses"
"?filter=name+eq+address-172-24-4-227")
response = self.request_gce("/fake_project/regions/%s/addresses"
"?filter=name+eq+address-172-24-4-227" %
REGION)
expected = {
"kind": "compute#addressList",
"id": "projects/fake_project/regions/RegionOne/addresses",
"id": "projects/fake_project/regions/%s/addresses" % REGION,
"selfLink": "http://localhost/compute/v1beta15/projects"
"/fake_project/regions/RegionOne/addresses",
"/fake_project/regions/%s/addresses" % REGION,
"items": [EXPECTED_ADDRESSES[0]]
}
self.assertEqual(response.json_body, expected)
def test_get_address_list(self):
response = self.request_gce("/fake_project/regions/RegionOne"
"/addresses")
response = self.request_gce("/fake_project/regions/%s"
"/addresses" % REGION)
expected = {
"kind": "compute#addressList",
"id": "projects/fake_project/regions/RegionOne/addresses",
"id": "projects/fake_project/regions/%s/addresses" % REGION,
"selfLink": "http://localhost/compute/v1beta15/projects"
"/fake_project/regions/RegionOne/addresses",
"/fake_project/regions/%s/addresses" % REGION,
"items": EXPECTED_ADDRESSES
}
@ -86,7 +92,7 @@ class AddressesTest(common.GCEControllerTest):
"selfLink": "http://localhost/compute/v1beta15/projects"
"/fake_project/aggregated/addresses",
"items": {
"regions/RegionOne": {
"regions/%s" % REGION: {
"addresses": [EXPECTED_ADDRESSES[0]]
},
}
@ -103,7 +109,7 @@ class AddressesTest(common.GCEControllerTest):
"selfLink": "http://localhost/compute/v1beta15/projects"
"/fake_project/aggregated/addresses",
"items": {
"regions/RegionOne": {
"regions/%s" % REGION: {
"addresses": EXPECTED_ADDRESSES
},
}
@ -112,21 +118,21 @@ class AddressesTest(common.GCEControllerTest):
self.assertEqual(response.json_body, expected)
def test_delete_address_with_invalid_name(self):
response = self.request_gce("/fake_project/regions/RegionOne"
"/addresses/fake-address", method="DELETE")
response = self.request_gce(
"/fake_project/regions/%s/addresses/fake-address" % REGION,
method="DELETE")
self.assertEqual(404, response.status_int)
def test_delete_address(self):
response = self.request_gce(
"/fake_project/regions/RegionOne/"
"addresses/address-172-24-4-227",
method="DELETE")
"/fake_project/regions/%s/addresses/address-172-24-4-227" % REGION,
method="DELETE")
expected = {
"operationType": "delete",
"targetId": "4065623605586261056",
"targetId": "1870839154859306350",
"targetLink": "http://localhost/compute/v1beta15/projects/"
"fake_project/regions/RegionOne/"
"addresses/address-172-24-4-227",
"fake_project/regions/%s/"
"addresses/address-172-24-4-227" % REGION,
}
expected.update(common.COMMON_REGION_FINISHED_OPERATION)
self.assertEqual(200, response.status_int)
@ -136,16 +142,17 @@ class AddressesTest(common.GCEControllerTest):
request_body = {
"name": "fake-address",
}
response = self.request_gce("/fake_project/regions/RegionOne/"
"addresses",
response = self.request_gce("/fake_project/regions/%s/"
"addresses" % REGION,
method="POST",
body=request_body)
self.assertEqual(200, response.status_int)
expected = {
"operationType": "insert",
"targetId": "4570437344333712421",
"targetId": "8754519975833457287",
"targetLink": "http://localhost/compute/v1beta15/projects/"
"fake_project/regions/RegionOne/addresses/fake-address",
"fake_project/regions/%s/addresses/fake-address" %
REGION,
}
expected.update(common.COMMON_REGION_FINISHED_OPERATION)
self.assertDictEqual(expected, response.json_body)

View File

@ -14,19 +14,22 @@
from gceapi.api import regions
from gceapi.tests.unit.api import common
from gceapi.tests.unit.api import fake_request
REGION = fake_request.REGION
EXPECTED_REGIONS = [
{
"id": "1905250285734383880",
"id": "8220497844553564918",
"kind": "compute#region",
"selfLink": "http://localhost/compute/v1beta15/projects/fake_project"
"/regions/RegionOne",
"name": "RegionOne",
"/regions/%s" % REGION,
"name": REGION,
"status": "UP",
"zones": [
"http://localhost/compute/v1beta15/projects/fake_project"
"/zones/nova"]
"http://localhost/compute/v1beta15/projects/fake_project"
"/zones/nova"
]
},
]
@ -46,14 +49,14 @@ class RegionsTest(common.GCEControllerTest):
self.assertEqual(404, response.status_int)
def test_get_region(self):
response = self.request_gce('/fake_project/regions/RegionOne')
response = self.request_gce('/fake_project/regions/%s' % REGION)
expected = EXPECTED_REGIONS[0]
self.assertEqual(response.json_body, expected)
def test_get_region_list_filtered(self):
response = self.request_gce("/fake_project/regions"
"?filter=name+eq+RegionOne")
"?filter=name+eq+%s" % REGION)
expected = {
"kind": "compute#regionList",
"id": "projects/fake_project/regions",

View File

@ -14,16 +14,18 @@
from gceapi.api import zones
from gceapi.tests.unit.api import common
from gceapi.tests.unit.api import fake_request
REGION = fake_request.REGION
EXPECTED_ZONES = [{
"id": "3924463100986466035",
"kind": "compute#zone",
"selfLink": "http://localhost/compute/v1beta15/projects/fake_project"
"/zones/nova",
"/zones/nova",
"name": "nova",
"status": "UP",
"region": "RegionOne",
"region": REGION,
}]