diff --git a/README.rst b/README.rst index dd3ae14..07a7f3e 100644 --- a/README.rst +++ b/README.rst @@ -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 ======================== diff --git a/gceapi/api/base_api.py b/gceapi/api/base_api.py index f3a7278..2ed4663 100644 --- a/gceapi/api/base_api.py +++ b/gceapi/api/base_api.py @@ -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 diff --git a/gceapi/api/clients.py b/gceapi/api/clients.py index 38d930e..70c5746 100644 --- a/gceapi/api/clients.py +++ b/gceapi/api/clients.py @@ -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 diff --git a/gceapi/api/discovery.py b/gceapi/api/discovery.py index c30fcc3..a5adcbd 100644 --- a/gceapi/api/discovery.py +++ b/gceapi/api/discovery.py @@ -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") diff --git a/gceapi/api/machine_type_api.py b/gceapi/api/machine_type_api.py index c95ae63..1c700f7 100644 --- a/gceapi/api/machine_type_api.py +++ b/gceapi/api/machine_type_api.py @@ -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(".", "-") diff --git a/gceapi/api/oauth.py b/gceapi/api/oauth.py index e657391..9cb932f 100644 --- a/gceapi/api/oauth.py +++ b/gceapi/api/oauth.py @@ -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 diff --git a/gceapi/api/region_api.py b/gceapi/api/region_api.py index 3c2b0e1..92675c3 100644 --- a/gceapi/api/region_api.py +++ b/gceapi/api/region_api.py @@ -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 diff --git a/gceapi/tests/contrib/post_test_hook.sh b/gceapi/tests/contrib/post_test_hook.sh index 0597184..727c7e2 100755 --- a/gceapi/tests/contrib/post_test_hook.sh +++ b/gceapi/tests/contrib/post_test_hook.sh @@ -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} diff --git a/gceapi/tests/functional/api/test_addresses.py b/gceapi/tests/functional/api/test_addresses.py new file mode 100644 index 0000000..8d071db --- /dev/null +++ b/gceapi/tests/functional/api/test_addresses.py @@ -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() diff --git a/gceapi/tests/functional/api/test_instances.py b/gceapi/tests/functional/api/test_instances.py index fd770ce..32ac023 100644 --- a/gceapi/tests/functional/api/test_instances.py +++ b/gceapi/tests/functional/api/test_instances.py @@ -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() diff --git a/gceapi/tests/functional/api/test_networks.py b/gceapi/tests/functional/api/test_networks.py new file mode 100644 index 0000000..5acc484 --- /dev/null +++ b/gceapi/tests/functional/api/test_networks.py @@ -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() diff --git a/gceapi/tests/functional/api/test_regions.py b/gceapi/tests/functional/api/test_regions.py index d66304f..2ca92d6 100644 --- a/gceapi/tests/functional/api/test_regions.py +++ b/gceapi/tests/functional/api/test_regions.py @@ -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 diff --git a/gceapi/tests/functional/base.py b/gceapi/tests/functional/base.py index 9234795..cbf2649 100644 --- a/gceapi/tests/functional/base.py +++ b/gceapi/tests/functional/base.py @@ -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): diff --git a/gceapi/tests/functional/config.py b/gceapi/tests/functional/config.py index 09f62e9..2452437 100644 --- a/gceapi/tests/functional/config.py +++ b/gceapi/tests/functional/config.py @@ -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', diff --git a/gceapi/tests/functional/credentials.py b/gceapi/tests/functional/credentials.py index 59d9f44..ee6c236 100644 --- a/gceapi/tests/functional/credentials.py +++ b/gceapi/tests/functional/credentials.py @@ -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: diff --git a/gceapi/tests/functional/test_base.py b/gceapi/tests/functional/test_base.py index 245f667..1d84647 100644 --- a/gceapi/tests/functional/test_base.py +++ b/gceapi/tests/functional/test_base.py @@ -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) diff --git a/gceapi/tests/unit/api/common.py b/gceapi/tests/unit/api/common.py index b6a4457..23f93ff 100644 --- a/gceapi/tests/unit/api/common.py +++ b/gceapi/tests/unit/api/common.py @@ -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) diff --git a/gceapi/tests/unit/api/fake_request.py b/gceapi/tests/unit/api/fake_request.py index 47e4cc5..c0414ba 100644 --- a/gceapi/tests/unit/api/fake_request.py +++ b/gceapi/tests/unit/api/fake_request.py @@ -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}], diff --git a/gceapi/tests/unit/api/test_addresses.py b/gceapi/tests/unit/api/test_addresses.py index 03faec3..961b8a0 100644 --- a/gceapi/tests/unit/api/test_addresses.py +++ b/gceapi/tests/unit/api/test_addresses.py @@ -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) diff --git a/gceapi/tests/unit/api/test_regions.py b/gceapi/tests/unit/api/test_regions.py index e4443f5..3cd8e0f 100644 --- a/gceapi/tests/unit/api/test_regions.py +++ b/gceapi/tests/unit/api/test_regions.py @@ -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", diff --git a/gceapi/tests/unit/api/test_zones.py b/gceapi/tests/unit/api/test_zones.py index ef7133a..781a37d 100644 --- a/gceapi/tests/unit/api/test_zones.py +++ b/gceapi/tests/unit/api/test_zones.py @@ -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, }]