Merge "Get rid of XML related trove client bindings"

This commit is contained in:
Jenkins 2014-03-27 02:08:47 +00:00 committed by Gerrit Code Review
commit 5b713c009b
6 changed files with 4 additions and 586 deletions

View File

@ -5,4 +5,3 @@ testrepository>=0.0.17
testtools>=0.9.32
mock>=1.0
httplib2
lxml>=2.3

View File

@ -21,7 +21,6 @@ import six
import sys
from troveclient.compat import client
from troveclient.compat import xml
from troveclient.compat import exceptions
from troveclient.openstack.common.py3kcompat import urlutils
@ -102,7 +101,6 @@ class CliOptions(object):
'verbose': False,
'debug': False,
'token': None,
'xml': None,
}
def __init__(self, **kwargs):
@ -177,12 +175,11 @@ class CliOptions(object):
add_option("insecure", action="store_true",
help="Run in insecure mode for https endpoints.")
add_option("token", help="Token from a prior login.")
add_option("xml", action="store_true", help="Changes format to XML.")
oparser.add_option("--secure", action="store_false", dest="insecure",
help="Run in insecure mode for https endpoints.")
oparser.add_option("--json", action="store_false", dest="xml",
help="Changes format to JSON.")
oparser.add_option("--secure", action="store_false", dest="insecure",
help="Run in insecure mode for https endpoints.")
oparser.add_option("--terse", action="store_false", dest="verbose",
help="Toggles verbose mode off.")
oparser.add_option("--hide-debug", action="store_false", dest="debug",
@ -218,10 +215,7 @@ class CommandsBase(object):
def _get_client(self):
"""Creates the all important client object."""
try:
if self.xml:
client_cls = xml.TroveXmlClient
else:
client_cls = client.TroveHTTPClient
client_cls = client.TroveHTTPClient
if self.verbose:
client.log_to_streamhandler(sys.stdout)
client.RDC_PP = True

View File

@ -103,7 +103,6 @@ class CliOptionsTest(testtools.TestCase):
self.assertFalse(co.verbose)
self.assertFalse(co.debug)
self.assertIsNone(co.token)
self.assertIsNone(co.xml)
def check_option(self, oparser, option_name):
option = oparser.get_option("--%s" % option_name)
@ -129,7 +128,7 @@ class CliOptionsTest(testtools.TestCase):
"tenant_id", "auth_type", "service_type",
"service_name", "service_type", "service_name",
"service_url", "region", "insecure", "token",
"xml", "secure", "json", "terse", "hide-debug"]
"secure", "json", "terse", "hide-debug"]
oparser = common.CliOptions.create_optparser(True)
for option_name in option_names:

View File

