diff --git a/test-requirements.txt b/test-requirements.txt index 9a23661b..cd052a67 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,4 +5,3 @@ testrepository>=0.0.17 testtools>=0.9.32 mock>=1.0 httplib2 -lxml>=2.3 diff --git a/troveclient/compat/common.py b/troveclient/compat/common.py index eea7719c..3321880b 100644 --- a/troveclient/compat/common.py +++ b/troveclient/compat/common.py @@ -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 diff --git a/troveclient/compat/tests/test_common.py b/troveclient/compat/tests/test_common.py index 2b3924e6..edd297d4 100644 --- a/troveclient/compat/tests/test_common.py +++ b/troveclient/compat/tests/test_common.py @@ -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: diff --git a/troveclient/compat/tests/test_xml.py b/troveclient/compat/tests/test_xml.py deleted file mode 100644 index 2bdf43c6..00000000 --- a/troveclient/compat/tests/test_xml.py +++ /dev/null @@ -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 = ''' - - - - - - - - - - ''' - 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 = ''' - - - - - - ''' - 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(''' True ''') - self.assertEqual((True, None), - xml.root_element_to_json("rootEnabled", t_element)) - - # Test rootEnabled False: - f_element = etree.fromstring(''' False ''') - 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 = ''' - - None - - ''' - 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 = ''' - - - - - - - - - - ''' - 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 = '' - self.assertEqual(exp, etree.tostring(element)) - - # Test creating when name is in REQUEST_AS_LIST - element = xml.create_root_xml_element("users", []) - exp = '' - 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 = '' - 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 = '' \ - '' - 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 = '\n' - exp = {'body': body, - 'headers': {'Content-Type': 'application/xml', - 'Accept': 'application/xml'}} - self.assertEqual(exp, request) - - # Test morph_response_body - request = "" - 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 diff --git a/troveclient/compat/xml.py b/troveclient/compat/xml.py deleted file mode 100644 index 4135b932..00000000 --- a/troveclient/compat/xml.py +++ /dev/null @@ -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 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 - 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 diff --git a/troveclient/utils.py b/troveclient/utils.py index 3bbbcabe..328bc13e 100644 --- a/troveclient/utils.py +++ b/troveclient/utils.py @@ -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')