Neutron LBaaS: Load Balancer Basic Scenario Test
* copied and modified for LBaaS 2.0 from the Tempest scenario test Change-Id: I932f2ccd6025b535028a035263953c066b7ba2f2 Co-Authored-By: Trevor Vardemon <trevor.vardeman@RACKSPACE.COM> Co-Authored-By: Phillip Toohill <phillip.toohill@RACKSPACE.COM> Co-Authored-By: Brandon Logan <brandon.logan@RACKSPACE.COM> Co-Authored-By: Madhusudhan Kandadai <madhusudhan.kandadai@hp.com>
This commit is contained in:
parent
fa365a6cb4
commit
c93da091f1
|
@ -1,47 +0,0 @@
|
|||
[DEFAULT]
|
||||
# Leaving this as a placeholder
|
||||
verbose=false
|
||||
debug=false
|
||||
use_stderr=false
|
||||
lock_path = /opt/stack/data/tempest
|
||||
|
||||
[identity]
|
||||
# Replace these with values that represent your identity configuration
|
||||
uri=http://localhost:5000/v2.0
|
||||
uri_v3=http://localhost:5000/v3
|
||||
auth_version=v2
|
||||
region=RegionOne
|
||||
|
||||
admin_domain_name = Default
|
||||
admin_tenant_id = 3c1f71f1a5c446d199809bd2f21d87ff
|
||||
admin_tenant_name = admin
|
||||
admin_password = password
|
||||
admin_username = admin
|
||||
|
||||
alt_tenant_name = alt_demo
|
||||
alt_password = password
|
||||
alt_username = alt_demo
|
||||
|
||||
tenant_name = demo
|
||||
password = password
|
||||
username = demo
|
||||
|
||||
[service_available]
|
||||
tuskar = False
|
||||
neutron = True
|
||||
heat = False
|
||||
ceilometer = False
|
||||
swift = False
|
||||
cinder = False
|
||||
nova = True
|
||||
glance = True
|
||||
horizon = False
|
||||
|
||||
[lbaas]
|
||||
# These are not currently being pulled in.
|
||||
# Need to implement CONF strategy for this.
|
||||
catalog_type=network
|
||||
region=RegionOne
|
||||
endpoint_type=publicURL
|
||||
build_interval=10
|
||||
build_timeout=300
|
|
@ -0,0 +1,58 @@
|
|||
# (c) 2014 Deutsche Telekom AG
|
||||
# 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.
|
||||
|
||||
common_flavor_details = {
|
||||
"name": "get-flavor-details",
|
||||
"http-method": "GET",
|
||||
"url": "flavors/%s",
|
||||
"resources": [
|
||||
{"name": "flavor", "expected_result": 404}
|
||||
]
|
||||
}
|
||||
|
||||
common_flavor_list = {
|
||||
"name": "list-flavors-with-detail",
|
||||
"http-method": "GET",
|
||||
"url": "flavors/detail",
|
||||
"json-schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
common_admin_flavor_create = {
|
||||
"name": "flavor-create",
|
||||
"http-method": "POST",
|
||||
"admin_client": True,
|
||||
"url": "flavors",
|
||||
"default_result_code": 400,
|
||||
"json-schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"flavor": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string",
|
||||
"exclude_tests": ["gen_str_min_length"]},
|
||||
"ram": {"type": "integer", "minimum": 1},
|
||||
"vcpus": {"type": "integer", "minimum": 1},
|
||||
"disk": {"type": "integer"},
|
||||
"id": {"type": "integer",
|
||||
"exclude_tests": ["gen_none", "gen_string"]
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
# (c) 2014 Deutsche Telekom AG
|
||||
# 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 neutron_lbaas.tests.tempest.lib.api_schema.request.compute import flavors
|
||||
|
||||
flavors_details = copy.deepcopy(flavors.common_flavor_details)
|
||||
|
||||
flavor_list = copy.deepcopy(flavors.common_flavor_list)
|
||||
|
||||
flavor_create = copy.deepcopy(flavors.common_admin_flavor_create)
|
||||
|
||||
flavor_list["json-schema"]["properties"] = {
|
||||
"minRam": {
|
||||
"type": "integer",
|
||||
"results": {
|
||||
"gen_none": 400,
|
||||
"gen_string": 400
|
||||
}
|
||||
},
|
||||
"minDisk": {
|
||||
"type": "integer",
|
||||
"results": {
|
||||
"gen_none": 400,
|
||||
"gen_string": 400
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
# Copyright 2014 NEC Corporation. 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 neutron_lbaas.tests.tempest.lib.api_schema.response.compute.v2_1 \
|
||||
import parameter_types
|
||||
|
||||
list_flavors = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'flavors': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': {'type': 'string'},
|
||||
'links': parameter_types.links,
|
||||
'id': {'type': 'string'}
|
||||
},
|
||||
'required': ['name', 'links', 'id']
|
||||
}
|
||||
},
|
||||
'flavors_links': parameter_types.links
|
||||
},
|
||||
# NOTE(gmann): flavors_links attribute is not necessary
|
||||
# to be present always So it is not 'required'.
|
||||
'required': ['flavors']
|
||||
}
|
||||
}
|
||||
|
||||
common_flavor_info = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': {'type': 'string'},
|
||||
'links': parameter_types.links,
|
||||
'ram': {'type': 'integer'},
|
||||
'vcpus': {'type': 'integer'},
|
||||
# 'swap' attributes comes as integer value but if it is empty
|
||||
# it comes as "". So defining type of as string and integer.
|
||||
'swap': {'type': ['integer', 'string']},
|
||||
'disk': {'type': 'integer'},
|
||||
'id': {'type': 'string'},
|
||||
'OS-FLV-DISABLED:disabled': {'type': 'boolean'},
|
||||
'os-flavor-access:is_public': {'type': 'boolean'},
|
||||
'rxtx_factor': {'type': 'number'},
|
||||
'OS-FLV-EXT-DATA:ephemeral': {'type': 'integer'}
|
||||
},
|
||||
# 'OS-FLV-DISABLED', 'os-flavor-access', 'rxtx_factor' and
|
||||
# 'OS-FLV-EXT-DATA' are API extensions. So they are not 'required'.
|
||||
'required': ['name', 'links', 'ram', 'vcpus', 'swap', 'disk', 'id']
|
||||
}
|
||||
|
||||
list_flavors_details = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'flavors': {
|
||||
'type': 'array',
|
||||
'items': common_flavor_info
|
||||
},
|
||||
# NOTE(gmann): flavors_links attribute is not necessary
|
||||
# to be present always So it is not 'required'.
|
||||
'flavors_links': parameter_types.links
|
||||
},
|
||||
'required': ['flavors']
|
||||
}
|
||||
}
|
||||
|
||||
unset_flavor_extra_specs = {
|
||||
'status_code': [200]
|
||||
}
|
||||
|
||||
create_get_flavor_details = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'flavor': common_flavor_info
|
||||
},
|
||||
'required': ['flavor']
|
||||
}
|
||||
}
|
||||
|
||||
delete_flavor = {
|
||||
'status_code': [202]
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
# Copyright 2014 NEC Corporation. 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.
|
||||
|
||||
add_remove_list_flavor_access = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'flavor_access': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'flavor_id': {'type': 'string'},
|
||||
'tenant_id': {'type': 'string'},
|
||||
},
|
||||
'required': ['flavor_id', 'tenant_id'],
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': ['flavor_access']
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
# Copyright 2014 NEC Corporation. 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.
|
||||
|
||||
set_get_flavor_extra_specs = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'extra_specs': {
|
||||
'type': 'object',
|
||||
'patternProperties': {
|
||||
'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': ['extra_specs']
|
||||
}
|
||||
}
|
||||
|
||||
set_get_flavor_extra_specs_key = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'patternProperties': {
|
||||
'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
# Copyright 2014 NEC Corporation. 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.
|
||||
|
||||
common_floating_ip_info = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
# NOTE: Now the type of 'id' is integer, but
|
||||
# here allows 'string' also because we will be
|
||||
# able to change it to 'uuid' in the future.
|
||||
'id': {'type': ['integer', 'string']},
|
||||
'pool': {'type': ['string', 'null']},
|
||||
'instance_id': {'type': ['string', 'null']},
|
||||
'ip': {
|
||||
'type': 'string',
|
||||
'format': 'ip-address'
|
||||
},
|
||||
'fixed_ip': {
|
||||
'type': ['string', 'null'],
|
||||
'format': 'ip-address'
|
||||
}
|
||||
},
|
||||
'required': ['id', 'pool', 'instance_id',
|
||||
'ip', 'fixed_ip'],
|
||||
|
||||
}
|
||||
list_floating_ips = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'floating_ips': {
|
||||
'type': 'array',
|
||||
'items': common_floating_ip_info
|
||||
},
|
||||
},
|
||||
'required': ['floating_ips'],
|
||||
}
|
||||
}
|
||||
|
||||
create_get_floating_ip = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'floating_ip': common_floating_ip_info
|
||||
},
|
||||
'required': ['floating_ip'],
|
||||
}
|
||||
}
|
||||
|
||||
list_floating_ip_pools = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'floating_ip_pools': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': {'type': 'string'}
|
||||
},
|
||||
'required': ['name'],
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': ['floating_ip_pools'],
|
||||
}
|
||||
}
|
||||
|
||||
add_remove_floating_ip = {
|
||||
'status_code': [202]
|
||||
}
|
||||
|
||||
create_floating_ips_bulk = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'floating_ips_bulk_create': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'interface': {'type': ['string', 'null']},
|
||||
'ip_range': {'type': 'string'},
|
||||
'pool': {'type': ['string', 'null']},
|
||||
},
|
||||
'required': ['interface', 'ip_range', 'pool'],
|
||||
}
|
||||
},
|
||||
'required': ['floating_ips_bulk_create'],
|
||||
}
|
||||
}
|
||||
|
||||
delete_floating_ips_bulk = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'floating_ips_bulk_delete': {'type': 'string'}
|
||||
},
|
||||
'required': ['floating_ips_bulk_delete'],
|
||||
}
|
||||
}
|
||||
|
||||
list_floating_ips_bulk = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'floating_ip_info': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'address': {
|
||||
'type': 'string',
|
||||
'format': 'ip-address'
|
||||
},
|
||||
'instance_uuid': {'type': ['string', 'null']},
|
||||
'interface': {'type': ['string', 'null']},
|
||||
'pool': {'type': ['string', 'null']},
|
||||
'project_id': {'type': ['string', 'null']},
|
||||
'fixed_ip': {
|
||||
'type': ['string', 'null'],
|
||||
'format': 'ip-address'
|
||||
}
|
||||
},
|
||||
# NOTE: fixed_ip is introduced after JUNO release,
|
||||
# So it is not defined as 'required'.
|
||||
'required': ['address', 'instance_uuid', 'interface',
|
||||
'pool', 'project_id'],
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': ['floating_ip_info'],
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
# Copyright 2014 NEC Corporation. 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.
|
||||
|
||||
import copy
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.api_schema.response.compute.v2_1 \
|
||||
import parameter_types
|
||||
|
||||
image_links = copy.deepcopy(parameter_types.links)
|
||||
image_links['items']['properties'].update({'type': {'type': 'string'}})
|
||||
|
||||
common_image_schema = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': 'string'},
|
||||
'status': {'type': 'string'},
|
||||
'updated': {'type': 'string'},
|
||||
'links': image_links,
|
||||
'name': {'type': 'string'},
|
||||
'created': {'type': 'string'},
|
||||
'minDisk': {'type': 'integer'},
|
||||
'minRam': {'type': 'integer'},
|
||||
'progress': {'type': 'integer'},
|
||||
'metadata': {'type': 'object'},
|
||||
'server': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': 'string'},
|
||||
'links': parameter_types.links
|
||||
},
|
||||
'required': ['id', 'links']
|
||||
},
|
||||
'OS-EXT-IMG-SIZE:size': {'type': 'integer'},
|
||||
'OS-DCF:diskConfig': {'type': 'string'}
|
||||
},
|
||||
# 'server' attributes only comes in response body if image is
|
||||
# associated with any server. 'OS-EXT-IMG-SIZE:size' & 'OS-DCF:diskConfig'
|
||||
# are API extension, So those are not defined as 'required'.
|
||||
'required': ['id', 'status', 'updated', 'links', 'name',
|
||||
'created', 'minDisk', 'minRam', 'progress',
|
||||
'metadata']
|
||||
}
|
||||
|
||||
get_image = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'image': common_image_schema
|
||||
},
|
||||
'required': ['image']
|
||||
}
|
||||
}
|
||||
|
||||
list_images = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'images': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': 'string'},
|
||||
'links': image_links,
|
||||
'name': {'type': 'string'}
|
||||
},
|
||||
'required': ['id', 'links', 'name']
|
||||
}
|
||||
},
|
||||
'images_links': parameter_types.links
|
||||
},
|
||||
# NOTE(gmann): images_links attribute is not necessary to be
|
||||
# present always So it is not 'required'.
|
||||
'required': ['images']
|
||||
}
|
||||
}
|
||||
|
||||
create_image = {
|
||||
'status_code': [202],
|
||||
'response_header': {
|
||||
'type': 'object',
|
||||
'properties': parameter_types.response_header
|
||||
}
|
||||
}
|
||||
create_image['response_header']['properties'].update(
|
||||
{'location': {
|
||||
'type': 'string',
|
||||
'format': 'uri'}
|
||||
}
|
||||
)
|
||||
create_image['response_header']['required'] = ['location']
|
||||
|
||||
delete = {
|
||||
'status_code': [204]
|
||||
}
|
||||
|
||||
image_metadata = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'metadata': {'type': 'object'}
|
||||
},
|
||||
'required': ['metadata']
|
||||
}
|
||||
}
|
||||
|
||||
image_meta_item = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'meta': {'type': 'object'}
|
||||
},
|
||||
'required': ['meta']
|
||||
}
|
||||
}
|
||||
|
||||
list_images_details = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'images': {
|
||||
'type': 'array',
|
||||
'items': common_image_schema
|
||||
},
|
||||
'images_links': parameter_types.links
|
||||
},
|
||||
# NOTE(gmann): images_links attribute is not necessary to be
|
||||
# present always So it is not 'required'.
|
||||
'required': ['images']
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
# Copyright 2014 NEC Corporation. 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 neutron_lbaas.tests.tempest.lib.api_schema.response.compute.v2_1 \
|
||||
import parameter_types
|
||||
|
||||
interface_common_info = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'port_state': {'type': 'string'},
|
||||
'fixed_ips': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'subnet_id': {
|
||||
'type': 'string',
|
||||
'format': 'uuid'
|
||||
},
|
||||
'ip_address': {
|
||||
'type': 'string',
|
||||
'format': 'ipv4'
|
||||
}
|
||||
},
|
||||
'required': ['subnet_id', 'ip_address']
|
||||
}
|
||||
},
|
||||
'port_id': {'type': 'string', 'format': 'uuid'},
|
||||
'net_id': {'type': 'string', 'format': 'uuid'},
|
||||
'mac_addr': parameter_types.mac_address
|
||||
},
|
||||
'required': ['port_state', 'fixed_ips', 'port_id', 'net_id', 'mac_addr']
|
||||
}
|
||||
|
||||
get_create_interfaces = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'interfaceAttachment': interface_common_info
|
||||
},
|
||||
'required': ['interfaceAttachment']
|
||||
}
|
||||
}
|
||||
|
||||
list_interfaces = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'interfaceAttachments': {
|
||||
'type': 'array',
|
||||
'items': interface_common_info
|
||||
}
|
||||
},
|
||||
'required': ['interfaceAttachments']
|
||||
}
|
||||
}
|
||||
|
||||
delete_interface = {
|
||||
'status_code': [202]
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
# Copyright 2014 NEC Corporation. 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.
|
||||
|
||||
get_keypair = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'keypair': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'public_key': {'type': 'string'},
|
||||
'name': {'type': 'string'},
|
||||
'fingerprint': {'type': 'string'},
|
||||
'user_id': {'type': 'string'},
|
||||
'deleted': {'type': 'boolean'},
|
||||
'created_at': {'type': 'string'},
|
||||
'updated_at': {'type': ['string', 'null']},
|
||||
'deleted_at': {'type': ['string', 'null']},
|
||||
'id': {'type': 'integer'}
|
||||
|
||||
},
|
||||
# When we run the get keypair API, response body includes
|
||||
# all the above mentioned attributes.
|
||||
# But in Nova API sample file, response body includes only
|
||||
# 'public_key', 'name' & 'fingerprint'. So only 'public_key',
|
||||
# 'name' & 'fingerprint' are defined as 'required'.
|
||||
'required': ['public_key', 'name', 'fingerprint']
|
||||
}
|
||||
},
|
||||
'required': ['keypair']
|
||||
}
|
||||
}
|
||||
|
||||
create_keypair = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'keypair': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'fingerprint': {'type': 'string'},
|
||||
'name': {'type': 'string'},
|
||||
'public_key': {'type': 'string'},
|
||||
'user_id': {'type': 'string'},
|
||||
'private_key': {'type': 'string'}
|
||||
},
|
||||
# When create keypair API is being called with 'Public key'
|
||||
# (Importing keypair) then, response body does not contain
|
||||
# 'private_key' So it is not defined as 'required'
|
||||
'required': ['fingerprint', 'name', 'public_key', 'user_id']
|
||||
}
|
||||
},
|
||||
'required': ['keypair']
|
||||
}
|
||||
}
|
||||
|
||||
delete_keypair = {
|
||||
'status_code': [202],
|
||||
}
|
||||
|
||||
list_keypairs = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'keypairs': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'keypair': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'public_key': {'type': 'string'},
|
||||
'name': {'type': 'string'},
|
||||
'fingerprint': {'type': 'string'}
|
||||
},
|
||||
'required': ['public_key', 'name', 'fingerprint']
|
||||
}
|
||||
},
|
||||
'required': ['keypair']
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': ['keypairs']
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
# Copyright 2014 NEC Corporation. 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.
|
||||
|
||||
links = {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'href': {
|
||||
'type': 'string',
|
||||
'format': 'uri'
|
||||
},
|
||||
'rel': {'type': 'string'}
|
||||
},
|
||||
'additionalProperties': False,
|
||||
'required': ['href', 'rel']
|
||||
}
|
||||
}
|
||||
|
||||
mac_address = {
|
||||
'type': 'string',
|
||||
'pattern': '(?:[a-f0-9]{2}:){5}[a-f0-9]{2}'
|
||||
}
|
||||
|
||||
access_ip_v4 = {
|
||||
'type': 'string',
|
||||
'anyOf': [{'format': 'ipv4'}, {'enum': ['']}]
|
||||
}
|
||||
|
||||
access_ip_v6 = {
|
||||
'type': 'string',
|
||||
'anyOf': [{'format': 'ipv6'}, {'enum': ['']}]
|
||||
}
|
||||
|
||||
addresses = {
|
||||
'type': 'object',
|
||||
'patternProperties': {
|
||||
# NOTE: Here is for 'private' or something.
|
||||
'^[a-zA-Z0-9-_.]+$': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'version': {'type': 'integer'},
|
||||
'addr': {
|
||||
'type': 'string',
|
||||
'anyOf': [
|
||||
{'format': 'ipv4'},
|
||||
{'format': 'ipv6'}
|
||||
]
|
||||
}
|
||||
},
|
||||
'additionalProperties': False,
|
||||
'required': ['version', 'addr']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response_header = {
|
||||
'connection': {'type': 'string'},
|
||||
'content-length': {'type': 'string'},
|
||||
'content-type': {'type': 'string'},
|
||||
'status': {'type': 'string'},
|
||||
'x-compute-request-id': {'type': 'string'},
|
||||
'vary': {'type': 'string'},
|
||||
'x-openstack-nova-api-version': {'type': 'string'},
|
||||
'date': {
|
||||
'type': 'string',
|
||||
'format': 'data-time'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
# Copyright 2014 NEC Corporation. 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.
|
||||
|
||||
common_security_group_rule = {
|
||||
'from_port': {'type': ['integer', 'null']},
|
||||
'to_port': {'type': ['integer', 'null']},
|
||||
'group': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'tenant_id': {'type': 'string'},
|
||||
'name': {'type': 'string'}
|
||||
}
|
||||
},
|
||||
'ip_protocol': {'type': ['string', 'null']},
|
||||
# 'parent_group_id' can be UUID so defining it as 'string' also.
|
||||
'parent_group_id': {'type': ['string', 'integer', 'null']},
|
||||
'ip_range': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'cidr': {'type': 'string'}
|
||||
}
|
||||
# When optional argument is provided in request body
|
||||
# like 'group_id' then, attribute 'cidr' does not
|
||||
# comes in response body. So it is not 'required'.
|
||||
},
|
||||
'id': {'type': ['string', 'integer']}
|
||||
}
|
||||
|
||||
common_security_group = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': ['integer', 'string']},
|
||||
'name': {'type': 'string'},
|
||||
'tenant_id': {'type': 'string'},
|
||||
'rules': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': ['object', 'null'],
|
||||
'properties': common_security_group_rule
|
||||
}
|
||||
},
|
||||
'description': {'type': 'string'},
|
||||
},
|
||||
'required': ['id', 'name', 'tenant_id', 'rules', 'description'],
|
||||
}
|
||||
|
||||
list_security_groups = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'security_groups': {
|
||||
'type': 'array',
|
||||
'items': common_security_group
|
||||
}
|
||||
},
|
||||
'required': ['security_groups']
|
||||
}
|
||||
}
|
||||
|
||||
get_security_group = create_security_group = update_security_group = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'security_group': common_security_group
|
||||
},
|
||||
'required': ['security_group']
|
||||
}
|
||||
}
|
||||
|
||||
delete_security_group = {
|
||||
'status_code': [202]
|
||||
}
|
||||
|
||||
create_security_group_rule = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'security_group_rule': {
|
||||
'type': 'object',
|
||||
'properties': common_security_group_rule,
|
||||
'required': ['from_port', 'to_port', 'group', 'ip_protocol',
|
||||
'parent_group_id', 'id', 'ip_range']
|
||||
}
|
||||
},
|
||||
'required': ['security_group_rule']
|
||||
}
|
||||
}
|
||||
|
||||
delete_security_group_rule = {
|
||||
'status_code': [202]
|
||||
}
|
|
@ -0,0 +1,519 @@
|
|||
# Copyright 2014 NEC Corporation. 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.
|
||||
|
||||
import copy
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.api_schema.response.compute.v2_1 \
|
||||
import parameter_types
|
||||
|
||||
create_server = {
|
||||
'status_code': [202],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'server': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': 'string'},
|
||||
'security_groups': {'type': 'array'},
|
||||
'links': parameter_types.links,
|
||||
'OS-DCF:diskConfig': {'type': 'string'}
|
||||
},
|
||||
# NOTE: OS-DCF:diskConfig & security_groups are API extension,
|
||||
# and some environments return a response without these
|
||||
# attributes.So they are not 'required'.
|
||||
'required': ['id', 'links']
|
||||
}
|
||||
},
|
||||
'required': ['server']
|
||||
}
|
||||
}
|
||||
|
||||
create_server_with_admin_pass = copy.deepcopy(create_server)
|
||||
create_server_with_admin_pass['response_body']['properties']['server'][
|
||||
'properties'].update({'adminPass': {'type': 'string'}})
|
||||
create_server_with_admin_pass['response_body']['properties']['server'][
|
||||
'required'].append('adminPass')
|
||||
|
||||
list_servers = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'servers': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': 'string'},
|
||||
'links': parameter_types.links,
|
||||
'name': {'type': 'string'}
|
||||
},
|
||||
'required': ['id', 'links', 'name']
|
||||
}
|
||||
},
|
||||
'servers_links': parameter_types.links
|
||||
},
|
||||
# NOTE(gmann): servers_links attribute is not necessary to be
|
||||
# present always So it is not 'required'.
|
||||
'required': ['servers']
|
||||
}
|
||||
}
|
||||
|
||||
delete_server = {
|
||||
'status_code': [204],
|
||||
}
|
||||
|
||||
common_show_server = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': 'string'},
|
||||
'name': {'type': 'string'},
|
||||
'status': {'type': 'string'},
|
||||
'image': {'oneOf': [
|
||||
{'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': 'string'},
|
||||
'links': parameter_types.links
|
||||
},
|
||||
'required': ['id', 'links']},
|
||||
{'type': ['string', 'null']}
|
||||
]},
|
||||
'flavor': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': 'string'},
|
||||
'links': parameter_types.links
|
||||
},
|
||||
'required': ['id', 'links']
|
||||
},
|
||||
'fault': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'code': {'type': 'integer'},
|
||||
'created': {'type': 'string'},
|
||||
'message': {'type': 'string'},
|
||||
'details': {'type': 'string'},
|
||||
},
|
||||
# NOTE(gmann): 'details' is not necessary to be present
|
||||
# in the 'fault'. So it is not defined as 'required'.
|
||||
'required': ['code', 'created', 'message']
|
||||
},
|
||||
'user_id': {'type': 'string'},
|
||||
'tenant_id': {'type': 'string'},
|
||||
'created': {'type': 'string'},
|
||||
'updated': {'type': 'string'},
|
||||
'progress': {'type': 'integer'},
|
||||
'metadata': {'type': 'object'},
|
||||
'links': parameter_types.links,
|
||||
'addresses': parameter_types.addresses,
|
||||
'hostId': {'type': 'string'},
|
||||
'OS-DCF:diskConfig': {'type': 'string'},
|
||||
'accessIPv4': parameter_types.access_ip_v4,
|
||||
'accessIPv6': parameter_types.access_ip_v6
|
||||
},
|
||||
# NOTE(GMann): 'progress' attribute is present in the response
|
||||
# only when server's status is one of the progress statuses
|
||||
# ("ACTIVE","BUILD", "REBUILD", "RESIZE","VERIFY_RESIZE")
|
||||
# 'fault' attribute is present in the response
|
||||
# only when server's status is one of the "ERROR", "DELETED".
|
||||
# OS-DCF:diskConfig and accessIPv4/v6 are API
|
||||
# extensions, and some environments return a response
|
||||
# without these attributes.So these are not defined as 'required'.
|
||||
'required': ['id', 'name', 'status', 'image', 'flavor',
|
||||
'user_id', 'tenant_id', 'created', 'updated',
|
||||
'metadata', 'links', 'addresses', 'hostId']
|
||||
}
|
||||
|
||||
update_server = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'server': common_show_server
|
||||
},
|
||||
'required': ['server']
|
||||
}
|
||||
}
|
||||
|
||||
server_detail = copy.deepcopy(common_show_server)
|
||||
server_detail['properties'].update({
|
||||
'key_name': {'type': ['string', 'null']},
|
||||
'security_groups': {'type': 'array'},
|
||||
|
||||
# NOTE: Non-admin users also can see "OS-SRV-USG" and "OS-EXT-AZ"
|
||||
# attributes.
|
||||
'OS-SRV-USG:launched_at': {'type': ['string', 'null']},
|
||||
'OS-SRV-USG:terminated_at': {'type': ['string', 'null']},
|
||||
'OS-EXT-AZ:availability_zone': {'type': 'string'},
|
||||
|
||||
# NOTE: Admin users only can see "OS-EXT-STS" and "OS-EXT-SRV-ATTR"
|
||||
# attributes.
|
||||
'OS-EXT-STS:task_state': {'type': ['string', 'null']},
|
||||
'OS-EXT-STS:vm_state': {'type': 'string'},
|
||||
'OS-EXT-STS:power_state': {'type': 'integer'},
|
||||
'OS-EXT-SRV-ATTR:host': {'type': ['string', 'null']},
|
||||
'OS-EXT-SRV-ATTR:instance_name': {'type': 'string'},
|
||||
'OS-EXT-SRV-ATTR:hypervisor_hostname': {'type': ['string', 'null']},
|
||||
'os-extended-volumes:volumes_attached': {'type': 'array'},
|
||||
'config_drive': {'type': 'string'}
|
||||
})
|
||||
server_detail['properties']['addresses']['patternProperties'][
|
||||
'^[a-zA-Z0-9-_.]+$']['items']['properties'].update({
|
||||
'OS-EXT-IPS:type': {'type': 'string'},
|
||||
'OS-EXT-IPS-MAC:mac_addr': parameter_types.mac_address})
|
||||
# NOTE(gmann): Update OS-EXT-IPS:type and OS-EXT-IPS-MAC:mac_addr
|
||||
# attributes in server address. Those are API extension,
|
||||
# and some environments return a response without
|
||||
# these attributes. So they are not 'required'.
|
||||
|
||||
get_server = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'server': server_detail
|
||||
},
|
||||
'required': ['server']
|
||||
}
|
||||
}
|
||||
|
||||
list_servers_detail = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'servers': {
|
||||
'type': 'array',
|
||||
'items': server_detail
|
||||
},
|
||||
'servers_links': parameter_types.links
|
||||
},
|
||||
# NOTE(gmann): servers_links attribute is not necessary to be
|
||||
# present always So it is not 'required'.
|
||||
'required': ['servers']
|
||||
}
|
||||
}
|
||||
|
||||
rebuild_server = copy.deepcopy(update_server)
|
||||
rebuild_server['status_code'] = [202]
|
||||
|
||||
rebuild_server_with_admin_pass = copy.deepcopy(rebuild_server)
|
||||
rebuild_server_with_admin_pass['response_body']['properties']['server'][
|
||||
'properties'].update({'adminPass': {'type': 'string'}})
|
||||
rebuild_server_with_admin_pass['response_body']['properties']['server'][
|
||||
'required'].append('adminPass')
|
||||
|
||||
rescue_server = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'adminPass': {'type': 'string'}
|
||||
},
|
||||
'required': ['adminPass']
|
||||
}
|
||||
}
|
||||
|
||||
list_virtual_interfaces = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'virtual_interfaces': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': 'string'},
|
||||
'mac_address': parameter_types.mac_address,
|
||||
'OS-EXT-VIF-NET:net_id': {'type': 'string'}
|
||||
},
|
||||
# 'OS-EXT-VIF-NET:net_id' is API extension So it is
|
||||
# not defined as 'required'
|
||||
'required': ['id', 'mac_address']
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': ['virtual_interfaces']
|
||||
}
|
||||
}
|
||||
|
||||
common_attach_volume_info = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': 'string'},
|
||||
'device': {'type': 'string'},
|
||||
'volumeId': {'type': 'string'},
|
||||
'serverId': {'type': ['integer', 'string']}
|
||||
},
|
||||
'required': ['id', 'device', 'volumeId', 'serverId']
|
||||
}
|
||||
|
||||
attach_volume = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'volumeAttachment': common_attach_volume_info
|
||||
},
|
||||
'required': ['volumeAttachment']
|
||||
}
|
||||
}
|
||||
|
||||
detach_volume = {
|
||||
'status_code': [202]
|
||||
}
|
||||
|
||||
get_volume_attachment = copy.deepcopy(attach_volume)
|
||||
get_volume_attachment['response_body']['properties'][
|
||||
'volumeAttachment']['properties'].update({'serverId': {'type': 'string'}})
|
||||
|
||||
list_volume_attachments = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'volumeAttachments': {
|
||||
'type': 'array',
|
||||
'items': common_attach_volume_info
|
||||
}
|
||||
},
|
||||
'required': ['volumeAttachments']
|
||||
}
|
||||
}
|
||||
list_volume_attachments['response_body']['properties'][
|
||||
'volumeAttachments']['items']['properties'].update(
|
||||
{'serverId': {'type': 'string'}})
|
||||
|
||||
list_addresses_by_network = {
|
||||
'status_code': [200],
|
||||
'response_body': parameter_types.addresses
|
||||
}
|
||||
|
||||
list_addresses = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'addresses': parameter_types.addresses
|
||||
},
|
||||
'required': ['addresses']
|
||||
}
|
||||
}
|
||||
|
||||
common_server_group = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': 'string'},
|
||||
'name': {'type': 'string'},
|
||||
'policies': {
|
||||
'type': 'array',
|
||||
'items': {'type': 'string'}
|
||||
},
|
||||
# 'members' attribute contains the array of instance's UUID of
|
||||
# instances present in server group
|
||||
'members': {
|
||||
'type': 'array',
|
||||
'items': {'type': 'string'}
|
||||
},
|
||||
'metadata': {'type': 'object'}
|
||||
},
|
||||
'required': ['id', 'name', 'policies', 'members', 'metadata']
|
||||
}
|
||||
|
||||
create_get_server_group = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'server_group': common_server_group
|
||||
},
|
||||
'required': ['server_group']
|
||||
}
|
||||
}
|
||||
|
||||
delete_server_group = {
|
||||
'status_code': [204]
|
||||
}
|
||||
|
||||
list_server_groups = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'server_groups': {
|
||||
'type': 'array',
|
||||
'items': common_server_group
|
||||
}
|
||||
},
|
||||
'required': ['server_groups']
|
||||
}
|
||||
}
|
||||
|
||||
instance_actions = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'action': {'type': 'string'},
|
||||
'request_id': {'type': 'string'},
|
||||
'user_id': {'type': 'string'},
|
||||
'project_id': {'type': 'string'},
|
||||
'start_time': {'type': 'string'},
|
||||
'message': {'type': ['string', 'null']},
|
||||
'instance_uuid': {'type': 'string'}
|
||||
},
|
||||
'required': ['action', 'request_id', 'user_id', 'project_id',
|
||||
'start_time', 'message', 'instance_uuid']
|
||||
}
|
||||
|
||||
instance_action_events = {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'event': {'type': 'string'},
|
||||
'start_time': {'type': 'string'},
|
||||
'finish_time': {'type': 'string'},
|
||||
'result': {'type': 'string'},
|
||||
'traceback': {'type': ['string', 'null']}
|
||||
},
|
||||
'required': ['event', 'start_time', 'finish_time', 'result',
|
||||
'traceback']
|
||||
}
|
||||
}
|
||||
|
||||
list_instance_actions = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'instanceActions': {
|
||||
'type': 'array',
|
||||
'items': instance_actions
|
||||
}
|
||||
},
|
||||
'required': ['instanceActions']
|
||||
}
|
||||
}
|
||||
|
||||
instance_actions_with_events = copy.deepcopy(instance_actions)
|
||||
instance_actions_with_events['properties'].update({
|
||||
'events': instance_action_events})
|
||||
# 'events' does not come in response body always so it is not
|
||||
# defined as 'required'
|
||||
|
||||
get_instance_action = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'instanceAction': instance_actions_with_events
|
||||
},
|
||||
'required': ['instanceAction']
|
||||
}
|
||||
}
|
||||
|
||||
get_password = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'password': {'type': 'string'}
|
||||
},
|
||||
'required': ['password']
|
||||
}
|
||||
}
|
||||
|
||||
get_vnc_console = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'console': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'type': {'type': 'string'},
|
||||
'url': {
|
||||
'type': 'string',
|
||||
'format': 'uri'
|
||||
}
|
||||
},
|
||||
'required': ['type', 'url']
|
||||
}
|
||||
},
|
||||
'required': ['console']
|
||||
}
|
||||
}
|
||||
|
||||
get_console_output = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'output': {'type': 'string'}
|
||||
},
|
||||
'required': ['output']
|
||||
}
|
||||
}
|
||||
|
||||
set_server_metadata = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'metadata': {
|
||||
'type': 'object',
|
||||
'patternProperties': {
|
||||
'^.+$': {'type': 'string'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': ['metadata']
|
||||
}
|
||||
}
|
||||
|
||||
list_server_metadata = copy.deepcopy(set_server_metadata)
|
||||
|
||||
update_server_metadata = copy.deepcopy(set_server_metadata)
|
||||
|
||||
delete_server_metadata_item = {
|
||||
'status_code': [204]
|
||||
}
|
||||
|
||||
set_get_server_metadata_item = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'meta': {
|
||||
'type': 'object',
|
||||
'patternProperties': {
|
||||
'^.+$': {'type': 'string'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': ['meta']
|
||||
}
|
||||
}
|
||||
|
||||
server_actions_common_schema = {
|
||||
'status_code': [202]
|
||||
}
|
||||
|
||||
server_actions_delete_password = {
|
||||
'status_code': [204]
|
||||
}
|
||||
|
||||
server_actions_confirm_resize = copy.deepcopy(
|
||||
server_actions_delete_password)
|
|
@ -0,0 +1,50 @@
|
|||
# Copyright 2015 NEC Corporation. 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.
|
||||
|
||||
param_network = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {'type': 'string'},
|
||||
'cidr': {'type': ['string', 'null']},
|
||||
'label': {'type': 'string'}
|
||||
},
|
||||
'required': ['id', 'cidr', 'label']
|
||||
}
|
||||
|
||||
|
||||
list_tenant_networks = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'networks': {
|
||||
'type': 'array',
|
||||
'items': param_network
|
||||
}
|
||||
},
|
||||
'required': ['networks']
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
get_tenant_network = {
|
||||
'status_code': [200],
|
||||
'response_body': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'network': param_network
|
||||
},
|
||||
'required': ['network']
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ from tempest_lib.services.identity.v2.token_client import TokenClientJSON
|
|||
from tempest_lib.services.identity.v3.token_client import V3TokenClientJSON
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.common import cred_provider
|
||||
from neutron_lbaas.tests.tempest.lib.common import negative_rest_client
|
||||
# from neutron_lbaas.tests.tempest.lib.common import negative_rest_client
|
||||
from neutron_lbaas.tests.tempest.lib import config
|
||||
from neutron_lbaas.tests.tempest.lib import exceptions
|
||||
from neutron_lbaas.tests.tempest.lib import manager
|
||||
|
@ -40,44 +40,44 @@ from neutron_lbaas.tests.tempest.lib import manager
|
|||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.extensions_client import \
|
||||
# ExtensionsClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.fixed_ips_client import FixedIPsClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.flavors_client import FlavorsClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.floating_ips_client import \
|
||||
# FloatingIPsClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.compute.json.flavors_client import FlavorsClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.compute.json.floating_ips_client import \
|
||||
FloatingIPsClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.hosts_client import HostsClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.hypervisor_client import \
|
||||
# HypervisorClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.images_client import ImagesClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.compute.json.images_client import ImagesClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.instance_usage_audit_log_client import \
|
||||
# InstanceUsagesAuditLogClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.interfaces_client import \
|
||||
# InterfacesClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.keypairs_client import KeyPairsClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.compute.json.interfaces_client import \
|
||||
InterfacesClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.compute.json.keypairs_client import KeyPairsClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.limits_client import LimitsClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.migrations_client import \
|
||||
# MigrationsClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.networks_client import NetworksClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.compute.json.networks_client import NetworksClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.quotas_client import QuotaClassesClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.quotas_client import QuotasClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.security_group_default_rules_client import \
|
||||
# SecurityGroupDefaultRulesClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.security_groups_client import \
|
||||
# SecurityGroupsClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.servers_client import ServersClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.compute.json.security_groups_client import \
|
||||
SecurityGroupsClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.compute.json.servers_client import ServersClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.services_client import ServicesClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.tenant_networks_client import \
|
||||
# TenantNetworksClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.compute.json.tenant_networks_client import \
|
||||
TenantNetworksClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.tenant_usages_client import \
|
||||
# TenantUsagesClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.compute.json.volumes_extensions_client import \
|
||||
# VolumesExtensionsClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.data_processing.v1_1.data_processing_client import \
|
||||
# DataProcessingClient
|
||||
# from neutron_lbaas.tests.tempest.lib.services.database.json.flavors_client import \
|
||||
# DatabaseFlavorsClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.database.json.limits_client import \
|
||||
# DatabaseLimitsClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.database.json.versions_client import \
|
||||
# DatabaseVersionsClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.database.json.flavors_client import \
|
||||
DatabaseFlavorsClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.database.json.limits_client import \
|
||||
DatabaseLimitsClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.database.json.versions_client import \
|
||||
DatabaseVersionsClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.identity.v2.json.identity_client import \
|
||||
IdentityClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.identity.v3.json.credentials_client import \
|
||||
|
@ -90,16 +90,16 @@ from neutron_lbaas.tests.tempest.lib.services.identity.v3.json.policy_client imp
|
|||
from neutron_lbaas.tests.tempest.lib.services.identity.v3.json.region_client import RegionClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.identity.v3.json.service_client import \
|
||||
ServiceClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.image.v1.json.image_client import ImageClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.image.v2.json.image_client import ImageClientV2JSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.image.v1.json.image_client import ImageClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.image.v2.json.image_client import ImageClientV2JSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.messaging.json.messaging_client import \
|
||||
# MessagingClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.network.json.network_client import NetworkClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.object_storage.account_client import AccountClient
|
||||
# from neutron_lbaas.tests.tempest.lib.services.object_storage.container_client import ContainerClient
|
||||
# from neutron_lbaas.tests.tempest.lib.services.object_storage.object_client import ObjectClient
|
||||
# from neutron_lbaas.tests.tempest.lib.services.orchestration.json.orchestration_client import \
|
||||
# OrchestrationClient
|
||||
from neutron_lbaas.tests.tempest.lib.services.orchestration.json.orchestration_client import \
|
||||
OrchestrationClient
|
||||
# from neutron_lbaas.tests.tempest.lib.services.telemetry.json.telemetry_client import \
|
||||
# TelemetryClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.volume.json.admin.volume_hosts_client import \
|
||||
|
@ -116,8 +116,8 @@ from neutron_lbaas.tests.tempest.lib.services.network.json.network_client import
|
|||
# from neutron_lbaas.tests.tempest.lib.services.volume.json.extensions_client import \
|
||||
# ExtensionsClientJSON as VolumeExtensionClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.volume.json.qos_client import QosSpecsClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.volume.json.snapshots_client import SnapshotsClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.volume.json.volumes_client import VolumesClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.volume.json.snapshots_client import SnapshotsClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.volume.json.volumes_client import VolumesClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.volume.v2.json.admin.volume_hosts_client import \
|
||||
# VolumeHostsV2ClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.volume.v2.json.admin.volume_quotas_client import \
|
||||
|
@ -132,9 +132,9 @@ from neutron_lbaas.tests.tempest.lib.services.network.json.network_client import
|
|||
# from neutron_lbaas.tests.tempest.lib.services.volume.v2.json.extensions_client import \
|
||||
# ExtensionsV2ClientJSON as VolumeV2ExtensionClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.volume.v2.json.qos_client import QosSpecsV2ClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.volume.v2.json.snapshots_client import \
|
||||
# SnapshotsV2ClientJSON
|
||||
# from neutron_lbaas.tests.tempest.lib.services.volume.v2.json.volumes_client import VolumesV2ClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.volume.v2.json.snapshots_client import \
|
||||
SnapshotsV2ClientJSON
|
||||
from neutron_lbaas.tests.tempest.lib.services.volume.v2.json.volumes_client import VolumesV2ClientJSON
|
||||
|
||||
CONF = config.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -164,10 +164,10 @@ class Manager(manager.Manager):
|
|||
def __init__(self, credentials=None, service=None):
|
||||
super(Manager, self).__init__(credentials=credentials)
|
||||
|
||||
# self._set_compute_clients()
|
||||
# self._set_database_clients()
|
||||
self._set_compute_clients()
|
||||
self._set_database_clients()
|
||||
self._set_identity_clients()
|
||||
# self._set_volume_clients()
|
||||
self._set_volume_clients()
|
||||
# self._set_object_storage_clients()
|
||||
|
||||
# self.baremetal_client = BaremetalClientJSON(
|
||||
|
@ -196,31 +196,31 @@ class Manager(manager.Manager):
|
|||
# CONF.identity.region,
|
||||
# endpoint_type=CONF.telemetry.endpoint_type,
|
||||
# **self.default_params_with_timeout_values)
|
||||
# if CONF.service_available.glance:
|
||||
# self.image_client = ImageClientJSON(
|
||||
# self.auth_provider,
|
||||
# CONF.image.catalog_type,
|
||||
# CONF.image.region or CONF.identity.region,
|
||||
# endpoint_type=CONF.image.endpoint_type,
|
||||
# build_interval=CONF.image.build_interval,
|
||||
# build_timeout=CONF.image.build_timeout,
|
||||
# **self.default_params)
|
||||
# self.image_client_v2 = ImageClientV2JSON(
|
||||
# self.auth_provider,
|
||||
# CONF.image.catalog_type,
|
||||
# CONF.image.region or CONF.identity.region,
|
||||
# endpoint_type=CONF.image.endpoint_type,
|
||||
# build_interval=CONF.image.build_interval,
|
||||
# build_timeout=CONF.image.build_timeout,
|
||||
# **self.default_params)
|
||||
# self.orchestration_client = OrchestrationClient(
|
||||
# self.auth_provider,
|
||||
# CONF.orchestration.catalog_type,
|
||||
# CONF.orchestration.region or CONF.identity.region,
|
||||
# endpoint_type=CONF.orchestration.endpoint_type,
|
||||
# build_interval=CONF.orchestration.build_interval,
|
||||
# build_timeout=CONF.orchestration.build_timeout,
|
||||
# **self.default_params)
|
||||
if CONF.service_available.glance:
|
||||
self.image_client = ImageClientJSON(
|
||||
self.auth_provider,
|
||||
CONF.image.catalog_type,
|
||||
CONF.image.region or CONF.identity.region,
|
||||
endpoint_type=CONF.image.endpoint_type,
|
||||
build_interval=CONF.image.build_interval,
|
||||
build_timeout=CONF.image.build_timeout,
|
||||
**self.default_params)
|
||||
self.image_client_v2 = ImageClientV2JSON(
|
||||
self.auth_provider,
|
||||
CONF.image.catalog_type,
|
||||
CONF.image.region or CONF.identity.region,
|
||||
endpoint_type=CONF.image.endpoint_type,
|
||||
build_interval=CONF.image.build_interval,
|
||||
build_timeout=CONF.image.build_timeout,
|
||||
**self.default_params)
|
||||
self.orchestration_client = OrchestrationClient(
|
||||
self.auth_provider,
|
||||
CONF.orchestration.catalog_type,
|
||||
CONF.orchestration.region or CONF.identity.region,
|
||||
endpoint_type=CONF.orchestration.endpoint_type,
|
||||
build_interval=CONF.orchestration.build_interval,
|
||||
build_timeout=CONF.orchestration.build_timeout,
|
||||
**self.default_params)
|
||||
# self.data_processing_client = DataProcessingClient(
|
||||
# self.auth_provider,
|
||||
# CONF.data_processing.catalog_type,
|
||||
|
@ -239,44 +239,43 @@ class Manager(manager.Manager):
|
|||
# self.ec2api_client = botoclients.APIClientEC2(self.identity_client)
|
||||
# self.s3_client = botoclients.ObjectClientS3(self.identity_client)
|
||||
|
||||
# def _set_compute_clients(self):
|
||||
# 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)
|
||||
def _set_compute_clients(self):
|
||||
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.agents_client = AgentsClientJSON(self.auth_provider, **params)
|
||||
# self.networks_client = NetworksClientJSON(self.auth_provider, **params)
|
||||
self.networks_client = NetworksClientJSON(self.auth_provider, **params)
|
||||
# self.migrations_client = MigrationsClientJSON(self.auth_provider,
|
||||
# **params)
|
||||
# self.security_group_default_rules_client = (
|
||||
# SecurityGroupDefaultRulesClientJSON(self.auth_provider, **params))
|
||||
# self.certificates_client = CertificatesClientJSON(self.auth_provider,
|
||||
# **params)
|
||||
# self.servers_client = ServersClientJSON(
|
||||
# self.auth_provider,
|
||||
# enable_instance_password=CONF.compute_feature_enabled
|
||||
# .enable_instance_password,
|
||||
# **params)
|
||||
self.servers_client = ServersClientJSON(
|
||||
self.auth_provider,
|
||||
enable_instance_password=CONF.compute_feature_enabled
|
||||
.enable_instance_password, **params)
|
||||
# self.limits_client = LimitsClientJSON(self.auth_provider, **params)
|
||||
# self.images_client = ImagesClientJSON(self.auth_provider, **params)
|
||||
# self.keypairs_client = KeyPairsClientJSON(self.auth_provider, **params)
|
||||
self.images_client = ImagesClientJSON(self.auth_provider, **params)
|
||||
self.keypairs_client = KeyPairsClientJSON(self.auth_provider, **params)
|
||||
# self.quotas_client = QuotasClientJSON(self.auth_provider, **params)
|
||||
# self.quota_classes_client = QuotaClassesClientJSON(self.auth_provider,
|
||||
# **params)
|
||||
# self.flavors_client = FlavorsClientJSON(self.auth_provider, **params)
|
||||
self.flavors_client = FlavorsClientJSON(self.auth_provider, **params)
|
||||
# self.extensions_client = ExtensionsClientJSON(self.auth_provider,
|
||||
# **params)
|
||||
# self.floating_ips_client = FloatingIPsClientJSON(self.auth_provider,
|
||||
# **params)
|
||||
# self.security_groups_client = SecurityGroupsClientJSON(
|
||||
# self.auth_provider, **params)
|
||||
# self.interfaces_client = InterfacesClientJSON(self.auth_provider,
|
||||
# **params)
|
||||
self.floating_ips_client = FloatingIPsClientJSON(self.auth_provider,
|
||||
**params)
|
||||
self.security_groups_client = SecurityGroupsClientJSON(
|
||||
self.auth_provider, **params)
|
||||
self.interfaces_client = InterfacesClientJSON(self.auth_provider,
|
||||
**params)
|
||||
# self.fixed_ips_client = FixedIPsClientJSON(self.auth_provider,
|
||||
# **params)
|
||||
# self.availability_zone_client = AvailabilityZoneClientJSON(
|
||||
|
@ -291,8 +290,8 @@ class Manager(manager.Manager):
|
|||
# **params)
|
||||
# self.instance_usages_audit_log_client = \
|
||||
# InstanceUsagesAuditLogClientJSON(self.auth_provider, **params)
|
||||
# self.tenant_networks_client = \
|
||||
# TenantNetworksClientJSON(self.auth_provider, **params)
|
||||
self.tenant_networks_client = \
|
||||
TenantNetworksClientJSON(self.auth_provider, **params)
|
||||
# self.baremetal_nodes_client = BaremetalNodesClientJSON(
|
||||
# self.auth_provider, **params)
|
||||
|
||||
|
@ -307,22 +306,22 @@ class Manager(manager.Manager):
|
|||
# self.auth_provider, default_volume_size=CONF.volume.volume_size,
|
||||
# **params_volume)
|
||||
|
||||
# def _set_database_clients(self):
|
||||
# self.database_flavors_client = DatabaseFlavorsClientJSON(
|
||||
# self.auth_provider,
|
||||
# CONF.database.catalog_type,
|
||||
# CONF.identity.region,
|
||||
# **self.default_params_with_timeout_values)
|
||||
# self.database_limits_client = DatabaseLimitsClientJSON(
|
||||
# self.auth_provider,
|
||||
# CONF.database.catalog_type,
|
||||
# CONF.identity.region,
|
||||
# **self.default_params_with_timeout_values)
|
||||
# self.database_versions_client = DatabaseVersionsClientJSON(
|
||||
# self.auth_provider,
|
||||
# CONF.database.catalog_type,
|
||||
# CONF.identity.region,
|
||||
# **self.default_params_with_timeout_values)
|
||||
def _set_database_clients(self):
|
||||
self.database_flavors_client = DatabaseFlavorsClientJSON(
|
||||
self.auth_provider,
|
||||
CONF.database.catalog_type,
|
||||
CONF.identity.region,
|
||||
**self.default_params_with_timeout_values)
|
||||
self.database_limits_client = DatabaseLimitsClientJSON(
|
||||
self.auth_provider,
|
||||
CONF.database.catalog_type,
|
||||
CONF.identity.region,
|
||||
**self.default_params_with_timeout_values)
|
||||
self.database_versions_client = DatabaseVersionsClientJSON(
|
||||
self.auth_provider,
|
||||
CONF.database.catalog_type,
|
||||
CONF.identity.region,
|
||||
**self.default_params_with_timeout_values)
|
||||
|
||||
def _set_identity_clients(self):
|
||||
params = {
|
||||
|
@ -361,15 +360,15 @@ class Manager(manager.Manager):
|
|||
msg = 'Identity v3 API enabled, but no identity.uri_v3 set'
|
||||
raise exceptions.InvalidConfiguration(msg)
|
||||
|
||||
# def _set_volume_clients(self):
|
||||
# params = {
|
||||
# 'service': CONF.volume.catalog_type,
|
||||
# 'region': CONF.volume.region or CONF.identity.region,
|
||||
# 'endpoint_type': CONF.volume.endpoint_type,
|
||||
# 'build_interval': CONF.volume.build_interval,
|
||||
# 'build_timeout': CONF.volume.build_timeout
|
||||
# }
|
||||
# params.update(self.default_params)
|
||||
def _set_volume_clients(self):
|
||||
params = {
|
||||
'service': CONF.volume.catalog_type,
|
||||
'region': CONF.volume.region or CONF.identity.region,
|
||||
'endpoint_type': CONF.volume.endpoint_type,
|
||||
'build_interval': CONF.volume.build_interval,
|
||||
'build_timeout': CONF.volume.build_timeout
|
||||
}
|
||||
params.update(self.default_params)
|
||||
|
||||
# self.volume_qos_client = QosSpecsClientJSON(self.auth_provider,
|
||||
# **params)
|
||||
|
@ -380,16 +379,16 @@ class Manager(manager.Manager):
|
|||
# self.backups_client = BackupsClientJSON(self.auth_provider, **params)
|
||||
# self.backups_v2_client = BackupsClientV2JSON(self.auth_provider,
|
||||
# **params)
|
||||
# self.snapshots_client = SnapshotsClientJSON(self.auth_provider,
|
||||
# **params)
|
||||
# self.snapshots_v2_client = SnapshotsV2ClientJSON(self.auth_provider,
|
||||
# **params)
|
||||
# self.volumes_client = VolumesClientJSON(
|
||||
# self.auth_provider, default_volume_size=CONF.volume.volume_size,
|
||||
# **params)
|
||||
# self.volumes_v2_client = VolumesV2ClientJSON(
|
||||
# self.auth_provider, default_volume_size=CONF.volume.volume_size,
|
||||
# **params)
|
||||
self.snapshots_client = SnapshotsClientJSON(self.auth_provider,
|
||||
**params)
|
||||
self.snapshots_v2_client = SnapshotsV2ClientJSON(self.auth_provider,
|
||||
**params)
|
||||
self.volumes_client = VolumesClientJSON(
|
||||
self.auth_provider, default_volume_size=CONF.volume.volume_size,
|
||||
**params)
|
||||
self.volumes_v2_client = VolumesV2ClientJSON(
|
||||
self.auth_provider, default_volume_size=CONF.volume.volume_size,
|
||||
**params)
|
||||
# self.volume_types_client = VolumeTypesClientJSON(self.auth_provider,
|
||||
# **params)
|
||||
# self.volume_services_client = VolumesServicesClientJSON(
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# Copyright 2013 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 os
|
||||
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture as conf_fixture
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib import config
|
||||
|
||||
|
||||
class ConfigFixture(conf_fixture.Config):
|
||||
|
||||
def __init__(self):
|
||||
config.register_opts()
|
||||
super(ConfigFixture, self).__init__()
|
||||
|
||||
def setUp(self):
|
||||
super(ConfigFixture, self).setUp()
|
||||
self.conf.set_default('build_interval', 10, group='compute')
|
||||
self.conf.set_default('build_timeout', 10, group='compute')
|
||||
self.conf.set_default('disable_ssl_certificate_validation', True,
|
||||
group='identity')
|
||||
self.conf.set_default('uri', 'http://fake_uri.com/auth',
|
||||
group='identity')
|
||||
self.conf.set_default('uri_v3', 'http://fake_uri_v3.com/auth',
|
||||
group='identity')
|
||||
self.conf.set_default('neutron', True, group='service_available')
|
||||
self.conf.set_default('heat', True, group='service_available')
|
||||
if not os.path.exists(str(os.environ.get('OS_TEST_LOCK_PATH'))):
|
||||
os.mkdir(str(os.environ.get('OS_TEST_LOCK_PATH')))
|
||||
lockutils.set_defaults(
|
||||
lock_path=str(os.environ.get('OS_TEST_LOCK_PATH')),
|
||||
)
|
||||
self.conf.set_default('auth_version', 'v2', group='identity')
|
||||
for config_option in ['username', 'password', 'tenant_name']:
|
||||
# Identity group items
|
||||
for prefix in ['', 'alt_', 'admin_']:
|
||||
self.conf.set_default(prefix + config_option,
|
||||
'fake_' + config_option,
|
||||
group='identity')
|
||||
|
||||
|
||||
class FakePrivate(config.TempestConfigPrivate):
|
||||
def __init__(self, parse_conf=True, config_path=None):
|
||||
cfg.CONF([], default_config_files=[])
|
||||
self._set_attrs()
|
||||
self.lock_path = cfg.CONF.lock_path
|
|
@ -0,0 +1,3 @@
|
|||
PING_IPV4_COMMAND = 'ping -c 3 '
|
||||
PING_IPV6_COMMAND = 'ping6 -c 3 '
|
||||
PING_PACKET_LOSS_REGEX = '(\d{1,3})\.?\d*\% packet loss'
|
|
@ -0,0 +1,170 @@
|
|||
# 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 netaddr
|
||||
import re
|
||||
import time
|
||||
|
||||
import six
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib import config
|
||||
from neutron_lbaas.tests.tempest.lib import exceptions
|
||||
from neutron_lbaas.tests.tempest.lib.common import ssh
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class RemoteClient(object):
|
||||
|
||||
# NOTE(afazekas): It should always get an address instead of server
|
||||
def __init__(self, server, username, password=None, pkey=None):
|
||||
ssh_timeout = CONF.compute.ssh_timeout
|
||||
network = CONF.compute.network_for_ssh
|
||||
ip_version = CONF.compute.ip_version_for_ssh
|
||||
ssh_channel_timeout = CONF.compute.ssh_channel_timeout
|
||||
if isinstance(server, six.string_types):
|
||||
ip_address = server
|
||||
else:
|
||||
addresses = server['addresses'][network]
|
||||
for address in addresses:
|
||||
if address['version'] == ip_version:
|
||||
ip_address = address['addr']
|
||||
break
|
||||
else:
|
||||
raise exceptions.ServerUnreachable()
|
||||
self.ssh_client = ssh.Client(ip_address, username, password,
|
||||
ssh_timeout, pkey=pkey,
|
||||
channel_timeout=ssh_channel_timeout)
|
||||
|
||||
def exec_command(self, cmd):
|
||||
# Shell options below add more clearness on failures,
|
||||
# path is extended for some non-cirros guest oses (centos7)
|
||||
cmd = "set -eu -o pipefail; PATH=$PATH:/sbin; " + cmd
|
||||
return self.ssh_client.exec_command(cmd)
|
||||
|
||||
def validate_authentication(self):
|
||||
"""Validate ssh connection and authentication
|
||||
This method raises an Exception when the validation fails.
|
||||
"""
|
||||
self.ssh_client.test_connection_auth()
|
||||
|
||||
def hostname_equals_servername(self, expected_hostname):
|
||||
# Get host name using command "hostname"
|
||||
actual_hostname = self.exec_command("hostname").rstrip()
|
||||
return expected_hostname == actual_hostname
|
||||
|
||||
def get_ram_size_in_mb(self):
|
||||
output = self.exec_command('free -m | grep Mem')
|
||||
if output:
|
||||
return output.split()[1]
|
||||
|
||||
def get_number_of_vcpus(self):
|
||||
command = 'cat /proc/cpuinfo | grep processor | wc -l'
|
||||
output = self.exec_command(command)
|
||||
return int(output)
|
||||
|
||||
def get_partitions(self):
|
||||
# Return the contents of /proc/partitions
|
||||
command = 'cat /proc/partitions'
|
||||
output = self.exec_command(command)
|
||||
return output
|
||||
|
||||
def get_boot_time(self):
|
||||
cmd = 'cut -f1 -d. /proc/uptime'
|
||||
boot_secs = self.exec_command(cmd)
|
||||
boot_time = time.time() - int(boot_secs)
|
||||
return time.localtime(boot_time)
|
||||
|
||||
def write_to_console(self, message):
|
||||
message = re.sub("([$\\`])", "\\\\\\\\\\1", message)
|
||||
# usually to /dev/ttyS0
|
||||
cmd = 'sudo sh -c "echo \\"%s\\" >/dev/console"' % message
|
||||
return self.exec_command(cmd)
|
||||
|
||||
def ping_host(self, host, count=CONF.compute.ping_count,
|
||||
size=CONF.compute.ping_size):
|
||||
addr = netaddr.IPAddress(host)
|
||||
cmd = 'ping6' if addr.version == 6 else 'ping'
|
||||
cmd += ' -c{0} -w{0} -s{1} {2}'.format(count, size, host)
|
||||
return self.exec_command(cmd)
|
||||
|
||||
def get_mac_address(self):
|
||||
cmd = "ip addr | awk '/ether/ {print $2}'"
|
||||
return self.exec_command(cmd)
|
||||
|
||||
def get_nic_name(self, address):
|
||||
cmd = "ip -o addr | awk '/%s/ {print $2}'" % address
|
||||
return self.exec_command(cmd)
|
||||
|
||||
def get_ip_list(self):
|
||||
cmd = "ip address"
|
||||
return self.exec_command(cmd)
|
||||
|
||||
def assign_static_ip(self, nic, addr):
|
||||
cmd = "sudo ip addr add {ip}/{mask} dev {nic}".format(
|
||||
ip=addr, mask=CONF.network.tenant_network_mask_bits,
|
||||
nic=nic
|
||||
)
|
||||
return self.exec_command(cmd)
|
||||
|
||||
def turn_nic_on(self, nic):
|
||||
cmd = "sudo ip link set {nic} up".format(nic=nic)
|
||||
return self.exec_command(cmd)
|
||||
|
||||
def get_pids(self, pr_name):
|
||||
# Get pid(s) of a process/program
|
||||
cmd = "ps -ef | grep %s | grep -v 'grep' | awk {'print $1'}" % pr_name
|
||||
return self.exec_command(cmd).split('\n')
|
||||
|
||||
def get_dns_servers(self):
|
||||
cmd = 'cat /etc/resolv.conf'
|
||||
resolve_file = self.exec_command(cmd).strip().split('\n')
|
||||
entries = (l.split() for l in resolve_file)
|
||||
dns_servers = [l[1] for l in entries
|
||||
if len(l) and l[0] == 'nameserver']
|
||||
return dns_servers
|
||||
|
||||
def send_signal(self, pid, signum):
|
||||
cmd = 'sudo /bin/kill -{sig} {pid}'.format(pid=pid, sig=signum)
|
||||
return self.exec_command(cmd)
|
||||
|
||||
def _renew_lease_udhcpc(self, fixed_ip=None):
|
||||
"""Renews DHCP lease via udhcpc client. """
|
||||
file_path = '/var/run/udhcpc.'
|
||||
nic_name = self.get_nic_name(fixed_ip)
|
||||
nic_name = nic_name.strip().lower()
|
||||
pid = self.exec_command('cat {path}{nic}.pid'.
|
||||
format(path=file_path, nic=nic_name))
|
||||
pid = pid.strip()
|
||||
self.send_signal(pid, 'USR1')
|
||||
|
||||
def _renew_lease_dhclient(self, fixed_ip=None):
|
||||
"""Renews DHCP lease via dhclient client. """
|
||||
cmd = "sudo /sbin/dhclient -r && sudo /sbin/dhclient"
|
||||
self.exec_command(cmd)
|
||||
|
||||
def renew_lease(self, fixed_ip=None):
|
||||
"""Wrapper method for renewing DHCP lease via given client
|
||||
|
||||
Supporting:
|
||||
* udhcpc
|
||||
* dhclient
|
||||
"""
|
||||
# TODO(yfried): add support for dhcpcd
|
||||
suported_clients = ['udhcpc', 'dhclient']
|
||||
dhcp_client = CONF.scenario.dhcp_client
|
||||
if dhcp_client not in suported_clients:
|
||||
raise exceptions.InvalidConfiguration('%s DHCP client unsupported'
|
||||
% dhcp_client)
|
||||
if dhcp_client == 'udhcpc' and not fixed_ip:
|
||||
raise ValueError("need to set 'fixed_ip' for udhcpc client")
|
||||
return getattr(self, '_renew_lease_' + dhcp_client)(fixed_ip=fixed_ip)
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,178 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
import json
|
||||
|
||||
from six.moves.urllib import parse as urllib
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.api_schema.response.compute.v2_1 import flavors as schema
|
||||
from neutron_lbaas.tests.tempest.lib.api_schema.response.compute.v2_1 import flavors_access as schema_access
|
||||
from neutron_lbaas.tests.tempest.lib.api_schema.response.compute.v2_1 import flavors_extra_specs as schema_extra_specs
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
|
||||
|
||||
class FlavorsClientJSON(service_client.ServiceClient):
|
||||
|
||||
def list_flavors(self, params=None):
|
||||
url = 'flavors'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_flavors, resp, body)
|
||||
return service_client.ResponseBodyList(resp, body['flavors'])
|
||||
|
||||
def list_flavors_with_detail(self, params=None):
|
||||
url = 'flavors/detail'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_flavors_details, resp, body)
|
||||
return service_client.ResponseBodyList(resp, body['flavors'])
|
||||
|
||||
def show_flavor(self, flavor_id):
|
||||
resp, body = self.get("flavors/%s" % str(flavor_id))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.create_get_flavor_details, resp, body)
|
||||
return service_client.ResponseBody(resp, body['flavor'])
|
||||
|
||||
def create_flavor(self, name, ram, vcpus, disk, flavor_id, **kwargs):
|
||||
"""Creates a new flavor or instance type."""
|
||||
post_body = {
|
||||
'name': name,
|
||||
'ram': ram,
|
||||
'vcpus': vcpus,
|
||||
'disk': disk,
|
||||
'id': flavor_id,
|
||||
}
|
||||
if kwargs.get('ephemeral'):
|
||||
post_body['OS-FLV-EXT-DATA:ephemeral'] = kwargs.get('ephemeral')
|
||||
if kwargs.get('swap'):
|
||||
post_body['swap'] = kwargs.get('swap')
|
||||
if kwargs.get('rxtx'):
|
||||
post_body['rxtx_factor'] = kwargs.get('rxtx')
|
||||
if kwargs.get('is_public'):
|
||||
post_body['os-flavor-access:is_public'] = kwargs.get('is_public')
|
||||
post_body = json.dumps({'flavor': post_body})
|
||||
resp, body = self.post('flavors', post_body)
|
||||
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.create_get_flavor_details, resp, body)
|
||||
return service_client.ResponseBody(resp, body['flavor'])
|
||||
|
||||
def delete_flavor(self, flavor_id):
|
||||
"""Deletes the given flavor."""
|
||||
resp, body = self.delete("flavors/{0}".format(flavor_id))
|
||||
self.validate_response(schema.delete_flavor, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
# Did not use show_flavor(id) for verification as it gives
|
||||
# 200 ok even for deleted id. LP #981263
|
||||
# we can remove the loop here and use get by ID when bug gets sortedout
|
||||
flavors = self.list_flavors_with_detail()
|
||||
for flavor in flavors:
|
||||
if flavor['id'] == id:
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def resource_type(self):
|
||||
"""Returns the primary type of resource this client works with."""
|
||||
return 'flavor'
|
||||
|
||||
def set_flavor_extra_spec(self, flavor_id, specs):
|
||||
"""Sets extra Specs to the mentioned flavor."""
|
||||
post_body = json.dumps({'extra_specs': specs})
|
||||
resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
|
||||
post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema_extra_specs.set_get_flavor_extra_specs,
|
||||
resp, body)
|
||||
return service_client.ResponseBody(resp, body['extra_specs'])
|
||||
|
||||
def list_flavor_extra_specs(self, flavor_id):
|
||||
"""Gets extra Specs details of the mentioned flavor."""
|
||||
resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema_extra_specs.set_get_flavor_extra_specs,
|
||||
resp, body)
|
||||
return service_client.ResponseBody(resp, body['extra_specs'])
|
||||
|
||||
def show_flavor_extra_spec(self, flavor_id, key):
|
||||
"""Gets extra Specs key-value of the mentioned flavor and key."""
|
||||
resp, body = self.get('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
|
||||
key))
|
||||
body = json.loads(body)
|
||||
self.validate_response(
|
||||
schema_extra_specs.set_get_flavor_extra_specs_key,
|
||||
resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
|
||||
"""Update specified extra Specs of the mentioned flavor and key."""
|
||||
resp, body = self.put('flavors/%s/os-extra_specs/%s' %
|
||||
(flavor_id, key), json.dumps(kwargs))
|
||||
body = json.loads(body)
|
||||
self.validate_response(
|
||||
schema_extra_specs.set_get_flavor_extra_specs_key,
|
||||
resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def unset_flavor_extra_spec(self, flavor_id, key):
|
||||
"""Unsets extra Specs from the mentioned flavor."""
|
||||
resp, body = self.delete('flavors/%s/os-extra_specs/%s' %
|
||||
(str(flavor_id), key))
|
||||
self.validate_response(schema.unset_flavor_extra_specs, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def list_flavor_access(self, flavor_id):
|
||||
"""Gets flavor access information given the flavor id."""
|
||||
resp, body = self.get('flavors/%s/os-flavor-access' % flavor_id)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema_access.add_remove_list_flavor_access,
|
||||
resp, body)
|
||||
return service_client.ResponseBodyList(resp, body['flavor_access'])
|
||||
|
||||
def add_flavor_access(self, flavor_id, tenant_id):
|
||||
"""Add flavor access for the specified tenant."""
|
||||
post_body = {
|
||||
'addTenantAccess': {
|
||||
'tenant': tenant_id
|
||||
}
|
||||
}
|
||||
post_body = json.dumps(post_body)
|
||||
resp, body = self.post('flavors/%s/action' % flavor_id, post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema_access.add_remove_list_flavor_access,
|
||||
resp, body)
|
||||
return service_client.ResponseBodyList(resp, body['flavor_access'])
|
||||
|
||||
def remove_flavor_access(self, flavor_id, tenant_id):
|
||||
"""Remove flavor access from the specified tenant."""
|
||||
post_body = {
|
||||
'removeTenantAccess': {
|
||||
'tenant': tenant_id
|
||||
}
|
||||
}
|
||||
post_body = json.dumps(post_body)
|
||||
resp, body = self.post('flavors/%s/action' % flavor_id, post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema_access.add_remove_list_flavor_access,
|
||||
resp, body)
|
||||
return service_client.ResponseBody(resp, body['flavor_access'])
|
|
@ -0,0 +1,142 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
import json
|
||||
|
||||
from six.moves.urllib import parse as urllib
|
||||
from tempest_lib import exceptions as lib_exc
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.api_schema.response.compute.v2_1 import floating_ips as schema
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
|
||||
|
||||
class FloatingIPsClientJSON(service_client.ServiceClient):
|
||||
|
||||
def list_floating_ips(self, params=None):
|
||||
"""Returns a list of all floating IPs filtered by any parameters."""
|
||||
url = 'os-floating-ips'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_floating_ips, resp, body)
|
||||
return service_client.ResponseBodyList(resp, body['floating_ips'])
|
||||
|
||||
def show_floating_ip(self, floating_ip_id):
|
||||
"""Get the details of a floating IP."""
|
||||
url = "os-floating-ips/%s" % str(floating_ip_id)
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.create_get_floating_ip, resp, body)
|
||||
return service_client.ResponseBody(resp, body['floating_ip'])
|
||||
|
||||
def create_floating_ip(self, pool_name=None):
|
||||
"""Allocate a floating IP to the project."""
|
||||
url = 'os-floating-ips'
|
||||
post_body = {'pool': pool_name}
|
||||
post_body = json.dumps(post_body)
|
||||
resp, body = self.post(url, post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.create_get_floating_ip, resp, body)
|
||||
return service_client.ResponseBody(resp, body['floating_ip'])
|
||||
|
||||
def delete_floating_ip(self, floating_ip_id):
|
||||
"""Deletes the provided floating IP from the project."""
|
||||
url = "os-floating-ips/%s" % str(floating_ip_id)
|
||||
resp, body = self.delete(url)
|
||||
self.validate_response(schema.add_remove_floating_ip, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def associate_floating_ip_to_server(self, floating_ip, server_id):
|
||||
"""Associate the provided floating IP to a specific server."""
|
||||
url = "servers/%s/action" % str(server_id)
|
||||
post_body = {
|
||||
'addFloatingIp': {
|
||||
'address': floating_ip,
|
||||
}
|
||||
}
|
||||
|
||||
post_body = json.dumps(post_body)
|
||||
resp, body = self.post(url, post_body)
|
||||
self.validate_response(schema.add_remove_floating_ip, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def disassociate_floating_ip_from_server(self, floating_ip, server_id):
|
||||
"""Disassociate the provided floating IP from a specific server."""
|
||||
url = "servers/%s/action" % str(server_id)
|
||||
post_body = {
|
||||
'removeFloatingIp': {
|
||||
'address': floating_ip,
|
||||
}
|
||||
}
|
||||
|
||||
post_body = json.dumps(post_body)
|
||||
resp, body = self.post(url, post_body)
|
||||
self.validate_response(schema.add_remove_floating_ip, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
self.show_floating_ip(id)
|
||||
except lib_exc.NotFound:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def resource_type(self):
|
||||
"""Returns the primary type of resource this client works with."""
|
||||
return 'floating_ip'
|
||||
|
||||
def list_floating_ip_pools(self, params=None):
|
||||
"""Returns a list of all floating IP Pools."""
|
||||
url = 'os-floating-ip-pools'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_floating_ip_pools, resp, body)
|
||||
return service_client.ResponseBodyList(resp, body['floating_ip_pools'])
|
||||
|
||||
def create_floating_ips_bulk(self, ip_range, pool, interface):
|
||||
"""Allocate floating IPs in bulk."""
|
||||
post_body = {
|
||||
'ip_range': ip_range,
|
||||
'pool': pool,
|
||||
'interface': interface
|
||||
}
|
||||
post_body = json.dumps({'floating_ips_bulk_create': post_body})
|
||||
resp, body = self.post('os-floating-ips-bulk', post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.create_floating_ips_bulk, resp, body)
|
||||
return service_client.ResponseBody(resp,
|
||||
body['floating_ips_bulk_create'])
|
||||
|
||||
def list_floating_ips_bulk(self):
|
||||
"""Returns a list of all floating IPs bulk."""
|
||||
resp, body = self.get('os-floating-ips-bulk')
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_floating_ips_bulk, resp, body)
|
||||
return service_client.ResponseBodyList(resp, body['floating_ip_info'])
|
||||
|
||||
def delete_floating_ips_bulk(self, ip_range):
|
||||
"""Deletes the provided floating IPs bulk."""
|
||||
post_body = json.dumps({'ip_range': ip_range})
|
||||
resp, body = self.put('os-floating-ips-bulk/delete', post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.delete_floating_ips_bulk, resp, body)
|
||||
data = body['floating_ips_bulk_delete']
|
||||
return service_client.ResponseBodyData(resp, data)
|
|
@ -0,0 +1,142 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
import json
|
||||
|
||||
from six.moves.urllib import parse as urllib
|
||||
from tempest_lib import exceptions as lib_exc
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.api_schema.response.compute.v2_1 import images as schema
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
from neutron_lbaas.tests.tempest.lib.common import waiters
|
||||
|
||||
|
||||
class ImagesClientJSON(service_client.ServiceClient):
|
||||
|
||||
def create_image(self, server_id, name, meta=None):
|
||||
"""Creates an image of the original server."""
|
||||
|
||||
post_body = {
|
||||
'createImage': {
|
||||
'name': name,
|
||||
}
|
||||
}
|
||||
|
||||
if meta is not None:
|
||||
post_body['createImage']['metadata'] = meta
|
||||
|
||||
post_body = json.dumps(post_body)
|
||||
resp, body = self.post('servers/%s/action' % str(server_id),
|
||||
post_body)
|
||||
self.validate_response(schema.create_image, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def list_images(self, params=None):
|
||||
"""Returns a list of all images filtered by any parameters."""
|
||||
url = 'images'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_images, resp, body)
|
||||
return service_client.ResponseBodyList(resp, body['images'])
|
||||
|
||||
def list_images_with_detail(self, params=None):
|
||||
"""Returns a detailed list of images filtered by any parameters."""
|
||||
url = 'images/detail'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_images_details, resp, body)
|
||||
return service_client.ResponseBodyList(resp, body['images'])
|
||||
|
||||
def show_image(self, image_id):
|
||||
"""Returns the details of a single image."""
|
||||
resp, body = self.get("images/%s" % str(image_id))
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.get_image, resp, body)
|
||||
return service_client.ResponseBody(resp, body['image'])
|
||||
|
||||
def delete_image(self, image_id):
|
||||
"""Deletes the provided image."""
|
||||
resp, body = self.delete("images/%s" % str(image_id))
|
||||
self.validate_response(schema.delete, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def wait_for_image_status(self, image_id, status):
|
||||
"""Waits for an image to reach a given status."""
|
||||
waiters.wait_for_image_status(self, image_id, status)
|
||||
|
||||
def list_image_metadata(self, image_id):
|
||||
"""Lists all metadata items for an image."""
|
||||
resp, body = self.get("images/%s/metadata" % str(image_id))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.image_metadata, resp, body)
|
||||
return service_client.ResponseBody(resp, body['metadata'])
|
||||
|
||||
def set_image_metadata(self, image_id, meta):
|
||||
"""Sets the metadata for an image."""
|
||||
post_body = json.dumps({'metadata': meta})
|
||||
resp, body = self.put('images/%s/metadata' % str(image_id), post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.image_metadata, resp, body)
|
||||
return service_client.ResponseBody(resp, body['metadata'])
|
||||
|
||||
def update_image_metadata(self, image_id, meta):
|
||||
"""Updates the metadata for an image."""
|
||||
post_body = json.dumps({'metadata': meta})
|
||||
resp, body = self.post('images/%s/metadata' % str(image_id), post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.image_metadata, resp, body)
|
||||
return service_client.ResponseBody(resp, body['metadata'])
|
||||
|
||||
def get_image_metadata_item(self, image_id, key):
|
||||
"""Returns the value for a specific image metadata key."""
|
||||
resp, body = self.get("images/%s/metadata/%s" % (str(image_id), key))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.image_meta_item, resp, body)
|
||||
return service_client.ResponseBody(resp, body['meta'])
|
||||
|
||||
def set_image_metadata_item(self, image_id, key, meta):
|
||||
"""Sets the value for a specific image metadata key."""
|
||||
post_body = json.dumps({'meta': meta})
|
||||
resp, body = self.put('images/%s/metadata/%s' % (str(image_id), key),
|
||||
post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.image_meta_item, resp, body)
|
||||
return service_client.ResponseBody(resp, body['meta'])
|
||||
|
||||
def delete_image_metadata_item(self, image_id, key):
|
||||
"""Deletes a single image metadata key/value pair."""
|
||||
resp, body = self.delete("images/%s/metadata/%s" %
|
||||
(str(image_id), key))
|
||||
self.validate_response(schema.delete, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
self.show_image(id)
|
||||
except lib_exc.NotFound:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def resource_type(self):
|
||||
"""Returns the primary type of resource this client works with."""
|
||||
return 'image'
|
|
@ -0,0 +1,109 @@
|
|||
# Copyright 2013 IBM Corp.
|
||||
# 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.
|
||||
|
||||
import json
|
||||
import time
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.api_schema.response.compute.v2_1 import interfaces as schema
|
||||
from neutron_lbaas.tests.tempest.lib.api_schema.response.compute.v2_1 import servers as servers_schema
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
from neutron_lbaas.tests.tempest.lib import exceptions
|
||||
|
||||
|
||||
class InterfacesClientJSON(service_client.ServiceClient):
|
||||
|
||||
def list_interfaces(self, server):
|
||||
resp, body = self.get('servers/%s/os-interface' % server)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_interfaces, resp, body)
|
||||
return service_client.ResponseBodyList(resp,
|
||||
body['interfaceAttachments'])
|
||||
|
||||
def create_interface(self, server, port_id=None, network_id=None,
|
||||
fixed_ip=None):
|
||||
post_body = dict(interfaceAttachment=dict())
|
||||
if port_id:
|
||||
post_body['interfaceAttachment']['port_id'] = port_id
|
||||
if network_id:
|
||||
post_body['interfaceAttachment']['net_id'] = network_id
|
||||
if fixed_ip:
|
||||
fip = dict(ip_address=fixed_ip)
|
||||
post_body['interfaceAttachment']['fixed_ips'] = [fip]
|
||||
post_body = json.dumps(post_body)
|
||||
resp, body = self.post('servers/%s/os-interface' % server,
|
||||
body=post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.get_create_interfaces, resp, body)
|
||||
return service_client.ResponseBody(resp, body['interfaceAttachment'])
|
||||
|
||||
def show_interface(self, server, port_id):
|
||||
resp, body = self.get('servers/%s/os-interface/%s' % (server, port_id))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.get_create_interfaces, resp, body)
|
||||
return service_client.ResponseBody(resp, body['interfaceAttachment'])
|
||||
|
||||
def delete_interface(self, server, port_id):
|
||||
resp, body = self.delete('servers/%s/os-interface/%s' % (server,
|
||||
port_id))
|
||||
self.validate_response(schema.delete_interface, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def wait_for_interface_status(self, server, port_id, status):
|
||||
"""Waits for a interface to reach a given status."""
|
||||
body = self.show_interface(server, port_id)
|
||||
interface_status = body['port_state']
|
||||
start = int(time.time())
|
||||
|
||||
while(interface_status != status):
|
||||
time.sleep(self.build_interval)
|
||||
body = self.show_interface(server, port_id)
|
||||
interface_status = body['port_state']
|
||||
|
||||
timed_out = int(time.time()) - start >= self.build_timeout
|
||||
|
||||
if interface_status != status and timed_out:
|
||||
message = ('Interface %s failed to reach %s status '
|
||||
'(current %s) within the required time (%s s).' %
|
||||
(port_id, status, interface_status,
|
||||
self.build_timeout))
|
||||
raise exceptions.TimeoutException(message)
|
||||
|
||||
return body
|
||||
|
||||
def add_fixed_ip(self, server_id, network_id):
|
||||
"""Add a fixed IP to input server instance."""
|
||||
post_body = json.dumps({
|
||||
'addFixedIp': {
|
||||
'networkId': network_id
|
||||
}
|
||||
})
|
||||
resp, body = self.post('servers/%s/action' % str(server_id),
|
||||
post_body)
|
||||
self.validate_response(servers_schema.server_actions_common_schema,
|
||||
resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def remove_fixed_ip(self, server_id, ip_address):
|
||||
"""Remove input fixed IP from input server instance."""
|
||||
post_body = json.dumps({
|
||||
'removeFixedIp': {
|
||||
'address': ip_address
|
||||
}
|
||||
})
|
||||
resp, body = self.post('servers/%s/action' % str(server_id),
|
||||
post_body)
|
||||
self.validate_response(servers_schema.server_actions_common_schema,
|
||||
resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
|
@ -0,0 +1,54 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
import json
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.api_schema.response.compute.v2_1 import keypairs as schema
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
|
||||
|
||||
class KeyPairsClientJSON(service_client.ServiceClient):
|
||||
|
||||
def list_keypairs(self):
|
||||
resp, body = self.get("os-keypairs")
|
||||
body = json.loads(body)
|
||||
# Each returned keypair is embedded within an unnecessary 'keypair'
|
||||
# element which is a deviation from other resources like floating-ips,
|
||||
# servers, etc. A bug?
|
||||
# For now we shall adhere to the spec, but the spec for keypairs
|
||||
# is yet to be found
|
||||
self.validate_response(schema.list_keypairs, resp, body)
|
||||
return service_client.ResponseBodyList(resp, body['keypairs'])
|
||||
|
||||
def get_keypair(self, key_name):
|
||||
resp, body = self.get("os-keypairs/%s" % str(key_name))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.get_keypair, resp, body)
|
||||
return service_client.ResponseBody(resp, body['keypair'])
|
||||
|
||||
def create_keypair(self, name, pub_key=None):
|
||||
post_body = {'keypair': {'name': name}}
|
||||
if pub_key:
|
||||
post_body['keypair']['public_key'] = pub_key
|
||||
post_body = json.dumps(post_body)
|
||||
resp, body = self.post("os-keypairs", body=post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.create_keypair, resp, body)
|
||||
return service_client.ResponseBody(resp, body['keypair'])
|
||||
|
||||
def delete_keypair(self, key_name):
|
||||
resp, body = self.delete("os-keypairs/%s" % str(key_name))
|
||||
self.validate_response(schema.delete_keypair, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
|
@ -0,0 +1,37 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
import json
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
|
||||
|
||||
class NetworksClientJSON(service_client.ServiceClient):
|
||||
|
||||
def list_networks(self, name=None):
|
||||
resp, body = self.get("os-networks")
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
if name:
|
||||
networks = [n for n in body['networks'] if n['label'] == name]
|
||||
else:
|
||||
networks = body['networks']
|
||||
return service_client.ResponseBodyList(resp, networks)
|
||||
|
||||
def get_network(self, network_id):
|
||||
resp, body = self.get("os-networks/%s" % str(network_id))
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body['network'])
|
|
@ -0,0 +1,144 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
import json
|
||||
|
||||
from six.moves.urllib import parse as urllib
|
||||
from tempest_lib import exceptions as lib_exc
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.api_schema.response.compute.v2_1 import security_groups as schema
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
|
||||
|
||||
class SecurityGroupsClientJSON(service_client.ServiceClient):
|
||||
|
||||
def list_security_groups(self, params=None):
|
||||
"""List all security groups for a user."""
|
||||
|
||||
url = 'os-security-groups'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_security_groups, resp, body)
|
||||
return service_client.ResponseBodyList(resp, body['security_groups'])
|
||||
|
||||
def get_security_group(self, security_group_id):
|
||||
"""Get the details of a Security Group."""
|
||||
url = "os-security-groups/%s" % str(security_group_id)
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.get_security_group, resp, body)
|
||||
return service_client.ResponseBody(resp, body['security_group'])
|
||||
|
||||
def create_security_group(self, name, description):
|
||||
"""
|
||||
Creates a new security group.
|
||||
name (Required): Name of security group.
|
||||
description (Required): Description of security group.
|
||||
"""
|
||||
post_body = {
|
||||
'name': name,
|
||||
'description': description,
|
||||
}
|
||||
post_body = json.dumps({'security_group': post_body})
|
||||
resp, body = self.post('os-security-groups', post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.get_security_group, resp, body)
|
||||
return service_client.ResponseBody(resp, body['security_group'])
|
||||
|
||||
def update_security_group(self, security_group_id, name=None,
|
||||
description=None):
|
||||
"""
|
||||
Update a security group.
|
||||
security_group_id: a security_group to update
|
||||
name: new name of security group
|
||||
description: new description of security group
|
||||
"""
|
||||
post_body = {}
|
||||
if name:
|
||||
post_body['name'] = name
|
||||
if description:
|
||||
post_body['description'] = description
|
||||
post_body = json.dumps({'security_group': post_body})
|
||||
resp, body = self.put('os-security-groups/%s' % str(security_group_id),
|
||||
post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.update_security_group, resp, body)
|
||||
return service_client.ResponseBody(resp, body['security_group'])
|
||||
|
||||
def delete_security_group(self, security_group_id):
|
||||
"""Deletes the provided Security Group."""
|
||||
resp, body = self.delete(
|
||||
'os-security-groups/%s' % str(security_group_id))
|
||||
self.validate_response(schema.delete_security_group, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def create_security_group_rule(self, parent_group_id, ip_proto, from_port,
|
||||
to_port, **kwargs):
|
||||
"""
|
||||
Creating a new security group rules.
|
||||
parent_group_id :ID of Security group
|
||||
ip_protocol : ip_proto (icmp, tcp, udp).
|
||||
from_port: Port at start of range.
|
||||
to_port : Port at end of range.
|
||||
Following optional keyword arguments are accepted:
|
||||
cidr : CIDR for address range.
|
||||
group_id : ID of the Source group
|
||||
"""
|
||||
post_body = {
|
||||
'parent_group_id': parent_group_id,
|
||||
'ip_protocol': ip_proto,
|
||||
'from_port': from_port,
|
||||
'to_port': to_port,
|
||||
'cidr': kwargs.get('cidr'),
|
||||
'group_id': kwargs.get('group_id'),
|
||||
}
|
||||
post_body = json.dumps({'security_group_rule': post_body})
|
||||
url = 'os-security-group-rules'
|
||||
resp, body = self.post(url, post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.create_security_group_rule, resp, body)
|
||||
return service_client.ResponseBody(resp, body['security_group_rule'])
|
||||
|
||||
def delete_security_group_rule(self, group_rule_id):
|
||||
"""Deletes the provided Security Group rule."""
|
||||
resp, body = self.delete('os-security-group-rules/%s' %
|
||||
str(group_rule_id))
|
||||
self.validate_response(schema.delete_security_group_rule, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def list_security_group_rules(self, security_group_id):
|
||||
"""List all rules for a security group."""
|
||||
resp, body = self.get('os-security-groups')
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_security_groups, resp, body)
|
||||
for sg in body['security_groups']:
|
||||
if sg['id'] == security_group_id:
|
||||
return service_client.ResponseBodyList(resp, sg['rules'])
|
||||
raise lib_exc.NotFound('No such Security Group')
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
self.get_security_group(id)
|
||||
except lib_exc.NotFound:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def resource_type(self):
|
||||
"""Returns the primary type of resource this client works with."""
|
||||
return 'security_group'
|
|
@ -0,0 +1,572 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
# 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.
|
||||
|
||||
import json
|
||||
import time
|
||||
|
||||
from six.moves.urllib import parse as urllib
|
||||
from tempest_lib import exceptions as lib_exc
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.api_schema.response.compute.v2_1 import servers as schema
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
from neutron_lbaas.tests.tempest.lib.common import waiters
|
||||
from neutron_lbaas.tests.tempest.lib import exceptions
|
||||
|
||||
|
||||
class ServersClientJSON(service_client.ServiceClient):
|
||||
|
||||
def __init__(self, auth_provider, service, region,
|
||||
enable_instance_password=True, **kwargs):
|
||||
super(ServersClientJSON, self).__init__(
|
||||
auth_provider, service, region, **kwargs)
|
||||
self.enable_instance_password = enable_instance_password
|
||||
|
||||
def create_server(self, name, image_ref, flavor_ref, **kwargs):
|
||||
"""
|
||||
Creates an instance of a server.
|
||||
name (Required): The name of the server.
|
||||
image_ref (Required): Reference to the image used to build the server.
|
||||
flavor_ref (Required): The flavor used to build the server.
|
||||
Following optional keyword arguments are accepted:
|
||||
adminPass: Sets the initial root password.
|
||||
key_name: Key name of keypair that was created earlier.
|
||||
meta: A dictionary of values to be used as metadata.
|
||||
personality: A list of dictionaries for files to be injected into
|
||||
the server.
|
||||
security_groups: A list of security group dicts.
|
||||
networks: A list of network dicts with UUID and fixed_ip.
|
||||
user_data: User data for instance.
|
||||
availability_zone: Availability zone in which to launch instance.
|
||||
accessIPv4: The IPv4 access address for the server.
|
||||
accessIPv6: The IPv6 access address for the server.
|
||||
min_count: Count of minimum number of instances to launch.
|
||||
max_count: Count of maximum number of instances to launch.
|
||||
disk_config: Determines if user or admin controls disk configuration.
|
||||
return_reservation_id: Enable/Disable the return of reservation id
|
||||
block_device_mapping: Block device mapping for the server.
|
||||
block_device_mapping_v2: Block device mapping V2 for the server.
|
||||
"""
|
||||
post_body = {
|
||||
'name': name,
|
||||
'imageRef': image_ref,
|
||||
'flavorRef': flavor_ref
|
||||
}
|
||||
|
||||
for option in ['personality', 'adminPass', 'key_name',
|
||||
'security_groups', 'networks', 'user_data',
|
||||
'availability_zone', 'accessIPv4', 'accessIPv6',
|
||||
'min_count', 'max_count', ('metadata', 'meta'),
|
||||
('OS-DCF:diskConfig', 'disk_config'),
|
||||
'return_reservation_id', 'block_device_mapping',
|
||||
'block_device_mapping_v2']:
|
||||
if isinstance(option, tuple):
|
||||
post_param = option[0]
|
||||
key = option[1]
|
||||
else:
|
||||
post_param = option
|
||||
key = option
|
||||
value = kwargs.get(key)
|
||||
if value is not None:
|
||||
post_body[post_param] = value
|
||||
|
||||
post_body = {'server': post_body}
|
||||
|
||||
if 'sched_hints' in kwargs:
|
||||
hints = {'os:scheduler_hints': kwargs.get('sched_hints')}
|
||||
post_body = dict(post_body.items() + hints.items())
|
||||
post_body = json.dumps(post_body)
|
||||
resp, body = self.post('servers', post_body)
|
||||
|
||||
body = json.loads(body)
|
||||
# NOTE(maurosr): this deals with the case of multiple server create
|
||||
# with return reservation id set True
|
||||
if 'reservation_id' in body:
|
||||
return service_client.ResponseBody(resp, body)
|
||||
if self.enable_instance_password:
|
||||
create_schema = schema.create_server_with_admin_pass
|
||||
else:
|
||||
create_schema = schema.create_server
|
||||
self.validate_response(create_schema, resp, body)
|
||||
return service_client.ResponseBody(resp, body['server'])
|
||||
|
||||
def update_server(self, server_id, name=None, meta=None, accessIPv4=None,
|
||||
accessIPv6=None, disk_config=None):
|
||||
"""
|
||||
Updates the properties of an existing server.
|
||||
server_id: The id of an existing server.
|
||||
name: The name of the server.
|
||||
personality: A list of files to be injected into the server.
|
||||
accessIPv4: The IPv4 access address for the server.
|
||||
accessIPv6: The IPv6 access address for the server.
|
||||
"""
|
||||
|
||||
post_body = {}
|
||||
|
||||
if meta is not None:
|
||||
post_body['metadata'] = meta
|
||||
|
||||
if name is not None:
|
||||
post_body['name'] = name
|
||||
|
||||
if accessIPv4 is not None:
|
||||
post_body['accessIPv4'] = accessIPv4
|
||||
|
||||
if accessIPv6 is not None:
|
||||
post_body['accessIPv6'] = accessIPv6
|
||||
|
||||
if disk_config is not None:
|
||||
post_body['OS-DCF:diskConfig'] = disk_config
|
||||
|
||||
post_body = json.dumps({'server': post_body})
|
||||
resp, body = self.put("servers/%s" % str(server_id), post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.update_server, resp, body)
|
||||
return service_client.ResponseBody(resp, body['server'])
|
||||
|
||||
def get_server(self, server_id):
|
||||
"""Returns the details of an existing server."""
|
||||
resp, body = self.get("servers/%s" % str(server_id))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.get_server, resp, body)
|
||||
return service_client.ResponseBody(resp, body['server'])
|
||||
|
||||
def delete_server(self, server_id):
|
||||
"""Deletes the given server."""
|
||||
resp, body = self.delete("servers/%s" % str(server_id))
|
||||
self.validate_response(schema.delete_server, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def list_servers(self, params=None):
|
||||
"""Lists all servers for a user."""
|
||||
|
||||
url = 'servers'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_servers, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def list_servers_with_detail(self, params=None):
|
||||
"""Lists all servers in detail for a user."""
|
||||
|
||||
url = 'servers/detail'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_servers_detail, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def wait_for_server_status(self, server_id, status, extra_timeout=0,
|
||||
raise_on_error=True, ready_wait=True):
|
||||
"""Waits for a server to reach a given status."""
|
||||
return waiters.wait_for_server_status(self, server_id, status,
|
||||
extra_timeout=extra_timeout,
|
||||
raise_on_error=raise_on_error,
|
||||
ready_wait=ready_wait)
|
||||
|
||||
def wait_for_server_termination(self, server_id, ignore_error=False):
|
||||
"""Waits for server to reach termination."""
|
||||
start_time = int(time.time())
|
||||
while True:
|
||||
try:
|
||||
body = self.get_server(server_id)
|
||||
except lib_exc.NotFound:
|
||||
return
|
||||
|
||||
server_status = body['status']
|
||||
if server_status == 'ERROR' and not ignore_error:
|
||||
raise exceptions.BuildErrorException(server_id=server_id)
|
||||
|
||||
if int(time.time()) - start_time >= self.build_timeout:
|
||||
raise exceptions.TimeoutException
|
||||
|
||||
time.sleep(self.build_interval)
|
||||
|
||||
def list_addresses(self, server_id):
|
||||
"""Lists all addresses for a server."""
|
||||
resp, body = self.get("servers/%s/ips" % str(server_id))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_addresses, resp, body)
|
||||
return service_client.ResponseBody(resp, body['addresses'])
|
||||
|
||||
def list_addresses_by_network(self, server_id, network_id):
|
||||
"""Lists all addresses of a specific network type for a server."""
|
||||
resp, body = self.get("servers/%s/ips/%s" %
|
||||
(str(server_id), network_id))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_addresses_by_network, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def action(self, server_id, action_name, response_key,
|
||||
schema=schema.server_actions_common_schema,
|
||||
response_class=service_client.ResponseBody, **kwargs):
|
||||
post_body = json.dumps({action_name: kwargs})
|
||||
resp, body = self.post('servers/%s/action' % str(server_id),
|
||||
post_body)
|
||||
if response_key is not None:
|
||||
body = json.loads(body)
|
||||
# Check for Schema as 'None' because if we do not have any server
|
||||
# action schema implemented yet then they can pass 'None' to skip
|
||||
# the validation.Once all server action has their schema
|
||||
# implemented then, this check can be removed if every actions are
|
||||
# supposed to validate their response.
|
||||
# TODO(GMann): Remove the below 'if' check once all server actions
|
||||
# schema are implemented.
|
||||
if schema is not None:
|
||||
self.validate_response(schema, resp, body)
|
||||
body = body[response_key]
|
||||
else:
|
||||
self.validate_response(schema, resp, body)
|
||||
return response_class(resp, body)
|
||||
|
||||
def create_backup(self, server_id, backup_type, rotation, name):
|
||||
"""Backup a server instance."""
|
||||
return self.action(server_id, "createBackup", None,
|
||||
backup_type=backup_type,
|
||||
rotation=rotation,
|
||||
name=name)
|
||||
|
||||
def change_password(self, server_id, adminPass):
|
||||
"""Changes the root password for the server."""
|
||||
return self.action(server_id, 'changePassword', None,
|
||||
adminPass=adminPass)
|
||||
|
||||
def get_password(self, server_id):
|
||||
resp, body = self.get("servers/%s/os-server-password" %
|
||||
str(server_id))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.get_password, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def delete_password(self, server_id):
|
||||
"""
|
||||
Removes the encrypted server password from the metadata server
|
||||
Note that this does not actually change the instance server
|
||||
password.
|
||||
"""
|
||||
resp, body = self.delete("servers/%s/os-server-password" %
|
||||
str(server_id))
|
||||
self.validate_response(schema.server_actions_delete_password,
|
||||
resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def reboot(self, server_id, reboot_type):
|
||||
"""Reboots a server."""
|
||||
return self.action(server_id, 'reboot', None, type=reboot_type)
|
||||
|
||||
def rebuild(self, server_id, image_ref, **kwargs):
|
||||
"""Rebuilds a server with a new image."""
|
||||
kwargs['imageRef'] = image_ref
|
||||
if 'disk_config' in kwargs:
|
||||
kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
|
||||
del kwargs['disk_config']
|
||||
if self.enable_instance_password:
|
||||
rebuild_schema = schema.rebuild_server_with_admin_pass
|
||||
else:
|
||||
rebuild_schema = schema.rebuild_server
|
||||
return self.action(server_id, 'rebuild', 'server',
|
||||
rebuild_schema, **kwargs)
|
||||
|
||||
def resize(self, server_id, flavor_ref, **kwargs):
|
||||
"""Changes the flavor of a server."""
|
||||
kwargs['flavorRef'] = flavor_ref
|
||||
if 'disk_config' in kwargs:
|
||||
kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
|
||||
del kwargs['disk_config']
|
||||
return self.action(server_id, 'resize', None, **kwargs)
|
||||
|
||||
def confirm_resize(self, server_id, **kwargs):
|
||||
"""Confirms the flavor change for a server."""
|
||||
return self.action(server_id, 'confirmResize',
|
||||
None, schema.server_actions_confirm_resize,
|
||||
**kwargs)
|
||||
|
||||
def revert_resize(self, server_id, **kwargs):
|
||||
"""Reverts a server back to its original flavor."""
|
||||
return self.action(server_id, 'revertResize', None, **kwargs)
|
||||
|
||||
def list_server_metadata(self, server_id):
|
||||
resp, body = self.get("servers/%s/metadata" % str(server_id))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_server_metadata, resp, body)
|
||||
return service_client.ResponseBody(resp, body['metadata'])
|
||||
|
||||
def set_server_metadata(self, server_id, meta, no_metadata_field=False):
|
||||
if no_metadata_field:
|
||||
post_body = ""
|
||||
else:
|
||||
post_body = json.dumps({'metadata': meta})
|
||||
resp, body = self.put('servers/%s/metadata' % str(server_id),
|
||||
post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.set_server_metadata, resp, body)
|
||||
return service_client.ResponseBody(resp, body['metadata'])
|
||||
|
||||
def update_server_metadata(self, server_id, meta):
|
||||
post_body = json.dumps({'metadata': meta})
|
||||
resp, body = self.post('servers/%s/metadata' % str(server_id),
|
||||
post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.update_server_metadata,
|
||||
resp, body)
|
||||
return service_client.ResponseBody(resp, body['metadata'])
|
||||
|
||||
def get_server_metadata_item(self, server_id, key):
|
||||
resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.set_get_server_metadata_item,
|
||||
resp, body)
|
||||
return service_client.ResponseBody(resp, body['meta'])
|
||||
|
||||
def set_server_metadata_item(self, server_id, key, meta):
|
||||
post_body = json.dumps({'meta': meta})
|
||||
resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key),
|
||||
post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.set_get_server_metadata_item,
|
||||
resp, body)
|
||||
return service_client.ResponseBody(resp, body['meta'])
|
||||
|
||||
def delete_server_metadata_item(self, server_id, key):
|
||||
resp, body = self.delete("servers/%s/metadata/%s" %
|
||||
(str(server_id), key))
|
||||
self.validate_response(schema.delete_server_metadata_item,
|
||||
resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def stop(self, server_id, **kwargs):
|
||||
return self.action(server_id, 'os-stop', None, **kwargs)
|
||||
|
||||
def start(self, server_id, **kwargs):
|
||||
return self.action(server_id, 'os-start', None, **kwargs)
|
||||
|
||||
def attach_volume(self, server_id, volume_id, device='/dev/vdz'):
|
||||
"""Attaches a volume to a server instance."""
|
||||
post_body = json.dumps({
|
||||
'volumeAttachment': {
|
||||
'volumeId': volume_id,
|
||||
'device': device,
|
||||
}
|
||||
})
|
||||
resp, body = self.post('servers/%s/os-volume_attachments' % server_id,
|
||||
post_body)
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.attach_volume, resp, body)
|
||||
return service_client.ResponseBody(resp, body['volumeAttachment'])
|
||||
|
||||
def detach_volume(self, server_id, volume_id):
|
||||
"""Detaches a volume from a server instance."""
|
||||
resp, body = self.delete('servers/%s/os-volume_attachments/%s' %
|
||||
(server_id, volume_id))
|
||||
self.validate_response(schema.detach_volume, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def get_volume_attachment(self, server_id, attach_id):
|
||||
"""Return details about the given volume attachment."""
|
||||
resp, body = self.get('servers/%s/os-volume_attachments/%s' % (
|
||||
str(server_id), attach_id))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.get_volume_attachment, resp, body)
|
||||
return service_client.ResponseBody(resp, body['volumeAttachment'])
|
||||
|
||||
def list_volume_attachments(self, server_id):
|
||||
"""Returns the list of volume attachments for a given instance."""
|
||||
resp, body = self.get('servers/%s/os-volume_attachments' % (
|
||||
str(server_id)))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_volume_attachments, resp, body)
|
||||
return service_client.ResponseBodyList(resp, body['volumeAttachments'])
|
||||
|
||||
def add_security_group(self, server_id, name):
|
||||
"""Adds a security group to the server."""
|
||||
return self.action(server_id, 'addSecurityGroup', None, name=name)
|
||||
|
||||
def remove_security_group(self, server_id, name):
|
||||
"""Removes a security group from the server."""
|
||||
return self.action(server_id, 'removeSecurityGroup', None, name=name)
|
||||
|
||||
def live_migrate_server(self, server_id, dest_host, use_block_migration):
|
||||
"""This should be called with administrator privileges ."""
|
||||
|
||||
migrate_params = {
|
||||
"disk_over_commit": False,
|
||||
"block_migration": use_block_migration,
|
||||
"host": dest_host
|
||||
}
|
||||
|
||||
req_body = json.dumps({'os-migrateLive': migrate_params})
|
||||
|
||||
resp, body = self.post("servers/%s/action" % str(server_id), req_body)
|
||||
self.validate_response(schema.server_actions_common_schema,
|
||||
resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def migrate_server(self, server_id, **kwargs):
|
||||
"""Migrates a server to a new host."""
|
||||
return self.action(server_id, 'migrate', None, **kwargs)
|
||||
|
||||
def lock_server(self, server_id, **kwargs):
|
||||
"""Locks the given server."""
|
||||
return self.action(server_id, 'lock', None, **kwargs)
|
||||
|
||||
def unlock_server(self, server_id, **kwargs):
|
||||
"""UNlocks the given server."""
|
||||
return self.action(server_id, 'unlock', None, **kwargs)
|
||||
|
||||
def suspend_server(self, server_id, **kwargs):
|
||||
"""Suspends the provided server."""
|
||||
return self.action(server_id, 'suspend', None, **kwargs)
|
||||
|
||||
def resume_server(self, server_id, **kwargs):
|
||||
"""Un-suspends the provided server."""
|
||||
return self.action(server_id, 'resume', None, **kwargs)
|
||||
|
||||
def pause_server(self, server_id, **kwargs):
|
||||
"""Pauses the provided server."""
|
||||
return self.action(server_id, 'pause', None, **kwargs)
|
||||
|
||||
def unpause_server(self, server_id, **kwargs):
|
||||
"""Un-pauses the provided server."""
|
||||
return self.action(server_id, 'unpause', None, **kwargs)
|
||||
|
||||
def reset_state(self, server_id, state='error'):
|
||||
"""Resets the state of a server to active/error."""
|
||||
return self.action(server_id, 'os-resetState', None, state=state)
|
||||
|
||||
def shelve_server(self, server_id, **kwargs):
|
||||
"""Shelves the provided server."""
|
||||
return self.action(server_id, 'shelve', None, **kwargs)
|
||||
|
||||
def unshelve_server(self, server_id, **kwargs):
|
||||
"""Un-shelves the provided server."""
|
||||
return self.action(server_id, 'unshelve', None, **kwargs)
|
||||
|
||||
def shelve_offload_server(self, server_id, **kwargs):
|
||||
"""Shelve-offload the provided server."""
|
||||
return self.action(server_id, 'shelveOffload', None, **kwargs)
|
||||
|
||||
def get_console_output(self, server_id, length):
|
||||
kwargs = {'length': length} if length else {}
|
||||
return self.action(server_id, 'os-getConsoleOutput', 'output',
|
||||
schema.get_console_output,
|
||||
response_class=service_client.ResponseBodyData,
|
||||
**kwargs)
|
||||
|
||||
def list_virtual_interfaces(self, server_id):
|
||||
"""
|
||||
List the virtual interfaces used in an instance.
|
||||
"""
|
||||
resp, body = self.get('/'.join(['servers', server_id,
|
||||
'os-virtual-interfaces']))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_virtual_interfaces, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def rescue_server(self, server_id, **kwargs):
|
||||
"""Rescue the provided server."""
|
||||
return self.action(server_id, 'rescue', 'adminPass',
|
||||
schema.rescue_server,
|
||||
response_class=service_client.ResponseBodyData,
|
||||
**kwargs)
|
||||
|
||||
def unrescue_server(self, server_id):
|
||||
"""Unrescue the provided server."""
|
||||
return self.action(server_id, 'unrescue', None)
|
||||
|
||||
def get_server_diagnostics(self, server_id):
|
||||
"""Get the usage data for a server."""
|
||||
resp, body = self.get("servers/%s/diagnostics" % str(server_id))
|
||||
return service_client.ResponseBody(resp, json.loads(body))
|
||||
|
||||
def list_instance_actions(self, server_id):
|
||||
"""List the provided server action."""
|
||||
resp, body = self.get("servers/%s/os-instance-actions" %
|
||||
str(server_id))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_instance_actions, resp, body)
|
||||
return service_client.ResponseBodyList(resp, body['instanceActions'])
|
||||
|
||||
def get_instance_action(self, server_id, request_id):
|
||||
"""Returns the action details of the provided server."""
|
||||
resp, body = self.get("servers/%s/os-instance-actions/%s" %
|
||||
(str(server_id), str(request_id)))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.get_instance_action, resp, body)
|
||||
return service_client.ResponseBody(resp, body['instanceAction'])
|
||||
|
||||
def force_delete_server(self, server_id, **kwargs):
|
||||
"""Force delete a server."""
|
||||
return self.action(server_id, 'forceDelete', None, **kwargs)
|
||||
|
||||
def restore_soft_deleted_server(self, server_id, **kwargs):
|
||||
"""Restore a soft-deleted server."""
|
||||
return self.action(server_id, 'restore', None, **kwargs)
|
||||
|
||||
def reset_network(self, server_id, **kwargs):
|
||||
"""Resets the Network of a server"""
|
||||
return self.action(server_id, 'resetNetwork', None, **kwargs)
|
||||
|
||||
def inject_network_info(self, server_id, **kwargs):
|
||||
"""Inject the Network Info into server"""
|
||||
return self.action(server_id, 'injectNetworkInfo', None, **kwargs)
|
||||
|
||||
def get_vnc_console(self, server_id, console_type):
|
||||
"""Get URL of VNC console."""
|
||||
return self.action(server_id, "os-getVNCConsole",
|
||||
"console", schema.get_vnc_console,
|
||||
type=console_type)
|
||||
|
||||
def create_server_group(self, name, policies):
|
||||
"""
|
||||
Create the server group
|
||||
name : Name of the server-group
|
||||
policies : List of the policies - affinity/anti-affinity)
|
||||
"""
|
||||
post_body = {
|
||||
'name': name,
|
||||
'policies': policies,
|
||||
}
|
||||
|
||||
post_body = json.dumps({'server_group': post_body})
|
||||
resp, body = self.post('os-server-groups', post_body)
|
||||
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.create_get_server_group, resp, body)
|
||||
return service_client.ResponseBody(resp, body['server_group'])
|
||||
|
||||
def delete_server_group(self, server_group_id):
|
||||
"""Delete the given server-group."""
|
||||
resp, body = self.delete("os-server-groups/%s" % str(server_group_id))
|
||||
self.validate_response(schema.delete_server_group, resp, body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def list_server_groups(self):
|
||||
"""List the server-groups."""
|
||||
resp, body = self.get("os-server-groups")
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_server_groups, resp, body)
|
||||
return service_client.ResponseBodyList(resp, body['server_groups'])
|
||||
|
||||
def get_server_group(self, server_group_id):
|
||||
"""Get the details of given server_group."""
|
||||
resp, body = self.get("os-server-groups/%s" % str(server_group_id))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.create_get_server_group, resp, body)
|
||||
return service_client.ResponseBody(resp, body['server_group'])
|
|
@ -0,0 +1,33 @@
|
|||
# Copyright 2015 NEC Corporation. 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.
|
||||
|
||||
import json
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.api_schema.response.compute.v2_1 import tenant_networks as schema
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
|
||||
|
||||
class TenantNetworksClientJSON(service_client.ServiceClient):
|
||||
|
||||
def list_tenant_networks(self):
|
||||
resp, body = self.get("os-tenant-networks")
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.list_tenant_networks, resp, body)
|
||||
return service_client.ResponseBodyList(resp, body['networks'])
|
||||
|
||||
def get_tenant_network(self, network_id):
|
||||
resp, body = self.get("os-tenant-networks/%s" % str(network_id))
|
||||
body = json.loads(body)
|
||||
self.validate_response(schema.get_tenant_network, resp, body)
|
||||
return service_client.ResponseBody(resp, body['network'])
|
|
@ -0,0 +1,35 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
import urllib
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
|
||||
|
||||
class DatabaseFlavorsClientJSON(service_client.ServiceClient):
|
||||
|
||||
def list_db_flavors(self, params=None):
|
||||
url = 'flavors'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBodyList(resp, self._parse_resp(body))
|
||||
|
||||
def get_db_flavor_details(self, db_flavor_id):
|
||||
resp, body = self.get("flavors/%s" % str(db_flavor_id))
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, self._parse_resp(body))
|
|
@ -0,0 +1,30 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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 six.moves.urllib import parse as urllib
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
|
||||
|
||||
class DatabaseLimitsClientJSON(service_client.ServiceClient):
|
||||
|
||||
def list_db_limits(self, params=None):
|
||||
"""List all limits."""
|
||||
url = 'limits'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBodyList(resp, self._parse_resp(body))
|
|
@ -0,0 +1,46 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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 six.moves.urllib import parse as urllib
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
|
||||
|
||||
class DatabaseVersionsClientJSON(service_client.ServiceClient):
|
||||
|
||||
def __init__(self, auth_provider, service, region,
|
||||
endpoint_type=None, build_interval=None, build_timeout=None,
|
||||
disable_ssl_certificate_validation=None, ca_certs=None,
|
||||
trace_requests=None):
|
||||
dscv = disable_ssl_certificate_validation
|
||||
super(DatabaseVersionsClientJSON, self).__init__(
|
||||
auth_provider, service, region,
|
||||
endpoint_type=endpoint_type,
|
||||
build_interval=build_interval,
|
||||
build_timeout=build_timeout,
|
||||
disable_ssl_certificate_validation=dscv,
|
||||
ca_certs=ca_certs,
|
||||
trace_requests=trace_requests)
|
||||
self.skip_path()
|
||||
|
||||
def list_db_versions(self, params=None):
|
||||
"""List all versions."""
|
||||
url = ''
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBodyList(resp, self._parse_resp(body))
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,318 @@
|
|||
# Copyright 2013 IBM Corp.
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
import errno
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
from oslo_log import log as logging
|
||||
import six
|
||||
from six.moves.urllib import parse as urllib
|
||||
from tempest_lib.common.utils import misc as misc_utils
|
||||
from tempest_lib import exceptions as lib_exc
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.common import glance_http
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
from neutron_lbaas.tests.tempest.lib import exceptions
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ImageClientJSON(service_client.ServiceClient):
|
||||
|
||||
def __init__(self, auth_provider, catalog_type, region, endpoint_type=None,
|
||||
build_interval=None, build_timeout=None,
|
||||
disable_ssl_certificate_validation=None,
|
||||
ca_certs=None, trace_requests=None):
|
||||
super(ImageClientJSON, self).__init__(
|
||||
auth_provider,
|
||||
catalog_type,
|
||||
region,
|
||||
endpoint_type=endpoint_type,
|
||||
build_interval=build_interval,
|
||||
build_timeout=build_timeout,
|
||||
disable_ssl_certificate_validation=(
|
||||
disable_ssl_certificate_validation),
|
||||
ca_certs=ca_certs,
|
||||
trace_requests=trace_requests)
|
||||
self._http = None
|
||||
self.dscv = disable_ssl_certificate_validation
|
||||
self.ca_certs = ca_certs
|
||||
|
||||
def _image_meta_from_headers(self, headers):
|
||||
meta = {'properties': {}}
|
||||
for key, value in six.iteritems(headers):
|
||||
if key.startswith('x-image-meta-property-'):
|
||||
_key = key[22:]
|
||||
meta['properties'][_key] = value
|
||||
elif key.startswith('x-image-meta-'):
|
||||
_key = key[13:]
|
||||
meta[_key] = value
|
||||
|
||||
for key in ['is_public', 'protected', 'deleted']:
|
||||
if key in meta:
|
||||
meta[key] = meta[key].strip().lower() in ('t', 'true', 'yes',
|
||||
'1')
|
||||
for key in ['size', 'min_ram', 'min_disk']:
|
||||
if key in meta:
|
||||
try:
|
||||
meta[key] = int(meta[key])
|
||||
except ValueError:
|
||||
pass
|
||||
return meta
|
||||
|
||||
def _image_meta_to_headers(self, fields):
|
||||
headers = {}
|
||||
fields_copy = copy.deepcopy(fields)
|
||||
copy_from = fields_copy.pop('copy_from', None)
|
||||
if copy_from is not None:
|
||||
headers['x-glance-api-copy-from'] = copy_from
|
||||
for key, value in six.iteritems(fields_copy.pop('properties', {})):
|
||||
headers['x-image-meta-property-%s' % key] = str(value)
|
||||
for key, value in six.iteritems(fields_copy.pop('api', {})):
|
||||
headers['x-glance-api-property-%s' % key] = str(value)
|
||||
for key, value in six.iteritems(fields_copy):
|
||||
headers['x-image-meta-%s' % key] = str(value)
|
||||
return headers
|
||||
|
||||
def _get_file_size(self, obj):
|
||||
"""Analyze file-like object and attempt to determine its size.
|
||||
|
||||
:param obj: file-like object, typically redirected from stdin.
|
||||
:retval The file's size or None if it cannot be determined.
|
||||
"""
|
||||
# For large images, we need to supply the size of the
|
||||
# image file. See LP Bugs #827660 and #845788.
|
||||
if hasattr(obj, 'seek') and hasattr(obj, 'tell'):
|
||||
try:
|
||||
obj.seek(0, os.SEEK_END)
|
||||
obj_size = obj.tell()
|
||||
obj.seek(0)
|
||||
return obj_size
|
||||
except IOError as e:
|
||||
if e.errno == errno.ESPIPE:
|
||||
# Illegal seek. This means the user is trying
|
||||
# to pipe image data to the client, e.g.
|
||||
# echo testdata | bin/glance add blah..., or
|
||||
# that stdin is empty, or that a file-like
|
||||
# object which doesn't support 'seek/tell' has
|
||||
# been supplied.
|
||||
return None
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
# Cannot determine size of input image
|
||||
return None
|
||||
|
||||
def _get_http(self):
|
||||
return glance_http.HTTPClient(auth_provider=self.auth_provider,
|
||||
filters=self.filters,
|
||||
insecure=self.dscv,
|
||||
ca_certs=self.ca_certs)
|
||||
|
||||
def _create_with_data(self, headers, data):
|
||||
resp, body_iter = self.http.raw_request('POST', '/v1/images',
|
||||
headers=headers, body=data)
|
||||
self._error_checker('POST', '/v1/images', headers, data, resp,
|
||||
body_iter)
|
||||
body = json.loads(''.join([c for c in body_iter]))
|
||||
return service_client.ResponseBody(resp, body['image'])
|
||||
|
||||
def _update_with_data(self, image_id, headers, data):
|
||||
url = '/v1/images/%s' % image_id
|
||||
resp, body_iter = self.http.raw_request('PUT', url, headers=headers,
|
||||
body=data)
|
||||
self._error_checker('PUT', url, headers, data,
|
||||
resp, body_iter)
|
||||
body = json.loads(''.join([c for c in body_iter]))
|
||||
return service_client.ResponseBody(resp, body['image'])
|
||||
|
||||
@property
|
||||
def http(self):
|
||||
if self._http is None:
|
||||
self._http = self._get_http()
|
||||
return self._http
|
||||
|
||||
def create_image(self, name, container_format, disk_format, **kwargs):
|
||||
params = {
|
||||
"name": name,
|
||||
"container_format": container_format,
|
||||
"disk_format": disk_format,
|
||||
}
|
||||
|
||||
headers = {}
|
||||
|
||||
for option in ['is_public', 'location', 'properties',
|
||||
'copy_from', 'min_ram']:
|
||||
if option in kwargs:
|
||||
params[option] = kwargs.get(option)
|
||||
|
||||
headers.update(self._image_meta_to_headers(params))
|
||||
|
||||
if 'data' in kwargs:
|
||||
return self._create_with_data(headers, kwargs.get('data'))
|
||||
|
||||
resp, body = self.post('v1/images', None, headers)
|
||||
self.expected_success(201, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body['image'])
|
||||
|
||||
def update_image(self, image_id, name=None, container_format=None,
|
||||
data=None, properties=None):
|
||||
params = {}
|
||||
headers = {}
|
||||
if name is not None:
|
||||
params['name'] = name
|
||||
|
||||
if container_format is not None:
|
||||
params['container_format'] = container_format
|
||||
|
||||
if properties is not None:
|
||||
params['properties'] = properties
|
||||
|
||||
headers.update(self._image_meta_to_headers(params))
|
||||
|
||||
if data is not None:
|
||||
return self._update_with_data(image_id, headers, data)
|
||||
|
||||
url = 'v1/images/%s' % image_id
|
||||
resp, body = self.put(url, data, headers)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body['image'])
|
||||
|
||||
def delete_image(self, image_id):
|
||||
url = 'v1/images/%s' % image_id
|
||||
resp, body = self.delete(url)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def list_images(self, detail=False, properties=dict(),
|
||||
changes_since=None, **kwargs):
|
||||
url = 'v1/images'
|
||||
|
||||
if detail:
|
||||
url += '/detail'
|
||||
|
||||
params = {}
|
||||
for key, value in properties.items():
|
||||
params['property-%s' % key] = value
|
||||
|
||||
kwargs.update(params)
|
||||
|
||||
if changes_since is not None:
|
||||
kwargs['changes-since'] = changes_since
|
||||
|
||||
if len(kwargs) > 0:
|
||||
url += '?%s' % urllib.urlencode(kwargs)
|
||||
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBodyList(resp, body['images'])
|
||||
|
||||
def get_image_meta(self, image_id):
|
||||
url = 'v1/images/%s' % image_id
|
||||
resp, __ = self.head(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._image_meta_from_headers(resp)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def show_image(self, image_id):
|
||||
url = 'v1/images/%s' % image_id
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBodyData(resp, body)
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
self.get_image_meta(id)
|
||||
except lib_exc.NotFound:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def resource_type(self):
|
||||
"""Returns the primary type of resource this client works with."""
|
||||
return 'image_meta'
|
||||
|
||||
def list_image_members(self, image_id):
|
||||
url = 'v1/images/%s/members' % image_id
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def list_shared_images(self, tenant_id):
|
||||
"""List shared images with the specified tenant"""
|
||||
url = 'v1/shared-images/%s' % tenant_id
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def add_member(self, member_id, image_id, can_share=False):
|
||||
url = 'v1/images/%s/members/%s' % (image_id, member_id)
|
||||
body = None
|
||||
if can_share:
|
||||
body = json.dumps({'member': {'can_share': True}})
|
||||
resp, __ = self.put(url, body)
|
||||
self.expected_success(204, resp.status)
|
||||
return service_client.ResponseBody(resp)
|
||||
|
||||
def delete_member(self, member_id, image_id):
|
||||
url = 'v1/images/%s/members/%s' % (image_id, member_id)
|
||||
resp, __ = self.delete(url)
|
||||
self.expected_success(204, resp.status)
|
||||
return service_client.ResponseBody(resp)
|
||||
|
||||
# NOTE(afazekas): just for the wait function
|
||||
def _get_image_status(self, image_id):
|
||||
meta = self.get_image_meta(image_id)
|
||||
status = meta['status']
|
||||
return status
|
||||
|
||||
# NOTE(afazkas): Wait reinvented again. It is not in the correct layer
|
||||
def wait_for_image_status(self, image_id, status):
|
||||
"""Waits for a Image to reach a given status."""
|
||||
start_time = time.time()
|
||||
old_value = value = self._get_image_status(image_id)
|
||||
while True:
|
||||
dtime = time.time() - start_time
|
||||
time.sleep(self.build_interval)
|
||||
if value != old_value:
|
||||
LOG.info('Value transition from "%s" to "%s"'
|
||||
'in %d second(s).', old_value,
|
||||
value, dtime)
|
||||
if value == status:
|
||||
return value
|
||||
|
||||
if value == 'killed':
|
||||
raise exceptions.ImageKilledException(image_id=image_id,
|
||||
status=status)
|
||||
if dtime > self.build_timeout:
|
||||
message = ('Time Limit Exceeded! (%ds)'
|
||||
'while waiting for %s, '
|
||||
'but we got %s.' %
|
||||
(self.build_timeout, status, value))
|
||||
caller = misc_utils.find_test_caller()
|
||||
if caller:
|
||||
message = '(%s) %s' % (caller, message)
|
||||
raise exceptions.TimeoutException(message)
|
||||
time.sleep(self.build_interval)
|
||||
old_value = value
|
||||
value = self._get_image_status(image_id)
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,214 @@
|
|||
# Copyright 2013 IBM Corp.
|
||||
# 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.
|
||||
|
||||
import json
|
||||
|
||||
import jsonschema
|
||||
from six.moves.urllib import parse as urllib
|
||||
from tempest_lib import exceptions as lib_exc
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.common import glance_http
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
|
||||
|
||||
class ImageClientV2JSON(service_client.ServiceClient):
|
||||
|
||||
def __init__(self, auth_provider, catalog_type, region, endpoint_type=None,
|
||||
build_interval=None, build_timeout=None,
|
||||
disable_ssl_certificate_validation=None, ca_certs=None,
|
||||
trace_requests=None):
|
||||
super(ImageClientV2JSON, self).__init__(
|
||||
auth_provider,
|
||||
catalog_type,
|
||||
region,
|
||||
endpoint_type=endpoint_type,
|
||||
build_interval=build_interval,
|
||||
build_timeout=build_timeout,
|
||||
disable_ssl_certificate_validation=(
|
||||
disable_ssl_certificate_validation),
|
||||
ca_certs=ca_certs,
|
||||
trace_requests=trace_requests)
|
||||
self._http = None
|
||||
self.dscv = disable_ssl_certificate_validation
|
||||
self.ca_certs = ca_certs
|
||||
|
||||
def _get_http(self):
|
||||
return glance_http.HTTPClient(auth_provider=self.auth_provider,
|
||||
filters=self.filters,
|
||||
insecure=self.dscv,
|
||||
ca_certs=self.ca_certs)
|
||||
|
||||
def _validate_schema(self, body, type='image'):
|
||||
if type in ['image', 'images']:
|
||||
schema = self.get_schema(type)
|
||||
else:
|
||||
raise ValueError("%s is not a valid schema type" % type)
|
||||
|
||||
jsonschema.validate(body, schema)
|
||||
|
||||
@property
|
||||
def http(self):
|
||||
if self._http is None:
|
||||
self._http = self._get_http()
|
||||
return self._http
|
||||
|
||||
def update_image(self, image_id, patch):
|
||||
data = json.dumps(patch)
|
||||
self._validate_schema(data)
|
||||
|
||||
headers = {"Content-Type": "application/openstack-images-v2.0"
|
||||
"-json-patch"}
|
||||
resp, body = self.patch('v2/images/%s' % image_id, data, headers)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, self._parse_resp(body))
|
||||
|
||||
def create_image(self, name, container_format, disk_format, **kwargs):
|
||||
params = {
|
||||
"name": name,
|
||||
"container_format": container_format,
|
||||
"disk_format": disk_format,
|
||||
}
|
||||
|
||||
for option in kwargs:
|
||||
value = kwargs.get(option)
|
||||
if isinstance(value, dict) or isinstance(value, tuple):
|
||||
params.update(value)
|
||||
else:
|
||||
params[option] = value
|
||||
|
||||
data = json.dumps(params)
|
||||
self._validate_schema(data)
|
||||
|
||||
resp, body = self.post('v2/images', data)
|
||||
self.expected_success(201, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def deactivate_image(self, image_id):
|
||||
url = 'v2/images/%s/actions/deactivate' % image_id
|
||||
resp, body = self.post(url, None)
|
||||
self.expected_success(204, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def reactivate_image(self, image_id):
|
||||
url = 'v2/images/%s/actions/reactivate' % image_id
|
||||
resp, body = self.post(url, None)
|
||||
self.expected_success(204, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def delete_image(self, image_id):
|
||||
url = 'v2/images/%s' % image_id
|
||||
resp, _ = self.delete(url)
|
||||
self.expected_success(204, resp.status)
|
||||
return service_client.ResponseBody(resp)
|
||||
|
||||
def list_images(self, params=None):
|
||||
url = 'v2/images'
|
||||
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
self._validate_schema(body, type='images')
|
||||
return service_client.ResponseBodyList(resp, body['images'])
|
||||
|
||||
def show_image(self, image_id):
|
||||
url = 'v2/images/%s' % image_id
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
self.show_image(id)
|
||||
except lib_exc.NotFound:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def resource_type(self):
|
||||
"""Returns the primary type of resource this client works with."""
|
||||
return 'image'
|
||||
|
||||
def store_image(self, image_id, data):
|
||||
url = 'v2/images/%s/file' % image_id
|
||||
headers = {'Content-Type': 'application/octet-stream'}
|
||||
resp, body = self.http.raw_request('PUT', url, headers=headers,
|
||||
body=data)
|
||||
self.expected_success(204, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def get_image_file(self, image_id):
|
||||
url = 'v2/images/%s/file' % image_id
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBodyData(resp, body)
|
||||
|
||||
def add_image_tag(self, image_id, tag):
|
||||
url = 'v2/images/%s/tags/%s' % (image_id, tag)
|
||||
resp, body = self.put(url, body=None)
|
||||
self.expected_success(204, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def delete_image_tag(self, image_id, tag):
|
||||
url = 'v2/images/%s/tags/%s' % (image_id, tag)
|
||||
resp, _ = self.delete(url)
|
||||
self.expected_success(204, resp.status)
|
||||
return service_client.ResponseBody(resp)
|
||||
|
||||
def list_image_members(self, image_id):
|
||||
url = 'v2/images/%s/members' % image_id
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def add_image_member(self, image_id, member_id):
|
||||
url = 'v2/images/%s/members' % image_id
|
||||
data = json.dumps({'member': member_id})
|
||||
resp, body = self.post(url, data)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def update_image_member(self, image_id, member_id, body):
|
||||
url = 'v2/images/%s/members/%s' % (image_id, member_id)
|
||||
data = json.dumps(body)
|
||||
resp, body = self.put(url, data)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def show_image_member(self, image_id, member_id):
|
||||
url = 'v2/images/%s/members/%s' % (image_id, member_id)
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, json.loads(body))
|
||||
|
||||
def remove_image_member(self, image_id, member_id):
|
||||
url = 'v2/images/%s/members/%s' % (image_id, member_id)
|
||||
resp, _ = self.delete(url)
|
||||
self.expected_success(204, resp.status)
|
||||
return service_client.ResponseBody(resp)
|
||||
|
||||
def get_schema(self, schema):
|
||||
url = 'v2/schemas/%s' % schema
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
|
@ -0,0 +1,449 @@
|
|||
# Copyright 2013 IBM Corp.
|
||||
# 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.
|
||||
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
|
||||
from six.moves.urllib import parse as urllib
|
||||
from tempest_lib import exceptions as lib_exc
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
from neutron_lbaas.tests.tempest.lib import exceptions
|
||||
|
||||
|
||||
class OrchestrationClient(service_client.ServiceClient):
|
||||
|
||||
def list_stacks(self, params=None):
|
||||
"""Lists all stacks for a user."""
|
||||
|
||||
uri = 'stacks'
|
||||
if params:
|
||||
uri += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(uri)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBodyList(resp, body['stacks'])
|
||||
|
||||
def create_stack(self, name, disable_rollback=True, parameters=None,
|
||||
timeout_mins=60, template=None, template_url=None,
|
||||
environment=None, files=None):
|
||||
if parameters is None:
|
||||
parameters = {}
|
||||
headers, body = self._prepare_update_create(
|
||||
name,
|
||||
disable_rollback,
|
||||
parameters,
|
||||
timeout_mins,
|
||||
template,
|
||||
template_url,
|
||||
environment,
|
||||
files)
|
||||
uri = 'stacks'
|
||||
resp, body = self.post(uri, headers=headers, body=body)
|
||||
self.expected_success(201, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def update_stack(self, stack_identifier, name, disable_rollback=True,
|
||||
parameters=None, timeout_mins=60, template=None,
|
||||
template_url=None, environment=None, files=None):
|
||||
if parameters is None:
|
||||
parameters = {}
|
||||
headers, body = self._prepare_update_create(
|
||||
name,
|
||||
disable_rollback,
|
||||
parameters,
|
||||
timeout_mins,
|
||||
template,
|
||||
template_url,
|
||||
environment)
|
||||
|
||||
uri = "stacks/%s" % stack_identifier
|
||||
resp, body = self.put(uri, headers=headers, body=body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def _prepare_update_create(self, name, disable_rollback=True,
|
||||
parameters=None, timeout_mins=60,
|
||||
template=None, template_url=None,
|
||||
environment=None, files=None):
|
||||
if parameters is None:
|
||||
parameters = {}
|
||||
post_body = {
|
||||
"stack_name": name,
|
||||
"disable_rollback": disable_rollback,
|
||||
"parameters": parameters,
|
||||
"timeout_mins": timeout_mins,
|
||||
"template": "HeatTemplateFormatVersion: '2012-12-12'\n",
|
||||
"environment": environment,
|
||||
"files": files
|
||||
}
|
||||
if template:
|
||||
post_body['template'] = template
|
||||
if template_url:
|
||||
post_body['template_url'] = template_url
|
||||
body = json.dumps(post_body)
|
||||
|
||||
# Password must be provided on stack create so that heat
|
||||
# can perform future operations on behalf of the user
|
||||
headers = self.get_headers()
|
||||
headers['X-Auth-Key'] = self.password
|
||||
headers['X-Auth-User'] = self.user
|
||||
return headers, body
|
||||
|
||||
def show_stack(self, stack_identifier):
|
||||
"""Returns the details of a single stack."""
|
||||
url = "stacks/%s" % stack_identifier
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body['stack'])
|
||||
|
||||
def suspend_stack(self, stack_identifier):
|
||||
"""Suspend a stack."""
|
||||
url = 'stacks/%s/actions' % stack_identifier
|
||||
body = {'suspend': None}
|
||||
resp, body = self.post(url, json.dumps(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp)
|
||||
|
||||
def resume_stack(self, stack_identifier):
|
||||
"""Resume a stack."""
|
||||
url = 'stacks/%s/actions' % stack_identifier
|
||||
body = {'resume': None}
|
||||
resp, body = self.post(url, json.dumps(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp)
|
||||
|
||||
def list_resources(self, stack_identifier):
|
||||
"""Returns the details of a single resource."""
|
||||
url = "stacks/%s/resources" % stack_identifier
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBodyList(resp, body['resources'])
|
||||
|
||||
def show_resource(self, stack_identifier, resource_name):
|
||||
"""Returns the details of a single resource."""
|
||||
url = "stacks/%s/resources/%s" % (stack_identifier, resource_name)
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body['resource'])
|
||||
|
||||
def delete_stack(self, stack_identifier):
|
||||
"""Deletes the specified Stack."""
|
||||
resp, _ = self.delete("stacks/%s" % str(stack_identifier))
|
||||
self.expected_success(204, resp.status)
|
||||
return service_client.ResponseBody(resp)
|
||||
|
||||
def wait_for_resource_status(self, stack_identifier, resource_name,
|
||||
status, failure_pattern='^.*_FAILED$'):
|
||||
"""Waits for a Resource to reach a given status."""
|
||||
start = int(time.time())
|
||||
fail_regexp = re.compile(failure_pattern)
|
||||
|
||||
while True:
|
||||
try:
|
||||
body = self.show_resource(
|
||||
stack_identifier, resource_name)
|
||||
except lib_exc.NotFound:
|
||||
# ignore this, as the resource may not have
|
||||
# been created yet
|
||||
pass
|
||||
else:
|
||||
resource_name = body['resource_name']
|
||||
resource_status = body['resource_status']
|
||||
if resource_status == status:
|
||||
return
|
||||
if fail_regexp.search(resource_status):
|
||||
raise exceptions.StackResourceBuildErrorException(
|
||||
resource_name=resource_name,
|
||||
stack_identifier=stack_identifier,
|
||||
resource_status=resource_status,
|
||||
resource_status_reason=body['resource_status_reason'])
|
||||
|
||||
if int(time.time()) - start >= self.build_timeout:
|
||||
message = ('Resource %s failed to reach %s status '
|
||||
'(current %s) within the required time (%s s).' %
|
||||
(resource_name,
|
||||
status,
|
||||
resource_status,
|
||||
self.build_timeout))
|
||||
raise exceptions.TimeoutException(message)
|
||||
time.sleep(self.build_interval)
|
||||
|
||||
def wait_for_stack_status(self, stack_identifier, status,
|
||||
failure_pattern='^.*_FAILED$'):
|
||||
"""Waits for a Stack to reach a given status."""
|
||||
start = int(time.time())
|
||||
fail_regexp = re.compile(failure_pattern)
|
||||
|
||||
while True:
|
||||
try:
|
||||
body = self.show_stack(stack_identifier)
|
||||
except lib_exc.NotFound:
|
||||
if status == 'DELETE_COMPLETE':
|
||||
return
|
||||
stack_name = body['stack_name']
|
||||
stack_status = body['stack_status']
|
||||
if stack_status == status:
|
||||
return body
|
||||
if fail_regexp.search(stack_status):
|
||||
raise exceptions.StackBuildErrorException(
|
||||
stack_identifier=stack_identifier,
|
||||
stack_status=stack_status,
|
||||
stack_status_reason=body['stack_status_reason'])
|
||||
|
||||
if int(time.time()) - start >= self.build_timeout:
|
||||
message = ('Stack %s failed to reach %s status (current: %s) '
|
||||
'within the required time (%s s).' %
|
||||
(stack_name, status, stack_status,
|
||||
self.build_timeout))
|
||||
raise exceptions.TimeoutException(message)
|
||||
time.sleep(self.build_interval)
|
||||
|
||||
def show_resource_metadata(self, stack_identifier, resource_name):
|
||||
"""Returns the resource's metadata."""
|
||||
url = ('stacks/{stack_identifier}/resources/{resource_name}'
|
||||
'/metadata'.format(**locals()))
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body['metadata'])
|
||||
|
||||
def list_events(self, stack_identifier):
|
||||
"""Returns list of all events for a stack."""
|
||||
url = 'stacks/{stack_identifier}/events'.format(**locals())
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBodyList(resp, body['events'])
|
||||
|
||||
def list_resource_events(self, stack_identifier, resource_name):
|
||||
"""Returns list of all events for a resource from stack."""
|
||||
url = ('stacks/{stack_identifier}/resources/{resource_name}'
|
||||
'/events'.format(**locals()))
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBodyList(resp, body['events'])
|
||||
|
||||
def show_event(self, stack_identifier, resource_name, event_id):
|
||||
"""Returns the details of a single stack's event."""
|
||||
url = ('stacks/{stack_identifier}/resources/{resource_name}/events'
|
||||
'/{event_id}'.format(**locals()))
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body['event'])
|
||||
|
||||
def show_template(self, stack_identifier):
|
||||
"""Returns the template for the stack."""
|
||||
url = ('stacks/{stack_identifier}/template'.format(**locals()))
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def _validate_template(self, post_body):
|
||||
"""Returns the validation request result."""
|
||||
post_body = json.dumps(post_body)
|
||||
resp, body = self.post('validate', post_body)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def validate_template(self, template, parameters=None):
|
||||
"""Returns the validation result for a template with parameters."""
|
||||
if parameters is None:
|
||||
parameters = {}
|
||||
post_body = {
|
||||
'template': template,
|
||||
'parameters': parameters,
|
||||
}
|
||||
return self._validate_template(post_body)
|
||||
|
||||
def validate_template_url(self, template_url, parameters=None):
|
||||
"""Returns the validation result for a template with parameters."""
|
||||
if parameters is None:
|
||||
parameters = {}
|
||||
post_body = {
|
||||
'template_url': template_url,
|
||||
'parameters': parameters,
|
||||
}
|
||||
return self._validate_template(post_body)
|
||||
|
||||
def list_resource_types(self):
|
||||
"""List resource types."""
|
||||
resp, body = self.get('resource_types')
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBodyList(resp, body['resource_types'])
|
||||
|
||||
def show_resource_type(self, resource_type_name):
|
||||
"""Return the schema of a resource type."""
|
||||
url = 'resource_types/%s' % resource_type_name
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, json.loads(body))
|
||||
|
||||
def show_resource_type_template(self, resource_type_name):
|
||||
"""Return the template of a resource type."""
|
||||
url = 'resource_types/%s/template' % resource_type_name
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, json.loads(body))
|
||||
|
||||
def create_software_config(self, name=None, config=None, group=None,
|
||||
inputs=None, outputs=None, options=None):
|
||||
headers, body = self._prep_software_config_create(
|
||||
name, config, group, inputs, outputs, options)
|
||||
|
||||
url = 'software_configs'
|
||||
resp, body = self.post(url, headers=headers, body=body)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def show_software_config(self, conf_id):
|
||||
"""Returns a software configuration resource."""
|
||||
url = 'software_configs/%s' % str(conf_id)
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def delete_software_config(self, conf_id):
|
||||
"""Deletes a specific software configuration."""
|
||||
url = 'software_configs/%s' % str(conf_id)
|
||||
resp, _ = self.delete(url)
|
||||
self.expected_success(204, resp.status)
|
||||
return service_client.ResponseBody(resp)
|
||||
|
||||
def create_software_deploy(self, server_id=None, config_id=None,
|
||||
action=None, status=None,
|
||||
input_values=None, output_values=None,
|
||||
status_reason=None, signal_transport=None):
|
||||
"""Creates or updates a software deployment."""
|
||||
headers, body = self._prep_software_deploy_update(
|
||||
None, server_id, config_id, action, status, input_values,
|
||||
output_values, status_reason, signal_transport)
|
||||
|
||||
url = 'software_deployments'
|
||||
resp, body = self.post(url, headers=headers, body=body)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def update_software_deploy(self, deploy_id=None, server_id=None,
|
||||
config_id=None, action=None, status=None,
|
||||
input_values=None, output_values=None,
|
||||
status_reason=None, signal_transport=None):
|
||||
"""Creates or updates a software deployment."""
|
||||
headers, body = self._prep_software_deploy_update(
|
||||
deploy_id, server_id, config_id, action, status, input_values,
|
||||
output_values, status_reason, signal_transport)
|
||||
|
||||
url = 'software_deployments/%s' % str(deploy_id)
|
||||
resp, body = self.put(url, headers=headers, body=body)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def list_software_deployments(self):
|
||||
"""Returns a list of all deployments."""
|
||||
url = 'software_deployments'
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def show_software_deployment(self, deploy_id):
|
||||
"""Returns a specific software deployment."""
|
||||
url = 'software_deployments/%s' % str(deploy_id)
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def show_software_deployment_metadata(self, server_id):
|
||||
"""Return a config metadata for a specific server."""
|
||||
url = 'software_deployments/metadata/%s' % server_id
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = json.loads(body)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def delete_software_deploy(self, deploy_id):
|
||||
"""Deletes a specific software deployment."""
|
||||
url = 'software_deployments/%s' % str(deploy_id)
|
||||
resp, _ = self.delete(url)
|
||||
self.expected_success(204, resp.status)
|
||||
return service_client.ResponseBody(resp)
|
||||
|
||||
def _prep_software_config_create(self, name=None, conf=None, group=None,
|
||||
inputs=None, outputs=None, options=None):
|
||||
"""Prepares a software configuration body."""
|
||||
post_body = {}
|
||||
if name is not None:
|
||||
post_body["name"] = name
|
||||
if conf is not None:
|
||||
post_body["config"] = conf
|
||||
if group is not None:
|
||||
post_body["group"] = group
|
||||
if inputs is not None:
|
||||
post_body["inputs"] = inputs
|
||||
if outputs is not None:
|
||||
post_body["outputs"] = outputs
|
||||
if options is not None:
|
||||
post_body["options"] = options
|
||||
body = json.dumps(post_body)
|
||||
|
||||
headers = self.get_headers()
|
||||
return headers, body
|
||||
|
||||
def _prep_software_deploy_update(self, deploy_id=None, server_id=None,
|
||||
config_id=None, action=None, status=None,
|
||||
input_values=None, output_values=None,
|
||||
status_reason=None,
|
||||
signal_transport=None):
|
||||
"""Prepares a deployment create or update (if an id was given)."""
|
||||
post_body = {}
|
||||
|
||||
if deploy_id is not None:
|
||||
post_body["id"] = deploy_id
|
||||
if server_id is not None:
|
||||
post_body["server_id"] = server_id
|
||||
if config_id is not None:
|
||||
post_body["config_id"] = config_id
|
||||
if action is not None:
|
||||
post_body["action"] = action
|
||||
if status is not None:
|
||||
post_body["status"] = status
|
||||
if input_values is not None:
|
||||
post_body["input_values"] = input_values
|
||||
if output_values is not None:
|
||||
post_body["output_values"] = output_values
|
||||
if status_reason is not None:
|
||||
post_body["status_reason"] = status_reason
|
||||
if signal_transport is not None:
|
||||
post_body["signal_transport"] = signal_transport
|
||||
body = json.dumps(post_body)
|
||||
|
||||
headers = self.get_headers()
|
||||
return headers, body
|
|
@ -0,0 +1,201 @@
|
|||
# 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 json
|
||||
import time
|
||||
|
||||
from oslo_log import log as logging
|
||||
from six.moves.urllib import parse as urllib
|
||||
from tempest_lib import exceptions as lib_exc
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
from neutron_lbaas.tests.tempest.lib import exceptions
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseSnapshotsClientJSON(service_client.ServiceClient):
|
||||
"""Base Client class to send CRUD Volume API requests."""
|
||||
|
||||
create_resp = 200
|
||||
|
||||
def list_snapshots(self, detail=False, params=None):
|
||||
"""List all the snapshot."""
|
||||
url = 'snapshots'
|
||||
if detail:
|
||||
url += '/detail'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBodyList(resp, body['snapshots'])
|
||||
|
||||
def show_snapshot(self, snapshot_id):
|
||||
"""Returns the details of a single snapshot."""
|
||||
url = "snapshots/%s" % str(snapshot_id)
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body['snapshot'])
|
||||
|
||||
def create_snapshot(self, volume_id, **kwargs):
|
||||
"""
|
||||
Creates a new snapshot.
|
||||
volume_id(Required): id of the volume.
|
||||
force: Create a snapshot even if the volume attached (Default=False)
|
||||
display_name: Optional snapshot Name.
|
||||
display_description: User friendly snapshot description.
|
||||
"""
|
||||
post_body = {'volume_id': volume_id}
|
||||
post_body.update(kwargs)
|
||||
post_body = json.dumps({'snapshot': post_body})
|
||||
resp, body = self.post('snapshots', post_body)
|
||||
body = json.loads(body)
|
||||
self.expected_success(self.create_resp, resp.status)
|
||||
return service_client.ResponseBody(resp, body['snapshot'])
|
||||
|
||||
def update_snapshot(self, snapshot_id, **kwargs):
|
||||
"""Updates a snapshot."""
|
||||
put_body = json.dumps({'snapshot': kwargs})
|
||||
resp, body = self.put('snapshots/%s' % snapshot_id, put_body)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body['snapshot'])
|
||||
|
||||
# NOTE(afazekas): just for the wait function
|
||||
def _get_snapshot_status(self, snapshot_id):
|
||||
body = self.show_snapshot(snapshot_id)
|
||||
status = body['status']
|
||||
# NOTE(afazekas): snapshot can reach an "error"
|
||||
# state in a "normal" lifecycle
|
||||
if (status == 'error'):
|
||||
raise exceptions.SnapshotBuildErrorException(
|
||||
snapshot_id=snapshot_id)
|
||||
|
||||
return status
|
||||
|
||||
# NOTE(afazkas): Wait reinvented again. It is not in the correct layer
|
||||
def wait_for_snapshot_status(self, snapshot_id, status):
|
||||
"""Waits for a Snapshot to reach a given status."""
|
||||
start_time = time.time()
|
||||
old_value = value = self._get_snapshot_status(snapshot_id)
|
||||
while True:
|
||||
dtime = time.time() - start_time
|
||||
time.sleep(self.build_interval)
|
||||
if value != old_value:
|
||||
LOG.info('Value transition from "%s" to "%s"'
|
||||
'in %d second(s).', old_value,
|
||||
value, dtime)
|
||||
if (value == status):
|
||||
return value
|
||||
|
||||
if dtime > self.build_timeout:
|
||||
message = ('Time Limit Exceeded! (%ds)'
|
||||
'while waiting for %s, '
|
||||
'but we got %s.' %
|
||||
(self.build_timeout, status, value))
|
||||
raise exceptions.TimeoutException(message)
|
||||
time.sleep(self.build_interval)
|
||||
old_value = value
|
||||
value = self._get_snapshot_status(snapshot_id)
|
||||
|
||||
def delete_snapshot(self, snapshot_id):
|
||||
"""Delete Snapshot."""
|
||||
resp, body = self.delete("snapshots/%s" % str(snapshot_id))
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
self.show_snapshot(id)
|
||||
except lib_exc.NotFound:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def resource_type(self):
|
||||
"""Returns the primary type of resource this client works with."""
|
||||
return 'volume-snapshot'
|
||||
|
||||
def reset_snapshot_status(self, snapshot_id, status):
|
||||
"""Reset the specified snapshot's status."""
|
||||
post_body = json.dumps({'os-reset_status': {"status": status}})
|
||||
resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def update_snapshot_status(self, snapshot_id, status, progress):
|
||||
"""Update the specified snapshot's status."""
|
||||
post_body = {
|
||||
'status': status,
|
||||
'progress': progress
|
||||
}
|
||||
post_body = json.dumps({'os-update_snapshot_status': post_body})
|
||||
url = 'snapshots/%s/action' % str(snapshot_id)
|
||||
resp, body = self.post(url, post_body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def create_snapshot_metadata(self, snapshot_id, metadata):
|
||||
"""Create metadata for the snapshot."""
|
||||
put_body = json.dumps({'metadata': metadata})
|
||||
url = "snapshots/%s/metadata" % str(snapshot_id)
|
||||
resp, body = self.post(url, put_body)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body['metadata'])
|
||||
|
||||
def show_snapshot_metadata(self, snapshot_id):
|
||||
"""Get metadata of the snapshot."""
|
||||
url = "snapshots/%s/metadata" % str(snapshot_id)
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body['metadata'])
|
||||
|
||||
def update_snapshot_metadata(self, snapshot_id, metadata):
|
||||
"""Update metadata for the snapshot."""
|
||||
put_body = json.dumps({'metadata': metadata})
|
||||
url = "snapshots/%s/metadata" % str(snapshot_id)
|
||||
resp, body = self.put(url, put_body)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body['metadata'])
|
||||
|
||||
def update_snapshot_metadata_item(self, snapshot_id, id, meta_item):
|
||||
"""Update metadata item for the snapshot."""
|
||||
put_body = json.dumps({'meta': meta_item})
|
||||
url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
|
||||
resp, body = self.put(url, put_body)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body['meta'])
|
||||
|
||||
def delete_snapshot_metadata_item(self, snapshot_id, id):
|
||||
"""Delete metadata item for the snapshot."""
|
||||
url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
|
||||
resp, body = self.delete(url)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def force_delete_snapshot(self, snapshot_id):
|
||||
"""Force Delete Snapshot."""
|
||||
post_body = json.dumps({'os-force_delete': {}})
|
||||
resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
|
||||
class SnapshotsClientJSON(BaseSnapshotsClientJSON):
|
||||
"""Client class to send CRUD Volume V1 API requests."""
|
|
@ -0,0 +1,351 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
import json
|
||||
import time
|
||||
|
||||
from six.moves.urllib import parse as urllib
|
||||
from tempest_lib import exceptions as lib_exc
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.common import service_client
|
||||
from neutron_lbaas.tests.tempest.lib import exceptions
|
||||
|
||||
|
||||
class BaseVolumesClientJSON(service_client.ServiceClient):
|
||||
"""
|
||||
Base client class to send CRUD Volume API requests to a Cinder endpoint
|
||||
"""
|
||||
|
||||
create_resp = 200
|
||||
|
||||
def __init__(self, auth_provider, service, region,
|
||||
default_volume_size=1, **kwargs):
|
||||
super(BaseVolumesClientJSON, self).__init__(
|
||||
auth_provider, service, region, **kwargs)
|
||||
self.default_volume_size = default_volume_size
|
||||
|
||||
def get_attachment_from_volume(self, volume):
|
||||
"""Return the element 'attachment' from input volumes."""
|
||||
return volume['attachments'][0]
|
||||
|
||||
def list_volumes(self, detail=False, params=None):
|
||||
"""List all the volumes created."""
|
||||
url = 'volumes'
|
||||
if detail:
|
||||
url += '/detail'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBodyList(resp, body['volumes'])
|
||||
|
||||
def show_volume(self, volume_id):
|
||||
"""Returns the details of a single volume."""
|
||||
url = "volumes/%s" % str(volume_id)
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body['volume'])
|
||||
|
||||
def create_volume(self, size=None, **kwargs):
|
||||
"""
|
||||
Creates a new Volume.
|
||||
size: Size of volume in GB.
|
||||
Following optional keyword arguments are accepted:
|
||||
display_name: Optional Volume Name(only for V1).
|
||||
name: Optional Volume Name(only for V2).
|
||||
metadata: A dictionary of values to be used as metadata.
|
||||
volume_type: Optional Name of volume_type for the volume
|
||||
snapshot_id: When specified the volume is created from this snapshot
|
||||
imageRef: When specified the volume is created from this image
|
||||
"""
|
||||
if size is None:
|
||||
size = self.default_volume_size
|
||||
post_body = {'size': size}
|
||||
post_body.update(kwargs)
|
||||
post_body = json.dumps({'volume': post_body})
|
||||
resp, body = self.post('volumes', post_body)
|
||||
body = json.loads(body)
|
||||
self.expected_success(self.create_resp, resp.status)
|
||||
return service_client.ResponseBody(resp, body['volume'])
|
||||
|
||||
def update_volume(self, volume_id, **kwargs):
|
||||
"""Updates the Specified Volume."""
|
||||
put_body = json.dumps({'volume': kwargs})
|
||||
resp, body = self.put('volumes/%s' % volume_id, put_body)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body['volume'])
|
||||
|
||||
def delete_volume(self, volume_id):
|
||||
"""Deletes the Specified Volume."""
|
||||
resp, body = self.delete("volumes/%s" % str(volume_id))
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def upload_volume(self, volume_id, image_name, disk_format):
|
||||
"""Uploads a volume in Glance."""
|
||||
post_body = {
|
||||
'image_name': image_name,
|
||||
'disk_format': disk_format
|
||||
}
|
||||
post_body = json.dumps({'os-volume_upload_image': post_body})
|
||||
url = 'volumes/%s/action' % (volume_id)
|
||||
resp, body = self.post(url, post_body)
|
||||
body = json.loads(body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp,
|
||||
body['os-volume_upload_image'])
|
||||
|
||||
def attach_volume(self, volume_id, instance_uuid, mountpoint):
|
||||
"""Attaches a volume to a given instance on a given mountpoint."""
|
||||
post_body = {
|
||||
'instance_uuid': instance_uuid,
|
||||
'mountpoint': mountpoint,
|
||||
}
|
||||
post_body = json.dumps({'os-attach': post_body})
|
||||
url = 'volumes/%s/action' % (volume_id)
|
||||
resp, body = self.post(url, post_body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def set_bootable_volume(self, volume_id, bootable):
|
||||
"""set a bootable flag for a volume - true or false."""
|
||||
post_body = {"bootable": bootable}
|
||||
post_body = json.dumps({'os-set_bootable': post_body})
|
||||
url = 'volumes/%s/action' % (volume_id)
|
||||
resp, body = self.post(url, post_body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def detach_volume(self, volume_id):
|
||||
"""Detaches a volume from an instance."""
|
||||
post_body = {}
|
||||
post_body = json.dumps({'os-detach': post_body})
|
||||
url = 'volumes/%s/action' % (volume_id)
|
||||
resp, body = self.post(url, post_body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def reserve_volume(self, volume_id):
|
||||
"""Reserves a volume."""
|
||||
post_body = {}
|
||||
post_body = json.dumps({'os-reserve': post_body})
|
||||
url = 'volumes/%s/action' % (volume_id)
|
||||
resp, body = self.post(url, post_body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def unreserve_volume(self, volume_id):
|
||||
"""Restore a reserved volume ."""
|
||||
post_body = {}
|
||||
post_body = json.dumps({'os-unreserve': post_body})
|
||||
url = 'volumes/%s/action' % (volume_id)
|
||||
resp, body = self.post(url, post_body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def wait_for_volume_status(self, volume_id, status):
|
||||
"""Waits for a Volume to reach a given status."""
|
||||
body = self.show_volume(volume_id)
|
||||
volume_status = body['status']
|
||||
start = int(time.time())
|
||||
|
||||
while volume_status != status:
|
||||
time.sleep(self.build_interval)
|
||||
body = self.show_volume(volume_id)
|
||||
volume_status = body['status']
|
||||
if volume_status == 'error':
|
||||
raise exceptions.VolumeBuildErrorException(volume_id=volume_id)
|
||||
|
||||
if int(time.time()) - start >= self.build_timeout:
|
||||
message = ('Volume %s failed to reach %s status (current: %s) '
|
||||
'within the required time '
|
||||
'(%s s).' % (volume_id,
|
||||
status,
|
||||
volume_status,
|
||||
self.build_timeout))
|
||||
raise exceptions.TimeoutException(message)
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
self.show_volume(id)
|
||||
except lib_exc.NotFound:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def resource_type(self):
|
||||
"""Returns the primary type of resource this client works with."""
|
||||
return 'volume'
|
||||
|
||||
def extend_volume(self, volume_id, extend_size):
|
||||
"""Extend a volume."""
|
||||
post_body = {
|
||||
'new_size': extend_size
|
||||
}
|
||||
post_body = json.dumps({'os-extend': post_body})
|
||||
url = 'volumes/%s/action' % (volume_id)
|
||||
resp, body = self.post(url, post_body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def reset_volume_status(self, volume_id, status):
|
||||
"""Reset the Specified Volume's Status."""
|
||||
post_body = json.dumps({'os-reset_status': {"status": status}})
|
||||
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def volume_begin_detaching(self, volume_id):
|
||||
"""Volume Begin Detaching."""
|
||||
# ref cinder/api/contrib/volume_actions.py#L158
|
||||
post_body = json.dumps({'os-begin_detaching': {}})
|
||||
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def volume_roll_detaching(self, volume_id):
|
||||
"""Volume Roll Detaching."""
|
||||
# cinder/api/contrib/volume_actions.py#L170
|
||||
post_body = json.dumps({'os-roll_detaching': {}})
|
||||
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def create_volume_transfer(self, vol_id, display_name=None):
|
||||
"""Create a volume transfer."""
|
||||
post_body = {
|
||||
'volume_id': vol_id
|
||||
}
|
||||
if display_name:
|
||||
post_body['name'] = display_name
|
||||
post_body = json.dumps({'transfer': post_body})
|
||||
resp, body = self.post('os-volume-transfer', post_body)
|
||||
body = json.loads(body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body['transfer'])
|
||||
|
||||
def show_volume_transfer(self, transfer_id):
|
||||
"""Returns the details of a volume transfer."""
|
||||
url = "os-volume-transfer/%s" % str(transfer_id)
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body['transfer'])
|
||||
|
||||
def list_volume_transfers(self, params=None):
|
||||
"""List all the volume transfers created."""
|
||||
url = 'os-volume-transfer'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBodyList(resp, body['transfers'])
|
||||
|
||||
def delete_volume_transfer(self, transfer_id):
|
||||
"""Delete a volume transfer."""
|
||||
resp, body = self.delete("os-volume-transfer/%s" % str(transfer_id))
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def accept_volume_transfer(self, transfer_id, transfer_auth_key):
|
||||
"""Accept a volume transfer."""
|
||||
post_body = {
|
||||
'auth_key': transfer_auth_key,
|
||||
}
|
||||
url = 'os-volume-transfer/%s/accept' % transfer_id
|
||||
post_body = json.dumps({'accept': post_body})
|
||||
resp, body = self.post(url, post_body)
|
||||
body = json.loads(body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body['transfer'])
|
||||
|
||||
def update_volume_readonly(self, volume_id, readonly):
|
||||
"""Update the Specified Volume readonly."""
|
||||
post_body = {
|
||||
'readonly': readonly
|
||||
}
|
||||
post_body = json.dumps({'os-update_readonly_flag': post_body})
|
||||
url = 'volumes/%s/action' % (volume_id)
|
||||
resp, body = self.post(url, post_body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def force_delete_volume(self, volume_id):
|
||||
"""Force Delete Volume."""
|
||||
post_body = json.dumps({'os-force_delete': {}})
|
||||
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
|
||||
self.expected_success(202, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def create_volume_metadata(self, volume_id, metadata):
|
||||
"""Create metadata for the volume."""
|
||||
put_body = json.dumps({'metadata': metadata})
|
||||
url = "volumes/%s/metadata" % str(volume_id)
|
||||
resp, body = self.post(url, put_body)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body['metadata'])
|
||||
|
||||
def show_volume_metadata(self, volume_id):
|
||||
"""Get metadata of the volume."""
|
||||
url = "volumes/%s/metadata" % str(volume_id)
|
||||
resp, body = self.get(url)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body['metadata'])
|
||||
|
||||
def update_volume_metadata(self, volume_id, metadata):
|
||||
"""Update metadata for the volume."""
|
||||
put_body = json.dumps({'metadata': metadata})
|
||||
url = "volumes/%s/metadata" % str(volume_id)
|
||||
resp, body = self.put(url, put_body)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body['metadata'])
|
||||
|
||||
def update_volume_metadata_item(self, volume_id, id, meta_item):
|
||||
"""Update metadata item for the volume."""
|
||||
put_body = json.dumps({'meta': meta_item})
|
||||
url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
|
||||
resp, body = self.put(url, put_body)
|
||||
body = json.loads(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body['meta'])
|
||||
|
||||
def delete_volume_metadata_item(self, volume_id, id):
|
||||
"""Delete metadata item for the volume."""
|
||||
url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
|
||||
resp, body = self.delete(url)
|
||||
self.expected_success(200, resp.status)
|
||||
return service_client.ResponseBody(resp, body)
|
||||
|
||||
def retype_volume(self, volume_id, volume_type, **kwargs):
|
||||
"""Updates volume with new volume type."""
|
||||
post_body = {'new_type': volume_type}
|
||||
post_body.update(kwargs)
|
||||
post_body = json.dumps({'os-retype': post_body})
|
||||
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
|
||||
self.expected_success(202, resp.status)
|
||||
|
||||
|
||||
class VolumesClientJSON(BaseVolumesClientJSON):
|
||||
"""
|
||||
Client class to send CRUD Volume V1 API requests to a Cinder endpoint
|
||||
"""
|
|
@ -0,0 +1,19 @@
|
|||
# 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 neutron_lbaas.tests.tempest.lib.services.volume.json import snapshots_client
|
||||
|
||||
|
||||
class SnapshotsV2ClientJSON(snapshots_client.BaseSnapshotsClientJSON):
|
||||
"""Client class to send CRUD Volume V2 API requests."""
|
||||
api_version = "v2"
|
||||
create_resp = 202
|
|
@ -0,0 +1,24 @@
|
|||
# Copyright 2012 OpenStack Foundation
|
||||
# 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 neutron_lbaas.tests.tempest.lib.services.volume.json import volumes_client
|
||||
|
||||
|
||||
class VolumesV2ClientJSON(volumes_client.BaseVolumesClientJSON):
|
||||
"""
|
||||
Client class to send CRUD Volume V2 API requests to a Cinder endpoint
|
||||
"""
|
||||
api_version = "v2"
|
||||
create_resp = 202
|
|
@ -0,0 +1,364 @@
|
|||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
# 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.
|
||||
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import six
|
||||
from six.moves.urllib import request as urllib2
|
||||
|
||||
from neutron_lbaas.tests.tempest.lib.common import commands
|
||||
from neutron_lbaas.tests.tempest.lib import config
|
||||
from neutron_lbaas.tests.tempest.lib import exceptions
|
||||
from neutron_lbaas.tests.tempest.lib.services.network import resources as \
|
||||
net_resources
|
||||
from neutron_lbaas.tests.tempest.lib import test
|
||||
from neutron_lbaas.tests.tempest.v2.scenario import manager
|
||||
|
||||
config = config.CONF
|
||||
|
||||
|
||||
class BaseTestCase(manager.NetworkScenarioTest):
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
super(BaseTestCase, cls).skip_checks()
|
||||
cfg = config.network
|
||||
if not test.is_extension_enabled('lbaasv2', 'network'):
|
||||
msg = 'LBaaS Extension is not enabled'
|
||||
raise cls.skipException(msg)
|
||||
if not (cfg.tenant_networks_reachable or cfg.public_network_id):
|
||||
msg = ('Either tenant_networks_reachable must be "true", or '
|
||||
'public_network_id must be defined.')
|
||||
raise cls.skipException(msg)
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(BaseTestCase, cls).resource_setup()
|
||||
cls.servers_keypairs = {}
|
||||
cls.members = []
|
||||
cls.floating_ips = {}
|
||||
cls.servers_floating_ips = {}
|
||||
cls.server_ips = {}
|
||||
cls.port1 = 80
|
||||
cls.port2 = 88
|
||||
cls.num = 50
|
||||
|
||||
@classmethod
|
||||
def resource_cleanup(cls):
|
||||
super(BaseTestCase, cls).resource_cleanup()
|
||||
|
||||
def _set_net_and_subnet(self):
|
||||
"""
|
||||
Query and set appropriate network and subnet attributes to be used
|
||||
for the test. Existing tenant networks are used if they are found.
|
||||
The configured private network and associated subnet is used as a
|
||||
fallback in absence of tenant networking.
|
||||
"""
|
||||
try:
|
||||
tenant_net = self._list_networks(tenant_id=self.tenant_id)[0]
|
||||
except IndexError:
|
||||
tenant_net = None
|
||||
|
||||
if tenant_net:
|
||||
tenant_subnet = self._list_subnets(tenant_id=self.tenant_id)[0]
|
||||
self.subnet = net_resources.DeletableSubnet(
|
||||
client=self.network_client,
|
||||
**tenant_subnet)
|
||||
self.network = tenant_net
|
||||
else:
|
||||
self.network = self._get_network_by_name(
|
||||
config.compute.fixed_network_name)
|
||||
# We are assuming that the first subnet associated
|
||||
# with the fixed network is the one we want. In the future, we
|
||||
# should instead pull a subnet id from config, which is set by
|
||||
# devstack/admin/etc.
|
||||
subnet = self._list_subnets(network_id=self.network['id'])[0]
|
||||
self.subnet = net_resources.AttributeDict(subnet)
|
||||
|
||||
def _create_security_group_for_test(self):
|
||||
self.security_group = self._create_security_group(
|
||||
tenant_id=self.tenant_id)
|
||||
self._create_security_group_rules_for_port(self.port1)
|
||||
self._create_security_group_rules_for_port(self.port2)
|
||||
|
||||
def _create_security_group_rules_for_port(self, port):
|
||||
rule = {
|
||||
'direction': 'ingress',
|
||||
'protocol': 'tcp',
|
||||
'port_range_min': port,
|
||||
'port_range_max': port,
|
||||
}
|
||||
self._create_security_group_rule(
|
||||
secgroup=self.security_group,
|
||||
tenant_id=self.tenant_id,
|
||||
**rule)
|
||||
|
||||
def _create_server(self, name):
|
||||
keypair = self.create_keypair()
|
||||
security_groups = [{'name': self.security_group['name']}]
|
||||
create_kwargs = {
|
||||
'networks': [
|
||||
{'uuid': self.network['id']},
|
||||
],
|
||||
'key_name': keypair['name'],
|
||||
'security_groups': security_groups,
|
||||
}
|
||||
net_name = self.network['name']
|
||||
server = self.create_server(name=name, create_kwargs=create_kwargs)
|
||||
self.servers_keypairs[server['id']] = keypair
|
||||
if (config.network.public_network_id and not
|
||||
config.network.tenant_networks_reachable):
|
||||
public_network_id = config.network.public_network_id
|
||||
floating_ip = self.create_floating_ip(
|
||||
server, public_network_id)
|
||||
self.floating_ips[floating_ip] = server
|
||||
self.server_ips[server['id']] = floating_ip.floating_ip_address
|
||||
else:
|
||||
self.server_ips[server['id']] =\
|
||||
server['addresses'][net_name][0]['addr']
|
||||
self.server_fixed_ips[server['id']] =\
|
||||
server['addresses'][net_name][0]['addr']
|
||||
self.assertTrue(self.servers_keypairs)
|
||||
return server
|
||||
|
||||
def _create_servers(self):
|
||||
for count in range(2):
|
||||
self._create_server(name=("server%s" % (count + 1)))
|
||||
self.assertEqual(len(self.servers_keypairs), 2)
|
||||
|
||||
def _start_servers(self):
|
||||
"""
|
||||
Start two backends
|
||||
1. SSH to the instance
|
||||
2. Start two http backends listening on ports 80 and 88 respectively
|
||||
"""
|
||||
for server_id, ip in six.iteritems(self.server_ips):
|
||||
private_key = self.servers_keypairs[server_id]['private_key']
|
||||
server_name = self.servers_client.get_server(server_id)['name']
|
||||
username = config.scenario.ssh_user
|
||||
ssh_client = self.get_remote_client(
|
||||
server_or_ip=ip,
|
||||
private_key=private_key)
|
||||
|
||||
# Write a backend's response into a file
|
||||
resp = ('echo -ne "HTTP/1.1 200 OK\r\nContent-Length: 7\r\n'
|
||||
'Connection: close\r\nContent-Type: text/html; '
|
||||
'charset=UTF-8\r\n\r\n%s"; cat >/dev/null')
|
||||
|
||||
with tempfile.NamedTemporaryFile() as script:
|
||||
script.write(resp % server_name)
|
||||
script.flush()
|
||||
with tempfile.NamedTemporaryFile() as key:
|
||||
key.write(private_key)
|
||||
key.flush()
|
||||
commands.copy_file_to_host(script.name,
|
||||
"/tmp/script1",
|
||||
ip,
|
||||
username, key.name)
|
||||
|
||||
# Start netcat
|
||||
start_server = ('while true; do '
|
||||
'sudo nc -ll -p %(port)s -e sh /tmp/%(script)s; '
|
||||
'done > /dev/null &')
|
||||
cmd = start_server % {'port': self.port1,
|
||||
'script': 'script1'}
|
||||
ssh_client.exec_command(cmd)
|
||||
|
||||
if len(self.server_ips) == 1:
|
||||
with tempfile.NamedTemporaryFile() as script:
|
||||
script.write(resp % 'server2')
|
||||
script.flush()
|
||||
with tempfile.NamedTemporaryFile() as key:
|
||||
key.write(private_key)
|
||||
key.flush()
|
||||
commands.copy_file_to_host(script.name,
|
||||
"/tmp/script2", ip,
|
||||
username, key.name)
|
||||
cmd = start_server % {'port': self.port2,
|
||||
'script': 'script2'}
|
||||
ssh_client.exec_command(cmd)
|
||||
|
||||
def _check_connection(self, check_ip, port=80):
|
||||
def try_connect(ip, port):
|
||||
try:
|
||||
resp = urllib2.urlopen("http://{0}:{1}/".format(ip, port))
|
||||
if resp.getcode() == 200:
|
||||
return True
|
||||
return False
|
||||
except IOError:
|
||||
return False
|
||||
except urllib2.HTTPError:
|
||||
return False
|
||||
timeout = config.compute.ping_timeout
|
||||
start = time.time()
|
||||
while not try_connect(check_ip, port):
|
||||
if (time.time() - start) > timeout:
|
||||
message = "Timed out trying to connect to %s" % check_ip
|
||||
raise exceptions.TimeoutException(message)
|
||||
|
||||
def _create_listener(self, load_balancer_id):
|
||||
"""Create a listener with HTTP protocol listening on port 80."""
|
||||
self.listener = self.listeners_client.create_listener(
|
||||
loadbalancer_id=load_balancer_id,
|
||||
protocol='HTTP', protocol_port=80)
|
||||
self.assertTrue(self.listener)
|
||||
return self.listener
|
||||
|
||||
def _create_pool(self, listener_id):
|
||||
"""Create a pool with ROUND_ROBIN algorithm."""
|
||||
self.pool = self.pools_client.create_pool(
|
||||
protocol='HTTP',
|
||||
lb_algorithm='ROUND_ROBIN',
|
||||
listener_id=listener_id)
|
||||
self.assertTrue(self.pool)
|
||||
return self.pool
|
||||
|
||||
def _create_members(self, load_balancer_id=None, pool_id=None,
|
||||
subnet_id=None):
|
||||
"""
|
||||
Create two members.
|
||||
|
||||
In case there is only one server, create both members with the same ip
|
||||
but with different ports to listen on.
|
||||
"""
|
||||
for server_id, ip in six.iteritems(self.server_fixed_ips):
|
||||
if len(self.server_fixed_ips) == 1:
|
||||
member1 = self.members_client.create_member(
|
||||
pool_id=pool_id,
|
||||
address=ip,
|
||||
protocol_port=self.port1,
|
||||
subnet_id=subnet_id)
|
||||
self._wait_for_load_balancer_status(load_balancer_id)
|
||||
member2 = self.members_client.create_member(
|
||||
pool_id=pool_id,
|
||||
address=ip,
|
||||
protocol_port=self.port2,
|
||||
subnet_id=subnet_id)
|
||||
self._wait_for_load_balancer_status(load_balancer_id)
|
||||
self.members.extend([member1, member2])
|
||||
else:
|
||||
member = self.members_client.create_member(
|
||||
pool_id=pool_id,
|
||||
address=ip,
|
||||
protocol_port=self.port1,
|
||||
subnet_id=subnet_id)
|
||||
self._wait_for_load_balancer_status(load_balancer_id)
|
||||
self.members.append(member)
|
||||
self.assertTrue(self.members)
|
||||
|
||||
def _assign_floating_ip_to_lb_vip(self, lb):
|
||||
public_network_id = config.network.public_network_id
|
||||
port_id = lb.vip_port_id
|
||||
floating_ip = self.create_floating_ip(lb, public_network_id,
|
||||
port_id=port_id)
|
||||
self.floating_ips.setdefault(lb.id, [])
|
||||
self.floating_ips[lb.id].append(floating_ip)
|
||||
# Check for floating ip status before you check load-balancer
|
||||
self.check_floating_ip_status(floating_ip, "ACTIVE")
|
||||
|
||||
def _create_load_balancer(self):
|
||||
self.create_lb_kwargs = {'tenant_id': self.tenant_id,
|
||||
'vip_subnet_id': self.subnet['id']}
|
||||
self.load_balancer = self.load_balancers_client.create_load_balancer(
|
||||
**self.create_lb_kwargs)
|
||||
load_balancer_id = self.load_balancer['id']
|
||||
self._wait_for_load_balancer_status(load_balancer_id)
|
||||
|
||||
listener = self._create_listener(load_balancer_id=load_balancer_id)
|
||||
self._wait_for_load_balancer_status(load_balancer_id)
|
||||
|
||||
self.pool = self._create_pool(listener_id=listener.get('id'))
|
||||
self._wait_for_load_balancer_status(load_balancer_id)
|
||||
|
||||
self._create_members(load_balancer_id=load_balancer_id,
|
||||
pool_id=self.pool['id'],
|
||||
subnet_id=self.subnet['id'])
|
||||
|
||||
if (config.network.public_network_id and not
|
||||
config.network.tenant_networks_reachable):
|
||||
load_balancer = net_resources.AttributeDict(self.load_balancer)
|
||||
self._assign_floating_ip_to_lb_vip(load_balancer)
|
||||
self.vip_ip = self.floating_ips[
|
||||
load_balancer.id][0]['floating_ip_address']
|
||||
|
||||
else:
|
||||
self.vip_ip = self.lb.vip_address
|
||||
|
||||
# Currently the ovs-agent is not enforcing security groups on the
|
||||
# vip port - see https://bugs.launchpad.net/neutron/+bug/1163569
|
||||
# However the linuxbridge-agent does, and it is necessary to add a
|
||||
# security group with a rule that allows tcp port 80 to the vip port.
|
||||
self.network_client.update_port(
|
||||
self.load_balancer.get('vip_port_id'),
|
||||
security_groups=[self.security_group.id])
|
||||
|
||||
def _wait_for_load_balancer_status(self, load_balancer_id,
|
||||
provisioning_status='ACTIVE',
|
||||
operating_status='ONLINE'):
|
||||
interval_time = 10
|
||||
timeout = 300
|
||||
end_time = time.time() + timeout
|
||||
while time.time() < end_time:
|
||||
lb = self.load_balancers_client.get_load_balancer(load_balancer_id)
|
||||
if (lb.get('provisioning_status') == provisioning_status and
|
||||
lb.get('operating_status') == operating_status):
|
||||
break
|
||||
elif (lb.get('provisioning_status') == 'ERROR' or
|
||||
lb.get('operating_status') == 'ERROR'):
|
||||
raise Exception(
|
||||
_("Wait for load balancer for load balancer: {lb_id} "
|
||||
"ran for {timeout} seconds and an ERROR was encountered "
|
||||
"with provisioning status: {provisioning_status} and "
|
||||
"operating status: {operating_status}").format(
|
||||
timeout=timeout,
|
||||
lb_id=lb.get('id'),
|
||||
provisioning_status=provisioning_status,
|
||||
operating_status=operating_status))
|
||||
time.sleep(interval_time)
|
||||
else:
|
||||
raise Exception(
|
||||
_("Wait for load balancer ran for {timeout} seconds and did "
|
||||
"not observe {lb_id} reach {provisioning_status} "
|
||||
"provisioning status and {operating_status} "
|
||||
"operating status.").format(
|
||||
timeout=timeout,
|
||||
lb_id=lb.get('id'),
|
||||
provisioning_status=provisioning_status,
|
||||
operating_status=operating_status))
|
||||
return lb
|
||||
|
||||
def _check_load_balancing(self):
|
||||
"""
|
||||
1. Send NUM requests on the floating ip associated with the VIP
|
||||
2. Check that the requests are shared between the two servers
|
||||
"""
|
||||
|
||||
self._check_connection(self.vip_ip)
|
||||
self._send_requests(self.vip_ip, ["server1", "server2"])
|
||||
|
||||
def _send_requests(self, vip_ip, servers):
|
||||
counters = dict.fromkeys(servers, 0)
|
||||
for i in range(self.num):
|
||||
try:
|
||||
server = urllib2.urlopen("http://{0}/".format(vip_ip)).read()
|
||||
counters[server] += 1
|
||||
# HTTP exception means fail of server, so don't increase counter
|
||||
# of success and continue connection tries
|
||||
except urllib2.HTTPError:
|
||||
continue
|
||||
# Assert that each member of the pool gets balanced at least once
|
||||
for member, counter in six.iteritems(counters):
|
||||
self.assertGreater(counter, 0, 'Member %s never balanced' % member)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,85 @@
|
|||
# Copyright 2015 Rackspace 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 neutron_lbaas.tests.tempest.lib import config
|
||||
from neutron_lbaas.tests.tempest.lib import test
|
||||
from neutron_lbaas.tests.tempest.v2.clients import listeners_client
|
||||
from neutron_lbaas.tests.tempest.v2.clients import load_balancers_client
|
||||
from neutron_lbaas.tests.tempest.v2.clients import members_client
|
||||
from neutron_lbaas.tests.tempest.v2.clients import pools_client
|
||||
from neutron_lbaas.tests.tempest.v2.scenario import base
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TestLoadBalancerBasic(base.BaseTestCase):
|
||||
|
||||
"""
|
||||
This test checks basic load balancing.
|
||||
The following is the scenario outline:
|
||||
1. Create an instance
|
||||
2. SSH to the instance and start two servers
|
||||
3. Create a load balancer with two members and with ROUND_ROBIN algorithm
|
||||
associate the VIP with a floating ip
|
||||
4. Send NUM requests to the floating ip and check that they are shared
|
||||
between the two servers.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestLoadBalancerBasic, self).setUp()
|
||||
self.server_ips = {}
|
||||
self.server_fixed_ips = {}
|
||||
self._create_security_group_for_test()
|
||||
self._set_net_and_subnet()
|
||||
|
||||
mgr = self.get_client_manager()
|
||||
auth_provider = mgr.auth_provider
|
||||
client_args = [auth_provider, 'network', 'regionOne']
|
||||
|
||||
self.load_balancers_client = (
|
||||
load_balancers_client.LoadBalancersClientJSON(*client_args))
|
||||
self.listeners_client = (
|
||||
listeners_client.ListenersClientJSON(*client_args))
|
||||
self.pools_client = pools_client.PoolsClientJSON(*client_args)
|
||||
self.members_client = members_client.MembersClientJSON(*client_args)
|
||||
|
||||
def tearDown(self):
|
||||
super(TestLoadBalancerBasic, self).tearDown()
|
||||
|
||||
@test.services('compute', 'network')
|
||||
def test_load_balancer_basic(self):
|
||||
self._create_servers()
|
||||
self._start_servers()
|
||||
self._create_load_balancer()
|
||||
self._check_load_balancing()
|
||||
|
||||
lbs = self.load_balancers_client.list_load_balancers()
|
||||
for lb_entity in lbs:
|
||||
lb_id = lb_entity['id']
|
||||
lb = self.load_balancers_client.get_load_balancer_status_tree(
|
||||
lb_id).get('loadbalancer')
|
||||
for listener in lb.get('listeners'):
|
||||
for pool in listener.get('pools'):
|
||||
self.delete_wrapper(self.pools_client.delete_pool,
|
||||
pool.get('id'))
|
||||
self._wait_for_load_balancer_status(lb_id)
|
||||
self.delete_wrapper(self.listeners_client.delete_listener,
|
||||
listener.get('id'))
|
||||
self._wait_for_load_balancer_status(lb_id)
|
||||
self.delete_wrapper(
|
||||
self.load_balancers_client.delete_load_balancer, lb_id)
|
Loading…
Reference in New Issue