@ -1,257 +0,0 @@
# Copyright (c) 2011 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 testtools
from lxml import etree
from troveclient.compat import xml
# Killing this until xml support is brought back.
#class XmlTest(testtools.TestCase):
class XmlTest(object):
ELEMENT = '''
<instances>
<instance>
<flavor>
<links>
</links>
<value value="5"/>
</flavor>
</instance>
</instances>
'''
ROOT = etree.fromstring(ELEMENT)
JSON = {'instances': {
'instances': ['1', '2', '3']}, 'dummy': {'dict': True}
}
def test_element_ancestors_match_list(self):
# Test normal operation:
self.assertTrue(xml.element_ancestors_match_list(self.ROOT[0][0],
['instance',
'instances']))
# Test itr_elem is None:
self.assertTrue(xml.element_ancestors_match_list(self.ROOT,
['instances']))
# Test that the first parent element does not match the first list
# element:
self.assertFalse(xml.element_ancestors_match_list(self.ROOT[0][0],
['instances',
'instance']))
def test_populate_element_from_dict(self):
# Test populate_element_from_dict with a None in the data
ele = '''
<instance>
<volume>
<value size="5"/>
</volume>
</instance>
'''
rt = etree.fromstring(ele)
self.assertIsNone(xml.populate_element_from_dict(rt, {'size': None}))
def test_element_must_be_list(self):
# Test for when name isn't in the dictionary
self.assertFalse(xml.element_must_be_list(self.ROOT, "not_in_list"))
# Test when name is in the dictionary but list is empty
self.assertTrue(xml.element_must_be_list(self.ROOT, "accounts"))
# Test when name is in the dictionary but list is not empty
self.assertTrue(xml.element_must_be_list(self.ROOT[0][0][0], "links"))
def test_element_to_json(self):
# Test when element must be list:
self.assertEqual([{'flavor': {'links': [], 'value': {'value': '5'}}}],
xml.element_to_json("accounts", self.ROOT))
# Test when element must not be list:
exp = {'instance': {'flavor': {'links': [], 'value': {'value': '5'}}}}
self.assertEqual(exp, xml.element_to_json("not_in_list", self.ROOT))
def test_root_element_to_json(self):
# Test when element must be list:
exp = ([{'flavor': {'links': [], 'value': {'value': '5'}}}], None)
self.assertEqual(exp, xml.root_element_to_json("accounts", self.ROOT))
# Test when element must not be list:
exp = {'instance': {'flavor': {'links': [], 'value': {'value': '5'}}}}
self.assertEqual((exp, None),
xml.root_element_to_json("not_in_list", self.ROOT))
# Test rootEnabled True:
t_element = etree.fromstring('''<rootEnabled> True </rootEnabled>''')
self.assertEqual((True, None),
xml.root_element_to_json("rootEnabled", t_element))
# Test rootEnabled False:
f_element = etree.fromstring('''<rootEnabled> False </rootEnabled>''')
self.assertEqual((False, None),
xml.root_element_to_json("rootEnabled", f_element))
def test_element_to_list(self):
# Test w/ no child elements
self.assertEqual([], xml.element_to_list(self.ROOT[0][0][0]))
# Test w/ no child elements and check_for_links = True
self.assertEqual(([], None),
xml.element_to_list(self.ROOT[0][0][0],
check_for_links=True))
# Test w/ child elements
self.assertEqual([{}, {'value': '5'}],
xml.element_to_list(self.ROOT[0][0]))
# Test w/ child elements and check_for_links = True
self.assertEqual(([{'value': '5'}], []),
xml.element_to_list(self.ROOT[0][0],
check_for_links=True))
def test_element_to_dict(self):
# Test when there is not a None
exp = {'instance': {'flavor': {'links': [], 'value': {'value': '5'}}}}
self.assertEqual(exp, xml.element_to_dict(self.ROOT))
# Test when there is a None
element = '''
<server>
None
</server>
'''
rt = etree.fromstring(element)
self.assertIsNone(xml.element_to_dict(rt))
def test_standarize_json(self):
xml.standardize_json_lists(self.JSON)
self.assertEqual({'instances': ['1', '2', '3'],
'dummy': {'dict': True}}, self.JSON)
def test_normalize_tag(self):
ELEMENT_NS = '''
<instances xmlns="http://www.w3.org/1999/xhtml">
<instance>
<flavor>
<links>
</links>
<value value="5"/>
</flavor>
</instance>
</instances>
'''
ROOT_NS = etree.fromstring(ELEMENT_NS)
# Test normalizing without namespace info
self.assertEqual('instances', xml.normalize_tag(self.ROOT))
# Test normalizing with namespace info
self.assertEqual('instances', xml.normalize_tag(ROOT_NS))
def test_create_root_xml_element(self):
# Test creating when name is not in REQUEST_AS_LIST
element = xml.create_root_xml_element("root", {"root": "value"})
exp = '<root xmlns="http://docs.openstack.org/database/api/v1.0" ' \
'root="value"/>'
self.assertEqual(exp, etree.tostring(element))
# Test creating when name is in REQUEST_AS_LIST
element = xml.create_root_xml_element("users", [])
exp = '<users xmlns="http://docs.openstack.org/database/api/v1.0"/>'
self.assertEqual(exp, etree.tostring(element))
def test_creating_subelements(self):
# Test creating a subelement as a dictionary
element = xml.create_root_xml_element("root", {"root": 5})
xml.create_subelement(element, "subelement", {"subelement": "value"})
exp = '<root xmlns="http://docs.openstack.org/database/api/v1.0" ' \
'root="5"><subelement subelement="value"/></root>'
self.assertEqual(exp, etree.tostring(element))
# Test creating a subelement as a list
element = xml.create_root_xml_element("root",
{"root": {"value": "nested"}})
xml.create_subelement(element, "subelement", [{"subelement": "value"}])
exp = '<root xmlns="http://docs.openstack.org/database/api/v1.0">' \
'<root value="nested"/><subelement><subelement subelement=' \
'"value"/></subelement></root>'
self.assertEqual(exp, etree.tostring(element))
# Test creating a subelement as a string (should raise TypeError)
element = xml.create_root_xml_element("root", {"root": "value"})
try:
xml.create_subelement(element, "subelement", ["value"])
self.fail("TypeError exception expected")
except TypeError:
pass
def test_modify_response_types(self):
TYPE_MAP = {
"Int": int,
"Bool": bool
}
#Is a string True
self.assertEqual(True, xml.modify_response_types('True', TYPE_MAP))
#Is a string False
self.assertEqual(False, xml.modify_response_types('False', TYPE_MAP))
#Is a dict
test_dict = {"Int": "5"}
test_dict = xml.modify_response_types(test_dict, TYPE_MAP)
self.assertEqual(int, test_dict["Int"].__class__)
#Is a list
test_list = {"a_list": [{"Int": "5"}, {"Str": "A"}]}
test_list = xml.modify_response_types(test_list["a_list"], TYPE_MAP)
self.assertEqual([{'Int': 5}, {'Str': 'A'}], test_list)
def test_trovexmlclient(self):
from troveclient import exceptions
client = xml.TroveXmlClient("user", "password", "tenant",
"auth_url", "service_name",
auth_strategy="fake")
request = {'headers': {}}
# Test morph_request, no body
client.morph_request(request)
self.assertEqual('application/xml', request['headers']['Accept'])
self.assertEqual('application/xml', request['headers']['Content-Type'])
# Test morph_request, with body
request['body'] = {'root': {'test': 'test'}}
client.morph_request(request)
body = '<root xmlns="http://docs.openstack.org/database/api/v1.0" ' \
'test="test"/>\n'
exp = {'body': body,
'headers': {'Content-Type': 'application/xml',
'Accept': 'application/xml'}}
self.assertEqual(exp, request)
# Test morph_response_body
request = "<users><links><user href='value'/></links></users>"
result = client.morph_response_body(request)
self.assertEqual({'users': [], 'links': [{'href': 'value'}]}, result)
# Test morph_response_body with improper input
try:
client.morph_response_body("value")
self.fail("ResponseFormatError exception expected")
except exceptions.ResponseFormatError:
pass

