remove xml_utils and all things that depend on it
This rips out xml_utils, and all the things that depend on it, which takes out a huge amount of the xml infrastructure in the process. Change-Id: I9d40f3065e007a531985da1ed56ef4f2e245912e
This commit is contained in:
parent
f3c7591ca2
commit
fc07254207
@ -26,8 +26,6 @@ from tempest import config
|
||||
from tempest.openstack.common import log as logging
|
||||
from tempest.services.identity.json import identity_client as json_id
|
||||
from tempest.services.identity.v3.json import identity_client as json_v3id
|
||||
from tempest.services.identity.v3.xml import identity_client as xml_v3id
|
||||
from tempest.services.identity.xml import identity_client as xml_id
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
@ -44,15 +42,14 @@ class AuthProvider(object):
|
||||
"""
|
||||
:param credentials: credentials for authentication
|
||||
:param interface: 'json' or 'xml'. Applicable for tempest client only
|
||||
(deprecated: only json now supported)
|
||||
"""
|
||||
credentials = self._convert_credentials(credentials)
|
||||
if self.check_credentials(credentials):
|
||||
self.credentials = credentials
|
||||
else:
|
||||
raise TypeError("Invalid credentials")
|
||||
self.interface = interface
|
||||
if self.interface is None:
|
||||
self.interface = 'json'
|
||||
self.interface = 'json'
|
||||
self.cache = None
|
||||
self.alt_auth_data = None
|
||||
self.alt_part = None
|
||||
@ -255,10 +252,7 @@ class KeystoneV2AuthProvider(KeystoneAuthProvider):
|
||||
EXPIRY_DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
|
||||
|
||||
def _auth_client(self):
|
||||
if self.interface == 'json':
|
||||
return json_id.TokenClientJSON()
|
||||
else:
|
||||
return xml_id.TokenClientXML()
|
||||
return json_id.TokenClientJSON()
|
||||
|
||||
def _auth_params(self):
|
||||
return dict(
|
||||
@ -336,10 +330,7 @@ class KeystoneV3AuthProvider(KeystoneAuthProvider):
|
||||
EXPIRY_DATE_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
|
||||
|
||||
def _auth_client(self):
|
||||
if self.interface == 'json':
|
||||
return json_v3id.V3TokenClientJSON()
|
||||
else:
|
||||
return xml_v3id.V3TokenClientXML()
|
||||
return json_v3id.V3TokenClientJSON()
|
||||
|
||||
def _auth_params(self):
|
||||
return dict(
|
||||
|
@ -21,12 +21,10 @@ import re
|
||||
import time
|
||||
|
||||
import jsonschema
|
||||
from lxml import etree
|
||||
import six
|
||||
|
||||
from tempest.common import http
|
||||
from tempest.common.utils import misc as misc_utils
|
||||
from tempest.common import xml_utils as common
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
from tempest.openstack.common import log as logging
|
||||
@ -328,48 +326,30 @@ class RestClient(object):
|
||||
req_body, resp_body, caller_name, extra)
|
||||
|
||||
def _parse_resp(self, body):
|
||||
if self._get_type() is "json":
|
||||
body = json.loads(body)
|
||||
body = json.loads(body)
|
||||
|
||||
# We assume, that if the first value of the deserialized body's
|
||||
# item set is a dict or a list, that we just return the first value
|
||||
# of deserialized body.
|
||||
# Essentially "cutting out" the first placeholder element in a body
|
||||
# that looks like this:
|
||||
#
|
||||
# {
|
||||
# "users": [
|
||||
# ...
|
||||
# ]
|
||||
# }
|
||||
try:
|
||||
# Ensure there are not more than one top-level keys
|
||||
if len(body.keys()) > 1:
|
||||
return body
|
||||
# Just return the "wrapped" element
|
||||
first_key, first_item = body.items()[0]
|
||||
if isinstance(first_item, (dict, list)):
|
||||
return first_item
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
return body
|
||||
elif self._get_type() is "xml":
|
||||
element = etree.fromstring(body)
|
||||
if any(s in element.tag for s in self.dict_tags):
|
||||
# Parse dictionary-like xmls (metadata, etc)
|
||||
dictionary = {}
|
||||
for el in element.getchildren():
|
||||
dictionary[u"%s" % el.get("key")] = u"%s" % el.text
|
||||
return dictionary
|
||||
if any(s in element.tag for s in self.list_tags):
|
||||
# Parse list-like xmls (users, roles, etc)
|
||||
array = []
|
||||
for child in element.getchildren():
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
# Parse one-item-like xmls (user, role, etc)
|
||||
return common.xml_to_json(element)
|
||||
# We assume, that if the first value of the deserialized body's
|
||||
# item set is a dict or a list, that we just return the first value
|
||||
# of deserialized body.
|
||||
# Essentially "cutting out" the first placeholder element in a body
|
||||
# that looks like this:
|
||||
#
|
||||
# {
|
||||
# "users": [
|
||||
# ...
|
||||
# ]
|
||||
# }
|
||||
try:
|
||||
# Ensure there are not more than one top-level keys
|
||||
if len(body.keys()) > 1:
|
||||
return body
|
||||
# Just return the "wrapped" element
|
||||
first_key, first_item = body.items()[0]
|
||||
if isinstance(first_item, (dict, list)):
|
||||
return first_item
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
return body
|
||||
|
||||
def response_checker(self, method, resp, resp_body):
|
||||
if (resp.status in set((204, 205, 304)) or resp.status < 200 or
|
||||
|
@ -1,173 +0,0 @@
|
||||
# Copyright 2012 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 collections
|
||||
import copy
|
||||
|
||||
XMLNS_11 = "http://docs.openstack.org/compute/api/v1.1"
|
||||
XMLNS_V3 = "http://docs.openstack.org/compute/api/v1.1"
|
||||
|
||||
NEUTRON_NAMESPACES = {
|
||||
'binding': "http://docs.openstack.org/ext/binding/api/v1.0",
|
||||
'router': "http://docs.openstack.org/ext/neutron/router/api/v1.0",
|
||||
'provider': 'http://docs.openstack.org/ext/provider/api/v1.0',
|
||||
}
|
||||
|
||||
|
||||
# NOTE(danms): This is just a silly implementation to help make generating
|
||||
# XML faster for prototyping. Could be replaced with proper etree gorp
|
||||
# if desired
|
||||
class Element(object):
|
||||
def __init__(self, element_name, *args, **kwargs):
|
||||
self.element_name = element_name
|
||||
self._attrs = kwargs
|
||||
self._elements = list(args)
|
||||
|
||||
def add_attr(self, name, value):
|
||||
self._attrs[name] = value
|
||||
|
||||
def append(self, element):
|
||||
self._elements.append(element)
|
||||
|
||||
def __str__(self):
|
||||
args = " ".join(['%s="%s"' %
|
||||
(k, v if v is not None else "")
|
||||
for k, v in self._attrs.items()])
|
||||
string = '<%s %s' % (self.element_name, args)
|
||||
if not self._elements:
|
||||
string += '/>'
|
||||
return string
|
||||
|
||||
string += '>'
|
||||
|
||||
for element in self._elements:
|
||||
string += str(element)
|
||||
|
||||
string += '</%s>' % self.element_name
|
||||
|
||||
return string
|
||||
|
||||
def __getitem__(self, name):
|
||||
for element in self._elements:
|
||||
if element.element_name == name:
|
||||
return element
|
||||
raise KeyError("No such element `%s'" % name)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name in self._attrs:
|
||||
return self._attrs[name]
|
||||
return object.__getattr__(self, name)
|
||||
|
||||
def attributes(self):
|
||||
return self._attrs.items()
|
||||
|
||||
def children(self):
|
||||
return self._elements
|
||||
|
||||
|
||||
class Document(Element):
|
||||
def __init__(self, *args, **kwargs):
|
||||
Element.__init__(self, '?xml', *args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
attrs = copy.copy(self._attrs)
|
||||
# pop the required standard attrs out and render in required
|
||||
# order.
|
||||
vers = attrs.pop('version', '1.0')
|
||||
enc = attrs.pop('encoding', 'UTF-8')
|
||||
args = 'version="%s" encoding="%s"' % (vers, enc)
|
||||
if attrs:
|
||||
args = " ".join([args] + ['%s="%s"' %
|
||||
(k, v if v is not None else "")
|
||||
for k, v in attrs.items()])
|
||||
string = '<?xml %s?>\n' % args
|
||||
for element in self._elements:
|
||||
string += str(element)
|
||||
return string
|
||||
|
||||
|
||||
class Text(Element):
|
||||
def __init__(self, content=""):
|
||||
Element.__init__(self, None)
|
||||
self.__content = content
|
||||
|
||||
def __str__(self):
|
||||
return self.__content
|
||||
|
||||
|
||||
def parse_array(node, plurals=None):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
array.append(xml_to_json(child,
|
||||
plurals))
|
||||
return array
|
||||
|
||||
|
||||
def xml_to_json(node, plurals=None):
|
||||
"""This does a really braindead conversion of an XML tree to
|
||||
something that looks like a json dump. In cases where the XML
|
||||
and json structures are the same, then this "just works". In
|
||||
others, it requires a little hand-editing of the result.
|
||||
"""
|
||||
json = {}
|
||||
bool_flag = False
|
||||
int_flag = False
|
||||
long_flag = False
|
||||
for attr in node.keys():
|
||||
if not attr.startswith("xmlns"):
|
||||
json[attr] = node.get(attr)
|
||||
if json[attr] == 'bool':
|
||||
bool_flag = True
|
||||
elif json[attr] == 'int':
|
||||
int_flag = True
|
||||
elif json[attr] == 'long':
|
||||
long_flag = True
|
||||
if not node.getchildren():
|
||||
if bool_flag:
|
||||
return node.text == 'True'
|
||||
elif int_flag:
|
||||
return int(node.text)
|
||||
elif long_flag:
|
||||
return long(node.text)
|
||||
else:
|
||||
return node.text or json
|
||||
for child in node.getchildren():
|
||||
tag = child.tag
|
||||
if tag.startswith("{"):
|
||||
ns, tag = tag.split("}", 1)
|
||||
for key, uri in NEUTRON_NAMESPACES.iteritems():
|
||||
if uri == ns[1:]:
|
||||
tag = key + ":" + tag
|
||||
if plurals is not None and tag in plurals:
|
||||
json[tag] = parse_array(child, plurals)
|
||||
else:
|
||||
json[tag] = xml_to_json(child, plurals)
|
||||
return json
|
||||
|
||||
|
||||
def deep_dict_to_xml(dest, source):
|
||||
"""Populates the ``dest`` xml element with the ``source`` ``Mapping``
|
||||
elements, if the source Mapping's value is also a ``Mapping``
|
||||
they will be recursively added as a child elements.
|
||||
:param source: A python ``Mapping`` (dict)
|
||||
:param dest: XML child element will be added to the ``dest``
|
||||
"""
|
||||
for element, content in source.iteritems():
|
||||
if isinstance(content, collections.Mapping):
|
||||
xml_element = Element(element)
|
||||
deep_dict_to_xml(xml_element, content)
|
||||
dest.append(xml_element)
|
||||
else:
|
||||
dest.append(Element(element, content))
|
@ -1,130 +0,0 @@
|
||||
# Copyright 2013 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 lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class AggregatesClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(AggregatesClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def _format_aggregate(self, g):
|
||||
agg = xml_utils.xml_to_json(g)
|
||||
aggregate = {}
|
||||
for key, value in agg.items():
|
||||
if key == 'hosts':
|
||||
aggregate['hosts'] = []
|
||||
for k, v in value.items():
|
||||
aggregate['hosts'].append(v)
|
||||
elif key == 'availability_zone':
|
||||
aggregate[key] = None if value == 'None' else value
|
||||
else:
|
||||
aggregate[key] = value
|
||||
return aggregate
|
||||
|
||||
def _parse_array(self, node):
|
||||
return [self._format_aggregate(x) for x in node]
|
||||
|
||||
def list_aggregates(self):
|
||||
"""Get aggregate list."""
|
||||
resp, body = self.get("os-aggregates")
|
||||
aggregates = self._parse_array(etree.fromstring(body))
|
||||
return resp, aggregates
|
||||
|
||||
def get_aggregate(self, aggregate_id):
|
||||
"""Get details of the given aggregate."""
|
||||
resp, body = self.get("os-aggregates/%s" % str(aggregate_id))
|
||||
aggregate = self._format_aggregate(etree.fromstring(body))
|
||||
return resp, aggregate
|
||||
|
||||
def create_aggregate(self, name, availability_zone=None):
|
||||
"""Creates a new aggregate."""
|
||||
if availability_zone is not None:
|
||||
post_body = xml_utils.Element("aggregate", name=name,
|
||||
availability_zone=availability_zone)
|
||||
else:
|
||||
post_body = xml_utils.Element("aggregate", name=name)
|
||||
resp, body = self.post('os-aggregates',
|
||||
str(xml_utils.Document(post_body)))
|
||||
aggregate = self._format_aggregate(etree.fromstring(body))
|
||||
return resp, aggregate
|
||||
|
||||
def update_aggregate(self, aggregate_id, name, availability_zone=None):
|
||||
"""Update a aggregate."""
|
||||
if availability_zone is not None:
|
||||
put_body = xml_utils.Element("aggregate", name=name,
|
||||
availability_zone=availability_zone)
|
||||
else:
|
||||
put_body = xml_utils.Element("aggregate", name=name)
|
||||
resp, body = self.put('os-aggregates/%s' % str(aggregate_id),
|
||||
str(xml_utils.Document(put_body)))
|
||||
aggregate = self._format_aggregate(etree.fromstring(body))
|
||||
return resp, aggregate
|
||||
|
||||
def delete_aggregate(self, aggregate_id):
|
||||
"""Deletes the given aggregate."""
|
||||
return self.delete("os-aggregates/%s" % str(aggregate_id))
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
self.get_aggregate(id)
|
||||
except exceptions.NotFound:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def resource_type(self):
|
||||
"""Returns the primary type of resource this client works with."""
|
||||
return 'aggregate'
|
||||
|
||||
def add_host(self, aggregate_id, host):
|
||||
"""Adds a host to the given aggregate."""
|
||||
post_body = xml_utils.Element("add_host", host=host)
|
||||
resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
|
||||
str(xml_utils.Document(post_body)))
|
||||
aggregate = self._format_aggregate(etree.fromstring(body))
|
||||
return resp, aggregate
|
||||
|
||||
def remove_host(self, aggregate_id, host):
|
||||
"""Removes a host from the given aggregate."""
|
||||
post_body = xml_utils.Element("remove_host", host=host)
|
||||
resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
|
||||
str(xml_utils.Document(post_body)))
|
||||
aggregate = self._format_aggregate(etree.fromstring(body))
|
||||
return resp, aggregate
|
||||
|
||||
def set_metadata(self, aggregate_id, meta):
|
||||
"""Replaces the aggregate's existing metadata with new metadata."""
|
||||
post_body = xml_utils.Element("set_metadata")
|
||||
metadata = xml_utils.Element("metadata")
|
||||
post_body.append(metadata)
|
||||
for k, v in meta.items():
|
||||
meta = xml_utils.Element(k)
|
||||
meta.append(xml_utils.Text(v))
|
||||
metadata.append(meta)
|
||||
resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
|
||||
str(xml_utils.Document(post_body)))
|
||||
aggregate = self._format_aggregate(etree.fromstring(body))
|
||||
return resp, aggregate
|
@ -1,44 +0,0 @@
|
||||
# Copyright 2013 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 lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class AvailabilityZoneClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(AvailabilityZoneClientXML, self).__init__(
|
||||
auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def _parse_array(self, node):
|
||||
return [xml_utils.xml_to_json(x) for x in node]
|
||||
|
||||
def get_availability_zone_list(self):
|
||||
resp, body = self.get('os-availability-zone')
|
||||
availability_zone = self._parse_array(etree.fromstring(body))
|
||||
return resp, availability_zone
|
||||
|
||||
def get_availability_zone_list_detail(self):
|
||||
resp, body = self.get('os-availability-zone/detail')
|
||||
availability_zone = self._parse_array(etree.fromstring(body))
|
||||
return resp, availability_zone
|
@ -1,41 +0,0 @@
|
||||
# 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.
|
||||
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class CertificatesClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(CertificatesClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def get_certificate(self, id):
|
||||
url = "os-certificates/%s" % (id)
|
||||
resp, body = self.get(url)
|
||||
body = self._parse_resp(body)
|
||||
return resp, body
|
||||
|
||||
def create_certificate(self):
|
||||
"""create certificates."""
|
||||
url = "os-certificates"
|
||||
resp, body = self.post(url, None)
|
||||
body = self._parse_resp(body)
|
||||
return resp, body
|
@ -1,52 +0,0 @@
|
||||
# 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 lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class ExtensionsClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(ExtensionsClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def _parse_array(self, node):
|
||||
array = []
|
||||
for child in node:
|
||||
array.append(xml_utils.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def list_extensions(self):
|
||||
url = 'extensions'
|
||||
resp, body = self.get(url)
|
||||
body = self._parse_array(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def is_enabled(self, extension):
|
||||
_, extensions = self.list_extensions()
|
||||
exts = extensions['extensions']
|
||||
return any([e for e in exts if e['name'] == extension])
|
||||
|
||||
def get_extension(self, extension_alias):
|
||||
resp, body = self.get('extensions/%s' % extension_alias)
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, body
|
@ -1,47 +0,0 @@
|
||||
# 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.
|
||||
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class FixedIPsClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(FixedIPsClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def get_fixed_ip_details(self, fixed_ip):
|
||||
url = "os-fixed-ips/%s" % (fixed_ip)
|
||||
resp, body = self.get(url)
|
||||
body = self._parse_resp(body)
|
||||
return resp, body
|
||||
|
||||
def reserve_fixed_ip(self, ip, body):
|
||||
"""This reserves and unreserves fixed ips."""
|
||||
url = "os-fixed-ips/%s/action" % (ip)
|
||||
# NOTE(maurosr): First converts the dict body to a json string then
|
||||
# accept any action key value here to permit tests to cover cases with
|
||||
# invalid actions raising badrequest.
|
||||
key, value = body.popitem()
|
||||
xml_body = xml_utils.Element(key)
|
||||
xml_body.append(xml_utils.Text(value))
|
||||
resp, body = self.post(url, str(xml_utils.Document(xml_body)))
|
||||
return resp, body
|
@ -1,216 +0,0 @@
|
||||
# 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 urllib
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
XMLNS_OS_FLV_EXT_DATA = \
|
||||
"http://docs.openstack.org/compute/ext/flavor_extra_data/api/v1.1"
|
||||
XMLNS_OS_FLV_ACCESS = \
|
||||
"http://docs.openstack.org/compute/ext/flavor_access/api/v2"
|
||||
|
||||
|
||||
class FlavorsClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(FlavorsClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def _format_flavor(self, f):
|
||||
flavor = {'links': []}
|
||||
for k, v in f.items():
|
||||
if k == 'id':
|
||||
flavor['id'] = v
|
||||
continue
|
||||
|
||||
if k == 'link':
|
||||
flavor['links'].append(v)
|
||||
continue
|
||||
|
||||
if k == '{%s}ephemeral' % XMLNS_OS_FLV_EXT_DATA:
|
||||
k = 'OS-FLV-EXT-DATA:ephemeral'
|
||||
|
||||
if k == '{%s}is_public' % XMLNS_OS_FLV_ACCESS:
|
||||
k = 'os-flavor-access:is_public'
|
||||
v = True if v == 'True' else False
|
||||
|
||||
if k == 'extra_specs':
|
||||
k = 'OS-FLV-WITH-EXT-SPECS:extra_specs'
|
||||
flavor[k] = dict(v)
|
||||
continue
|
||||
|
||||
try:
|
||||
v = int(v)
|
||||
except ValueError:
|
||||
try:
|
||||
v = float(v)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
flavor[k] = v
|
||||
|
||||
return flavor
|
||||
|
||||
def _parse_array(self, node):
|
||||
return [self._format_flavor(xml_utils.xml_to_json(x)) for x in node]
|
||||
|
||||
def _list_flavors(self, url, params):
|
||||
if params:
|
||||
url += "?%s" % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
flavors = self._parse_array(etree.fromstring(body))
|
||||
return resp, flavors
|
||||
|
||||
def list_flavors(self, params=None):
|
||||
url = 'flavors'
|
||||
return self._list_flavors(url, params)
|
||||
|
||||
def list_flavors_with_detail(self, params=None):
|
||||
url = 'flavors/detail'
|
||||
return self._list_flavors(url, params)
|
||||
|
||||
def get_flavor_details(self, flavor_id):
|
||||
resp, body = self.get("flavors/%s" % str(flavor_id))
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
flavor = self._format_flavor(body)
|
||||
return resp, flavor
|
||||
|
||||
def create_flavor(self, name, ram, vcpus, disk, flavor_id, **kwargs):
|
||||
"""Creates a new flavor or instance type."""
|
||||
flavor = xml_utils.Element("flavor",
|
||||
xmlns=xml_utils.XMLNS_11,
|
||||
ram=ram,
|
||||
vcpus=vcpus,
|
||||
disk=disk,
|
||||
id=flavor_id,
|
||||
name=name)
|
||||
if kwargs.get('rxtx'):
|
||||
flavor.add_attr('rxtx_factor', kwargs.get('rxtx'))
|
||||
if kwargs.get('swap'):
|
||||
flavor.add_attr('swap', kwargs.get('swap'))
|
||||
if kwargs.get('ephemeral'):
|
||||
flavor.add_attr('OS-FLV-EXT-DATA:ephemeral',
|
||||
kwargs.get('ephemeral'))
|
||||
if kwargs.get('is_public'):
|
||||
flavor.add_attr('os-flavor-access:is_public',
|
||||
kwargs.get('is_public'))
|
||||
flavor.add_attr('xmlns:OS-FLV-EXT-DATA', XMLNS_OS_FLV_EXT_DATA)
|
||||
flavor.add_attr('xmlns:os-flavor-access', XMLNS_OS_FLV_ACCESS)
|
||||
resp, body = self.post('flavors', str(xml_utils.Document(flavor)))
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
flavor = self._format_flavor(body)
|
||||
return resp, flavor
|
||||
|
||||
def delete_flavor(self, flavor_id):
|
||||
"""Deletes the given flavor."""
|
||||
return self.delete("flavors/%s" % str(flavor_id))
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
# Did not use get_flavor_details(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
|
||||
resp, 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."""
|
||||
extra_specs = xml_utils.Element("extra_specs")
|
||||
for key in specs.keys():
|
||||
extra_specs.add_attr(key, specs[key])
|
||||
resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
|
||||
str(xml_utils.Document(extra_specs)))
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def get_flavor_extra_spec(self, flavor_id):
|
||||
"""Gets extra Specs of the mentioned flavor."""
|
||||
resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id)
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def get_flavor_extra_spec_with_key(self, flavor_id, key):
|
||||
"""Gets extra Specs key-value of the mentioned flavor and key."""
|
||||
resp, xml_body = self.get('flavors/%s/os-extra_specs/%s' %
|
||||
(str(flavor_id), key))
|
||||
body = {}
|
||||
element = etree.fromstring(xml_body)
|
||||
key = element.get('key')
|
||||
body[key] = xml_utils.xml_to_json(element)
|
||||
return resp, body
|
||||
|
||||
def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
|
||||
"""Update extra Specs details of the mentioned flavor and key."""
|
||||
doc = xml_utils.Document()
|
||||
for (k, v) in kwargs.items():
|
||||
element = xml_utils.Element(k)
|
||||
doc.append(element)
|
||||
value = xml_utils.Text(v)
|
||||
element.append(value)
|
||||
|
||||
resp, body = self.put('flavors/%s/os-extra_specs/%s' %
|
||||
(flavor_id, key), str(doc))
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, {key: body}
|
||||
|
||||
def unset_flavor_extra_spec(self, flavor_id, key):
|
||||
"""Unsets an extra spec based on the mentioned flavor and key."""
|
||||
return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
|
||||
key))
|
||||
|
||||
def _parse_array_access(self, node):
|
||||
return [xml_utils.xml_to_json(x) for x in node]
|
||||
|
||||
def list_flavor_access(self, flavor_id):
|
||||
"""Gets flavor access information given the flavor id."""
|
||||
resp, body = self.get('flavors/%s/os-flavor-access' % str(flavor_id))
|
||||
body = self._parse_array(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def add_flavor_access(self, flavor_id, tenant_id):
|
||||
"""Add flavor access for the specified tenant."""
|
||||
doc = xml_utils.Document()
|
||||
server = xml_utils.Element("addTenantAccess")
|
||||
doc.append(server)
|
||||
server.add_attr("tenant", tenant_id)
|
||||
resp, body = self.post('flavors/%s/action' % str(flavor_id), str(doc))
|
||||
body = self._parse_array_access(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def remove_flavor_access(self, flavor_id, tenant_id):
|
||||
"""Remove flavor access from the specified tenant."""
|
||||
doc = xml_utils.Document()
|
||||
server = xml_utils.Element("removeTenantAccess")
|
||||
doc.append(server)
|
||||
server.add_attr("tenant", tenant_id)
|
||||
resp, body = self.post('flavors/%s/action' % str(flavor_id), str(doc))
|
||||
body = self._parse_array_access(etree.fromstring(body))
|
||||
return resp, body
|
@ -1,124 +0,0 @@
|
||||
# Copyright 2012 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 urllib
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class FloatingIPsClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(FloatingIPsClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def _parse_array(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
array.append(xml_utils.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def _parse_floating_ip(self, body):
|
||||
json = xml_utils.xml_to_json(body)
|
||||
return json
|
||||
|
||||
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 = self._parse_array(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def get_floating_ip_details(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 = self._parse_floating_ip(etree.fromstring(body))
|
||||
if resp.status == 404:
|
||||
raise exceptions.NotFound(body)
|
||||
return resp, body
|
||||
|
||||
def create_floating_ip(self, pool_name=None):
|
||||
"""Allocate a floating IP to the project."""
|
||||
url = 'os-floating-ips'
|
||||
if pool_name:
|
||||
doc = xml_utils.Document()
|
||||
pool = xml_utils.Element("pool")
|
||||
pool.append(xml_utils.Text(pool_name))
|
||||
doc.append(pool)
|
||||
resp, body = self.post(url, str(doc))
|
||||
else:
|
||||
resp, body = self.post(url, None)
|
||||
body = self._parse_floating_ip(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
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)
|
||||
return 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)
|
||||
doc = xml_utils.Document()
|
||||
server = xml_utils.Element("addFloatingIp")
|
||||
doc.append(server)
|
||||
server.add_attr("address", floating_ip)
|
||||
resp, body = self.post(url, str(doc))
|
||||
return 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)
|
||||
doc = xml_utils.Document()
|
||||
server = xml_utils.Element("removeFloatingIp")
|
||||
doc.append(server)
|
||||
server.add_attr("address", floating_ip)
|
||||
resp, body = self.post(url, str(doc))
|
||||
return resp, body
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
self.get_floating_ip_details(id)
|
||||
except exceptions.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 = self._parse_array(etree.fromstring(body))
|
||||
return resp, body
|
@ -1,88 +0,0 @@
|
||||
# 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 urllib
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class HostsClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(HostsClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def list_hosts(self, params=None):
|
||||
"""Lists all hosts."""
|
||||
|
||||
url = 'os-hosts'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
node = etree.fromstring(body)
|
||||
body = [xml_utils.xml_to_json(x) for x in node.getchildren()]
|
||||
return resp, body
|
||||
|
||||
def show_host_detail(self, hostname):
|
||||
"""Show detail information for the host."""
|
||||
|
||||
resp, body = self.get("os-hosts/%s" % str(hostname))
|
||||
node = etree.fromstring(body)
|
||||
body = [xml_utils.xml_to_json(node)]
|
||||
return resp, body
|
||||
|
||||
def update_host(self, hostname, **kwargs):
|
||||
"""Update a host."""
|
||||
|
||||
request_body = xml_utils.Element("updates")
|
||||
if kwargs:
|
||||
for k, v in kwargs.iteritems():
|
||||
request_body.append(xml_utils.Element(k, v))
|
||||
resp, body = self.put("os-hosts/%s" % str(hostname),
|
||||
str(xml_utils.Document(request_body)))
|
||||
node = etree.fromstring(body)
|
||||
body = [xml_utils.xml_to_json(x) for x in node.getchildren()]
|
||||
return resp, body
|
||||
|
||||
def startup_host(self, hostname):
|
||||
"""Startup a host."""
|
||||
|
||||
resp, body = self.get("os-hosts/%s/startup" % str(hostname))
|
||||
node = etree.fromstring(body)
|
||||
body = [xml_utils.xml_to_json(x) for x in node.getchildren()]
|
||||
return resp, body
|
||||
|
||||
def shutdown_host(self, hostname):
|
||||
"""Shutdown a host."""
|
||||
|
||||
resp, body = self.get("os-hosts/%s/shutdown" % str(hostname))
|
||||
node = etree.fromstring(body)
|
||||
body = [xml_utils.xml_to_json(x) for x in node.getchildren()]
|
||||
return resp, body
|
||||
|
||||
def reboot_host(self, hostname):
|
||||
"""Reboot a host."""
|
||||
|
||||
resp, body = self.get("os-hosts/%s/reboot" % str(hostname))
|
||||
node = etree.fromstring(body)
|
||||
body = [xml_utils.xml_to_json(x) for x in node.getchildren()]
|
||||
return resp, body
|
@ -1,75 +0,0 @@
|
||||
# Copyright 2013 IBM 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 lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class HypervisorClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(HypervisorClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def _parse_array(self, node):
|
||||
return [xml_utils.xml_to_json(x) for x in node]
|
||||
|
||||
def get_hypervisor_list(self):
|
||||
"""List hypervisors information."""
|
||||
resp, body = self.get('os-hypervisors')
|
||||
hypervisors = self._parse_array(etree.fromstring(body))
|
||||
return resp, hypervisors
|
||||
|
||||
def get_hypervisor_list_details(self):
|
||||
"""Show detailed hypervisors information."""
|
||||
resp, body = self.get('os-hypervisors/detail')
|
||||
hypervisors = self._parse_array(etree.fromstring(body))
|
||||
return resp, hypervisors
|
||||
|
||||
def get_hypervisor_show_details(self, hyper_id):
|
||||
"""Display the details of the specified hypervisor."""
|
||||
resp, body = self.get('os-hypervisors/%s' % hyper_id)
|
||||
hypervisor = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, hypervisor
|
||||
|
||||
def get_hypervisor_servers(self, hyper_name):
|
||||
"""List instances belonging to the specified hypervisor."""
|
||||
resp, body = self.get('os-hypervisors/%s/servers' % hyper_name)
|
||||
hypervisors = self._parse_array(etree.fromstring(body))
|
||||
return resp, hypervisors
|
||||
|
||||
def get_hypervisor_stats(self):
|
||||
"""Get hypervisor statistics over all compute nodes."""
|
||||
resp, body = self.get('os-hypervisors/statistics')
|
||||
stats = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, stats
|
||||
|
||||
def get_hypervisor_uptime(self, hyper_id):
|
||||
"""Display the uptime of the specified hypervisor."""
|
||||
resp, body = self.get('os-hypervisors/%s/uptime' % hyper_id)
|
||||
uptime = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, uptime
|
||||
|
||||
def search_hypervisor(self, hyper_name):
|
||||
"""Search specified hypervisor."""
|
||||
resp, body = self.get('os-hypervisors/%s/search' % hyper_name)
|
||||
hypervisors = self._parse_array(etree.fromstring(body))
|
||||
return resp, hypervisors
|
@ -1,211 +0,0 @@
|
||||
# Copyright 2012 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 urllib
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import waiters
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class ImagesClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(ImagesClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
self.build_interval = CONF.compute.build_interval
|
||||
self.build_timeout = CONF.compute.build_timeout
|
||||
|
||||
def _parse_server(self, node):
|
||||
data = xml_utils.xml_to_json(node)
|
||||
return self._parse_links(node, data)
|
||||
|
||||
def _parse_image(self, node):
|
||||
"""Parses detailed XML image information into dictionary."""
|
||||
data = xml_utils.xml_to_json(node)
|
||||
|
||||
self._parse_links(node, data)
|
||||
|
||||
# parse all metadata
|
||||
if 'metadata' in data:
|
||||
tag = node.find('{%s}metadata' % xml_utils.XMLNS_11)
|
||||
data['metadata'] = dict((x.get('key'), x.text)
|
||||
for x in tag.getchildren())
|
||||
|
||||
# parse server information
|
||||
if 'server' in data:
|
||||
tag = node.find('{%s}server' % xml_utils.XMLNS_11)
|
||||
data['server'] = self._parse_server(tag)
|
||||
return data
|
||||
|
||||
def _parse_links(self, node, data):
|
||||
"""Append multiple links under a list."""
|
||||
# look for links
|
||||
if 'link' in data:
|
||||
# remove single link element
|
||||
del data['link']
|
||||
data['links'] = [xml_utils.xml_to_json(x) for x in
|
||||
node.findall('{http://www.w3.org/2005/Atom}link')]
|
||||
return data
|
||||
|
||||
def _parse_images(self, xml):
|
||||
data = {'images': []}
|
||||
images = xml.getchildren()
|
||||
for image in images:
|
||||
data['images'].append(self._parse_image(image))
|
||||
return data
|
||||
|
||||
def _parse_key_value(self, node):
|
||||
"""Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
|
||||
data = {}
|
||||
for node in node.getchildren():
|
||||
data[node.get('key')] = node.text
|
||||
return data
|
||||
|
||||
def _parse_metadata(self, node):
|
||||
"""Parse the response body without children."""
|
||||
data = {}
|
||||
data[node.get('key')] = node.text
|
||||
return data
|
||||
|
||||
def create_image(self, server_id, name, meta=None):
|
||||
"""Creates an image of the original server."""
|
||||
post_body = xml_utils.Element('createImage', name=name)
|
||||
|
||||
if meta:
|
||||
metadata = xml_utils.Element('metadata')
|
||||
post_body.append(metadata)
|
||||
for k, v in meta.items():
|
||||
data = xml_utils.Element('meta', key=k)
|
||||
data.append(xml_utils.Text(v))
|
||||
metadata.append(data)
|
||||
resp, body = self.post('servers/%s/action' % str(server_id),
|
||||
str(xml_utils.Document(post_body)))
|
||||
return 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 = self._parse_images(etree.fromstring(body))
|
||||
return 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:
|
||||
param_list = urllib.urlencode(params)
|
||||
|
||||
url = "images/detail?" + param_list
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = self._parse_images(etree.fromstring(body))
|
||||
return resp, body['images']
|
||||
|
||||
def get_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 = self._parse_image(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_image(self, image_id):
|
||||
"""Deletes the provided image."""
|
||||
return self.delete("images/%s" % str(image_id))
|
||||
|
||||
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 _metadata_body(self, meta):
|
||||
post_body = xml_utils.Element('metadata')
|
||||
for k, v in meta.items():
|
||||
data = xml_utils.Element('meta', key=k)
|
||||
data.append(xml_utils.Text(v))
|
||||
post_body.append(data)
|
||||
return post_body
|
||||
|
||||
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 = self._parse_key_value(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def set_image_metadata(self, image_id, meta):
|
||||
"""Sets the metadata for an image."""
|
||||
post_body = self._metadata_body(meta)
|
||||
resp, body = self.put('images/%s/metadata' % image_id,
|
||||
str(xml_utils.Document(post_body)))
|
||||
body = self._parse_key_value(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def update_image_metadata(self, image_id, meta):
|
||||
"""Updates the metadata for an image."""
|
||||
post_body = self._metadata_body(meta)
|
||||
resp, body = self.post('images/%s/metadata' % str(image_id),
|
||||
str(xml_utils.Document(post_body)))
|
||||
body = self._parse_key_value(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
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.xml" %
|
||||
(str(image_id), key))
|
||||
body = self._parse_metadata(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def set_image_metadata_item(self, image_id, key, meta):
|
||||
"""Sets the value for a specific image metadata key."""
|
||||
for k, v in meta.items():
|
||||
post_body = xml_utils.Element('meta', key=key)
|
||||
post_body.append(xml_utils.Text(v))
|
||||
resp, body = self.put('images/%s/metadata/%s' % (str(image_id), key),
|
||||
str(xml_utils.Document(post_body)))
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def update_image_metadata_item(self, image_id, key, meta):
|
||||
"""Sets the value for a specific image metadata key."""
|
||||
post_body = xml_utils.Document('meta', xml_utils.Text(meta), key=key)
|
||||
resp, body = self.put('images/%s/metadata/%s' % (str(image_id), key),
|
||||
post_body)
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, body['meta']
|
||||
|
||||
def delete_image_metadata_item(self, image_id, key):
|
||||
"""Deletes a single image metadata key/value pair."""
|
||||
return self.delete("images/%s/metadata/%s" % (str(image_id), key))
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
self.get_image(id)
|
||||
except exceptions.NotFound:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def resource_type(self):
|
||||
"""Returns the primary type of resource this client works with."""
|
||||
return 'image'
|
@ -1,45 +0,0 @@
|
||||
# Copyright 2013 IBM 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 lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class InstanceUsagesAuditLogClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(InstanceUsagesAuditLogClientXML, self).__init__(
|
||||
auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def list_instance_usage_audit_logs(self):
|
||||
url = 'os-instance_usage_audit_log'
|
||||
resp, body = self.get(url)
|
||||
instance_usage_audit_logs = xml_utils.xml_to_json(
|
||||
etree.fromstring(body))
|
||||
return resp, instance_usage_audit_logs
|
||||
|
||||
def get_instance_usage_audit_log(self, time_before):
|
||||
url = 'os-instance_usage_audit_log/%s' % time_before
|
||||
resp, body = self.get(url)
|
||||
instance_usage_audit_log = xml_utils.xml_to_json(
|
||||
etree.fromstring(body))
|
||||
return resp, instance_usage_audit_log
|
@ -1,121 +0,0 @@
|
||||
# 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 time
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class InterfacesClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(InterfacesClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def _process_xml_interface(self, node):
|
||||
iface = xml_utils.xml_to_json(node)
|
||||
# NOTE(danms): if multiple addresses per interface is ever required,
|
||||
# xml_utils.xml_to_json will need to be fixed or replaced in this case
|
||||
iface['fixed_ips'] = [dict(iface['fixed_ips']['fixed_ip'].items())]
|
||||
return iface
|
||||
|
||||
def list_interfaces(self, server):
|
||||
resp, body = self.get('servers/%s/os-interface' % server)
|
||||
node = etree.fromstring(body)
|
||||
interfaces = [self._process_xml_interface(x)
|
||||
for x in node.getchildren()]
|
||||
return resp, interfaces
|
||||
|
||||
def create_interface(self, server, port_id=None, network_id=None,
|
||||
fixed_ip=None):
|
||||
doc = xml_utils.Document()
|
||||
iface = xml_utils.Element('interfaceAttachment')
|
||||
if port_id:
|
||||
_port_id = xml_utils.Element('port_id')
|
||||
_port_id.append(xml_utils.Text(port_id))
|
||||
iface.append(_port_id)
|
||||
if network_id:
|
||||
_network_id = xml_utils.Element('net_id')
|
||||
_network_id.append(xml_utils.Text(network_id))
|
||||
iface.append(_network_id)
|
||||
if fixed_ip:
|
||||
_fixed_ips = xml_utils.Element('fixed_ips')
|
||||
_fixed_ip = xml_utils.Element('fixed_ip')
|
||||
_ip_address = xml_utils.Element('ip_address')
|
||||
_ip_address.append(xml_utils.Text(fixed_ip))
|
||||
_fixed_ip.append(_ip_address)
|
||||
_fixed_ips.append(_fixed_ip)
|
||||
iface.append(_fixed_ips)
|
||||
doc.append(iface)
|
||||
resp, body = self.post('servers/%s/os-interface' % server,
|
||||
body=str(doc))
|
||||
body = self._process_xml_interface(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def show_interface(self, server, port_id):
|
||||
resp, body = self.get('servers/%s/os-interface/%s' % (server, port_id))
|
||||
body = self._process_xml_interface(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_interface(self, server, port_id):
|
||||
resp, body = self.delete('servers/%s/os-interface/%s' % (server,
|
||||
port_id))
|
||||
return resp, body
|
||||
|
||||
def wait_for_interface_status(self, server, port_id, status):
|
||||
"""Waits for a interface to reach a given status."""
|
||||
resp, 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)
|
||||
resp, 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 within '
|
||||
'the required time (%s s).' %
|
||||
(port_id, status, self.build_timeout))
|
||||
raise exceptions.TimeoutException(message)
|
||||
return resp, body
|
||||
|
||||
def add_fixed_ip(self, server_id, network_id):
|
||||
"""Add a fixed IP to input server instance."""
|
||||
post_body = xml_utils.Element("addFixedIp",
|
||||
xmlns=xml_utils.XMLNS_11,
|
||||
networkId=network_id)
|
||||
resp, body = self.post('servers/%s/action' % str(server_id),
|
||||
str(xml_utils.Document(post_body)))
|
||||
return resp, body
|
||||
|
||||
def remove_fixed_ip(self, server_id, ip_address):
|
||||
"""Remove input fixed IP from input server instance."""
|
||||
post_body = xml_utils.Element("removeFixedIp",
|
||||
xmlns=xml_utils.XMLNS_11,
|
||||
address=ip_address)
|
||||
resp, body = self.post('servers/%s/action' % str(server_id),
|
||||
str(xml_utils.Document(post_body)))
|
||||
return resp, body
|
@ -1,68 +0,0 @@
|
||||
# Copyright 2012 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.
|
||||
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class KeyPairsClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(KeyPairsClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def list_keypairs(self):
|
||||
resp, body = self.get("os-keypairs")
|
||||
node = etree.fromstring(body)
|
||||
body = [{'keypair': xml_utils.xml_to_json(x)} for x in
|
||||
node.getchildren()]
|
||||
return resp, body
|
||||
|
||||
def get_keypair(self, key_name):
|
||||
resp, body = self.get("os-keypairs/%s" % str(key_name))
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def create_keypair(self, name, pub_key=None):
|
||||
doc = xml_utils.Document()
|
||||
|
||||
keypair_element = xml_utils.Element("keypair")
|
||||
|
||||
if pub_key:
|
||||
public_key_element = xml_utils.Element("public_key")
|
||||
public_key_text = xml_utils.Text(pub_key)
|
||||
public_key_element.append(public_key_text)
|
||||
keypair_element.append(public_key_element)
|
||||
|
||||
name_element = xml_utils.Element("name")
|
||||
name_text = xml_utils.Text(name)
|
||||
name_element.append(name_text)
|
||||
keypair_element.append(name_element)
|
||||
|
||||
doc.append(keypair_element)
|
||||
|
||||
resp, body = self.post("os-keypairs", body=str(doc))
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_keypair(self, key_name):
|
||||
return self.delete("os-keypairs/%s" % str(key_name))
|
@ -1,56 +0,0 @@
|
||||
# Copyright 2012 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.
|
||||
|
||||
from lxml import objectify
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
NS = "{http://docs.openstack.org/common/api/v1.0}"
|
||||
|
||||
|
||||
class LimitsClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(LimitsClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def get_absolute_limits(self):
|
||||
resp, body = self.get("limits")
|
||||
body = objectify.fromstring(body)
|
||||
lim = NS + 'absolute'
|
||||
ret = {}
|
||||
|
||||
for el in body[lim].iterchildren():
|
||||
attributes = el.attrib
|
||||
ret[attributes['name']] = attributes['value']
|
||||
return resp, ret
|
||||
|
||||
def get_specific_absolute_limit(self, absolute_limit):
|
||||
resp, body = self.get("limits")
|
||||
body = objectify.fromstring(body)
|
||||
lim = NS + 'absolute'
|
||||
ret = {}
|
||||
|
||||
for el in body[lim].iterchildren():
|
||||
attributes = el.attrib
|
||||
ret[attributes['name']] = attributes['value']
|
||||
if absolute_limit not in ret:
|
||||
return None
|
||||
else:
|
||||
return ret[absolute_limit]
|
@ -1,165 +0,0 @@
|
||||
# Copyright 2012 NTT Data
|
||||
# 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 lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
def format_quota(q):
|
||||
quota = {}
|
||||
for k, v in q.items():
|
||||
try:
|
||||
v = int(v)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
quota[k] = v
|
||||
|
||||
return quota
|
||||
|
||||
|
||||
class QuotasClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(QuotasClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def get_quota_set(self, tenant_id, user_id=None):
|
||||
"""List the quota set for a tenant."""
|
||||
|
||||
url = 'os-quota-sets/%s' % str(tenant_id)
|
||||
if user_id:
|
||||
url += '?user_id=%s' % str(user_id)
|
||||
resp, body = self.get(url)
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
body = format_quota(body)
|
||||
return resp, body
|
||||
|
||||
def get_default_quota_set(self, tenant_id):
|
||||
"""List the default quota set for a tenant."""
|
||||
|
||||
url = 'os-quota-sets/%s/defaults' % str(tenant_id)
|
||||
resp, body = self.get(url)
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
body = format_quota(body)
|
||||
return resp, body
|
||||
|
||||
def update_quota_set(self, tenant_id, user_id=None,
|
||||
force=None, injected_file_content_bytes=None,
|
||||
metadata_items=None, ram=None, floating_ips=None,
|
||||
fixed_ips=None, key_pairs=None, instances=None,
|
||||
security_group_rules=None, injected_files=None,
|
||||
cores=None, injected_file_path_bytes=None,
|
||||
security_groups=None):
|
||||
"""
|
||||
Updates the tenant's quota limits for one or more resources
|
||||
"""
|
||||
post_body = xml_utils.Element("quota_set",
|
||||
xmlns=xml_utils.XMLNS_11)
|
||||
|
||||
if force is not None:
|
||||
post_body.add_attr('force', force)
|
||||
|
||||
if injected_file_content_bytes is not None:
|
||||
post_body.add_attr('injected_file_content_bytes',
|
||||
injected_file_content_bytes)
|
||||
|
||||
if metadata_items is not None:
|
||||
post_body.add_attr('metadata_items', metadata_items)
|
||||
|
||||
if ram is not None:
|
||||
post_body.add_attr('ram', ram)
|
||||
|
||||
if floating_ips is not None:
|
||||
post_body.add_attr('floating_ips', floating_ips)
|
||||
|
||||
if fixed_ips is not None:
|
||||
post_body.add_attr('fixed_ips', fixed_ips)
|
||||
|
||||
if key_pairs is not None:
|
||||
post_body.add_attr('key_pairs', key_pairs)
|
||||
|
||||
if instances is not None:
|
||||
post_body.add_attr('instances', instances)
|
||||
|
||||
if security_group_rules is not None:
|
||||
post_body.add_attr('security_group_rules', security_group_rules)
|
||||
|
||||
if injected_files is not None:
|
||||
post_body.add_attr('injected_files', injected_files)
|
||||
|
||||
if cores is not None:
|
||||
post_body.add_attr('cores', cores)
|
||||
|
||||
if injected_file_path_bytes is not None:
|
||||
post_body.add_attr('injected_file_path_bytes',
|
||||
injected_file_path_bytes)
|
||||
|
||||
if security_groups is not None:
|
||||
post_body.add_attr('security_groups', security_groups)
|
||||
|
||||
if user_id:
|
||||
resp, body = self.put('os-quota-sets/%s?user_id=%s' %
|
||||
(str(tenant_id), str(user_id)),
|
||||
str(xml_utils.Document(post_body)))
|
||||
else:
|
||||
resp, body = self.put('os-quota-sets/%s' % str(tenant_id),
|
||||
str(xml_utils.Document(post_body)))
|
||||
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
body = format_quota(body)
|
||||
return resp, body
|
||||
|
||||
def delete_quota_set(self, tenant_id):
|
||||
"""Delete the tenant's quota set."""
|
||||
return self.delete('os-quota-sets/%s' % str(tenant_id))
|
||||
|
||||
|
||||
class QuotaClassesClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(QuotaClassesClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def get_quota_class_set(self, quota_class_id):
|
||||
"""List the quota class set for a quota class."""
|
||||
|
||||
url = 'os-quota-class-sets/%s' % str(quota_class_id)
|
||||
resp, body = self.get(url)
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
body = format_quota(body)
|
||||
return resp, body
|
||||
|
||||
def update_quota_class_set(self, quota_class_id, **kwargs):
|
||||
"""
|
||||
Updates the quota class's limits for one or more resources.
|
||||
"""
|
||||
post_body = xml_utils.Element("quota_class_set",
|
||||
xmlns=xml_utils.XMLNS_11,
|
||||
**kwargs)
|
||||
|
||||
resp, body = self.put('os-quota-class-sets/%s' % str(quota_class_id),
|
||||
str(xml_utils.Document(post_body)))
|
||||
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
body = format_quota(body)
|
||||
return resp, body
|
@ -1,166 +0,0 @@
|
||||
# Copyright 2012 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 urllib
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class SecurityGroupsClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(SecurityGroupsClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def _parse_array(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
array.append(xml_utils.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def _parse_body(self, body):
|
||||
json = xml_utils.xml_to_json(body)
|
||||
return json
|
||||
|
||||
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 = self._parse_array(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
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 = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def create_security_group(self, name, description):
|
||||
"""
|
||||
Creates a new security group.
|
||||
name (Required): Name of security group.
|
||||
description (Required): Description of security group.
|
||||
"""
|
||||
security_group = xml_utils.Element("security_group", name=name)
|
||||
des = xml_utils.Element("description")
|
||||
des.append(xml_utils.Text(content=description))
|
||||
security_group.append(des)
|
||||
resp, body = self.post('os-security-groups',
|
||||
str(xml_utils.Document(security_group)))
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
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
|
||||
"""
|
||||
security_group = xml_utils.Element("security_group")
|
||||
if name:
|
||||
sg_name = xml_utils.Element("name")
|
||||
sg_name.append(xml_utils.Text(content=name))
|
||||
security_group.append(sg_name)
|
||||
if description:
|
||||
des = xml_utils.Element("description")
|
||||
des.append(xml_utils.Text(content=description))
|
||||
security_group.append(des)
|
||||
resp, body = self.put('os-security-groups/%s' %
|
||||
str(security_group_id),
|
||||
str(xml_utils.Document(security_group)))
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_security_group(self, security_group_id):
|
||||
"""Deletes the provided Security Group."""
|
||||
return self.delete('os-security-groups/%s' % str(security_group_id))
|
||||
|
||||
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
|
||||
"""
|
||||
group_rule = xml_utils.Element("security_group_rule")
|
||||
|
||||
elements = dict()
|
||||
elements['cidr'] = kwargs.get('cidr')
|
||||
elements['group_id'] = kwargs.get('group_id')
|
||||
elements['parent_group_id'] = parent_group_id
|
||||
elements['ip_protocol'] = ip_proto
|
||||
elements['from_port'] = from_port
|
||||
elements['to_port'] = to_port
|
||||
|
||||
for k, v in elements.items():
|
||||
if v is not None:
|
||||
element = xml_utils.Element(k)
|
||||
element.append(xml_utils.Text(content=str(v)))
|
||||
group_rule.append(element)
|
||||
|
||||
url = 'os-security-group-rules'
|
||||
resp, body = self.post(url, str(xml_utils.Document(group_rule)))
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_security_group_rule(self, group_rule_id):
|
||||
"""Deletes the provided Security Group rule."""
|
||||
return self.delete('os-security-group-rules/%s' %
|
||||
str(group_rule_id))
|
||||
|
||||
def list_security_group_rules(self, security_group_id):
|
||||
"""List all rules for a security group."""
|
||||
url = "os-security-groups"
|
||||
resp, body = self.get(url)
|
||||
body = etree.fromstring(body)
|
||||
secgroups = body.getchildren()
|
||||
for secgroup in secgroups:
|
||||
if secgroup.get('id') == security_group_id:
|
||||
node = secgroup.find('{%s}rules' % xml_utils.XMLNS_11)
|
||||
rules = [xml_utils.xml_to_json(x) for x in node.getchildren()]
|
||||
return resp, rules
|
||||
raise exceptions.NotFound('No such Security Group')
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
self.get_security_group(id)
|
||||
except exceptions.NotFound:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def resource_type(self):
|
||||
"""Returns the primary type of resource this client works with."""
|
||||
return 'security_group'
|
@ -1,673 +0,0 @@
|
||||
# Copyright 2012 IBM Corp.
|
||||
# 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 time
|
||||
import urllib
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import waiters
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
from tempest.openstack.common import log as logging
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _translate_ip_xml_json(ip):
|
||||
"""
|
||||
Convert the address version to int.
|
||||
"""
|
||||
ip = dict(ip)
|
||||
version = ip.get('version')
|
||||
if version:
|
||||
ip['version'] = int(version)
|
||||
# NOTE(maurosr): just a fast way to avoid the xml version with the
|
||||
# expanded xml namespace.
|
||||
type_ns_prefix = ('{http://docs.openstack.org/compute/ext/extended_ips/'
|
||||
'api/v1.1}type')
|
||||
mac_ns_prefix = ('{http://docs.openstack.org/compute/ext/extended_ips_mac'
|
||||
'/api/v1.1}mac_addr')
|
||||
|
||||
if type_ns_prefix in ip:
|
||||
ip['OS-EXT-IPS:type'] = ip.pop(type_ns_prefix)
|
||||
|
||||
if mac_ns_prefix in ip:
|
||||
ip['OS-EXT-IPS-MAC:mac_addr'] = ip.pop(mac_ns_prefix)
|
||||
return ip
|
||||
|
||||
|
||||
def _translate_network_xml_to_json(network):
|
||||
return [_translate_ip_xml_json(ip.attrib)
|
||||
for ip in network.findall('{%s}ip' % xml_utils.XMLNS_11)]
|
||||
|
||||
|
||||
def _translate_addresses_xml_to_json(xml_addresses):
|
||||
return dict((network.attrib['id'], _translate_network_xml_to_json(network))
|
||||
for network in xml_addresses.findall('{%s}network' %
|
||||
xml_utils.XMLNS_11))
|
||||
|
||||
|
||||
def _translate_server_xml_to_json(xml_dom):
|
||||
"""Convert server XML to server JSON.
|
||||
|
||||
The addresses collection does not convert well by the dumb xml_to_json.
|
||||
This method does some pre and post-processing to deal with that.
|
||||
|
||||
Translate XML addresses subtree to JSON.
|
||||
|
||||
Having xml_doc similar to
|
||||
<api:server xmlns:api="http://docs.openstack.org/compute/api/v1.1">
|
||||
<api:addresses>
|
||||
<api:network id="foo_novanetwork">
|
||||
<api:ip version="4" addr="192.168.0.4"/>
|
||||
</api:network>
|
||||
<api:network id="bar_novanetwork">
|
||||
<api:ip version="4" addr="10.1.0.4"/>
|
||||
<api:ip version="6" addr="2001:0:0:1:2:3:4:5"/>
|
||||
</api:network>
|
||||
</api:addresses>
|
||||
</api:server>
|
||||
|
||||
the _translate_server_xml_to_json(etree.fromstring(xml_doc)) should produce
|
||||
something like
|
||||
|
||||
{'addresses': {'bar_novanetwork': [{'addr': '10.1.0.4', 'version': 4},
|
||||
{'addr': '2001:0:0:1:2:3:4:5',
|
||||
'version': 6}],
|
||||
'foo_novanetwork': [{'addr': '192.168.0.4', 'version': 4}]}}
|
||||
"""
|
||||
nsmap = {'api': xml_utils.XMLNS_11}
|
||||
addresses = xml_dom.xpath('/api:server/api:addresses', namespaces=nsmap)
|
||||
if addresses:
|
||||
if len(addresses) > 1:
|
||||
raise ValueError('Expected only single `addresses` element.')
|
||||
json_addresses = _translate_addresses_xml_to_json(addresses[0])
|
||||
json = xml_utils.xml_to_json(xml_dom)
|
||||
json['addresses'] = json_addresses
|
||||
else:
|
||||
json = xml_utils.xml_to_json(xml_dom)
|
||||
diskConfig = ('{http://docs.openstack.org'
|
||||
'/compute/ext/disk_config/api/v1.1}diskConfig')
|
||||
terminated_at = ('{http://docs.openstack.org/'
|
||||
'compute/ext/server_usage/api/v1.1}terminated_at')
|
||||
launched_at = ('{http://docs.openstack.org'
|
||||
'/compute/ext/server_usage/api/v1.1}launched_at')
|
||||
power_state = ('{http://docs.openstack.org'
|
||||
'/compute/ext/extended_status/api/v1.1}power_state')
|
||||
availability_zone = ('{http://docs.openstack.org'
|
||||
'/compute/ext/extended_availability_zone/api/v2}'
|
||||
'availability_zone')
|
||||
vm_state = ('{http://docs.openstack.org'
|
||||
'/compute/ext/extended_status/api/v1.1}vm_state')
|
||||
task_state = ('{http://docs.openstack.org'
|
||||
'/compute/ext/extended_status/api/v1.1}task_state')
|
||||
if 'tenantId' in json:
|
||||
json['tenant_id'] = json.pop('tenantId')
|
||||
if 'userId' in json:
|
||||
json['user_id'] = json.pop('userId')
|
||||
if diskConfig in json:
|
||||
json['OS-DCF:diskConfig'] = json.pop(diskConfig)
|
||||
if terminated_at in json:
|
||||
json['OS-SRV-USG:terminated_at'] = json.pop(terminated_at)
|
||||
if launched_at in json:
|
||||
json['OS-SRV-USG:launched_at'] = json.pop(launched_at)
|
||||
if power_state in json:
|
||||
json['OS-EXT-STS:power_state'] = json.pop(power_state)
|
||||
if availability_zone in json:
|
||||
json['OS-EXT-AZ:availability_zone'] = json.pop(availability_zone)
|
||||
if vm_state in json:
|
||||
json['OS-EXT-STS:vm_state'] = json.pop(vm_state)
|
||||
if task_state in json:
|
||||
json['OS-EXT-STS:task_state'] = json.pop(task_state)
|
||||
return json
|
||||
|
||||
|
||||
class ServersClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(ServersClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def _parse_key_value(self, node):
|
||||
"""Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
|
||||
data = {}
|
||||
for node in node.getchildren():
|
||||
data[node.get('key')] = node.text
|
||||
return data
|
||||
|
||||
def _parse_links(self, node, json):
|
||||
del json['link']
|
||||
json['links'] = []
|
||||
for linknode in node.findall('{http://www.w3.org/2005/Atom}link'):
|
||||
json['links'].append(xml_utils.xml_to_json(linknode))
|
||||
|
||||
def _parse_server(self, body):
|
||||
json = _translate_server_xml_to_json(body)
|
||||
|
||||
if 'metadata' in json and json['metadata']:
|
||||
# NOTE(danms): if there was metadata, we need to re-parse
|
||||
# that as a special type
|
||||
metadata_tag = body.find('{%s}metadata' % xml_utils.XMLNS_11)
|
||||
json["metadata"] = self._parse_key_value(metadata_tag)
|
||||
if 'link' in json:
|
||||
self._parse_links(body, json)
|
||||
for sub in ['image', 'flavor']:
|
||||
if sub in json and 'link' in json[sub]:
|
||||
self._parse_links(body, json[sub])
|
||||
return json
|
||||
|
||||
def _parse_xml_virtual_interfaces(self, xml_dom):
|
||||
"""
|
||||
Return server's virtual interfaces XML as JSON.
|
||||
"""
|
||||
data = {"virtual_interfaces": []}
|
||||
for iface in xml_dom.getchildren():
|
||||
data["virtual_interfaces"].append(
|
||||
{"id": iface.get("id"),
|
||||
"mac_address": iface.get("mac_address")})
|
||||
return data
|
||||
|
||||
def get_server(self, server_id):
|
||||
"""Returns the details of an existing server."""
|
||||
resp, body = self.get("servers/%s" % str(server_id))
|
||||
server = self._parse_server(etree.fromstring(body))
|
||||
return resp, server
|
||||
|
||||
def migrate_server(self, server_id, **kwargs):
|
||||
"""Migrates the given server ."""
|
||||
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 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 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 delete_server(self, server_id):
|
||||
"""Deletes the given server."""
|
||||
return self.delete("servers/%s" % str(server_id))
|
||||
|
||||
def _parse_array(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
array.append(xml_utils.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def _parse_server_array(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
array.append(self._parse_server(child))
|
||||
return array
|
||||
|
||||
def list_servers(self, params=None):
|
||||
url = 'servers'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
servers = self._parse_server_array(etree.fromstring(body))
|
||||
return resp, {"servers": servers}
|
||||
|
||||
def list_servers_with_detail(self, params=None):
|
||||
url = 'servers/detail'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
servers = self._parse_server_array(etree.fromstring(body))
|
||||
return resp, {"servers": servers}
|
||||
|
||||
def update_server(self, server_id, name=None, meta=None, accessIPv4=None,
|
||||
accessIPv6=None, disk_config=None):
|
||||
doc = xml_utils.Document()
|
||||
server = xml_utils.Element("server")
|
||||
doc.append(server)
|
||||
|
||||
if name is not None:
|
||||
server.add_attr("name", name)
|
||||
if accessIPv4 is not None:
|
||||
server.add_attr("accessIPv4", accessIPv4)
|
||||
if accessIPv6 is not None:
|
||||
server.add_attr("accessIPv6", accessIPv6)
|
||||
if disk_config is not None:
|
||||
server.add_attr('xmlns:OS-DCF', "http://docs.openstack.org/"
|
||||
"compute/ext/disk_config/api/v1.1")
|
||||
server.add_attr("OS-DCF:diskConfig", disk_config)
|
||||
if meta is not None:
|
||||
metadata = xml_utils.Element("metadata")
|
||||
server.append(metadata)
|
||||
for k, v in meta:
|
||||
meta = xml_utils.Element("meta", key=k)
|
||||
meta.append(xml_utils.Text(v))
|
||||
metadata.append(meta)
|
||||
|
||||
resp, body = self.put('servers/%s' % str(server_id), str(doc))
|
||||
return resp, xml_utils.xml_to_json(etree.fromstring(body))
|
||||
|
||||
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.
|
||||
block_device_mapping: Block device mapping for the server.
|
||||
"""
|
||||
server = xml_utils.Element("server",
|
||||
xmlns=xml_utils.XMLNS_11,
|
||||
imageRef=image_ref,
|
||||
flavorRef=flavor_ref,
|
||||
name=name)
|
||||
|
||||
for attr in ["adminPass", "accessIPv4", "accessIPv6", "key_name",
|
||||
"user_data", "availability_zone", "min_count",
|
||||
"max_count", "return_reservation_id",
|
||||
"block_device_mapping"]:
|
||||
if attr in kwargs:
|
||||
server.add_attr(attr, kwargs[attr])
|
||||
|
||||
if 'disk_config' in kwargs:
|
||||
server.add_attr('xmlns:OS-DCF', "http://docs.openstack.org/"
|
||||
"compute/ext/disk_config/api/v1.1")
|
||||
server.add_attr('OS-DCF:diskConfig', kwargs['disk_config'])
|
||||
|
||||
if 'security_groups' in kwargs:
|
||||
secgroups = xml_utils.Element("security_groups")
|
||||
server.append(secgroups)
|
||||
for secgroup in kwargs['security_groups']:
|
||||
s = xml_utils.Element("security_group", name=secgroup['name'])
|
||||
secgroups.append(s)
|
||||
|
||||
if 'networks' in kwargs:
|
||||
networks = xml_utils.Element("networks")
|
||||
server.append(networks)
|
||||
for network in kwargs['networks']:
|
||||
if 'fixed_ip' in network:
|
||||
s = xml_utils.Element("network", uuid=network['uuid'],
|
||||
fixed_ip=network['fixed_ip'])
|
||||
else:
|
||||
s = xml_utils.Element("network", uuid=network['uuid'])
|
||||
networks.append(s)
|
||||
|
||||
if 'meta' in kwargs:
|
||||
metadata = xml_utils.Element("metadata")
|
||||
server.append(metadata)
|
||||
for k, v in kwargs['meta'].items():
|
||||
meta = xml_utils.Element("meta", key=k)
|
||||
meta.append(xml_utils.Text(v))
|
||||
metadata.append(meta)
|
||||
|
||||
if 'personality' in kwargs:
|
||||
personality = xml_utils.Element('personality')
|
||||
server.append(personality)
|
||||
for k in kwargs['personality']:
|
||||
temp = xml_utils.Element('file', path=k['path'])
|
||||
temp.append(xml_utils.Text(k['contents']))
|
||||
personality.append(temp)
|
||||
|
||||
if 'sched_hints' in kwargs:
|
||||
sched_hints = kwargs.get('sched_hints')
|
||||
hints = xml_utils.Element("os:scheduler_hints")
|
||||
hints.add_attr('xmlns:os', xml_utils.XMLNS_11)
|
||||
for attr in sched_hints:
|
||||
p1 = xml_utils.Element(attr)
|
||||
p1.append(sched_hints[attr])
|
||||
hints.append(p1)
|
||||
server.append(hints)
|
||||
resp, body = self.post('servers', str(xml_utils.Document(server)))
|
||||
server = self._parse_server(etree.fromstring(body))
|
||||
return resp, server
|
||||
|
||||
def wait_for_server_status(self, server_id, status, extra_timeout=0,
|
||||
raise_on_error=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)
|
||||
|
||||
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:
|
||||
resp, body = self.get_server(server_id)
|
||||
except exceptions.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 _parse_network(self, node):
|
||||
addrs = []
|
||||
for child in node.getchildren():
|
||||
addrs.append({'version': int(child.get('version')),
|
||||
'addr': child.get('addr')})
|
||||
return {node.get('id'): addrs}
|
||||
|
||||
def list_addresses(self, server_id):
|
||||
"""Lists all addresses for a server."""
|
||||
resp, body = self.get("servers/%s/ips" % str(server_id))
|
||||
|
||||
networks = {}
|
||||
xml_list = etree.fromstring(body)
|
||||
for child in xml_list.getchildren():
|
||||
network = self._parse_network(child)
|
||||
networks.update(**network)
|
||||
|
||||
return resp, networks
|
||||
|
||||
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))
|
||||
network = self._parse_network(etree.fromstring(body))
|
||||
|
||||
return resp, network
|
||||
|
||||
def action(self, server_id, action_name, response_key, **kwargs):
|
||||
if 'xmlns' not in kwargs:
|
||||
kwargs['xmlns'] = xml_utils.XMLNS_11
|
||||
doc = xml_utils.Document((xml_utils.Element(action_name, **kwargs)))
|
||||
resp, body = self.post("servers/%s/action" % server_id, str(doc))
|
||||
if response_key is not None:
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return 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, password):
|
||||
return self.action(server_id, "changePassword", None,
|
||||
adminPass=password)
|
||||
|
||||
def get_password(self, server_id):
|
||||
resp, body = self.get("servers/%s/os-server-password" % str(server_id))
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return 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.
|
||||
"""
|
||||
return self.delete("servers/%s/os-server-password" % str(server_id))
|
||||
|
||||
def reboot(self, server_id, reboot_type):
|
||||
return self.action(server_id, "reboot", None, type=reboot_type)
|
||||
|
||||
def rebuild(self, server_id, image_ref, **kwargs):
|
||||
kwargs['imageRef'] = image_ref
|
||||
if 'disk_config' in kwargs:
|
||||
kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
|
||||
del kwargs['disk_config']
|
||||
kwargs['xmlns:OS-DCF'] = "http://docs.openstack.org/"\
|
||||
"compute/ext/disk_config/api/v1.1"
|
||||
kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
|
||||
if 'xmlns' not in kwargs:
|
||||
kwargs['xmlns'] = xml_utils.XMLNS_11
|
||||
|
||||
attrs = kwargs.copy()
|
||||
if 'metadata' in attrs:
|
||||
del attrs['metadata']
|
||||
rebuild = xml_utils.Element("rebuild", **attrs)
|
||||
|
||||
if 'metadata' in kwargs:
|
||||
metadata = xml_utils.Element("metadata")
|
||||
rebuild.append(metadata)
|
||||
for k, v in kwargs['metadata'].items():
|
||||
meta = xml_utils.Element("meta", key=k)
|
||||
meta.append(xml_utils.Text(v))
|
||||
metadata.append(meta)
|
||||
|
||||
resp, body = self.post('servers/%s/action' % server_id,
|
||||
str(xml_utils.Document(rebuild)))
|
||||
server = self._parse_server(etree.fromstring(body))
|
||||
return resp, server
|
||||
|
||||
def resize(self, server_id, flavor_ref, **kwargs):
|
||||
if 'disk_config' in kwargs:
|
||||
kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
|
||||
del kwargs['disk_config']
|
||||
kwargs['xmlns:OS-DCF'] = "http://docs.openstack.org/"\
|
||||
"compute/ext/disk_config/api/v1.1"
|
||||
kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
|
||||
kwargs['flavorRef'] = flavor_ref
|
||||
return self.action(server_id, 'resize', None, **kwargs)
|
||||
|
||||
def confirm_resize(self, server_id, **kwargs):
|
||||
return self.action(server_id, 'confirmResize', None, **kwargs)
|
||||
|
||||
def revert_resize(self, server_id, **kwargs):
|
||||
return self.action(server_id, 'revertResize', None, **kwargs)
|
||||
|
||||
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 create_image(self, server_id, name):
|
||||
return self.action(server_id, 'createImage', None, name=name)
|
||||
|
||||
def add_security_group(self, server_id, name):
|
||||
return self.action(server_id, 'addSecurityGroup', None, name=name)
|
||||
|
||||
def remove_security_group(self, server_id, name):
|
||||
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 ."""
|
||||
|
||||
req_body = xml_utils.Element("os-migrateLive",
|
||||
xmlns=xml_utils.XMLNS_11,
|
||||
disk_over_commit=False,
|
||||
block_migration=use_block_migration,
|
||||
host=dest_host)
|
||||
|
||||
resp, body = self.post("servers/%s/action" % str(server_id),
|
||||
str(xml_utils.Document(req_body)))
|
||||
return resp, body
|
||||
|
||||
def list_server_metadata(self, server_id):
|
||||
resp, body = self.get("servers/%s/metadata" % str(server_id))
|
||||
body = self._parse_key_value(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def set_server_metadata(self, server_id, meta, no_metadata_field=False):
|
||||
doc = xml_utils.Document()
|
||||
if not no_metadata_field:
|
||||
metadata = xml_utils.Element("metadata")
|
||||
doc.append(metadata)
|
||||
for k, v in meta.items():
|
||||
meta_element = xml_utils.Element("meta", key=k)
|
||||
meta_element.append(xml_utils.Text(v))
|
||||
metadata.append(meta_element)
|
||||
resp, body = self.put('servers/%s/metadata' % str(server_id), str(doc))
|
||||
return resp, xml_utils.xml_to_json(etree.fromstring(body))
|
||||
|
||||
def update_server_metadata(self, server_id, meta):
|
||||
doc = xml_utils.Document()
|
||||
metadata = xml_utils.Element("metadata")
|
||||
doc.append(metadata)
|
||||
for k, v in meta.items():
|
||||
meta_element = xml_utils.Element("meta", key=k)
|
||||
meta_element.append(xml_utils.Text(v))
|
||||
metadata.append(meta_element)
|
||||
resp, body = self.post("/servers/%s/metadata" % str(server_id),
|
||||
str(doc))
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def get_server_metadata_item(self, server_id, key):
|
||||
resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key))
|
||||
return resp, dict([(etree.fromstring(body).attrib['key'],
|
||||
xml_utils.xml_to_json(etree.fromstring(body)))])
|
||||
|
||||
def set_server_metadata_item(self, server_id, key, meta):
|
||||
doc = xml_utils.Document()
|
||||
for k, v in meta.items():
|
||||
meta_element = xml_utils.Element("meta", key=k)
|
||||
meta_element.append(xml_utils.Text(v))
|
||||
doc.append(meta_element)
|
||||
resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key),
|
||||
str(doc))
|
||||
return resp, xml_utils.xml_to_json(etree.fromstring(body))
|
||||
|
||||
def delete_server_metadata_item(self, server_id, key):
|
||||
resp, body = self.delete("servers/%s/metadata/%s" %
|
||||
(str(server_id), key))
|
||||
return resp, body
|
||||
|
||||
def get_console_output(self, server_id, length):
|
||||
kwargs = {'length': length} if length else {}
|
||||
return self.action(server_id, 'os-getConsoleOutput', 'output',
|
||||
**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']))
|
||||
virt_int = self._parse_xml_virtual_interfaces(etree.fromstring(body))
|
||||
return resp, virt_int
|
||||
|
||||
def rescue_server(self, server_id, **kwargs):
|
||||
"""Rescue the provided server."""
|
||||
return self.action(server_id, 'rescue', None, **kwargs)
|
||||
|
||||
def unrescue_server(self, server_id):
|
||||
"""Unrescue the provided server."""
|
||||
return self.action(server_id, 'unrescue', None)
|
||||
|
||||
def attach_volume(self, server_id, volume_id, device='/dev/vdz'):
|
||||
post_body = xml_utils.Element("volumeAttachment", volumeId=volume_id,
|
||||
device=device)
|
||||
resp, body = self.post('servers/%s/os-volume_attachments' % server_id,
|
||||
str(xml_utils.Document(post_body)))
|
||||
return resp, body
|
||||
|
||||
def detach_volume(self, server_id, volume_id):
|
||||
headers = {'Content-Type': 'application/xml',
|
||||
'Accept': 'application/xml'}
|
||||
resp, body = self.delete('servers/%s/os-volume_attachments/%s' %
|
||||
(server_id, volume_id), headers)
|
||||
return resp, body
|
||||
|
||||
def get_server_diagnostics(self, server_id):
|
||||
"""Get the usage data for a server."""
|
||||
resp, body = self.get("servers/%s/diagnostics" % server_id)
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def list_instance_actions(self, server_id):
|
||||
"""List the provided server action."""
|
||||
resp, body = self.get("servers/%s/os-instance-actions" % server_id)
|
||||
body = self._parse_array(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
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" %
|
||||
(server_id, request_id))
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
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", type=console_type)
|
@ -1,73 +0,0 @@
|
||||
# Copyright 2013 NEC Corporation
|
||||
# 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 urllib
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class ServicesClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(ServicesClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def list_services(self, params=None):
|
||||
url = 'os-services'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
node = etree.fromstring(body)
|
||||
body = [xml_utils.xml_to_json(x) for x in node.getchildren()]
|
||||
return resp, body
|
||||
|
||||
def enable_service(self, host_name, binary):
|
||||
"""
|
||||
Enable service on a host
|
||||
host_name: Name of host
|
||||
binary: Service binary
|
||||
"""
|
||||
post_body = xml_utils.Element("service")
|
||||
post_body.add_attr('binary', binary)
|
||||
post_body.add_attr('host', host_name)
|
||||
|
||||
resp, body = self.put('os-services/enable', str(
|
||||
xml_utils.Document(post_body)))
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def disable_service(self, host_name, binary):
|
||||
"""
|
||||
Disable service on a host
|
||||
host_name: Name of host
|
||||
binary: Service binary
|
||||
"""
|
||||
post_body = xml_utils.Element("service")
|
||||
post_body.add_attr('binary', binary)
|
||||
post_body.add_attr('host', host_name)
|
||||
|
||||
resp, body = self.put('os-services/disable', str(
|
||||
xml_utils.Document(post_body)))
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, body
|
@ -1,54 +0,0 @@
|
||||
# Copyright 2013 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 urllib
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class TenantUsagesClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(TenantUsagesClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
|
||||
def _parse_array(self, node):
|
||||
json = xml_utils.xml_to_json(node)
|
||||
return json
|
||||
|
||||
def list_tenant_usages(self, params=None):
|
||||
url = 'os-simple-tenant-usage'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
tenant_usage = self._parse_array(etree.fromstring(body))
|
||||
return resp, tenant_usage['tenant_usage']
|
||||
|
||||
def get_tenant_usage(self, tenant_id, params=None):
|
||||
url = 'os-simple-tenant-usage/%s' % tenant_id
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
tenant_usage = self._parse_array(etree.fromstring(body))
|
||||
return resp, tenant_usage
|
@ -1,148 +0,0 @@
|
||||
# Copyright 2012 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 time
|
||||
import urllib
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class VolumesExtensionsClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(VolumesExtensionsClientXML, self).__init__(
|
||||
auth_provider)
|
||||
self.service = CONF.compute.catalog_type
|
||||
self.build_interval = CONF.compute.build_interval
|
||||
self.build_timeout = CONF.compute.build_timeout
|
||||
|
||||
def _parse_volume(self, body):
|
||||
vol = dict((attr, body.get(attr)) for attr in body.keys())
|
||||
|
||||
for child in body.getchildren():
|
||||
tag = child.tag
|
||||
if tag.startswith("{"):
|
||||
ns, tag = tag.split("}", 1)
|
||||
if tag == 'metadata':
|
||||
vol['metadata'] = dict((meta.get('key'),
|
||||
meta.text) for meta in list(child))
|
||||
else:
|
||||
vol[tag] = xml_utils.xml_to_json(child)
|
||||
return vol
|
||||
|
||||
def list_volumes(self, params=None):
|
||||
"""List all the volumes created."""
|
||||
url = 'os-volumes'
|
||||
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = etree.fromstring(body)
|
||||
volumes = []
|
||||
if body is not None:
|
||||
volumes += [self._parse_volume(vol) for vol in list(body)]
|
||||
return resp, volumes
|
||||
|
||||
def list_volumes_with_detail(self, params=None):
|
||||
"""List all the details of volumes."""
|
||||
url = 'os-volumes/detail'
|
||||
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = etree.fromstring(body)
|
||||
volumes = []
|
||||
if body is not None:
|
||||
volumes += [self._parse_volume(vol) for vol in list(body)]
|
||||
return resp, volumes
|
||||
|
||||
def get_volume(self, volume_id):
|
||||
"""Returns the details of a single volume."""
|
||||
url = "os-volumes/%s" % str(volume_id)
|
||||
resp, body = self.get(url)
|
||||
body = etree.fromstring(body)
|
||||
return resp, self._parse_volume(body)
|
||||
|
||||
def create_volume(self, size, display_name=None, metadata=None):
|
||||
"""Creates a new Volume.
|
||||
|
||||
:param size: Size of volume in GB. (Required)
|
||||
:param display_name: Optional Volume Name.
|
||||
:param metadata: An optional dictionary of values for metadata.
|
||||
"""
|
||||
volume = xml_utils.Element("volume",
|
||||
xmlns=xml_utils.XMLNS_11,
|
||||
size=size)
|
||||
if display_name:
|
||||
volume.add_attr('display_name', display_name)
|
||||
|
||||
if metadata:
|
||||
_metadata = xml_utils.Element('metadata')
|
||||
volume.append(_metadata)
|
||||
for key, value in metadata.items():
|
||||
meta = xml_utils.Element('meta')
|
||||
meta.add_attr('key', key)
|
||||
meta.append(xml_utils.Text(value))
|
||||
_metadata.append(meta)
|
||||
|
||||
resp, body = self.post('os-volumes', str(xml_utils.Document(volume)))
|
||||
body = xml_utils.xml_to_json(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_volume(self, volume_id):
|
||||
"""Deletes the Specified Volume."""
|
||||
return self.delete("os-volumes/%s" % str(volume_id))
|
||||
|
||||
def wait_for_volume_status(self, volume_id, status):
|
||||
"""Waits for a Volume to reach a given status."""
|
||||
resp, body = self.get_volume(volume_id)
|
||||
volume_name = body['displayName']
|
||||
volume_status = body['status']
|
||||
start = int(time.time())
|
||||
|
||||
while volume_status != status:
|
||||
time.sleep(self.build_interval)
|
||||
resp, body = self.get_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 within '\
|
||||
'the required time (%s s).' % (volume_name, status,
|
||||
self.build_timeout)
|
||||
raise exceptions.TimeoutException(message)
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
self.get_volume(id)
|
||||
except exceptions.NotFound:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def resource_type(self):
|
||||
"""Returns the primary type of resource this client works with."""
|
||||
return 'volume'
|
@ -1,111 +0,0 @@
|
||||
# Copyright 2013 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 lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils as common
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
XMLNS = "http://docs.openstack.org/identity/api/v3"
|
||||
|
||||
|
||||
class CredentialsClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(CredentialsClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.identity.catalog_type
|
||||
self.endpoint_url = 'adminURL'
|
||||
self.api_version = "v3"
|
||||
|
||||
def _parse_body(self, body):
|
||||
data = common.xml_to_json(body)
|
||||
return data
|
||||
|
||||
def _parse_creds(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
tag_list = child.tag.split('}', 1)
|
||||
if tag_list[1] == "credential":
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def create_credential(self, access_key, secret_key, user_id, project_id):
|
||||
"""Creates a credential."""
|
||||
cred_type = 'ec2'
|
||||
access = ""access": "%s"" % access_key
|
||||
secret = ""secret": "%s"" % secret_key
|
||||
blob = common.Element('blob',
|
||||
xmlns=XMLNS)
|
||||
blob.append(common.Text("{%s , %s}"
|
||||
% (access, secret)))
|
||||
credential = common.Element('credential', project_id=project_id,
|
||||
type=cred_type, user_id=user_id)
|
||||
credential.append(blob)
|
||||
resp, body = self.post('credentials', str(common.Document(credential)))
|
||||
self.expected_success(201, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
body['blob'] = json.loads(body['blob'])
|
||||
return resp, body
|
||||
|
||||
def update_credential(self, credential_id, **kwargs):
|
||||
"""Updates a credential."""
|
||||
_, body = self.get_credential(credential_id)
|
||||
cred_type = kwargs.get('type', body['type'])
|
||||
access_key = kwargs.get('access_key', body['blob']['access'])
|
||||
secret_key = kwargs.get('secret_key', body['blob']['secret'])
|
||||
project_id = kwargs.get('project_id', body['project_id'])
|
||||
user_id = kwargs.get('user_id', body['user_id'])
|
||||
access = ""access": "%s"" % access_key
|
||||
secret = ""secret": "%s"" % secret_key
|
||||
blob = common.Element('blob',
|
||||
xmlns=XMLNS)
|
||||
blob.append(common.Text("{%s , %s}"
|
||||
% (access, secret)))
|
||||
credential = common.Element('credential', project_id=project_id,
|
||||
type=cred_type, user_id=user_id)
|
||||
credential.append(blob)
|
||||
resp, body = self.patch('credentials/%s' % credential_id,
|
||||
str(common.Document(credential)))
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
body['blob'] = json.loads(body['blob'])
|
||||
return resp, body
|
||||
|
||||
def get_credential(self, credential_id):
|
||||
"""To GET Details of a credential."""
|
||||
resp, body = self.get('credentials/%s' % credential_id)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
body['blob'] = json.loads(body['blob'])
|
||||
return resp, body
|
||||
|
||||
def list_credentials(self):
|
||||
"""Lists out all the available credentials."""
|
||||
resp, body = self.get('credentials')
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_creds(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_credential(self, credential_id):
|
||||
"""Deletes a credential."""
|
||||
resp, body = self.delete('credentials/%s' % credential_id)
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
@ -1,133 +0,0 @@
|
||||
# Copyright 2013 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 lxml import etree
|
||||
|
||||
from tempest.common import http
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils as common
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
XMLNS = "http://docs.openstack.org/identity/api/v3"
|
||||
|
||||
|
||||
class EndPointClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(EndPointClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.identity.catalog_type
|
||||
self.endpoint_url = 'adminURL'
|
||||
self.api_version = "v3"
|
||||
|
||||
def _parse_array(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
tag_list = child.tag.split('}', 1)
|
||||
if tag_list[1] == "endpoint":
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def _parse_body(self, body):
|
||||
json = common.xml_to_json(body)
|
||||
return json
|
||||
|
||||
def request(self, method, url, extra_headers=False, headers=None,
|
||||
body=None, wait=None):
|
||||
"""Overriding the existing HTTP request in super class RestClient."""
|
||||
if extra_headers:
|
||||
try:
|
||||
headers.update(self.get_headers())
|
||||
except (ValueError, TypeError):
|
||||
headers = self.get_headers()
|
||||
dscv = CONF.identity.disable_ssl_certificate_validation
|
||||
self.http_obj = http.ClosingHttp(
|
||||
disable_ssl_certificate_validation=dscv)
|
||||
return super(EndPointClientXML, self).request(method, url,
|
||||
extra_headers,
|
||||
headers=headers,
|
||||
body=body)
|
||||
|
||||
def list_endpoints(self):
|
||||
"""Get the list of endpoints."""
|
||||
resp, body = self.get("endpoints")
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_array(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def create_endpoint(self, service_id, interface, url, **kwargs):
|
||||
"""Create endpoint.
|
||||
|
||||
Normally this function wouldn't allow setting values that are not
|
||||
allowed for 'enabled'. Use `force_enabled` to set a non-boolean.
|
||||
|
||||
"""
|
||||
region = kwargs.get('region', None)
|
||||
if 'force_enabled' in kwargs:
|
||||
enabled = kwargs['force_enabled']
|
||||
else:
|
||||
enabled = kwargs.get('enabled', None)
|
||||
if enabled is not None:
|
||||
enabled = str(enabled).lower()
|
||||
create_endpoint = common.Element("endpoint",
|
||||
xmlns=XMLNS,
|
||||
service_id=service_id,
|
||||
interface=interface,
|
||||
url=url, region=region,
|
||||
enabled=enabled)
|
||||
resp, body = self.post('endpoints',
|
||||
str(common.Document(create_endpoint)))
|
||||
self.expected_success(201, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def update_endpoint(self, endpoint_id, service_id=None, interface=None,
|
||||
url=None, region=None, enabled=None, **kwargs):
|
||||
"""Updates an endpoint with given parameters.
|
||||
|
||||
Normally this function wouldn't allow setting values that are not
|
||||
allowed for 'enabled'. Use `force_enabled` to set a non-boolean.
|
||||
|
||||
"""
|
||||
doc = common.Document()
|
||||
endpoint = common.Element("endpoint")
|
||||
doc.append(endpoint)
|
||||
|
||||
if service_id:
|
||||
endpoint.add_attr("service_id", service_id)
|
||||
if interface:
|
||||
endpoint.add_attr("interface", interface)
|
||||
if url:
|
||||
endpoint.add_attr("url", url)
|
||||
if region:
|
||||
endpoint.add_attr("region", region)
|
||||
|
||||
if 'force_enabled' in kwargs:
|
||||
endpoint.add_attr("enabled", kwargs['force_enabled'])
|
||||
elif enabled is not None:
|
||||
endpoint.add_attr("enabled", str(enabled).lower())
|
||||
|
||||
resp, body = self.patch('endpoints/%s' % str(endpoint_id), str(doc))
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_endpoint(self, endpoint_id):
|
||||
"""Delete endpoint."""
|
||||
resp_header, resp_body = self.delete('endpoints/%s' % endpoint_id)
|
||||
self.expected_success(204, resp_header.status)
|
||||
return resp_header, resp_body
|
@ -1,632 +0,0 @@
|
||||
# Copyright 2013 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 urllib
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils as common
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
XMLNS = "http://docs.openstack.org/identity/api/v3"
|
||||
|
||||
|
||||
class IdentityV3ClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(IdentityV3ClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.identity.catalog_type
|
||||
self.endpoint_url = 'adminURL'
|
||||
self.api_version = "v3"
|
||||
|
||||
def _parse_projects(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
tag_list = child.tag.split('}', 1)
|
||||
if tag_list[1] == "project":
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def _parse_domains(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
tag_list = child.tag.split('}', 1)
|
||||
if tag_list[1] == "domain":
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def _parse_groups(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
tag_list = child.tag.split('}', 1)
|
||||
if tag_list[1] == "group":
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def _parse_group_users(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
tag_list = child.tag.split('}', 1)
|
||||
if tag_list[1] == "user":
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def _parse_roles(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
tag_list = child.tag.split('}', 1)
|
||||
if tag_list[1] == "role":
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def _parse_users(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
tag_list = child.tag.split('}', 1)
|
||||
if tag_list[1] == "user":
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def _parse_array(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def _parse_body(self, body):
|
||||
_json = common.xml_to_json(body)
|
||||
return _json
|
||||
|
||||
def create_user(self, user_name, password=None, project_id=None,
|
||||
email=None, domain_id='default', **kwargs):
|
||||
"""Creates a user."""
|
||||
en = kwargs.get('enabled', 'true')
|
||||
description = kwargs.get('description', None)
|
||||
post_body = common.Element("user",
|
||||
xmlns=XMLNS,
|
||||
name=user_name,
|
||||
password=password,
|
||||
description=description,
|
||||
email=email,
|
||||
enabled=str(en).lower(),
|
||||
project_id=project_id,
|
||||
domain_id=domain_id)
|
||||
resp, body = self.post('users', str(common.Document(post_body)))
|
||||
self.expected_success(201, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def update_user(self, user_id, name, **kwargs):
|
||||
"""Updates a user."""
|
||||
_, body = self.get_user(user_id)
|
||||
email = kwargs.get('email', body['email'])
|
||||
en = kwargs.get('enabled', body['enabled'])
|
||||
project_id = kwargs.get('project_id', body['project_id'])
|
||||
description = kwargs.get('description', body['description'])
|
||||
domain_id = kwargs.get('domain_id', body['domain_id'])
|
||||
update_user = common.Element("user",
|
||||
xmlns=XMLNS,
|
||||
name=name,
|
||||
email=email,
|
||||
project_id=project_id,
|
||||
domain_id=domain_id,
|
||||
description=description,
|
||||
enabled=str(en).lower())
|
||||
resp, body = self.patch('users/%s' % user_id,
|
||||
str(common.Document(update_user)))
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def update_user_password(self, user_id, password, original_password):
|
||||
"""Updates a user password."""
|
||||
update_user = common.Element("user",
|
||||
xmlns=XMLNS,
|
||||
password=password,
|
||||
original_password=original_password)
|
||||
resp, _ = self.post('users/%s/password' % user_id,
|
||||
str(common.Document(update_user)))
|
||||
self.expected_success(204, resp.status)
|
||||
return resp
|
||||
|
||||
def list_user_projects(self, user_id):
|
||||
"""Lists the projects on which a user has roles assigned."""
|
||||
resp, body = self.get('users/%s/projects' % user_id)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_projects(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def get_users(self, params=None):
|
||||
"""Get the list of users."""
|
||||
url = 'users'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_users(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def get_user(self, user_id):
|
||||
"""GET a user."""
|
||||
resp, body = self.get("users/%s" % user_id)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_user(self, user_id):
|
||||
"""Deletes a User."""
|
||||
resp, body = self.delete("users/%s" % user_id)
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def create_project(self, name, **kwargs):
|
||||
"""Creates a project."""
|
||||
description = kwargs.get('description', None)
|
||||
en = kwargs.get('enabled', 'true')
|
||||
domain_id = kwargs.get('domain_id', 'default')
|
||||
post_body = common.Element("project",
|
||||
xmlns=XMLNS,
|
||||
description=description,
|
||||
domain_id=domain_id,
|
||||
enabled=str(en).lower(),
|
||||
name=name)
|
||||
resp, body = self.post('projects',
|
||||
str(common.Document(post_body)))
|
||||
self.expected_success(201, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def list_projects(self, params=None):
|
||||
"""Get the list of projects."""
|
||||
url = 'projects'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_projects(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def update_project(self, project_id, **kwargs):
|
||||
"""Updates a Project."""
|
||||
resp, body = self.get_project(project_id)
|
||||
name = kwargs.get('name', body['name'])
|
||||
desc = kwargs.get('description', body['description'])
|
||||
en = kwargs.get('enabled', body['enabled'])
|
||||
domain_id = kwargs.get('domain_id', body['domain_id'])
|
||||
post_body = common.Element("project",
|
||||
xmlns=XMLNS,
|
||||
name=name,
|
||||
description=desc,
|
||||
enabled=str(en).lower(),
|
||||
domain_id=domain_id)
|
||||
resp, body = self.patch('projects/%s' % project_id,
|
||||
str(common.Document(post_body)))
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def get_project(self, project_id):
|
||||
"""GET a Project."""
|
||||
resp, body = self.get("projects/%s" % project_id)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_project(self, project_id):
|
||||
"""Delete a project."""
|
||||
resp, body = self.delete('projects/%s' % str(project_id))
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def create_role(self, name):
|
||||
"""Create a Role."""
|
||||
post_body = common.Element("role",
|
||||
xmlns=XMLNS,
|
||||
name=name)
|
||||
resp, body = self.post('roles', str(common.Document(post_body)))
|
||||
self.expected_success(201, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def get_role(self, role_id):
|
||||
"""GET a Role."""
|
||||
resp, body = self.get('roles/%s' % str(role_id))
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def list_roles(self):
|
||||
"""Get the list of Roles."""
|
||||
resp, body = self.get("roles")
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_roles(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def update_role(self, name, role_id):
|
||||
"""Updates a Role."""
|
||||
post_body = common.Element("role",
|
||||
xmlns=XMLNS,
|
||||
name=name)
|
||||
resp, body = self.patch('roles/%s' % str(role_id),
|
||||
str(common.Document(post_body)))
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_role(self, role_id):
|
||||
"""Delete a role."""
|
||||
resp, body = self.delete('roles/%s' % str(role_id))
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def assign_user_role(self, project_id, user_id, role_id):
|
||||
"""Add roles to a user on a tenant."""
|
||||
resp, body = self.put('projects/%s/users/%s/roles/%s' %
|
||||
(project_id, user_id, role_id), '')
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def create_domain(self, name, **kwargs):
|
||||
"""Creates a domain."""
|
||||
description = kwargs.get('description', None)
|
||||
en = kwargs.get('enabled', True)
|
||||
post_body = common.Element("domain",
|
||||
xmlns=XMLNS,
|
||||
name=name,
|
||||
description=description,
|
||||
enabled=str(en).lower())
|
||||
resp, body = self.post('domains', str(common.Document(post_body)))
|
||||
self.expected_success(201, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def list_domains(self):
|
||||
"""Get the list of domains."""
|
||||
resp, body = self.get("domains")
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_domains(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_domain(self, domain_id):
|
||||
"""Delete a domain."""
|
||||
resp, body = self.delete('domains/%s' % domain_id)
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def update_domain(self, domain_id, **kwargs):
|
||||
"""Updates a domain."""
|
||||
_, body = self.get_domain(domain_id)
|
||||
description = kwargs.get('description', body['description'])
|
||||
en = kwargs.get('enabled', body['enabled'])
|
||||
name = kwargs.get('name', body['name'])
|
||||
post_body = common.Element("domain",
|
||||
xmlns=XMLNS,
|
||||
name=name,
|
||||
description=description,
|
||||
enabled=str(en).lower())
|
||||
resp, body = self.patch('domains/%s' % domain_id,
|
||||
str(common.Document(post_body)))
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def get_domain(self, domain_id):
|
||||
"""Get Domain details."""
|
||||
resp, body = self.get('domains/%s' % domain_id)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def get_token(self, resp_token):
|
||||
"""GET a Token Details."""
|
||||
headers = {'Content-Type': 'application/xml',
|
||||
'Accept': 'application/xml',
|
||||
'X-Subject-Token': resp_token}
|
||||
resp, body = self.get("auth/tokens", headers=headers)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_token(self, resp_token):
|
||||
"""Delete a Given Token."""
|
||||
headers = {'X-Subject-Token': resp_token}
|
||||
resp, body = self.delete("auth/tokens", headers=headers)
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def create_group(self, name, **kwargs):
|
||||
"""Creates a group."""
|
||||
description = kwargs.get('description', None)
|
||||
domain_id = kwargs.get('domain_id', 'default')
|
||||
project_id = kwargs.get('project_id', None)
|
||||
post_body = common.Element("group",
|
||||
xmlns=XMLNS,
|
||||
name=name,
|
||||
description=description,
|
||||
domain_id=domain_id,
|
||||
project_id=project_id)
|
||||
resp, body = self.post('groups', str(common.Document(post_body)))
|
||||
self.expected_success(201, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def get_group(self, group_id):
|
||||
"""Get group details."""
|
||||
resp, body = self.get('groups/%s' % group_id)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def update_group(self, group_id, **kwargs):
|
||||
"""Updates a group."""
|
||||
_, body = self.get_group(group_id)
|
||||
name = kwargs.get('name', body['name'])
|
||||
description = kwargs.get('description', body['description'])
|
||||
post_body = common.Element("group",
|
||||
xmlns=XMLNS,
|
||||
name=name,
|
||||
description=description)
|
||||
resp, body = self.patch('groups/%s' % group_id,
|
||||
str(common.Document(post_body)))
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_group(self, group_id):
|
||||
"""Delete a group."""
|
||||
resp, body = self.delete('groups/%s' % group_id)
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def add_group_user(self, group_id, user_id):
|
||||
"""Add user into group."""
|
||||
resp, body = self.put('groups/%s/users/%s' % (group_id, user_id), '')
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def list_group_users(self, group_id):
|
||||
"""List users in group."""
|
||||
resp, body = self.get('groups/%s/users' % group_id)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_group_users(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def list_user_groups(self, user_id):
|
||||
"""Lists the groups which a user belongs to."""
|
||||
resp, body = self.get('users/%s/groups' % user_id)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_groups(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_group_user(self, group_id, user_id):
|
||||
"""Delete user in group."""
|
||||
resp, body = self.delete('groups/%s/users/%s' % (group_id, user_id))
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def assign_user_role_on_project(self, project_id, user_id, role_id):
|
||||
"""Add roles to a user on a project."""
|
||||
resp, body = self.put('projects/%s/users/%s/roles/%s' %
|
||||
(project_id, user_id, role_id), '')
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def assign_user_role_on_domain(self, domain_id, user_id, role_id):
|
||||
"""Add roles to a user on a domain."""
|
||||
resp, body = self.put('domains/%s/users/%s/roles/%s' %
|
||||
(domain_id, user_id, role_id), '')
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def list_user_roles_on_project(self, project_id, user_id):
|
||||
"""list roles of a user on a project."""
|
||||
resp, body = self.get('projects/%s/users/%s/roles' %
|
||||
(project_id, user_id))
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_roles(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def list_user_roles_on_domain(self, domain_id, user_id):
|
||||
"""list roles of a user on a domain."""
|
||||
resp, body = self.get('domains/%s/users/%s/roles' %
|
||||
(domain_id, user_id))
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_roles(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def revoke_role_from_user_on_project(self, project_id, user_id, role_id):
|
||||
"""Delete role of a user on a project."""
|
||||
resp, body = self.delete('projects/%s/users/%s/roles/%s' %
|
||||
(project_id, user_id, role_id))
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def revoke_role_from_user_on_domain(self, domain_id, user_id, role_id):
|
||||
"""Delete role of a user on a domain."""
|
||||
resp, body = self.delete('domains/%s/users/%s/roles/%s' %
|
||||
(domain_id, user_id, role_id))
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def assign_group_role_on_project(self, project_id, group_id, role_id):
|
||||
"""Add roles to a user on a project."""
|
||||
resp, body = self.put('projects/%s/groups/%s/roles/%s' %
|
||||
(project_id, group_id, role_id), '')
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def assign_group_role_on_domain(self, domain_id, group_id, role_id):
|
||||
"""Add roles to a user on a domain."""
|
||||
resp, body = self.put('domains/%s/groups/%s/roles/%s' %
|
||||
(domain_id, group_id, role_id), '')
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def list_group_roles_on_project(self, project_id, group_id):
|
||||
"""list roles of a user on a project."""
|
||||
resp, body = self.get('projects/%s/groups/%s/roles' %
|
||||
(project_id, group_id))
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_roles(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def list_group_roles_on_domain(self, domain_id, group_id):
|
||||
"""list roles of a user on a domain."""
|
||||
resp, body = self.get('domains/%s/groups/%s/roles' %
|
||||
(domain_id, group_id))
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_roles(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def revoke_role_from_group_on_project(self, project_id, group_id, role_id):
|
||||
"""Delete role of a user on a project."""
|
||||
resp, body = self.delete('projects/%s/groups/%s/roles/%s' %
|
||||
(project_id, group_id, role_id))
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def revoke_role_from_group_on_domain(self, domain_id, group_id, role_id):
|
||||
"""Delete role of a user on a domain."""
|
||||
resp, body = self.delete('domains/%s/groups/%s/roles/%s' %
|
||||
(domain_id, group_id, role_id))
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
|
||||
class V3TokenClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self):
|
||||
super(V3TokenClientXML, self).__init__(None)
|
||||
auth_url = CONF.identity.uri_v3
|
||||
if not auth_url:
|
||||
raise exceptions.InvalidConfiguration('you must specify a v3 uri '
|
||||
'if using the v3 identity '
|
||||
'api')
|
||||
if 'auth/tokens' not in auth_url:
|
||||
auth_url = auth_url.rstrip('/') + '/auth/tokens'
|
||||
|
||||
self.auth_url = auth_url
|
||||
|
||||
def auth(self, user=None, password=None, tenant=None, user_type='id',
|
||||
domain=None, token=None):
|
||||
"""
|
||||
:param user: user id or name, as specified in user_type
|
||||
:param domain: the user and tenant domain
|
||||
:param token: a token to re-scope.
|
||||
|
||||
Accepts different combinations of credentials. Restrictions:
|
||||
- tenant and domain are only name (no id)
|
||||
- user domain and tenant domain are assumed identical
|
||||
- domain scope is not supported here
|
||||
Sample sample valid combinations:
|
||||
- token
|
||||
- token, tenant, domain
|
||||
- user_id, password
|
||||
- username, password, domain
|
||||
- username, password, tenant, domain
|
||||
Validation is left to the server side.
|
||||
"""
|
||||
|
||||
methods = common.Element('methods')
|
||||
identity = common.Element('identity')
|
||||
|
||||
if token:
|
||||
method = common.Element('method')
|
||||
method.append(common.Text('token'))
|
||||
methods.append(method)
|
||||
|
||||
token = common.Element('token', id=token)
|
||||
identity.append(token)
|
||||
|
||||
if user and password:
|
||||
if user_type == 'id':
|
||||
_user = common.Element('user', id=user, password=password)
|
||||
else:
|
||||
_user = common.Element('user', name=user, password=password)
|
||||
if domain is not None:
|
||||
_domain = common.Element('domain', name=domain)
|
||||
_user.append(_domain)
|
||||
|
||||
password = common.Element('password')
|
||||
password.append(_user)
|
||||
method = common.Element('method')
|
||||
method.append(common.Text('password'))
|
||||
methods.append(method)
|
||||
identity.append(password)
|
||||
|
||||
identity.append(methods)
|
||||
|
||||
auth = common.Element('auth')
|
||||
auth.append(identity)
|
||||
|
||||
if tenant is not None:
|
||||
project = common.Element('project', name=tenant)
|
||||
_domain = common.Element('domain', name=domain)
|
||||
project.append(_domain)
|
||||
scope = common.Element('scope')
|
||||
scope.append(project)
|
||||
auth.append(scope)
|
||||
|
||||
resp, body = self.post(self.auth_url, body=str(common.Document(auth)))
|
||||
self.expected_success(201, resp.status)
|
||||
return resp, body
|
||||
|
||||
def request(self, method, url, extra_headers=False, headers=None,
|
||||
body=None):
|
||||
"""A simple HTTP request interface."""
|
||||
if headers is None:
|
||||
# Always accept 'json', for xml token client too.
|
||||
# Because XML response is not easily
|
||||
# converted to the corresponding JSON one
|
||||
headers = self.get_headers(accept_type="json")
|
||||
elif extra_headers:
|
||||
try:
|
||||
headers.update(self.get_headers(accept_type="json"))
|
||||
except (ValueError, TypeError):
|
||||
headers = self.get_headers(accept_type="json")
|
||||
resp, resp_body = self.http_obj.request(url, method,
|
||||
headers=headers, body=body)
|
||||
self._log_request(method, url, resp)
|
||||
|
||||
if resp.status in [401, 403]:
|
||||
resp_body = json.loads(resp_body)
|
||||
raise exceptions.Unauthorized(resp_body['error']['message'])
|
||||
elif resp.status not in [200, 201, 204]:
|
||||
raise exceptions.IdentityError(
|
||||
'Unexpected status code {0}'.format(resp.status))
|
||||
|
||||
return resp, json.loads(resp_body)
|
||||
|
||||
def get_token(self, user, password, tenant, domain='Default',
|
||||
auth_data=False):
|
||||
"""
|
||||
:param user: username
|
||||
Returns (token id, token data) for supplied credentials
|
||||
"""
|
||||
resp, body = self.auth(user, password, tenant, user_type='name',
|
||||
domain=domain)
|
||||
|
||||
token = resp.get('x-subject-token')
|
||||
if auth_data:
|
||||
return token, body['token']
|
||||
else:
|
||||
return token
|
@ -1,104 +0,0 @@
|
||||
# Copyright 2013 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 lxml import etree
|
||||
|
||||
from tempest.common import http
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils as common
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
XMLNS = "http://docs.openstack.org/identity/api/v3"
|
||||
|
||||
|
||||
class PolicyClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(PolicyClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.identity.catalog_type
|
||||
self.endpoint_url = 'adminURL'
|
||||
self.api_version = "v3"
|
||||
|
||||
def _parse_array(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
tag_list = child.tag.split('}', 1)
|
||||
if tag_list[1] == "policy":
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def _parse_body(self, body):
|
||||
json = common.xml_to_json(body)
|
||||
return json
|
||||
|
||||
def request(self, method, url, extra_headers=False, headers=None,
|
||||
body=None, wait=None):
|
||||
"""Overriding the existing HTTP request in super class RestClient."""
|
||||
if extra_headers:
|
||||
try:
|
||||
headers.update(self.get_headers())
|
||||
except (ValueError, TypeError):
|
||||
headers = self.get_headers()
|
||||
dscv = CONF.identity.disable_ssl_certificate_validation
|
||||
self.http_obj = http.ClosingHttp(
|
||||
disable_ssl_certificate_validation=dscv)
|
||||
return super(PolicyClientXML, self).request(method, url,
|
||||
extra_headers,
|
||||
headers=headers,
|
||||
body=body)
|
||||
|
||||
def create_policy(self, blob, type):
|
||||
"""Creates a Policy."""
|
||||
create_policy = common.Element("policy", xmlns=XMLNS,
|
||||
blob=blob, type=type)
|
||||
resp, body = self.post('policies', str(common.Document(create_policy)))
|
||||
self.expected_success(201, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def list_policies(self):
|
||||
"""Lists the policies."""
|
||||
resp, body = self.get('policies')
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_array(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def get_policy(self, policy_id):
|
||||
"""Lists out the given policy."""
|
||||
url = 'policies/%s' % policy_id
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def update_policy(self, policy_id, **kwargs):
|
||||
"""Updates a policy."""
|
||||
type = kwargs.get('type')
|
||||
update_policy = common.Element("policy", xmlns=XMLNS, type=type)
|
||||
url = 'policies/%s' % policy_id
|
||||
resp, body = self.patch(url, str(common.Document(update_policy)))
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_policy(self, policy_id):
|
||||
"""Deletes the policy."""
|
||||
url = "policies/%s" % policy_id
|
||||
resp, body = self.delete(url)
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
@ -1,125 +0,0 @@
|
||||
# Copyright 2014 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 urllib
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import http
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils as common
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
XMLNS = "http://docs.openstack.org/identity/api/v3"
|
||||
|
||||
|
||||
class RegionClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(RegionClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.identity.catalog_type
|
||||
self.region_url = 'adminURL'
|
||||
self.api_version = "v3"
|
||||
|
||||
def _parse_array(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
tag_list = child.tag.split('}', 1)
|
||||
if tag_list[1] == "region":
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def _parse_body(self, body):
|
||||
json = common.xml_to_json(body)
|
||||
return json
|
||||
|
||||
def request(self, method, url, extra_headers=False, headers=None,
|
||||
body=None, wait=None):
|
||||
"""Overriding the existing HTTP request in super class RestClient."""
|
||||
if extra_headers:
|
||||
try:
|
||||
headers.update(self.get_headers())
|
||||
except (ValueError, TypeError):
|
||||
headers = self.get_headers()
|
||||
dscv = CONF.identity.disable_ssl_certificate_validation
|
||||
self.http_obj = http.ClosingHttp(
|
||||
disable_ssl_certificate_validation=dscv)
|
||||
return super(RegionClientXML, self).request(method, url,
|
||||
extra_headers,
|
||||
headers=headers,
|
||||
body=body)
|
||||
|
||||
def create_region(self, description, **kwargs):
|
||||
"""Create region."""
|
||||
create_region = common.Element("region",
|
||||
xmlns=XMLNS,
|
||||
description=description)
|
||||
if 'parent_region_id' in kwargs:
|
||||
create_region.append(common.Element(
|
||||
'parent_region_id', kwargs.get('parent_region_id')))
|
||||
if 'unique_region_id' in kwargs:
|
||||
resp, body = self.put(
|
||||
'regions/%s' % kwargs.get('unique_region_id'),
|
||||
str(common.Document(create_region)))
|
||||
else:
|
||||
resp, body = self.post('regions',
|
||||
str(common.Document(create_region)))
|
||||
self.expected_success(201, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def update_region(self, region_id, **kwargs):
|
||||
"""Updates an region with given parameters.
|
||||
"""
|
||||
description = kwargs.get('description', None)
|
||||
update_region = common.Element("region",
|
||||
xmlns=XMLNS,
|
||||
description=description)
|
||||
if 'parent_region_id' in kwargs:
|
||||
update_region.append(common.Element('parent_region_id',
|
||||
kwargs.get('parent_region_id')))
|
||||
|
||||
resp, body = self.patch('regions/%s' % str(region_id),
|
||||
str(common.Document(update_region)))
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def get_region(self, region_id):
|
||||
"""Get Region."""
|
||||
url = 'regions/%s' % region_id
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def list_regions(self, params=None):
|
||||
"""Get the list of regions."""
|
||||
url = 'regions'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_array(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_region(self, region_id):
|
||||
"""Delete region."""
|
||||
resp, body = self.delete('regions/%s' % region_id)
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
@ -1,95 +0,0 @@
|
||||
# Copyright 2013 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 lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils as common
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
XMLNS = "http://docs.openstack.org/identity/api/v3"
|
||||
|
||||
|
||||
class ServiceClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(ServiceClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.identity.catalog_type
|
||||
self.endpoint_url = 'adminURL'
|
||||
self.api_version = "v3"
|
||||
|
||||
def _parse_body(self, body):
|
||||
data = common.xml_to_json(body)
|
||||
return data
|
||||
|
||||
def _parse_array(self, node):
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
tag_list = child.tag.split('}', 1)
|
||||
if tag_list[1] == "service":
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def update_service(self, service_id, **kwargs):
|
||||
"""Updates a service_id."""
|
||||
resp, body = self.get_service(service_id)
|
||||
name = kwargs.get('name', body['name'])
|
||||
description = kwargs.get('description', body['description'])
|
||||
type = kwargs.get('type', body['type'])
|
||||
update_service = common.Element("service",
|
||||
xmlns=XMLNS,
|
||||
id=service_id,
|
||||
name=name,
|
||||
description=description,
|
||||
type=type)
|
||||
resp, body = self.patch('services/%s' % service_id,
|
||||
str(common.Document(update_service)))
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def get_service(self, service_id):
|
||||
"""Get Service."""
|
||||
url = 'services/%s' % service_id
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def create_service(self, serv_type, name=None, description=None):
|
||||
post_body = common.Element("service",
|
||||
xmlns=XMLNS,
|
||||
name=name,
|
||||
description=description,
|
||||
type=serv_type)
|
||||
resp, body = self.post("services", str(common.Document(post_body)))
|
||||
self.expected_success(201, resp.status)
|
||||
body = self._parse_body(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def delete_service(self, serv_id):
|
||||
url = "services/" + serv_id
|
||||
resp, body = self.delete(url)
|
||||
self.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def list_services(self):
|
||||
resp, body = self.get('services')
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_array(etree.fromstring(body))
|
||||
return resp, body
|
@ -1,172 +0,0 @@
|
||||
# Copyright 2012 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.
|
||||
from tempest.common import xml_utils as xml
|
||||
from tempest import config
|
||||
from tempest.services.identity.json import identity_client
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
XMLNS = "http://docs.openstack.org/identity/api/v2.0"
|
||||
|
||||
|
||||
class IdentityClientXML(identity_client.IdentityClientJSON):
|
||||
TYPE = "xml"
|
||||
|
||||
def create_role(self, name):
|
||||
"""Create a role."""
|
||||
create_role = xml.Element("role", xmlns=XMLNS, name=name)
|
||||
resp, body = self.post('OS-KSADM/roles',
|
||||
str(xml.Document(create_role)))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def get_role(self, role_id):
|
||||
"""Get a role by its id."""
|
||||
resp, body = self.get('OS-KSADM/roles/%s' % role_id)
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def create_tenant(self, name, **kwargs):
|
||||
"""
|
||||
Create a tenant
|
||||
name (required): New tenant name
|
||||
description: Description of new tenant (default is none)
|
||||
enabled <true|false>: Initial tenant status (default is true)
|
||||
"""
|
||||
en = kwargs.get('enabled', 'true')
|
||||
create_tenant = xml.Element("tenant",
|
||||
xmlns=XMLNS,
|
||||
name=name,
|
||||
description=kwargs.get('description', ''),
|
||||
enabled=str(en).lower())
|
||||
resp, body = self.post('tenants', str(xml.Document(create_tenant)))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def list_tenants(self):
|
||||
"""Returns tenants."""
|
||||
resp, body = self.get('tenants')
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def update_tenant(self, tenant_id, **kwargs):
|
||||
"""Updates a tenant."""
|
||||
_, body = self.get_tenant(tenant_id)
|
||||
name = kwargs.get('name', body['name'])
|
||||
desc = kwargs.get('description', body['description'])
|
||||
en = kwargs.get('enabled', body['enabled'])
|
||||
update_tenant = xml.Element("tenant",
|
||||
xmlns=XMLNS,
|
||||
id=tenant_id,
|
||||
name=name,
|
||||
description=desc,
|
||||
enabled=str(en).lower())
|
||||
|
||||
resp, body = self.post('tenants/%s' % tenant_id,
|
||||
str(xml.Document(update_tenant)))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def create_user(self, name, password, tenant_id, email, **kwargs):
|
||||
"""Create a user."""
|
||||
create_user = xml.Element("user",
|
||||
xmlns=XMLNS,
|
||||
name=name,
|
||||
password=password,
|
||||
email=email)
|
||||
if tenant_id:
|
||||
create_user.add_attr('tenantId', tenant_id)
|
||||
if 'enabled' in kwargs:
|
||||
create_user.add_attr('enabled', str(kwargs['enabled']).lower())
|
||||
|
||||
resp, body = self.post('users', str(xml.Document(create_user)))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def update_user(self, user_id, **kwargs):
|
||||
"""Updates a user."""
|
||||
if 'enabled' in kwargs:
|
||||
kwargs['enabled'] = str(kwargs['enabled']).lower()
|
||||
update_user = xml.Element("user", xmlns=XMLNS, **kwargs)
|
||||
|
||||
resp, body = self.put('users/%s' % user_id,
|
||||
str(xml.Document(update_user)))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def enable_disable_user(self, user_id, enabled):
|
||||
"""Enables or disables a user."""
|
||||
enable_user = xml.Element("user", enabled=str(enabled).lower())
|
||||
resp, body = self.put('users/%s/enabled' % user_id,
|
||||
str(xml.Document(enable_user)))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def create_service(self, name, service_type, **kwargs):
|
||||
"""Create a service."""
|
||||
OS_KSADM = "http://docs.openstack.org/identity/api/ext/OS-KSADM/v1.0"
|
||||
create_service = xml.Element("service",
|
||||
xmlns=OS_KSADM,
|
||||
name=name,
|
||||
type=service_type,
|
||||
description=kwargs.get('description'))
|
||||
resp, body = self.post('OS-KSADM/services',
|
||||
str(xml.Document(create_service)))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def update_user_password(self, user_id, new_pass):
|
||||
"""Update User Password."""
|
||||
put_body = xml.Element("user",
|
||||
id=user_id,
|
||||
password=new_pass)
|
||||
resp, body = self.put('users/%s/OS-KSADM/password' % user_id,
|
||||
str(xml.Document(put_body)))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
def list_extensions(self):
|
||||
"""List all the extensions."""
|
||||
resp, body = self.get('/extensions')
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, self._parse_resp(body)
|
||||
|
||||
|
||||
class TokenClientXML(identity_client.TokenClientJSON):
|
||||
TYPE = "xml"
|
||||
|
||||
def auth(self, user, password, tenant=None):
|
||||
passwordCreds = xml.Element('passwordCredentials',
|
||||
username=user,
|
||||
password=password)
|
||||
auth_kwargs = {}
|
||||
if tenant:
|
||||
auth_kwargs['tenantName'] = tenant
|
||||
auth = xml.Element('auth', **auth_kwargs)
|
||||
auth.append(passwordCreds)
|
||||
resp, body = self.post(self.auth_url, body=str(xml.Document(auth)))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body['access']
|
||||
|
||||
def auth_token(self, token_id, tenant=None):
|
||||
tokenCreds = xml.Element('token', id=token_id)
|
||||
auth_kwargs = {}
|
||||
if tenant:
|
||||
auth_kwargs['tenantName'] = tenant
|
||||
auth = xml.Element('auth', **auth_kwargs)
|
||||
auth.append(tokenCreds)
|
||||
resp, body = self.post(self.auth_url, body=str(xml.Document(auth)))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body['access']
|
@ -1,320 +0,0 @@
|
||||
# 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 xml.etree.ElementTree as ET
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils as common
|
||||
from tempest.services.network import network_client_base as client_base
|
||||
|
||||
|
||||
class NetworkClientXML(client_base.NetworkClientBase):
|
||||
TYPE = "xml"
|
||||
|
||||
# list of plurals used for xml serialization
|
||||
PLURALS = ['dns_nameservers', 'host_routes', 'allocation_pools',
|
||||
'fixed_ips', 'extensions', 'extra_dhcp_opts', 'pools',
|
||||
'health_monitors', 'vips', 'members', 'allowed_address_pairs',
|
||||
'firewall_rules', 'security_groups']
|
||||
|
||||
def get_rest_client(self, auth_provider):
|
||||
rc = rest_client.RestClient(auth_provider)
|
||||
rc.TYPE = self.TYPE
|
||||
return rc
|
||||
|
||||
def deserialize_list(self, body):
|
||||
return common.parse_array(etree.fromstring(body), self.PLURALS)
|
||||
|
||||
def deserialize_single(self, body):
|
||||
return _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
|
||||
def serialize(self, body):
|
||||
# TODO(enikanorov): implement better json to xml conversion
|
||||
# expecting the dict with single key
|
||||
root = body.keys()[0]
|
||||
post_body = common.Element(root)
|
||||
post_body.add_attr('xmlns:xsi',
|
||||
'http://www.w3.org/2001/XMLSchema-instance')
|
||||
elements = set()
|
||||
for name, attr in body[root].items():
|
||||
elt = self._get_element(name, attr)
|
||||
post_body.append(elt)
|
||||
if ":" in name:
|
||||
elements.add(name.split(":")[0])
|
||||
if elements:
|
||||
self._add_namespaces(post_body, elements)
|
||||
return str(common.Document(post_body))
|
||||
|
||||
def serialize_list(self, body, root_name=None, item_name=None):
|
||||
# expecting dict in form
|
||||
# body = {'resources': [res_dict1, res_dict2, ...]
|
||||
post_body = common.Element(root_name)
|
||||
post_body.add_attr('xmlns:xsi',
|
||||
'http://www.w3.org/2001/XMLSchema-instance')
|
||||
for item in body[body.keys()[0]]:
|
||||
elt = common.Element(item_name)
|
||||
for name, attr in item.items():
|
||||
elt_content = self._get_element(name, attr)
|
||||
elt.append(elt_content)
|
||||
post_body.append(elt)
|
||||
return str(common.Document(post_body))
|
||||
|
||||
def _get_element(self, name, value):
|
||||
if value is None:
|
||||
xml_elem = common.Element(name)
|
||||
xml_elem.add_attr("xsi:nil", "true")
|
||||
return xml_elem
|
||||
elif isinstance(value, dict):
|
||||
dict_element = common.Element(name)
|
||||
for key, value in value.iteritems():
|
||||
elem = self._get_element(key, value)
|
||||
dict_element.append(elem)
|
||||
return dict_element
|
||||
elif isinstance(value, list):
|
||||
list_element = common.Element(name)
|
||||
for element in value:
|
||||
elem = self._get_element(name[:-1], element)
|
||||
list_element.append(elem)
|
||||
return list_element
|
||||
else:
|
||||
return common.Element(name, value)
|
||||
|
||||
def _add_namespaces(self, root, elements):
|
||||
for element in elements:
|
||||
root.add_attr('xmlns:%s' % element,
|
||||
common.NEUTRON_NAMESPACES[element])
|
||||
|
||||
def associate_health_monitor_with_pool(self, health_monitor_id,
|
||||
pool_id):
|
||||
uri = '%s/lb/pools/%s/health_monitors' % (self.uri_prefix,
|
||||
pool_id)
|
||||
post_body = common.Element("health_monitor")
|
||||
p1 = common.Element("id", health_monitor_id,)
|
||||
post_body.append(p1)
|
||||
resp, body = self.post(uri, str(common.Document(post_body)))
|
||||
body = _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
self.rest_client.expected_success(201, resp.status)
|
||||
return resp, body
|
||||
|
||||
def disassociate_health_monitor_with_pool(self, health_monitor_id,
|
||||
pool_id):
|
||||
uri = '%s/lb/pools/%s/health_monitors/%s' % (self.uri_prefix, pool_id,
|
||||
health_monitor_id)
|
||||
resp, body = self.delete(uri)
|
||||
self.rest_client.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def show_extension_details(self, ext_alias):
|
||||
uri = '%s/extensions/%s' % (self.uri_prefix, str(ext_alias))
|
||||
resp, body = self.get(uri)
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
body = _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
return resp, body
|
||||
|
||||
def create_router(self, name, **kwargs):
|
||||
uri = '%s/routers' % (self.uri_prefix)
|
||||
router = common.Element("router")
|
||||
router.append(common.Element("name", name))
|
||||
common.deep_dict_to_xml(router, kwargs)
|
||||
resp, body = self.post(uri, str(common.Document(router)))
|
||||
self.rest_client.expected_success(201, resp.status)
|
||||
body = _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
return resp, body
|
||||
|
||||
def update_router(self, router_id, **kwargs):
|
||||
uri = '%s/routers/%s' % (self.uri_prefix, router_id)
|
||||
router = common.Element("router")
|
||||
for element, content in kwargs.iteritems():
|
||||
router.append(common.Element(element, content))
|
||||
resp, body = self.put(uri, str(common.Document(router)))
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
body = _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
return resp, body
|
||||
|
||||
def add_router_interface_with_subnet_id(self, router_id, subnet_id):
|
||||
uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix,
|
||||
router_id)
|
||||
subnet = common.Element("subnet_id", subnet_id)
|
||||
resp, body = self.put(uri, str(common.Document(subnet)))
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
body = _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
return resp, body
|
||||
|
||||
def add_router_interface_with_port_id(self, router_id, port_id):
|
||||
uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix,
|
||||
router_id)
|
||||
port = common.Element("port_id", port_id)
|
||||
resp, body = self.put(uri, str(common.Document(port)))
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
body = _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
return resp, body
|
||||
|
||||
def remove_router_interface_with_subnet_id(self, router_id, subnet_id):
|
||||
uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix,
|
||||
router_id)
|
||||
subnet = common.Element("subnet_id", subnet_id)
|
||||
resp, body = self.put(uri, str(common.Document(subnet)))
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
body = _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
return resp, body
|
||||
|
||||
def remove_router_interface_with_port_id(self, router_id, port_id):
|
||||
uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix,
|
||||
router_id)
|
||||
port = common.Element("port_id", port_id)
|
||||
resp, body = self.put(uri, str(common.Document(port)))
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
body = _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
return resp, body
|
||||
|
||||
def list_router_interfaces(self, uuid):
|
||||
uri = '%s/ports?device_id=%s' % (self.uri_prefix, uuid)
|
||||
resp, body = self.get(uri)
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
ports = common.parse_array(etree.fromstring(body), self.PLURALS)
|
||||
ports = {"ports": ports}
|
||||
return resp, ports
|
||||
|
||||
def update_agent(self, agent_id, agent_info):
|
||||
uri = '%s/agents/%s' % (self.uri_prefix, agent_id)
|
||||
agent = common.Element('agent')
|
||||
for (key, value) in agent_info.items():
|
||||
p = common.Element(key, value)
|
||||
agent.append(p)
|
||||
resp, body = self.put(uri, str(common.Document(agent)))
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
body = _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
return resp, body
|
||||
|
||||
def list_pools_hosted_by_one_lbaas_agent(self, agent_id):
|
||||
uri = '%s/agents/%s/loadbalancer-pools' % (self.uri_prefix, agent_id)
|
||||
resp, body = self.get(uri)
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
pools = common.parse_array(etree.fromstring(body))
|
||||
body = {'pools': pools}
|
||||
return resp, body
|
||||
|
||||
def show_lbaas_agent_hosting_pool(self, pool_id):
|
||||
uri = ('%s/lb/pools/%s/loadbalancer-agent' %
|
||||
(self.uri_prefix, pool_id))
|
||||
resp, body = self.get(uri)
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
body = _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
return resp, body
|
||||
|
||||
def list_routers_on_l3_agent(self, agent_id):
|
||||
uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id)
|
||||
resp, body = self.get(uri)
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
routers = common.parse_array(etree.fromstring(body))
|
||||
body = {'routers': routers}
|
||||
return resp, body
|
||||
|
||||
def list_l3_agents_hosting_router(self, router_id):
|
||||
uri = '%s/routers/%s/l3-agents' % (self.uri_prefix, router_id)
|
||||
resp, body = self.get(uri)
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
agents = common.parse_array(etree.fromstring(body))
|
||||
body = {'agents': agents}
|
||||
return resp, body
|
||||
|
||||
def add_router_to_l3_agent(self, agent_id, router_id):
|
||||
uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id)
|
||||
router = (common.Element("router_id", router_id))
|
||||
resp, body = self.post(uri, str(common.Document(router)))
|
||||
self.rest_client.expected_success(201, resp.status)
|
||||
body = _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
return resp, body
|
||||
|
||||
def remove_router_from_l3_agent(self, agent_id, router_id):
|
||||
uri = '%s/agents/%s/l3-routers/%s' % (
|
||||
self.uri_prefix, agent_id, router_id)
|
||||
resp, body = self.delete(uri)
|
||||
self.rest_client.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def list_dhcp_agent_hosting_network(self, network_id):
|
||||
uri = '%s/networks/%s/dhcp-agents' % (self.uri_prefix, network_id)
|
||||
resp, body = self.get(uri)
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
agents = common.parse_array(etree.fromstring(body))
|
||||
body = {'agents': agents}
|
||||
return resp, body
|
||||
|
||||
def list_networks_hosted_by_one_dhcp_agent(self, agent_id):
|
||||
uri = '%s/agents/%s/dhcp-networks' % (self.uri_prefix, agent_id)
|
||||
resp, body = self.get(uri)
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
networks = common.parse_array(etree.fromstring(body))
|
||||
body = {'networks': networks}
|
||||
return resp, body
|
||||
|
||||
def remove_network_from_dhcp_agent(self, agent_id, network_id):
|
||||
uri = '%s/agents/%s/dhcp-networks/%s' % (self.uri_prefix, agent_id,
|
||||
network_id)
|
||||
resp, body = self.delete(uri)
|
||||
self.rest_client.expected_success(204, resp.status)
|
||||
return resp, body
|
||||
|
||||
def list_lb_pool_stats(self, pool_id):
|
||||
uri = '%s/lb/pools/%s/stats' % (self.uri_prefix, pool_id)
|
||||
resp, body = self.get(uri)
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
body = _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
return resp, body
|
||||
|
||||
def add_dhcp_agent_to_network(self, agent_id, network_id):
|
||||
uri = '%s/agents/%s/dhcp-networks' % (self.uri_prefix, agent_id)
|
||||
network = common.Element("network_id", network_id)
|
||||
resp, body = self.post(uri, str(common.Document(network)))
|
||||
self.rest_client.expected_success(201, resp.status)
|
||||
body = _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
return resp, body
|
||||
|
||||
def insert_firewall_rule_in_policy(self, firewall_policy_id,
|
||||
firewall_rule_id, insert_after="",
|
||||
insert_before=""):
|
||||
uri = '%s/fw/firewall_policies/%s/insert_rule' % (self.uri_prefix,
|
||||
firewall_policy_id)
|
||||
rule = common.Element("firewall_rule_id", firewall_rule_id)
|
||||
resp, body = self.put(uri, str(common.Document(rule)))
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
body = _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
return resp, body
|
||||
|
||||
def remove_firewall_rule_from_policy(self, firewall_policy_id,
|
||||
firewall_rule_id):
|
||||
uri = '%s/fw/firewall_policies/%s/remove_rule' % (self.uri_prefix,
|
||||
firewall_policy_id)
|
||||
rule = common.Element("firewall_rule_id", firewall_rule_id)
|
||||
resp, body = self.put(uri, str(common.Document(rule)))
|
||||
self.rest_client.expected_success(200, resp.status)
|
||||
body = _root_tag_fetcher_and_xml_to_json_parse(body)
|
||||
return resp, body
|
||||
|
||||
|
||||
def _root_tag_fetcher_and_xml_to_json_parse(xml_returned_body):
|
||||
body = ET.fromstring(xml_returned_body)
|
||||
root_tag = body.tag
|
||||
if root_tag.startswith("{"):
|
||||
ns, root_tag = root_tag.split("}", 1)
|
||||
body = common.xml_to_json(etree.fromstring(xml_returned_body),
|
||||
NetworkClientXML.PLURALS)
|
||||
nil = '{http://www.w3.org/2001/XMLSchema-instance}nil'
|
||||
for key, val in body.iteritems():
|
||||
if isinstance(val, dict):
|
||||
if (nil in val and val[nil] == 'true'):
|
||||
body[key] = None
|
||||
body = {root_tag: body}
|
||||
return body
|
@ -1,41 +0,0 @@
|
||||
# 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 lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils as common
|
||||
import tempest.services.telemetry.telemetry_client_base as client
|
||||
|
||||
|
||||
class TelemetryClientXML(client.TelemetryClientBase):
|
||||
TYPE = "xml"
|
||||
|
||||
def get_rest_client(self, auth_provider):
|
||||
rc = rest_client.RestClient(auth_provider)
|
||||
rc.TYPE = self.TYPE
|
||||
return rc
|
||||
|
||||
def _parse_array(self, body):
|
||||
array = []
|
||||
for child in body.getchildren():
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def serialize(self, body):
|
||||
return str(common.Document(body))
|
||||
|
||||
def deserialize(self, body):
|
||||
return self._parse_array(etree.fromstring(body))
|
@ -1,26 +0,0 @@
|
||||
# Copyright 2014 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.
|
||||
|
||||
from tempest.services.volume.xml import availability_zone_client
|
||||
|
||||
|
||||
class VolumeV2AvailabilityZoneClientXML(
|
||||
availability_zone_client.BaseVolumeAvailabilityZoneClientXML):
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(VolumeV2AvailabilityZoneClientXML, self).__init__(
|
||||
auth_provider)
|
||||
|
||||
self.api_version = "v2"
|
@ -1,24 +0,0 @@
|
||||
# Copyright 2014 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.
|
||||
|
||||
from tempest.services.volume.xml import extensions_client
|
||||
|
||||
|
||||
class ExtensionsV2ClientXML(extensions_client.BaseExtensionsClientXML):
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(ExtensionsV2ClientXML, self).__init__(auth_provider)
|
||||
|
||||
self.api_version = "v2"
|
@ -1,23 +0,0 @@
|
||||
# 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 tempest.services.volume.xml import snapshots_client
|
||||
|
||||
|
||||
class SnapshotsV2ClientXML(snapshots_client.BaseSnapshotsClientXML):
|
||||
"""Client class to send CRUD Volume V2 API requests."""
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(SnapshotsV2ClientXML, self).__init__(auth_provider)
|
||||
|
||||
self.api_version = "v2"
|
||||
self.create_resp = 202
|
@ -1,75 +0,0 @@
|
||||
# Copyright 2012 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 urllib
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import xml_utils as common
|
||||
from tempest.services.volume.xml import volumes_client
|
||||
|
||||
|
||||
class VolumesV2ClientXML(volumes_client.BaseVolumesClientXML):
|
||||
"""
|
||||
Client class to send CRUD Volume API V2 requests to a Cinder endpoint
|
||||
"""
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(VolumesV2ClientXML, self).__init__(auth_provider)
|
||||
|
||||
self.api_version = "v2"
|
||||
self.create_resp = 202
|
||||
|
||||
def _parse_volume(self, body):
|
||||
vol = dict((attr, body.get(attr)) for attr in body.keys())
|
||||
|
||||
for child in body.getchildren():
|
||||
tag = child.tag
|
||||
if tag.startswith("{"):
|
||||
ns, tag = tag.split("}", 1)
|
||||
if tag == 'metadata':
|
||||
vol['metadata'] = dict((meta.get('key'),
|
||||
meta.text) for meta in
|
||||
child.getchildren())
|
||||
else:
|
||||
vol[tag] = common.xml_to_json(child)
|
||||
self._translate_attributes_to_json(vol)
|
||||
return vol
|
||||
|
||||
def list_volumes_with_detail(self, params=None):
|
||||
"""List all the details of volumes."""
|
||||
url = 'volumes/detail'
|
||||
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = etree.fromstring(body)
|
||||
volumes = []
|
||||
if body is not None:
|
||||
volumes += [self._parse_volume(vol) for vol in list(body)]
|
||||
for v in volumes:
|
||||
v = self._check_if_bootable(v)
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, volumes
|
||||
|
||||
def get_volume(self, volume_id):
|
||||
"""Returns the details of a single volume."""
|
||||
url = "volumes/%s" % str(volume_id)
|
||||
resp, body = self.get(url)
|
||||
body = self._parse_volume(etree.fromstring(body))
|
||||
body = self._check_if_bootable(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
@ -1,80 +0,0 @@
|
||||
# Copyright 2013 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 lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils as common
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class BaseVolumeHostsClientXML(rest_client.RestClient):
|
||||
"""
|
||||
Client class to send CRUD Volume Hosts API requests to a Cinder endpoint
|
||||
"""
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(BaseVolumeHostsClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.volume.catalog_type
|
||||
self.build_interval = CONF.compute.build_interval
|
||||
self.build_timeout = CONF.compute.build_timeout
|
||||
|
||||
def _parse_array(self, node):
|
||||
"""
|
||||
This method is to parse the "list" response body
|
||||
Eg:
|
||||
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<hosts>
|
||||
<host service-status="available" service="cinder-scheduler"/>
|
||||
<host service-status="available" service="cinder-volume"/>
|
||||
</hosts>
|
||||
|
||||
This method will append the details of specified tag,
|
||||
here it is "host"
|
||||
Return value would be list of hosts as below
|
||||
|
||||
[{'service-status': 'available', 'service': 'cinder-scheduler'},
|
||||
{'service-status': 'available', 'service': 'cinder-volume'}]
|
||||
"""
|
||||
array = []
|
||||
for child in node.getchildren():
|
||||
tag_list = child.tag.split('}', 1)
|
||||
if tag_list[0] == "host":
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def list_hosts(self, params=None):
|
||||
"""List all the hosts."""
|
||||
url = 'os-hosts'
|
||||
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self._parse_array(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
|
||||
class VolumeHostsClientXML(BaseVolumeHostsClientXML):
|
||||
"""
|
||||
Client class to send CRUD Volume Host API V1 requests to a Cinder endpoint
|
||||
"""
|
@ -1,78 +0,0 @@
|
||||
# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
|
||||
#
|
||||
# Author: Sylvain Baubeau <sylvain.baubeau@enovance.com>
|
||||
#
|
||||
# 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 ast
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import xml_utils as xml
|
||||
from tempest import config
|
||||
from tempest.services.volume.json.admin import volume_quotas_client
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class VolumeQuotasClientXML(volume_quotas_client.VolumeQuotasClientJSON):
|
||||
"""
|
||||
Client class to send CRUD Volume Quotas API requests to a Cinder endpoint
|
||||
"""
|
||||
|
||||
TYPE = "xml"
|
||||
|
||||
def _format_quota(self, q):
|
||||
quota = {}
|
||||
for k, v in q.items():
|
||||
try:
|
||||
v = ast.literal_eval(v)
|
||||
except (ValueError, SyntaxError):
|
||||
pass
|
||||
|
||||
quota[k] = v
|
||||
|
||||
return quota
|
||||
|
||||
def get_quota_usage(self, tenant_id):
|
||||
"""List the quota set for a tenant."""
|
||||
|
||||
resp, body = self.get_quota_set(tenant_id, params={'usage': True})
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, self._format_quota(body)
|
||||
|
||||
def update_quota_set(self, tenant_id, gigabytes=None, volumes=None,
|
||||
snapshots=None):
|
||||
post_body = {}
|
||||
element = xml.Element("quota_set")
|
||||
|
||||
if gigabytes is not None:
|
||||
post_body['gigabytes'] = gigabytes
|
||||
|
||||
if volumes is not None:
|
||||
post_body['volumes'] = volumes
|
||||
|
||||
if snapshots is not None:
|
||||
post_body['snapshots'] = snapshots
|
||||
|
||||
xml.deep_dict_to_xml(element, post_body)
|
||||
resp, body = self.put('os-quota-sets/%s' % tenant_id,
|
||||
str(xml.Document(element)))
|
||||
body = xml.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, self._format_quota(body)
|
||||
|
||||
def delete_quota_set(self, tenant_id):
|
||||
"""Delete the tenant's quota set."""
|
||||
resp, body = self.delete('os-quota-sets/%s' % tenant_id)
|
||||
self.expected_success(200, resp.status)
|
@ -1,43 +0,0 @@
|
||||
# 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 urllib
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class VolumesServicesClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(VolumesServicesClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.volume.catalog_type
|
||||
|
||||
def list_services(self, params=None):
|
||||
url = 'os-services'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
node = etree.fromstring(body)
|
||||
body = [xml_utils.xml_to_json(x) for x in node.getchildren()]
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
@ -1,218 +0,0 @@
|
||||
# Copyright 2012 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 urllib
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils as common
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class BaseVolumeTypesClientXML(rest_client.RestClient):
|
||||
"""
|
||||
Client class to send CRUD Volume Types API requests to a Cinder endpoint
|
||||
"""
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(BaseVolumeTypesClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.volume.catalog_type
|
||||
self.build_interval = CONF.compute.build_interval
|
||||
self.build_timeout = CONF.compute.build_timeout
|
||||
|
||||
def _parse_volume_type(self, body):
|
||||
vol_type = dict((attr, body.get(attr)) for attr in body.keys())
|
||||
|
||||
for child in body.getchildren():
|
||||
tag = child.tag
|
||||
if tag.startswith("{"):
|
||||
ns, tag = tag.split("}", 1)
|
||||
if tag == 'extra_specs':
|
||||
vol_type['extra_specs'] = dict((meta.get('key'),
|
||||
meta.text)
|
||||
for meta in list(child))
|
||||
else:
|
||||
vol_type[tag] = common.xml_to_json(child)
|
||||
return vol_type
|
||||
|
||||
def _parse_volume_type_extra_specs(self, body):
|
||||
extra_spec = dict((attr, body.get(attr)) for attr in body.keys())
|
||||
|
||||
for child in body.getchildren():
|
||||
tag = child.tag
|
||||
if tag.startswith("{"):
|
||||
ns, tag = tag.split("}", 1)
|
||||
else:
|
||||
extra_spec[tag] = common.xml_to_json(child)
|
||||
return extra_spec
|
||||
|
||||
def list_volume_types(self, params=None):
|
||||
"""List all the volume_types created."""
|
||||
url = 'types'
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = etree.fromstring(body)
|
||||
volume_types = []
|
||||
if body is not None:
|
||||
volume_types += [self._parse_volume_type(vol)
|
||||
for vol in list(body)]
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, volume_types
|
||||
|
||||
def get_volume_type(self, type_id):
|
||||
"""Returns the details of a single volume_type."""
|
||||
url = "types/%s" % str(type_id)
|
||||
resp, body = self.get(url)
|
||||
body = etree.fromstring(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, self._parse_volume_type(body)
|
||||
|
||||
def create_volume_type(self, name, **kwargs):
|
||||
"""
|
||||
Creates a new Volume_type.
|
||||
name(Required): Name of volume_type.
|
||||
Following optional keyword arguments are accepted:
|
||||
extra_specs: A dictionary of values to be used as extra_specs.
|
||||
"""
|
||||
vol_type = common.Element("volume_type", xmlns=common.XMLNS_11)
|
||||
if name:
|
||||
vol_type.add_attr('name', name)
|
||||
|
||||
extra_specs = kwargs.get('extra_specs')
|
||||
if extra_specs:
|
||||
_extra_specs = common.Element('extra_specs')
|
||||
vol_type.append(_extra_specs)
|
||||
for key, value in extra_specs.items():
|
||||
spec = common.Element('extra_spec')
|
||||
spec.add_attr('key', key)
|
||||
spec.append(common.Text(value))
|
||||
_extra_specs.append(spec)
|
||||
|
||||
resp, body = self.post('types', str(common.Document(vol_type)))
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
||||
|
||||
def delete_volume_type(self, type_id):
|
||||
"""Deletes the Specified Volume_type."""
|
||||
resp, body = self.delete("types/%s" % str(type_id))
|
||||
self.expected_success(202, resp.status)
|
||||
|
||||
def list_volume_types_extra_specs(self, vol_type_id, params=None):
|
||||
"""List all the volume_types extra specs created."""
|
||||
url = 'types/%s/extra_specs' % str(vol_type_id)
|
||||
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = etree.fromstring(body)
|
||||
extra_specs = []
|
||||
if body is not None:
|
||||
extra_specs += [self._parse_volume_type_extra_specs(spec)
|
||||
for spec in list(body)]
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, extra_specs
|
||||
|
||||
def get_volume_type_extra_specs(self, vol_type_id, extra_spec_name):
|
||||
"""Returns the details of a single volume_type extra spec."""
|
||||
url = "types/%s/extra_specs/%s" % (str(vol_type_id),
|
||||
str(extra_spec_name))
|
||||
resp, body = self.get(url)
|
||||
body = etree.fromstring(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, self._parse_volume_type_extra_specs(body)
|
||||
|
||||
def create_volume_type_extra_specs(self, vol_type_id, extra_spec):
|
||||
"""
|
||||
Creates a new Volume_type extra spec.
|
||||
vol_type_id: Id of volume_type.
|
||||
extra_specs: A dictionary of values to be used as extra_specs.
|
||||
"""
|
||||
url = "types/%s/extra_specs" % str(vol_type_id)
|
||||
extra_specs = common.Element("extra_specs", xmlns=common.XMLNS_11)
|
||||
if extra_spec:
|
||||
if isinstance(extra_spec, list):
|
||||
extra_specs.append(extra_spec)
|
||||
else:
|
||||
for key, value in extra_spec.items():
|
||||
spec = common.Element('extra_spec')
|
||||
spec.add_attr('key', key)
|
||||
spec.append(common.Text(value))
|
||||
extra_specs.append(spec)
|
||||
else:
|
||||
extra_specs = None
|
||||
|
||||
resp, body = self.post(url, str(common.Document(extra_specs)))
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
||||
|
||||
def delete_volume_type_extra_specs(self, vol_id, extra_spec_name):
|
||||
"""Deletes the Specified Volume_type extra spec."""
|
||||
resp, body = self.delete("types/%s/extra_specs/%s" % (
|
||||
(str(vol_id)), str(extra_spec_name)))
|
||||
self.expected_success(202, resp.status)
|
||||
return resp, body
|
||||
|
||||
def update_volume_type_extra_specs(self, vol_type_id, extra_spec_name,
|
||||
extra_spec):
|
||||
"""
|
||||
Update a volume_type extra spec.
|
||||
vol_type_id: Id of volume_type.
|
||||
extra_spec_name: Name of the extra spec to be updated.
|
||||
extra_spec: A dictionary of with key as extra_spec_name and the
|
||||
updated value.
|
||||
"""
|
||||
url = "types/%s/extra_specs/%s" % (str(vol_type_id),
|
||||
str(extra_spec_name))
|
||||
extra_specs = common.Element("extra_specs", xmlns=common.XMLNS_11)
|
||||
|
||||
if extra_spec is not None:
|
||||
for key, value in extra_spec.items():
|
||||
spec = common.Element('extra_spec')
|
||||
spec.add_attr('key', key)
|
||||
spec.append(common.Text(value))
|
||||
extra_specs.append(spec)
|
||||
|
||||
resp, body = self.put(url, str(common.Document(extra_specs)))
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
self.get_volume_type(id)
|
||||
except exceptions.NotFound:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def resource_type(self):
|
||||
"""Returns the primary type of resource this client works with."""
|
||||
return 'volume-type'
|
||||
|
||||
|
||||
class VolumeTypesClientXML(BaseVolumeTypesClientXML):
|
||||
"""
|
||||
Client class to send CRUD Volume Type API V1 requests to a Cinder endpoint
|
||||
"""
|
@ -1,46 +0,0 @@
|
||||
# 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 lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class BaseVolumeAvailabilityZoneClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(BaseVolumeAvailabilityZoneClientXML, self).__init__(
|
||||
auth_provider)
|
||||
self.service = CONF.volume.catalog_type
|
||||
|
||||
def _parse_array(self, node):
|
||||
return [xml_utils.xml_to_json(x) for x in node]
|
||||
|
||||
def get_availability_zone_list(self):
|
||||
resp, body = self.get('os-availability-zone')
|
||||
availability_zone = self._parse_array(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, availability_zone
|
||||
|
||||
|
||||
class VolumeAvailabilityZoneClientXML(BaseVolumeAvailabilityZoneClientXML):
|
||||
"""
|
||||
Volume V1 availability zone client.
|
||||
"""
|
@ -1,26 +0,0 @@
|
||||
# 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 tempest.services.volume.json import backups_client
|
||||
|
||||
|
||||
class BackupsClientXML(backups_client.BackupsClientJSON):
|
||||
"""
|
||||
Client class to send CRUD Volume Backup API requests to a Cinder endpoint
|
||||
"""
|
||||
TYPE = "xml"
|
||||
|
||||
# TODO(gfidente): XML client isn't yet implemented because of bug 1270589
|
||||
pass
|
@ -1,49 +0,0 @@
|
||||
# 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 lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils as common
|
||||
from tempest import config
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class BaseExtensionsClientXML(rest_client.RestClient):
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(BaseExtensionsClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.volume.catalog_type
|
||||
|
||||
def _parse_array(self, node):
|
||||
array = []
|
||||
for child in node:
|
||||
array.append(common.xml_to_json(child))
|
||||
return array
|
||||
|
||||
def list_extensions(self):
|
||||
url = 'extensions'
|
||||
resp, body = self.get(url)
|
||||
body = self._parse_array(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
||||
|
||||
|
||||
class ExtensionsClientXML(BaseExtensionsClientXML):
|
||||
"""
|
||||
Volume V1 extensions client.
|
||||
"""
|
@ -1,255 +0,0 @@
|
||||
# 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 time
|
||||
import urllib
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils as common
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
from tempest.openstack.common import log as logging
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseSnapshotsClientXML(rest_client.RestClient):
|
||||
"""Base Client class to send CRUD Volume API requests."""
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(BaseSnapshotsClientXML, self).__init__(auth_provider)
|
||||
|
||||
self.service = CONF.volume.catalog_type
|
||||
self.build_interval = CONF.volume.build_interval
|
||||
self.build_timeout = CONF.volume.build_timeout
|
||||
self.create_resp = 200
|
||||
|
||||
def list_snapshots(self, params=None):
|
||||
"""List all snapshot."""
|
||||
url = 'snapshots'
|
||||
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = etree.fromstring(body)
|
||||
snapshots = []
|
||||
for snap in body:
|
||||
snapshots.append(common.xml_to_json(snap))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, snapshots
|
||||
|
||||
def list_snapshots_with_detail(self, params=None):
|
||||
"""List all the details of snapshot."""
|
||||
url = 'snapshots/detail'
|
||||
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = etree.fromstring(body)
|
||||
snapshots = []
|
||||
for snap in body:
|
||||
snapshots.append(common.xml_to_json(snap))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, snapshots
|
||||
|
||||
def get_snapshot(self, snapshot_id):
|
||||
"""Returns the details of a single snapshot."""
|
||||
url = "snapshots/%s" % str(snapshot_id)
|
||||
resp, body = self.get(url)
|
||||
body = etree.fromstring(body)
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, common.xml_to_json(body)
|
||||
|
||||
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.
|
||||
"""
|
||||
# NOTE(afazekas): it should use the volume namespace
|
||||
snapshot = common.Element("snapshot", xmlns=common.XMLNS_11,
|
||||
volume_id=volume_id)
|
||||
for key, value in kwargs.items():
|
||||
snapshot.add_attr(key, value)
|
||||
resp, body = self.post('snapshots',
|
||||
str(common.Document(snapshot)))
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(self.create_resp, resp.status)
|
||||
return resp, body
|
||||
|
||||
def update_snapshot(self, snapshot_id, **kwargs):
|
||||
"""Updates a snapshot."""
|
||||
put_body = common.Element("snapshot", xmlns=common.XMLNS_11, **kwargs)
|
||||
|
||||
resp, body = self.put('snapshots/%s' % snapshot_id,
|
||||
str(common.Document(put_body)))
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
||||
|
||||
# NOTE(afazekas): just for the wait function
|
||||
def _get_snapshot_status(self, snapshot_id):
|
||||
resp, body = self.get_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)
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
self.get_snapshot(id)
|
||||
except exceptions.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 = common.Element("os-reset_status", status=status)
|
||||
url = 'snapshots/%s/action' % str(snapshot_id)
|
||||
resp, body = self.post(url, str(common.Document(post_body)))
|
||||
if body:
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(202, resp.status)
|
||||
return resp, body
|
||||
|
||||
def update_snapshot_status(self, snapshot_id, status, progress):
|
||||
"""Update the specified snapshot's status."""
|
||||
post_body = common.Element("os-update_snapshot_status",
|
||||
status=status,
|
||||
progress=progress
|
||||
)
|
||||
url = 'snapshots/%s/action' % str(snapshot_id)
|
||||
resp, body = self.post(url, str(common.Document(post_body)))
|
||||
if body:
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(202, resp.status)
|
||||
return resp, body
|
||||
|
||||
def _metadata_body(self, meta):
|
||||
post_body = common.Element('metadata')
|
||||
for k, v in meta.items():
|
||||
data = common.Element('meta', key=k)
|
||||
data.append(common.Text(v))
|
||||
post_body.append(data)
|
||||
return post_body
|
||||
|
||||
def _parse_key_value(self, node):
|
||||
"""Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
|
||||
data = {}
|
||||
for node in node.getchildren():
|
||||
data[node.get('key')] = node.text
|
||||
return data
|
||||
|
||||
def create_snapshot_metadata(self, snapshot_id, metadata):
|
||||
"""Create metadata for the snapshot."""
|
||||
post_body = self._metadata_body(metadata)
|
||||
resp, body = self.post('snapshots/%s/metadata' % snapshot_id,
|
||||
str(common.Document(post_body)))
|
||||
body = self._parse_key_value(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
||||
|
||||
def get_snapshot_metadata(self, snapshot_id):
|
||||
"""Get metadata of the snapshot."""
|
||||
url = "snapshots/%s/metadata" % str(snapshot_id)
|
||||
resp, body = self.get(url)
|
||||
body = self._parse_key_value(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
||||
|
||||
def update_snapshot_metadata(self, snapshot_id, metadata):
|
||||
"""Update metadata for the snapshot."""
|
||||
put_body = self._metadata_body(metadata)
|
||||
url = "snapshots/%s/metadata" % str(snapshot_id)
|
||||
resp, body = self.put(url, str(common.Document(put_body)))
|
||||
body = self._parse_key_value(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
||||
|
||||
def update_snapshot_metadata_item(self, snapshot_id, id, meta_item):
|
||||
"""Update metadata item for the snapshot."""
|
||||
for k, v in meta_item.items():
|
||||
put_body = common.Element('meta', key=k)
|
||||
put_body.append(common.Text(v))
|
||||
url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
|
||||
resp, body = self.put(url, str(common.Document(put_body)))
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
||||
|
||||
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 resp, body
|
||||
|
||||
def force_delete_snapshot(self, snapshot_id):
|
||||
"""Force Delete Snapshot."""
|
||||
post_body = common.Element("os-force_delete")
|
||||
url = 'snapshots/%s/action' % str(snapshot_id)
|
||||
resp, body = self.post(url, str(common.Document(post_body)))
|
||||
if body:
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(202, resp.status)
|
||||
return resp, body
|
||||
|
||||
|
||||
class SnapshotsClientXML(BaseSnapshotsClientXML):
|
||||
"""Client class to send CRUD Volume V1 API requests."""
|
@ -1,472 +0,0 @@
|
||||
# Copyright 2012 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 time
|
||||
import urllib
|
||||
from xml.sax import saxutils
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils as common
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
VOLUME_NS_BASE = 'http://docs.openstack.org/volume/ext/'
|
||||
VOLUME_HOST_NS = VOLUME_NS_BASE + 'volume_host_attribute/api/v1'
|
||||
VOLUME_MIG_STATUS_NS = VOLUME_NS_BASE + 'volume_mig_status_attribute/api/v1'
|
||||
VOLUMES_TENANT_NS = VOLUME_NS_BASE + 'volume_tenant_attribute/api/v1'
|
||||
|
||||
|
||||
class BaseVolumesClientXML(rest_client.RestClient):
|
||||
"""
|
||||
Base client class to send CRUD Volume API requests to a Cinder endpoint
|
||||
"""
|
||||
TYPE = "xml"
|
||||
|
||||
def __init__(self, auth_provider):
|
||||
super(BaseVolumesClientXML, self).__init__(auth_provider)
|
||||
self.service = CONF.volume.catalog_type
|
||||
self.build_interval = CONF.compute.build_interval
|
||||
self.build_timeout = CONF.compute.build_timeout
|
||||
self.create_resp = 200
|
||||
|
||||
def _translate_attributes_to_json(self, volume):
|
||||
volume_host_attr = '{' + VOLUME_HOST_NS + '}host'
|
||||
volume_mig_stat_attr = '{' + VOLUME_MIG_STATUS_NS + '}migstat'
|
||||
volume_mig_name_attr = '{' + VOLUME_MIG_STATUS_NS + '}name_id'
|
||||
volume_tenant_id_attr = '{' + VOLUMES_TENANT_NS + '}tenant_id'
|
||||
if volume_host_attr in volume:
|
||||
volume['os-vol-host-attr:host'] = volume.pop(volume_host_attr)
|
||||
if volume_mig_stat_attr in volume:
|
||||
volume['os-vol-mig-status-attr:migstat'] = volume.pop(
|
||||
volume_mig_stat_attr)
|
||||
if volume_mig_name_attr in volume:
|
||||
volume['os-vol-mig-status-attr:name_id'] = volume.pop(
|
||||
volume_mig_name_attr)
|
||||
if volume_tenant_id_attr in volume:
|
||||
volume['os-vol-tenant-attr:tenant_id'] = volume.pop(
|
||||
volume_tenant_id_attr)
|
||||
|
||||
def _parse_volume(self, body):
|
||||
vol = dict((attr, body.get(attr)) for attr in body.keys())
|
||||
|
||||
for child in body.getchildren():
|
||||
tag = child.tag
|
||||
if tag.startswith("{"):
|
||||
ns, tag = tag.split("}", 1)
|
||||
if tag == 'metadata':
|
||||
vol['metadata'] = dict((meta.get('key'),
|
||||
meta.text) for meta in
|
||||
child.getchildren())
|
||||
else:
|
||||
vol[tag] = common.xml_to_json(child)
|
||||
self._translate_attributes_to_json(vol)
|
||||
self._check_if_bootable(vol)
|
||||
return vol
|
||||
|
||||
def get_attachment_from_volume(self, volume):
|
||||
"""Return the element 'attachment' from input volumes."""
|
||||
return volume['attachments']['attachment']
|
||||
|
||||
def _check_if_bootable(self, volume):
|
||||
"""
|
||||
Check if the volume is bootable, also change the value
|
||||
of 'bootable' from string to boolean.
|
||||
"""
|
||||
|
||||
# NOTE(jdg): Version 1 of Cinder API uses lc strings
|
||||
# We should consider being explicit in this check to
|
||||
# avoid introducing bugs like: LP #1227837
|
||||
|
||||
if volume['bootable'].lower() == 'true':
|
||||
volume['bootable'] = True
|
||||
elif volume['bootable'].lower() == 'false':
|
||||
volume['bootable'] = False
|
||||
else:
|
||||
raise ValueError(
|
||||
'bootable flag is supposed to be either True or False,'
|
||||
'it is %s' % volume['bootable'])
|
||||
return volume
|
||||
|
||||
def list_volumes(self, params=None):
|
||||
"""List all the volumes created."""
|
||||
url = 'volumes'
|
||||
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = etree.fromstring(body)
|
||||
volumes = []
|
||||
if body is not None:
|
||||
volumes += [self._parse_volume(vol) for vol in list(body)]
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, volumes
|
||||
|
||||
def list_volumes_with_detail(self, params=None):
|
||||
"""List all the details of volumes."""
|
||||
url = 'volumes/detail'
|
||||
|
||||
if params:
|
||||
url += '?%s' % urllib.urlencode(params)
|
||||
|
||||
resp, body = self.get(url)
|
||||
body = etree.fromstring(body)
|
||||
volumes = []
|
||||
if body is not None:
|
||||
volumes += [self._parse_volume(vol) for vol in list(body)]
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, volumes
|
||||
|
||||
def get_volume(self, volume_id):
|
||||
"""Returns the details of a single volume."""
|
||||
url = "volumes/%s" % str(volume_id)
|
||||
resp, body = self.get(url)
|
||||
body = self._parse_volume(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
||||
|
||||
def create_volume(self, size=None, **kwargs):
|
||||
"""Creates a new Volume.
|
||||
|
||||
:param size: Size of volume in GB.
|
||||
:param display_name: Optional Volume Name(only for V1).
|
||||
:param name: Optional Volume Name(only for V2).
|
||||
:param display_name: Optional Volume Name.
|
||||
:param metadata: An optional dictionary of values for metadata.
|
||||
:param volume_type: Optional Name of volume_type for the volume
|
||||
:param snapshot_id: When specified the volume is created from
|
||||
this snapshot
|
||||
:param imageRef: When specified the volume is created from this
|
||||
image
|
||||
"""
|
||||
# for bug #1293885:
|
||||
# If no size specified, read volume size from CONF
|
||||
if size is None:
|
||||
size = CONF.volume.volume_size
|
||||
# NOTE(afazekas): it should use a volume namespace
|
||||
volume = common.Element("volume", xmlns=common.XMLNS_11, size=size)
|
||||
|
||||
if 'metadata' in kwargs:
|
||||
_metadata = common.Element('metadata')
|
||||
volume.append(_metadata)
|
||||
for key, value in kwargs['metadata'].items():
|
||||
meta = common.Element('meta')
|
||||
meta.add_attr('key', key)
|
||||
meta.append(common.Text(value))
|
||||
_metadata.append(meta)
|
||||
attr_to_add = kwargs.copy()
|
||||
del attr_to_add['metadata']
|
||||
else:
|
||||
attr_to_add = kwargs
|
||||
|
||||
for key, value in attr_to_add.items():
|
||||
volume.add_attr(key, value)
|
||||
|
||||
resp, body = self.post('volumes', str(common.Document(volume)))
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(self.create_resp, resp.status)
|
||||
return resp, body
|
||||
|
||||
def update_volume(self, volume_id, **kwargs):
|
||||
"""Updates the Specified Volume."""
|
||||
put_body = common.Element("volume", xmlns=common.XMLNS_11, **kwargs)
|
||||
|
||||
resp, body = self.put('volumes/%s' % volume_id,
|
||||
str(common.Document(put_body)))
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
||||
|
||||
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 resp, body
|
||||
|
||||
def wait_for_volume_status(self, volume_id, status):
|
||||
"""Waits for a Volume to reach a given status."""
|
||||
resp, body = self.get_volume(volume_id)
|
||||
volume_status = body['status']
|
||||
start = int(time.time())
|
||||
|
||||
while volume_status != status:
|
||||
time.sleep(self.build_interval)
|
||||
resp, body = self.get_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 within '\
|
||||
'the required time (%s s).' % (volume_id,
|
||||
status,
|
||||
self.build_timeout)
|
||||
raise exceptions.TimeoutException(message)
|
||||
|
||||
def is_resource_deleted(self, id):
|
||||
try:
|
||||
self.get_volume(id)
|
||||
except exceptions.NotFound:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def resource_type(self):
|
||||
"""Returns the primary type of resource this client works with."""
|
||||
return 'volume'
|
||||
|
||||
def attach_volume(self, volume_id, instance_uuid, mountpoint):
|
||||
"""Attaches a volume to a given instance on a given mountpoint."""
|
||||
post_body = common.Element("os-attach",
|
||||
instance_uuid=instance_uuid,
|
||||
mountpoint=mountpoint
|
||||
)
|
||||
url = 'volumes/%s/action' % str(volume_id)
|
||||
resp, body = self.post(url, str(common.Document(post_body)))
|
||||
if body:
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(202, resp.status)
|
||||
return resp, body
|
||||
|
||||
def detach_volume(self, volume_id):
|
||||
"""Detaches a volume from an instance."""
|
||||
post_body = common.Element("os-detach")
|
||||
url = 'volumes/%s/action' % str(volume_id)
|
||||
resp, body = self.post(url, str(common.Document(post_body)))
|
||||
if body:
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(202, resp.status)
|
||||
return resp, body
|
||||
|
||||
def upload_volume(self, volume_id, image_name, disk_format):
|
||||
"""Uploads a volume in Glance."""
|
||||
post_body = common.Element("os-volume_upload_image",
|
||||
image_name=image_name,
|
||||
disk_format=disk_format)
|
||||
url = 'volumes/%s/action' % str(volume_id)
|
||||
resp, body = self.post(url, str(common.Document(post_body)))
|
||||
volume = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(202, resp.status)
|
||||
return resp, volume
|
||||
|
||||
def extend_volume(self, volume_id, extend_size):
|
||||
"""Extend a volume."""
|
||||
post_body = common.Element("os-extend",
|
||||
new_size=extend_size)
|
||||
url = 'volumes/%s/action' % str(volume_id)
|
||||
resp, body = self.post(url, str(common.Document(post_body)))
|
||||
if body:
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(202, resp.status)
|
||||
return resp, body
|
||||
|
||||
def reset_volume_status(self, volume_id, status):
|
||||
"""Reset the Specified Volume's Status."""
|
||||
post_body = common.Element("os-reset_status",
|
||||
status=status
|
||||
)
|
||||
url = 'volumes/%s/action' % str(volume_id)
|
||||
resp, body = self.post(url, str(common.Document(post_body)))
|
||||
if body:
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(202, resp.status)
|
||||
return resp, body
|
||||
|
||||
def volume_begin_detaching(self, volume_id):
|
||||
"""Volume Begin Detaching."""
|
||||
post_body = common.Element("os-begin_detaching")
|
||||
url = 'volumes/%s/action' % str(volume_id)
|
||||
resp, body = self.post(url, str(common.Document(post_body)))
|
||||
if body:
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def volume_roll_detaching(self, volume_id):
|
||||
"""Volume Roll Detaching."""
|
||||
post_body = common.Element("os-roll_detaching")
|
||||
url = 'volumes/%s/action' % str(volume_id)
|
||||
resp, body = self.post(url, str(common.Document(post_body)))
|
||||
if body:
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
def reserve_volume(self, volume_id):
|
||||
"""Reserves a volume."""
|
||||
post_body = common.Element("os-reserve")
|
||||
url = 'volumes/%s/action' % str(volume_id)
|
||||
resp, body = self.post(url, str(common.Document(post_body)))
|
||||
if body:
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(202, resp.status)
|
||||
return resp, body
|
||||
|
||||
def unreserve_volume(self, volume_id):
|
||||
"""Restore a reserved volume ."""
|
||||
post_body = common.Element("os-unreserve")
|
||||
url = 'volumes/%s/action' % str(volume_id)
|
||||
resp, body = self.post(url, str(common.Document(post_body)))
|
||||
if body:
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(202, resp.status)
|
||||
return resp, body
|
||||
|
||||
def create_volume_transfer(self, vol_id, display_name=None):
|
||||
"""Create a volume transfer."""
|
||||
post_body = common.Element("transfer",
|
||||
volume_id=vol_id)
|
||||
if display_name:
|
||||
post_body.add_attr('name', display_name)
|
||||
resp, body = self.post('os-volume-transfer',
|
||||
str(common.Document(post_body)))
|
||||
volume = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(202, resp.status)
|
||||
return resp, volume
|
||||
|
||||
def get_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)
|
||||
volume = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, volume
|
||||
|
||||
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 = etree.fromstring(body)
|
||||
volumes = []
|
||||
if body is not None:
|
||||
volumes += [self._parse_volume_transfer(vol) for vol in list(body)]
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, volumes
|
||||
|
||||
def _parse_volume_transfer(self, body):
|
||||
vol = dict((attr, body.get(attr)) for attr in body.keys())
|
||||
for child in body.getchildren():
|
||||
tag = child.tag
|
||||
if tag.startswith("{"):
|
||||
tag = tag.split("}", 1)
|
||||
vol[tag] = common.xml_to_json(child)
|
||||
return vol
|
||||
|
||||
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 resp, body
|
||||
|
||||
def accept_volume_transfer(self, transfer_id, transfer_auth_key):
|
||||
"""Accept a volume transfer."""
|
||||
post_body = common.Element("accept", auth_key=transfer_auth_key)
|
||||
url = 'os-volume-transfer/%s/accept' % transfer_id
|
||||
resp, body = self.post(url, str(common.Document(post_body)))
|
||||
volume = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(202, resp.status)
|
||||
return resp, volume
|
||||
|
||||
def update_volume_readonly(self, volume_id, readonly):
|
||||
"""Update the Specified Volume readonly."""
|
||||
post_body = common.Element("os-update_readonly_flag",
|
||||
readonly=readonly)
|
||||
url = 'volumes/%s/action' % str(volume_id)
|
||||
resp, body = self.post(url, str(common.Document(post_body)))
|
||||
if body:
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(202, resp.status)
|
||||
return resp, body
|
||||
|
||||
def force_delete_volume(self, volume_id):
|
||||
"""Force Delete Volume."""
|
||||
post_body = common.Element("os-force_delete")
|
||||
url = 'volumes/%s/action' % str(volume_id)
|
||||
resp, body = self.post(url, str(common.Document(post_body)))
|
||||
if body:
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
self.expected_success(202, resp.status)
|
||||
return resp, body
|
||||
|
||||
def _metadata_body(self, meta):
|
||||
post_body = common.Element('metadata')
|
||||
for k, v in meta.items():
|
||||
data = common.Element('meta', key=k)
|
||||
# Escape value to allow for special XML chars
|
||||
data.append(common.Text(saxutils.escape(v)))
|
||||
post_body.append(data)
|
||||
return post_body
|
||||
|
||||
def _parse_key_value(self, node):
|
||||
"""Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
|
||||
data = {}
|
||||
for node in node.getchildren():
|
||||
data[node.get('key')] = node.text
|
||||
return data
|
||||
|
||||
def create_volume_metadata(self, volume_id, metadata):
|
||||
"""Create metadata for the volume."""
|
||||
post_body = self._metadata_body(metadata)
|
||||
resp, body = self.post('volumes/%s/metadata' % volume_id,
|
||||
str(common.Document(post_body)))
|
||||
body = self._parse_key_value(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
||||
|
||||
def get_volume_metadata(self, volume_id):
|
||||
"""Get metadata of the volume."""
|
||||
url = "volumes/%s/metadata" % str(volume_id)
|
||||
resp, body = self.get(url)
|
||||
body = self._parse_key_value(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
||||
|
||||
def update_volume_metadata(self, volume_id, metadata):
|
||||
"""Update metadata for the volume."""
|
||||
put_body = self._metadata_body(metadata)
|
||||
url = "volumes/%s/metadata" % str(volume_id)
|
||||
resp, body = self.put(url, str(common.Document(put_body)))
|
||||
body = self._parse_key_value(etree.fromstring(body))
|
||||
self.expected_success(200, resp.status)
|
||||
return resp, body
|
||||
|
||||
def update_volume_metadata_item(self, volume_id, id, meta_item):
|
||||
"""Update metadata item for the volume."""
|
||||
for k, v in meta_item.items():
|
||||
put_body = common.Element('meta', key=k)
|
||||
put_body.append(common.Text(v))
|
||||
url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
|
||||
resp, body = self.put(url, str(common.Document(put_body)))
|
||||
self.expected_success(200, resp.status)
|
||||
body = common.xml_to_json(etree.fromstring(body))
|
||||
return resp, body
|
||||
|
||||
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 resp, body
|
||||
|
||||
|
||||
class VolumesClientXML(BaseVolumesClientXML):
|
||||
"""
|
||||
Client class to send CRUD Volume API V1 requests to a Cinder endpoint
|
||||
"""
|
@ -1,67 +0,0 @@
|
||||
# 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.
|
||||
from lxml import etree
|
||||
|
||||
from tempest.common import xml_utils as common
|
||||
from tempest.tests import base
|
||||
|
||||
|
||||
class TestXMLParser(base.TestCase):
|
||||
|
||||
def test_xml_to_json_parser_bool_value(self):
|
||||
node = etree.fromstring('''<health_monitor
|
||||
xmlns="http://openstack.org/quantum/api/v2.0"
|
||||
xmlns:quantum="http://openstack.org/quantum/api/v2.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<admin_state_up quantum:type="bool">False</admin_state_up>
|
||||
<fake_state_up quantum:type="bool">True</fake_state_up>
|
||||
</health_monitor>''')
|
||||
body = common.xml_to_json(node)
|
||||
self.assertEqual(body['admin_state_up'], False)
|
||||
self.assertEqual(body['fake_state_up'], True)
|
||||
|
||||
def test_xml_to_json_parser_int_value(self):
|
||||
node = etree.fromstring('''<health_monitor
|
||||
xmlns="http://openstack.org/quantum/api/v2.0"
|
||||
xmlns:quantum="http://openstack.org/quantum/api/v2.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<delay quantum:type="long">4</delay>
|
||||
<max_retries quantum:type="int">3</max_retries>
|
||||
</health_monitor>''')
|
||||
body = common.xml_to_json(node)
|
||||
self.assertEqual(body['delay'], 4L)
|
||||
self.assertEqual(body['max_retries'], 3)
|
||||
|
||||
def test_xml_to_json_parser_text_value(self):
|
||||
node = etree.fromstring('''<health_monitor
|
||||
xmlns="http://openstack.org/quantum/api/v2.0"
|
||||
xmlns:quantum="http://openstack.org/quantum/api/v2.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<status>ACTIVE</status>
|
||||
</health_monitor>''')
|
||||
body = common.xml_to_json(node)
|
||||
self.assertEqual(body['status'], 'ACTIVE')
|
||||
|
||||
def test_xml_to_json_parser_list_as_value(self):
|
||||
node = etree.fromstring('''<health_monitor
|
||||
xmlns="http://openstack.org/quantum/api/v2.0"
|
||||
xmlns:quantum="http://openstack.org/quantum/api/v2.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<elements>
|
||||
<element>first_element</element>
|
||||
<element>second_element</element>
|
||||
</elements>
|
||||
</health_monitor>''')
|
||||
body = common.xml_to_json(node, 'elements')
|
||||
self.assertEqual(body['elements'], ['first_element', 'second_element'])
|
@ -18,7 +18,6 @@ import httplib2
|
||||
from oslotest import mockpatch
|
||||
|
||||
from tempest.common import rest_client
|
||||
from tempest.common import xml_utils as xml
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
from tempest.tests import base
|
||||
@ -236,29 +235,8 @@ class TestRestClientUpdateHeaders(BaseRestClientTestClass):
|
||||
)
|
||||
|
||||
|
||||
class TestRestClientHeadersXML(TestRestClientHeadersJSON):
|
||||
TYPE = "xml"
|
||||
|
||||
# These two tests are needed in one exemplar
|
||||
def test_send_json_accept_xml(self):
|
||||
resp, __ = self.rest_client.get(self.url,
|
||||
self.rest_client.get_headers("xml",
|
||||
"json"))
|
||||
resp = dict((k.lower(), v) for k, v in resp.iteritems())
|
||||
self.assertEqual("application/json", resp["content-type"])
|
||||
self.assertEqual("application/xml", resp["accept"])
|
||||
|
||||
def test_send_xml_accept_json(self):
|
||||
resp, __ = self.rest_client.get(self.url,
|
||||
self.rest_client.get_headers("json",
|
||||
"xml"))
|
||||
resp = dict((k.lower(), v) for k, v in resp.iteritems())
|
||||
self.assertEqual("application/json", resp["accept"])
|
||||
self.assertEqual("application/xml", resp["content-type"])
|
||||
|
||||
|
||||
class TestRestClientParseRespXML(BaseRestClientTestClass):
|
||||
TYPE = "xml"
|
||||
class TestRestClientParseRespJSON(BaseRestClientTestClass):
|
||||
TYPE = "json"
|
||||
|
||||
keys = ["fake_key1", "fake_key2"]
|
||||
values = ["fake_value1", "fake_value2"]
|
||||
@ -274,38 +252,9 @@ class TestRestClientParseRespXML(BaseRestClientTestClass):
|
||||
|
||||
def setUp(self):
|
||||
self.fake_http = fake_http.fake_httplib2()
|
||||
super(TestRestClientParseRespXML, self).setUp()
|
||||
super(TestRestClientParseRespJSON, self).setUp()
|
||||
self.rest_client.TYPE = self.TYPE
|
||||
|
||||
def test_parse_resp_body_item(self):
|
||||
body_item = xml.Element("item", **self.item_expected)
|
||||
body = self.rest_client._parse_resp(str(xml.Document(body_item)))
|
||||
self.assertEqual(self.item_expected, body)
|
||||
|
||||
def test_parse_resp_body_list(self):
|
||||
self.rest_client.list_tags = ["fake_list", ]
|
||||
body_list = xml.Element(self.rest_client.list_tags[0])
|
||||
for i in range(2):
|
||||
body_list.append(xml.Element("fake_item",
|
||||
**self.list_expected["body_list"][i]))
|
||||
body = self.rest_client._parse_resp(str(xml.Document(body_list)))
|
||||
self.assertEqual(self.list_expected["body_list"], body)
|
||||
|
||||
def test_parse_resp_body_dict(self):
|
||||
self.rest_client.dict_tags = ["fake_dict", ]
|
||||
body_dict = xml.Element(self.rest_client.dict_tags[0])
|
||||
|
||||
for i in range(2):
|
||||
body_dict.append(xml.Element("fake_item", xml.Text(self.values[i]),
|
||||
key=self.keys[i]))
|
||||
|
||||
body = self.rest_client._parse_resp(str(xml.Document(body_dict)))
|
||||
self.assertEqual(self.dict_expected["body_dict"], body)
|
||||
|
||||
|
||||
class TestRestClientParseRespJSON(TestRestClientParseRespXML):
|
||||
TYPE = "json"
|
||||
|
||||
def test_parse_resp_body_item(self):
|
||||
body = self.rest_client._parse_resp(json.dumps(self.item_expected))
|
||||
self.assertEqual(self.item_expected, body)
|
||||
@ -426,10 +375,6 @@ class TestRestClientErrorCheckerJSON(base.TestCase):
|
||||
**self.set_data("402"))
|
||||
|
||||
|
||||
class TestRestClientErrorCheckerXML(TestRestClientErrorCheckerJSON):
|
||||
c_type = "application/xml"
|
||||
|
||||
|
||||
class TestRestClientErrorCheckerTEXT(TestRestClientErrorCheckerJSON):
|
||||
c_type = "text/plain"
|
||||
|
||||
|
@ -1,35 +0,0 @@
|
||||
#
|
||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 tempest.common import xml_utils
|
||||
from tempest.tests import base
|
||||
|
||||
|
||||
class TestDocumentXML(base.TestCase):
|
||||
def test_xml_document_ordering_version_encoding(self):
|
||||
expected = '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
xml_out = str(xml_utils.Document())
|
||||
self.assertEqual(expected, xml_out.strip())
|
||||
|
||||
xml_out = str(xml_utils.Document(encoding='UTF-8', version='1.0'))
|
||||
self.assertEqual(expected, xml_out.strip())
|
||||
|
||||
xml_out = str(xml_utils.Document(version='1.0', encoding='UTF-8'))
|
||||
self.assertEqual(expected, xml_out.strip())
|
||||
|
||||
def test_xml_document_additonal_attrs(self):
|
||||
expected = '<?xml version="1.0" encoding="UTF-8" foo="bar"?>'
|
||||
xml_out = str(xml_utils.Document(foo='bar'))
|
||||
self.assertEqual(expected, xml_out.strip())
|
Loading…
x
Reference in New Issue
Block a user