View File

@ -1,315 +0,0 @@
# Copyright (c) 2011 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
import numbers
from troveclient.compat import exceptions
from troveclient.compat import client
XML_NS = {None: "http://docs.openstack.org/database/api/v1.0"}
# If XML element is listed here then this searches through the ancestors.
LISTIFY = {
"accounts": [[]],
"databases": [[]],
"flavors": [[]],
"instances": [[]],
"links": [[]],
"hosts": [[]],
"devices": [[]],
"users": [[]],
"versions": [[]],
"attachments": [[]],
"limits": [[]],
"security_groups": [[]],
"backups": [[]],
"datastores": [[]],
"datastore_versions": [[]],
"configuration_parameters": [[]],
}
class IntDict(object):
pass
TYPE_MAP = {
"instance": {
"volume": {
"used": float,
"size": int,
"total": float,
},
"deleted": bool,
"server": {
"local_id": int,
"deleted": bool,
},
},
"instances": {
"deleted": bool,
},
"deleted": bool,
"flavor": {
"ram": int,
},
"diagnostics": {
"vmHwm": int,
"vmPeak": int,
"vmSize": int,
"threads": int,
"vmRss": int,
"fdSize": int,
},
"security_group_rule": {
"from_port": int,
"to_port": int,
},
"quotas": IntDict,
"configuration_parameter": {
"max": int,
"min": int,
},
}
TYPE_MAP["flavors"] = TYPE_MAP["flavor"]
REQUEST_AS_LIST = set(['databases', 'users'])
def element_ancestors_match_list(element, list):
"""
For element root at <foo><blah><root></blah></foo> matches against
list ["blah", "foo"].
"""
itr_elem = element.getparent()
for name in list:
if itr_elem is None:
break
if name != normalize_tag(itr_elem):
return False
itr_elem = itr_elem.getparent()
return True
def element_must_be_list(parent_element, name):
"""Determines if an element to be created should be a dict or list."""
if name in LISTIFY:
list_of_lists = LISTIFY[name]
for tag_list in list_of_lists:
if element_ancestors_match_list(parent_element, tag_list):
return True
return False
def element_to_json(name, element):
if element_must_be_list(element, name):
return element_to_list(element)
else:
return element_to_dict(element)
def root_element_to_json(name, element):
"""Returns a tuple of the root JSON value, plus the links if found."""
if name == "rootEnabled": # Why oh why were we inconsistent here? :'(
if element.text.strip() == "False":
return False, None
elif element.text.strip() == "True":
return True, None
if element_must_be_list(element, name):
return element_to_list(element, True)
else:
return element_to_dict(element), None
def element_to_list(element, check_for_links=False):
"""
For element "foo" in <foos><foo/><foo/></foos>
Returns [{}, {}]
"""
links = None
result = []
for child_element in element:
# The "links" element gets jammed into the root element.
if check_for_links and normalize_tag(child_element) == "links":
links = element_to_list(child_element)
else:
result.append(element_to_dict(child_element))
if check_for_links:
return result, links
else:
return result
def element_to_dict(element):
result = {}
for name, value in element.items():
result[name] = value
for child_element in element:
name = normalize_tag(child_element)
result[name] = element_to_json(name, child_element)
if len(result) == 0 and element.text:
string_value = element.text.strip()
if len(string_value):
if string_value == 'None':
return None
return string_value
return result
def standardize_json_lists(json_dict):
"""
In XML, we might see something like {'instances':{'instances':[...]}},
which we must change to just {'instances':[...]} to be compatible with
the true JSON format.
If any items are dictionaries with only one item which is a list,
simply remove the dictionary and insert its list directly.
"""
found_items = []
for key, value in json_dict.items():
value = json_dict[key]
if isinstance(value, dict):
if len(value) == 1 and isinstance(value.values()[0], list):
found_items.append(key)
else:
standardize_json_lists(value)
for key in found_items:
json_dict[key] = json_dict[key].values()[0]
def normalize_tag(elem):
"""Given an element, returns the tag minus the XMLNS junk.
IOW, .tag may sometimes return the XML namespace at the start of the
string. This gets rids of that.
"""
try:
prefix = "{" + elem.nsmap[None] + "}"
if elem.tag.startswith(prefix):
return elem.tag[len(prefix):]
except KeyError:
pass
return elem.tag
def create_root_xml_element(name, value):
"""Create the first element using a name and a dictionary."""
element = etree.Element(name, nsmap=XML_NS)
if name in REQUEST_AS_LIST:
add_subelements_from_list(element, name, value)
else:
populate_element_from_dict(element, value)
return element
def create_subelement(parent_element, name, value):
"""Attaches a new element onto the parent element."""
if isinstance(value, dict):
create_subelement_from_dict(parent_element, name, value)
elif isinstance(value, list):
create_subelement_from_list(parent_element, name, value)
else:
raise TypeError("Can't handle type %s." % type(value))
def create_subelement_from_dict(parent_element, name, dict):
element = etree.SubElement(parent_element, name)
populate_element_from_dict(element, dict)
def create_subelement_from_list(parent_element, name, list):
element = etree.SubElement(parent_element, name)
add_subelements_from_list(element, name, list)
def add_subelements_from_list(element, name, list):
if name.endswith("s"):
item_name = name[:len(name) - 1]
else:
item_name = name
for item in list:
create_subelement(element, item_name, item)
def populate_element_from_dict(element, dict):
for key, value in dict.items():
if isinstance(value, basestring):
element.set(key, value)
elif isinstance(value, numbers.Number):
element.set(key, str(value))
elif isinstance(value, None.__class__):
element.set(key, '')
else:
create_subelement(element, key, value)
def modify_response_types(value, type_translator):
"""
This will convert some string in response dictionary to ints or bool
so that our response is compatible with code expecting JSON style responses
"""
if isinstance(value, str):
if value == 'True':
return True
elif value == 'False':
return False
else:
return type_translator(value)
elif isinstance(value, dict):
for k, v in value.iteritems():
if type_translator is not IntDict:
if v.__class__ is dict and v.__len__() == 0:
value[k] = None
elif k in type_translator:
value[k] = modify_response_types(value[k],
type_translator[k])
else:
value[k] = int(value[k])
return value
elif isinstance(value, list):
return [modify_response_types(element, type_translator)
for element in value]
class TroveXmlClient(client.TroveHTTPClient):
@classmethod
def morph_request(self, kwargs):
kwargs['headers']['Accept'] = 'application/xml'
kwargs['headers']['Content-Type'] = 'application/xml'
if 'body' in kwargs:
body = kwargs['body']
root_name = body.keys()[0]
xml = create_root_xml_element(root_name, body[root_name])
xml_string = etree.tostring(xml, pretty_print=True)
kwargs['body'] = xml_string
@classmethod
def morph_response_body(self, body_string):
# The root XML element always becomes a dictionary with a single
# field, which has the same key as the elements name.
result = {}
try:
root_element = etree.XML(body_string)
except etree.XMLSyntaxError:
raise exceptions.ResponseFormatError()
root_name = normalize_tag(root_element)
root_value, links = root_element_to_json(root_name, root_element)
result = {root_name: root_value}
if links:
result['links'] = links
modify_response_types(result, TYPE_MAP)
return result

View File

@ -126,8 +126,6 @@ def _output_override(objs, print_as):
new_objs = objs
# pretty print the json
print(json.dumps(new_objs, indent=' '))
elif 'xml_output' in globals():
print('not implemented')
else:
raise BaseException('No valid output override')