diff -Nru python-novaclient-2.16.0/ChangeLog python-novaclient-2.17.0/ChangeLog --- python-novaclient-2.16.0/ChangeLog 2014-02-26 16:29:28.000000000 +0000 +++ python-novaclient-2.17.0/ChangeLog 2014-03-07 21:11:56.000000000 +0000 @@ -1,10 +1,23 @@ CHANGES ======= +2.17.0 +------ + +* Add os-server-external-events support +* Add classifiers for specific versions of Python +* oslo sync apiclient and cliutils +* Fix in in novaclient, to avoid excessive conns +* Revert "'name' should as be optional param on aggregate-update" +* oslo-sync of low hanging fruit +* Updated from global requirements +* 'name' should as be optional param on aggregate-update + 2.16.0 ------ * Fix typo in novaclient +* Remove usage of module py3kcompat * Updated from global requirements * Invalid client version message unclear * Remove None for dict.get() diff -Nru python-novaclient-2.16.0/debian/changelog python-novaclient-2.17.0/debian/changelog --- python-novaclient-2.16.0/debian/changelog 2014-03-06 20:33:00.000000000 +0000 +++ python-novaclient-2.17.0/debian/changelog 2014-03-18 00:16:40.000000000 +0000 @@ -1,3 +1,9 @@ +python-novaclient (1:2.17.0-0ubuntu1) trusty; urgency=medium + + * New upstream release. + + -- Chuck Short Mon, 17 Mar 2014 20:16:30 -0400 + python-novaclient (1:2.16.0-0ubuntu1) trusty; urgency=low * New upstream release. diff -Nru python-novaclient-2.16.0/novaclient/client.py python-novaclient-2.17.0/novaclient/client.py --- python-novaclient-2.16.0/novaclient/client.py 2014-02-26 16:28:52.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/client.py 2014-03-07 21:11:20.000000000 +0000 @@ -24,21 +24,35 @@ import time import requests +from requests import adapters try: import json except ImportError: import simplejson as json +from six.moves.urllib import parse + from novaclient import exceptions from novaclient.openstack.common.gettextutils import _ -from novaclient.openstack.common.py3kcompat import urlutils from novaclient import service_catalog from novaclient import utils -class HTTPClient(object): +_ADAPTERS = {} + + +def _adapter_pool(url): + """ + Store and reuse HTTP adapters per Service URL. + """ + if url not in _ADAPTERS: + _ADAPTERS[url] = adapters.HTTPAdapter() + + return _ADAPTERS[url] + +class HTTPClient(object): USER_AGENT = 'python-novaclient' def __init__(self, user, password, projectid=None, auth_url=None, @@ -104,8 +118,10 @@ self.auth_system = auth_system self.auth_plugin = auth_plugin - + self._current_url = None + self._http = None self._logger = logging.getLogger(__name__) + if self.http_log_debug and not self._logger.handlers: # Logging level is already set on the root logger ch = logging.StreamHandler() @@ -118,8 +134,6 @@ # have to set it up here on WARNING (its original level) # otherwise we will get all the requests logging messages rql.setLevel(logging.WARNING) - # requests within the same session can reuse TCP connections from pool - self.http = requests.Session() def use_token_cache(self, use_it): self.os_cache = use_it @@ -166,6 +180,20 @@ 'headers': resp.headers, 'text': resp.text}) + def http(self, url): + magic_tuple = parse.urlsplit(url) + scheme, netloc, path, query, frag = magic_tuple + service_url = '%s://%s' % (scheme, netloc) + if self._current_url != service_url: + # Invalidate Session object in case the url is somehow changed + if self._http: + self._http.close() + self._current_url = service_url + self._logger.debug("New session created for: (%s)" % service_url) + self._http = requests.Session() + self._http.mount(service_url, _adapter_pool(service_url)) + return self._http + def request(self, url, method, **kwargs): kwargs.setdefault('headers', kwargs.get('headers', {})) kwargs['headers']['User-Agent'] = self.USER_AGENT @@ -179,10 +207,11 @@ kwargs['verify'] = self.verify_cert self.http_log_req(method, url, kwargs) - resp = self.http.request( + resp = self.http(url).request( method, url, **kwargs) + self.http_log_resp(resp) if resp.text: @@ -325,7 +354,7 @@ extract_token=False) def authenticate(self): - magic_tuple = urlutils.urlsplit(self.auth_url) + magic_tuple = parse.urlsplit(self.auth_url) scheme, netloc, path, query, frag = magic_tuple port = magic_tuple.port if port is None: @@ -343,7 +372,7 @@ # TODO(sandy): Assume admin endpoint is 35357 for now. # Ideally this is going to have to be provided by the service catalog. new_netloc = netloc.replace(':%d' % port, ':%d' % (35357,)) - admin_url = urlutils.urlunsplit( + admin_url = parse.urlunsplit( (scheme, new_netloc, path, query, frag)) auth_url = self.auth_url diff -Nru python-novaclient-2.16.0/novaclient/openstack/common/apiclient/auth.py python-novaclient-2.17.0/novaclient/openstack/common/apiclient/auth.py --- python-novaclient-2.16.0/novaclient/openstack/common/apiclient/auth.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/openstack/common/apiclient/auth.py 2014-03-07 21:11:20.000000000 +0000 @@ -19,7 +19,6 @@ import abc import argparse -import logging import os import six @@ -28,9 +27,6 @@ from novaclient.openstack.common.apiclient import exceptions -logger = logging.getLogger(__name__) - - _discovered_plugins = {} @@ -80,7 +76,7 @@ alphabetical order. :type args: argparse.Namespace - :raises: AuthorizationFailure + :raises: AuthPluginOptionsMissing """ auth_system = args.os_auth_system if auth_system: diff -Nru python-novaclient-2.16.0/novaclient/openstack/common/apiclient/base.py python-novaclient-2.17.0/novaclient/openstack/common/apiclient/base.py --- python-novaclient-2.16.0/novaclient/openstack/common/apiclient/base.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/openstack/common/apiclient/base.py 2014-03-07 21:11:20.000000000 +0000 @@ -24,11 +24,12 @@ # pylint: disable=E1102 import abc +import copy import six +from six.moves.urllib import parse from novaclient.openstack.common.apiclient import exceptions -from novaclient.openstack.common.py3kcompat import urlutils from novaclient.openstack.common import strutils @@ -327,7 +328,7 @@ return self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), - 'query': '?%s' % urlutils.urlencode(kwargs) if kwargs else '', + 'query': '?%s' % parse.urlencode(kwargs) if kwargs else '', }, self.collection_key) @@ -366,7 +367,7 @@ rl = self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), - 'query': '?%s' % urlutils.urlencode(kwargs) if kwargs else '', + 'query': '?%s' % parse.urlencode(kwargs) if kwargs else '', }, self.collection_key) num = len(rl) @@ -465,6 +466,11 @@ return self.__dict__[k] def get(self): + """Support for lazy loading details. + + Some clients, such as novaclient have the option to lazy load the + details, details which can be loaded with this function. + """ # set_loaded() first ... so if we have to bail, we know we tried. self.set_loaded(True) if not hasattr(self.manager, 'get'): @@ -489,3 +495,6 @@ def set_loaded(self, val): self._loaded = val + + def to_dict(self): + return copy.deepcopy(self._info) diff -Nru python-novaclient-2.16.0/novaclient/openstack/common/apiclient/exceptions.py python-novaclient-2.17.0/novaclient/openstack/common/apiclient/exceptions.py --- python-novaclient-2.16.0/novaclient/openstack/common/apiclient/exceptions.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/openstack/common/apiclient/exceptions.py 2014-03-07 21:11:20.000000000 +0000 @@ -60,6 +60,11 @@ pass +class ConnectionRefused(ClientException): + """Cannot connect to API service.""" + pass + + class AuthPluginOptionsMissing(AuthorizationFailure): """Auth plugin misses some options.""" def __init__(self, opt_names): @@ -122,6 +127,11 @@ super(HttpError, self).__init__(formatted_string) +class HTTPRedirection(HttpError): + """HTTP Redirection.""" + message = "HTTP Redirection" + + class HTTPClientError(HttpError): """Client-side HTTP error. @@ -139,6 +149,16 @@ message = "HTTP Server Error" +class MultipleChoices(HTTPRedirection): + """HTTP 300 - Multiple Choices. + + Indicates multiple options for the resource that the client may follow. + """ + + http_status = 300 + message = "Multiple Choices" + + class BadRequest(HTTPClientError): """HTTP 400 - Bad Request. @@ -420,8 +440,8 @@ except ValueError: pass else: - if hasattr(body, "keys"): - error = body[body.keys()[0]] + if isinstance(body, dict): + error = list(body.values())[0] kwargs["message"] = error.get("message") kwargs["details"] = error.get("details") elif content_type.startswith("text/"): diff -Nru python-novaclient-2.16.0/novaclient/openstack/common/apiclient/fake_client.py python-novaclient-2.17.0/novaclient/openstack/common/apiclient/fake_client.py --- python-novaclient-2.16.0/novaclient/openstack/common/apiclient/fake_client.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/openstack/common/apiclient/fake_client.py 2014-03-07 21:11:20.000000000 +0000 @@ -28,10 +28,9 @@ import requests import six +from six.moves.urllib import parse from novaclient.openstack.common.apiclient import client -from novaclient.openstack.common.py3kcompat import urlutils -from novaclient.openstack.common import strutils def assert_has_keys(dct, required=[], optional=[]): @@ -64,7 +63,7 @@ self._content = text default_headers = {} if six.PY3 and isinstance(self._content, six.string_types): - self._content = strutils.safe_encode(self._content) + self._content = self._content.encode('utf-8', 'strict') self.headers = data.get('headers') or default_headers else: self.status_code = data @@ -148,7 +147,7 @@ "text": fixture[1]}) # Call the method - args = urlutils.parse_qsl(urlutils.urlparse(url)[4]) + args = parse.parse_qsl(parse.urlparse(url)[4]) kwargs.update(args) munged_url = url.rsplit('?', 1)[0] munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_') diff -Nru python-novaclient-2.16.0/novaclient/openstack/common/apiclient/__init__.py python-novaclient-2.17.0/novaclient/openstack/common/apiclient/__init__.py --- python-novaclient-2.16.0/novaclient/openstack/common/apiclient/__init__.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/openstack/common/apiclient/__init__.py 2014-03-07 21:11:20.000000000 +0000 @@ -1,14 +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. diff -Nru python-novaclient-2.16.0/novaclient/openstack/common/cliutils.py python-novaclient-2.17.0/novaclient/openstack/common/cliutils.py --- python-novaclient-2.16.0/novaclient/openstack/common/cliutils.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/openstack/common/cliutils.py 2014-03-07 21:11:20.000000000 +0000 @@ -16,6 +16,8 @@ # W0621: Redefining name %s from outer scope # pylint: disable=W0603,W0621 +from __future__ import print_function + import getpass import inspect import os @@ -27,7 +29,9 @@ from six import moves from novaclient.openstack.common.apiclient import exceptions +from novaclient.openstack.common.gettextutils import _ from novaclient.openstack.common import strutils +from novaclient.openstack.common import uuidutils def validate_args(fn, *args, **kwargs): @@ -176,9 +180,9 @@ for k, v in six.iteritems(dct): # convert dict to str to check length if isinstance(v, dict): - v = str(v) + v = six.text_type(v) if wrap > 0: - v = textwrap.fill(str(v), wrap) + v = textwrap.fill(six.text_type(v), wrap) # if value has a newline, add in multiple rows # e.g. fault with stacktrace if v and isinstance(v, six.string_types) and r'\n' in v: @@ -199,7 +203,7 @@ if hasattr(sys.stdin, "isatty") and sys.stdin.isatty(): # Check for Ctrl-D try: - for _ in moves.range(max_password_prompts): + for __ in moves.range(max_password_prompts): pw1 = getpass.getpass("OS Password: ") if verify: pw2 = getpass.getpass("Please verify: ") @@ -211,3 +215,95 @@ except EOFError: pass return pw + + +def find_resource(manager, name_or_id, **find_args): + """Look for resource in a given manager. + + Used as a helper for the _find_* methods. + Example: + + def _find_hypervisor(cs, hypervisor): + #Get a hypervisor by name or ID. + return cliutils.find_resource(cs.hypervisors, hypervisor) + """ + # first try to get entity as integer id + try: + return manager.get(int(name_or_id)) + except (TypeError, ValueError, exceptions.NotFound): + pass + + # now try to get entity as uuid + try: + tmp_id = strutils.safe_encode(name_or_id) + + if uuidutils.is_uuid_like(tmp_id): + return manager.get(tmp_id) + except (TypeError, ValueError, exceptions.NotFound): + pass + + # for str id which is not uuid + if getattr(manager, 'is_alphanum_id_allowed', False): + try: + return manager.get(name_or_id) + except exceptions.NotFound: + pass + + try: + try: + return manager.find(human_id=name_or_id, **find_args) + except exceptions.NotFound: + pass + + # finally try to find entity by name + try: + resource = getattr(manager, 'resource_class', None) + name_attr = resource.NAME_ATTR if resource else 'name' + kwargs = {name_attr: name_or_id} + kwargs.update(find_args) + return manager.find(**kwargs) + except exceptions.NotFound: + msg = _("No %(name)s with a name or " + "ID of '%(name_or_id)s' exists.") % \ + { + "name": manager.resource_class.__name__.lower(), + "name_or_id": name_or_id + } + raise exceptions.CommandError(msg) + except exceptions.NoUniqueMatch: + msg = _("Multiple %(name)s matches found for " + "'%(name_or_id)s', use an ID to be more specific.") % \ + { + "name": manager.resource_class.__name__.lower(), + "name_or_id": name_or_id + } + raise exceptions.CommandError(msg) + + +def service_type(stype): + """Adds 'service_type' attribute to decorated function. + + Usage: + @service_type('volume') + def mymethod(f): + ... + """ + def inner(f): + f.service_type = stype + return f + return inner + + +def get_service_type(f): + """Retrieves service type from function.""" + return getattr(f, 'service_type', None) + + +def pretty_choice_list(l): + return ', '.join("'%s'" % i for i in l) + + +def exit(msg=''): + if msg: + print (msg, file=sys.stderr) + sys.exit(1) diff -Nru python-novaclient-2.16.0/novaclient/openstack/common/gettextutils.py python-novaclient-2.17.0/novaclient/openstack/common/gettextutils.py --- python-novaclient-2.16.0/novaclient/openstack/common/gettextutils.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/openstack/common/gettextutils.py 2014-03-07 21:11:20.000000000 +0000 @@ -23,6 +23,7 @@ """ import copy +import functools import gettext import locale from logging import handlers @@ -35,6 +36,17 @@ _localedir = os.environ.get('novaclient'.upper() + '_LOCALEDIR') _t = gettext.translation('novaclient', localedir=_localedir, fallback=True) +# We use separate translation catalogs for each log level, so set up a +# mapping between the log level name and the translator. The domain +# for the log level is project_name + "-log-" + log_level so messages +# for each level end up in their own catalog. +_t_log_levels = dict( + (level, gettext.translation('novaclient' + '-log-' + level, + localedir=_localedir, + fallback=True)) + for level in ['info', 'warning', 'error', 'critical'] +) + _AVAILABLE_LANGUAGES = {} USE_LAZY = False @@ -60,6 +72,28 @@ return _t.ugettext(msg) +def _log_translation(msg, level): + """Build a single translation of a log message + """ + if USE_LAZY: + return Message(msg, domain='novaclient' + '-log-' + level) + else: + translator = _t_log_levels[level] + if six.PY3: + return translator.gettext(msg) + return translator.ugettext(msg) + +# Translators for log levels. +# +# The abbreviated names are meant to reflect the usual use of a short +# name like '_'. The "L" is for "log" and the other letter comes from +# the level. +_LI = functools.partial(_log_translation, level='info') +_LW = functools.partial(_log_translation, level='warning') +_LE = functools.partial(_log_translation, level='error') +_LC = functools.partial(_log_translation, level='critical') + + def install(domain, lazy=False): """Install a _() function using the given translation domain. @@ -118,7 +152,8 @@ and can be treated as such. """ - def __new__(cls, msgid, msgtext=None, params=None, domain='novaclient', *args): + def __new__(cls, msgid, msgtext=None, params=None, + domain='novaclient', *args): """Create a new Message object. In order for translation to work gettext requires a message ID, this @@ -297,9 +332,27 @@ list_identifiers = (getattr(localedata, 'list', None) or getattr(localedata, 'locale_identifiers')) locale_identifiers = list_identifiers() + for i in locale_identifiers: if find(i) is not None: language_list.append(i) + + # NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported + # locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they + # are perfectly legitimate locales: + # https://github.com/mitsuhiko/babel/issues/37 + # In Babel 1.3 they fixed the bug and they support these locales, but + # they are still not explicitly "listed" by locale_identifiers(). + # That is why we add the locales here explicitly if necessary so that + # they are listed as supported. + aliases = {'zh': 'zh_CN', + 'zh_Hant_HK': 'zh_HK', + 'zh_Hant': 'zh_TW', + 'fil': 'tl_PH'} + for (locale, alias) in six.iteritems(aliases): + if locale in language_list and alias not in language_list: + language_list.append(alias) + _AVAILABLE_LANGUAGES[domain] = language_list return copy.copy(language_list) diff -Nru python-novaclient-2.16.0/novaclient/openstack/common/importutils.py python-novaclient-2.17.0/novaclient/openstack/common/importutils.py --- python-novaclient-2.16.0/novaclient/openstack/common/importutils.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/openstack/common/importutils.py 2014-03-07 21:11:20.000000000 +0000 @@ -58,6 +58,13 @@ return sys.modules[import_str] +def import_versioned_module(version, submodule=None): + module = 'novaclient.v%s' % version + if submodule: + module = '.'.join((module, submodule)) + return import_module(module) + + def try_import(import_str, default=None): """Try to import a module and if it fails return default.""" try: diff -Nru python-novaclient-2.16.0/novaclient/openstack/common/__init__.py python-novaclient-2.17.0/novaclient/openstack/common/__init__.py --- python-novaclient-2.16.0/novaclient/openstack/common/__init__.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/openstack/common/__init__.py 2014-03-07 21:11:20.000000000 +0000 @@ -0,0 +1,2 @@ +import six +six.add_move(six.MovedModule('mox', 'mox', 'mox3.mox')) diff -Nru python-novaclient-2.16.0/novaclient/openstack/common/strutils.py python-novaclient-2.17.0/novaclient/openstack/common/strutils.py --- python-novaclient-2.16.0/novaclient/openstack/common/strutils.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/openstack/common/strutils.py 2014-03-07 21:11:20.000000000 +0000 @@ -17,6 +17,7 @@ System-level utilities and helper functions. """ +import math import re import sys import unicodedata @@ -26,16 +27,21 @@ from novaclient.openstack.common.gettextutils import _ -# Used for looking up extensions of text -# to their 'multiplied' byte amount -BYTE_MULTIPLIERS = { - '': 1, - 't': 1024 ** 4, - 'g': 1024 ** 3, - 'm': 1024 ** 2, - 'k': 1024, +UNIT_PREFIX_EXPONENT = { + 'k': 1, + 'K': 1, + 'Ki': 1, + 'M': 2, + 'Mi': 2, + 'G': 3, + 'Gi': 3, + 'T': 4, + 'Ti': 4, +} +UNIT_SYSTEM_INFO = { + 'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')), + 'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')), } -BYTE_REGEX = re.compile(r'(^-?\d+)(\D*)') TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes') FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no') @@ -102,7 +108,7 @@ :raises TypeError: If text is not an instance of str """ if not isinstance(text, six.string_types): - raise TypeError(_("%s can't be decoded") % type(text)) + raise TypeError("%s can't be decoded" % type(text)) if isinstance(text, six.text_type): return text @@ -145,7 +151,7 @@ :raises TypeError: If text is not an instance of str """ if not isinstance(text, six.string_types): - raise TypeError(_("%s can't be encoded") % type(text)) + raise TypeError("%s can't be encoded" % type(text)) if not incoming: incoming = (sys.stdin.encoding or @@ -167,34 +173,50 @@ return text -def to_bytes(text, default=0): - """Converts a string into an integer of bytes. +def string_to_bytes(text, unit_system='IEC', return_int=False): + """Converts a string into an float representation of bytes. + + The units supported for IEC :: + + Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it) + KB, KiB, MB, MiB, GB, GiB, TB, TiB + + The units supported for SI :: + + kb(it), Mb(it), Gb(it), Tb(it) + kB, MB, GB, TB - Looks at the last characters of the text to determine - what conversion is needed to turn the input text into a byte number. - Supports "B, K(B), M(B), G(B), and T(B)". (case insensitive) + Note that the SI unit system does not support capital letter 'K' :param text: String input for bytes size conversion. - :param default: Default return value when text is blank. + :param unit_system: Unit system for byte size conversion. + :param return_int: If True, returns integer representation of text + in bytes. (default: decimal) + :returns: Numerical representation of text in bytes. + :raises ValueError: If text has an invalid value. """ - match = BYTE_REGEX.search(text) + try: + base, reg_ex = UNIT_SYSTEM_INFO[unit_system] + except KeyError: + msg = _('Invalid unit system: "%s"') % unit_system + raise ValueError(msg) + match = reg_ex.match(text) if match: - magnitude = int(match.group(1)) - mult_key_org = match.group(2) - if not mult_key_org: - return magnitude - elif text: + magnitude = float(match.group(1)) + unit_prefix = match.group(2) + if match.group(3) in ['b', 'bit']: + magnitude /= 8 + else: msg = _('Invalid string format: %s') % text - raise TypeError(msg) + raise ValueError(msg) + if not unit_prefix: + res = magnitude else: - return default - mult_key = mult_key_org.lower().replace('b', '', 1) - multiplier = BYTE_MULTIPLIERS.get(mult_key) - if multiplier is None: - msg = _('Unknown byte multiplier: %s') % mult_key_org - raise TypeError(msg) - return magnitude * multiplier + res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix]) + if return_int: + return int(math.ceil(res)) + return res def to_slug(value, incoming=None, errors="strict"): diff -Nru python-novaclient-2.16.0/novaclient/openstack/common/timeutils.py python-novaclient-2.17.0/novaclient/openstack/common/timeutils.py --- python-novaclient-2.16.0/novaclient/openstack/common/timeutils.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/openstack/common/timeutils.py 2014-03-07 21:11:20.000000000 +0000 @@ -114,7 +114,7 @@ def iso8601_from_timestamp(timestamp): - """Returns a iso8601 formated date from timestamp.""" + """Returns a iso8601 formatted date from timestamp.""" return isotime(datetime.datetime.utcfromtimestamp(timestamp)) diff -Nru python-novaclient-2.16.0/novaclient/tests/v1_1/contrib/fakes.py python-novaclient-2.17.0/novaclient/tests/v1_1/contrib/fakes.py --- python-novaclient-2.16.0/novaclient/tests/v1_1/contrib/fakes.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/tests/v1_1/contrib/fakes.py 2014-03-07 21:11:21.000000000 +0000 @@ -134,3 +134,14 @@ def delete_os_assisted_volume_snapshots_x(self, **kw): return (202, {}, {}) + + def post_os_server_external_events(self, **kw): + return (200, {}, {'events': [ + {'name': 'test-event', + 'status': 'completed', + 'tag': 'tag', + 'server_uuid': 'fake-uuid1'}, + {'name': 'test-event', + 'status': 'completed', + 'tag': 'tag', + 'server_uuid': 'fake-uuid2'}]}) diff -Nru python-novaclient-2.16.0/novaclient/tests/v1_1/contrib/test_server_external_events.py python-novaclient-2.17.0/novaclient/tests/v1_1/contrib/test_server_external_events.py --- python-novaclient-2.16.0/novaclient/tests/v1_1/contrib/test_server_external_events.py 1970-01-01 00:00:00.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/tests/v1_1/contrib/test_server_external_events.py 2014-03-07 21:11:21.000000000 +0000 @@ -0,0 +1,44 @@ +# Copyright (C) 2014, Red Hat, Inc. +# +# 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. + +""" +External event triggering for servers, not to be used by users. +""" + +from novaclient import extension +from novaclient.tests import utils +from novaclient.tests.v1_1.contrib import fakes +from novaclient.v1_1.contrib import server_external_events as ext_events + + +extensions = [ + extension.Extension(ext_events.__name__.split(".")[-1], + ext_events), +] +cs = fakes.FakeClient(extensions=extensions) + + +class ServerExternalEventsTestCase(utils.TestCase): + def test_external_event(self): + events = [{'server_uuid': 'fake-uuid1', + 'name': 'test-event', + 'status': 'completed', + 'tag': 'tag'}, + {'server_uuid': 'fake-uuid2', + 'name': 'test-event', + 'status': 'completed', + 'tag': 'tag'}] + result = cs.server_external_events.create(events) + self.assertEqual(events, result) + cs.assert_called('POST', '/os-server-external-events') diff -Nru python-novaclient-2.16.0/novaclient/tests/v1_1/fakes.py python-novaclient-2.17.0/novaclient/tests/v1_1/fakes.py --- python-novaclient-2.16.0/novaclient/tests/v1_1/fakes.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/tests/v1_1/fakes.py 2014-03-07 21:11:21.000000000 +0000 @@ -17,10 +17,10 @@ from datetime import datetime import six +from six.moves.urllib import parse from novaclient import client as base_client from novaclient import exceptions -from novaclient.openstack.common.py3kcompat import urlutils from novaclient.openstack.common import strutils from novaclient.tests import fakes from novaclient.tests import utils @@ -64,7 +64,7 @@ assert 'body' in kwargs # Call the method - args = urlutils.parse_qsl(urlutils.urlparse(url)[4]) + args = parse.parse_qsl(parse.urlparse(url)[4]) kwargs.update(args) munged_url = url.rsplit('?', 1)[0] munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_') @@ -1991,3 +1991,8 @@ "updated_at": "2012-10-29T13:42:02.000000" }]} return (200, {}, migrations) + + def post_os_server_external_events(self, **kw): + return (200, {}, {'events': [ + {'name': 'network-changed', + 'server_uuid': '1234'}]}) diff -Nru python-novaclient-2.16.0/novaclient/tests/v1_1/test_shell.py python-novaclient-2.17.0/novaclient/tests/v1_1/test_shell.py --- python-novaclient-2.16.0/novaclient/tests/v1_1/test_shell.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/tests/v1_1/test_shell.py 2014-03-07 21:11:21.000000000 +0000 @@ -946,6 +946,12 @@ self.run_command('diagnostics sample-server') self.assert_called('GET', '/servers/1234/diagnostics') + def test_refresh_network(self): + self.run_command('refresh-network 1234') + self.assert_called('POST', '/os-server-external-events', + {'events': [{'name': 'network-changed', + 'server_uuid': 1234}]}) + def test_set_meta_set(self): self.run_command('meta 1234 set key1=val1 key2=val2') self.assert_called('POST', '/servers/1234/metadata', diff -Nru python-novaclient-2.16.0/novaclient/v1_1/contrib/migrations.py python-novaclient-2.17.0/novaclient/v1_1/contrib/migrations.py --- python-novaclient-2.16.0/novaclient/v1_1/contrib/migrations.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/v1_1/contrib/migrations.py 2014-03-07 21:11:20.000000000 +0000 @@ -14,9 +14,10 @@ migration interface """ +from six.moves.urllib import parse + from novaclient import base from novaclient.openstack.common.gettextutils import _ -from novaclient.openstack.common.py3kcompat import urlutils from novaclient import utils @@ -47,7 +48,7 @@ # order, then the encoded string will be consistent in Python 2&3. new_opts = sorted(opts.items(), key=lambda x: x[0]) - query_string = "?%s" % urlutils.urlencode(new_opts) if new_opts else "" + query_string = "?%s" % parse.urlencode(new_opts) if new_opts else "" return self._list("/os-migrations%s" % query_string, "migrations") diff -Nru python-novaclient-2.16.0/novaclient/v1_1/contrib/server_external_events.py python-novaclient-2.17.0/novaclient/v1_1/contrib/server_external_events.py --- python-novaclient-2.16.0/novaclient/v1_1/contrib/server_external_events.py 1970-01-01 00:00:00.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/v1_1/contrib/server_external_events.py 2014-03-07 21:11:21.000000000 +0000 @@ -0,0 +1,43 @@ +# Copyright (C) 2014, Red Hat, Inc. +# +# 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. + +""" +External event triggering for servers, not to be used by users. +""" + +from novaclient import base + + +class Event(base.Resource): + def __repr__(self): + return "" % self.name + + +class ServerExternalEventManager(base.Manager): + resource_class = Event + + def create(self, events): + """Create one or more server events. + + :param:events: A list of dictionaries containing 'server_uuid', 'name', + 'status', and 'tag' (which may be absent) + """ + + body = {'events': events} + return self._create('/os-server-external-events', body, 'events', + return_raw=True) + + +manager_class = ServerExternalEventManager +name = 'server_external_events' diff -Nru python-novaclient-2.16.0/novaclient/v1_1/flavors.py python-novaclient-2.17.0/novaclient/v1_1/flavors.py --- python-novaclient-2.16.0/novaclient/v1_1/flavors.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/v1_1/flavors.py 2014-03-07 21:11:20.000000000 +0000 @@ -16,10 +16,11 @@ Flavor interface. """ +from six.moves.urllib import parse + from novaclient import base from novaclient import exceptions from novaclient.openstack.common.gettextutils import _ -from novaclient.openstack.common.py3kcompat import urlutils from novaclient.openstack.common import strutils from novaclient import utils @@ -112,7 +113,7 @@ # and flavors from their own projects only. if not is_public: qparams['is_public'] = is_public - query_string = "?%s" % urlutils.urlencode(qparams) if qparams else "" + query_string = "?%s" % parse.urlencode(qparams) if qparams else "" detail = "" if detailed: diff -Nru python-novaclient-2.16.0/novaclient/v1_1/floating_ip_dns.py python-novaclient-2.17.0/novaclient/v1_1/floating_ip_dns.py --- python-novaclient-2.16.0/novaclient/v1_1/floating_ip_dns.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/v1_1/floating_ip_dns.py 2014-03-07 21:11:20.000000000 +0000 @@ -13,8 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. +from six.moves.urllib import parse + from novaclient import base -from novaclient.openstack.common.py3kcompat import urlutils def _quote_domain(domain): @@ -24,7 +25,7 @@ but Routes tends to choke on them, so we need an extra level of by-hand quoting here. """ - return urlutils.quote(domain.replace('.', '%2E')) + return parse.quote(domain.replace('.', '%2E')) class FloatingIPDNSDomain(base.Resource): @@ -101,7 +102,7 @@ def get_for_ip(self, domain, ip): """Return a list of entries for the given domain and ip or name.""" qparams = {'ip': ip} - params = "?%s" % urlutils.urlencode(qparams) + params = "?%s" % parse.urlencode(qparams) return self._list("/os-floating-ip-dns/%s/entries%s" % (_quote_domain(domain), params), diff -Nru python-novaclient-2.16.0/novaclient/v1_1/hypervisors.py python-novaclient-2.17.0/novaclient/v1_1/hypervisors.py --- python-novaclient-2.16.0/novaclient/v1_1/hypervisors.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/v1_1/hypervisors.py 2014-03-07 21:11:20.000000000 +0000 @@ -17,8 +17,9 @@ Hypervisors interface (1.1 extension). """ +from six.moves.urllib import parse + from novaclient import base -from novaclient.openstack.common.py3kcompat import urlutils class Hypervisor(base.Resource): @@ -48,7 +49,7 @@ """ target = 'servers' if servers else 'search' url = ('/os-hypervisors/%s/%s' % - (urlutils.quote(hypervisor_match, safe=''), target)) + (parse.quote(hypervisor_match, safe=''), target)) return self._list(url, 'hypervisors') def get(self, hypervisor): diff -Nru python-novaclient-2.16.0/novaclient/v1_1/images.py python-novaclient-2.17.0/novaclient/v1_1/images.py --- python-novaclient-2.16.0/novaclient/v1_1/images.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/v1_1/images.py 2014-03-07 21:11:20.000000000 +0000 @@ -15,8 +15,10 @@ """ Image interface. """ + +from six.moves.urllib import parse + from novaclient import base -from novaclient.openstack.common.py3kcompat import urlutils class Image(base.Resource): @@ -63,7 +65,7 @@ detail = '/detail' if limit: params['limit'] = int(limit) - query = '?%s' % urlutils.urlencode(params) if params else '' + query = '?%s' % parse.urlencode(params) if params else '' return self._list('/images%s%s' % (detail, query), 'images') def delete(self, image): diff -Nru python-novaclient-2.16.0/novaclient/v1_1/limits.py python-novaclient-2.17.0/novaclient/v1_1/limits.py --- python-novaclient-2.16.0/novaclient/v1_1/limits.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/v1_1/limits.py 2014-03-07 21:11:20.000000000 +0000 @@ -12,8 +12,9 @@ # License for the specific language governing permissions and limitations # under the License. +from six.moves.urllib import parse + from novaclient import base -from novaclient.openstack.common.py3kcompat import urlutils class Limits(base.Resource): @@ -94,6 +95,6 @@ opts['reserved'] = 1 if tenant_id: opts['tenant_id'] = tenant_id - query_string = "?%s" % urlutils.urlencode(opts) if opts else "" + query_string = "?%s" % parse.urlencode(opts) if opts else "" return self._get("/limits%s" % query_string, "limits") diff -Nru python-novaclient-2.16.0/novaclient/v1_1/security_groups.py python-novaclient-2.17.0/novaclient/v1_1/security_groups.py --- python-novaclient-2.16.0/novaclient/v1_1/security_groups.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/v1_1/security_groups.py 2014-03-07 21:11:20.000000000 +0000 @@ -18,9 +18,9 @@ """ import six +from six.moves.urllib import parse from novaclient import base -from novaclient.openstack.common.py3kcompat import urlutils class SecurityGroup(base.Resource): @@ -90,7 +90,7 @@ qparams = dict((k, v) for (k, v) in six.iteritems(search_opts) if v) - query_string = '?%s' % urlutils.urlencode(qparams) if qparams else '' + query_string = '?%s' % parse.urlencode(qparams) if qparams else '' return self._list('/os-security-groups%s' % query_string, 'security_groups') diff -Nru python-novaclient-2.16.0/novaclient/v1_1/servers.py python-novaclient-2.17.0/novaclient/v1_1/servers.py --- python-novaclient-2.16.0/novaclient/v1_1/servers.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/v1_1/servers.py 2014-03-07 21:11:20.000000000 +0000 @@ -22,10 +22,10 @@ import base64 import six +from six.moves.urllib import parse from novaclient import base from novaclient import crypto -from novaclient.openstack.common.py3kcompat import urlutils from novaclient.openstack.common import strutils from novaclient.v1_1.security_groups import SecurityGroup @@ -573,7 +573,7 @@ # order, then the encoded string will be consistent in Python 2&3. if qparams: new_qparams = sorted(qparams.items(), key=lambda x: x[0]) - query_string = "?%s" % urlutils.urlencode(new_qparams) + query_string = "?%s" % parse.urlencode(new_qparams) else: query_string = "" diff -Nru python-novaclient-2.16.0/novaclient/v1_1/shell.py python-novaclient-2.17.0/novaclient/v1_1/shell.py --- python-novaclient-2.16.0/novaclient/v1_1/shell.py 2014-02-26 16:28:52.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/v1_1/shell.py 2014-03-07 21:11:21.000000000 +0000 @@ -1366,6 +1366,16 @@ utils.print_dict(cs.servers.diagnostics(server)[1], wrap=80) +@utils.arg('server', metavar='', + help=_('Name or ID of a server for which the network cache should ' + 'be refreshed from neutron (Admin only).')) +def do_refresh_network(cs, args): + """Refresh server network information.""" + server = _find_server(cs, args.server) + cs.server_external_events.create([{'server_uuid': server.id, + 'name': 'network-changed'}]) + + @utils.arg('server', metavar='', help=_('Name or ID of server.')) def do_root_password(cs, args): """ diff -Nru python-novaclient-2.16.0/novaclient/v1_1/volumes.py python-novaclient-2.17.0/novaclient/v1_1/volumes.py --- python-novaclient-2.16.0/novaclient/v1_1/volumes.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/v1_1/volumes.py 2014-03-07 21:11:20.000000000 +0000 @@ -18,9 +18,9 @@ """ import six +from six.moves.urllib import parse from novaclient import base -from novaclient.openstack.common.py3kcompat import urlutils class Volume(base.Resource): @@ -89,7 +89,7 @@ qparams = dict((k, v) for (k, v) in six.iteritems(search_opts) if v) - query_string = '?%s' % urlutils.urlencode(qparams) if qparams else '' + query_string = '?%s' % parse.urlencode(qparams) if qparams else '' if detailed is True: return self._list("/volumes/detail%s" % query_string, "volumes") diff -Nru python-novaclient-2.16.0/novaclient/v3/hypervisors.py python-novaclient-2.17.0/novaclient/v3/hypervisors.py --- python-novaclient-2.16.0/novaclient/v3/hypervisors.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/v3/hypervisors.py 2014-03-07 21:11:20.000000000 +0000 @@ -17,7 +17,8 @@ Hypervisors interface """ -from novaclient.openstack.common.py3kcompat import urlutils +from six.moves.urllib import parse + from novaclient.v1_1 import hypervisors @@ -35,7 +36,7 @@ :param servers: If True, server information is also retrieved. """ url = ('/os-hypervisors/search?query=%s' % - urlutils.quote(hypervisor_match, safe='')) + parse.quote(hypervisor_match, safe='')) return self._list(url, 'hypervisors') def servers(self, hypervisor): diff -Nru python-novaclient-2.16.0/novaclient/v3/images.py python-novaclient-2.17.0/novaclient/v3/images.py --- python-novaclient-2.16.0/novaclient/v3/images.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/v3/images.py 2014-03-07 21:11:20.000000000 +0000 @@ -16,8 +16,10 @@ """ Image interface. """ + +from six.moves.urllib import parse + from novaclient import base -from novaclient.openstack.common.py3kcompat import urlutils from novaclient.openstack.common import strutils @@ -100,5 +102,5 @@ detail = '/detail' if limit: params['limit'] = int(limit) - query = '?%s' % urlutils.urlencode(params) if params else '' + query = '?%s' % parse.urlencode(params) if params else '' return self._list('/v1/images%s%s' % (detail, query), 'images') diff -Nru python-novaclient-2.16.0/novaclient/v3/servers.py python-novaclient-2.17.0/novaclient/v3/servers.py --- python-novaclient-2.16.0/novaclient/v3/servers.py 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/novaclient/v3/servers.py 2014-03-07 21:11:20.000000000 +0000 @@ -22,10 +22,10 @@ import base64 import six +from six.moves.urllib import parse from novaclient import base from novaclient import crypto -from novaclient.openstack.common.py3kcompat import urlutils from novaclient.openstack.common import strutils REBOOT_SOFT, REBOOT_HARD = 'SOFT', 'HARD' @@ -507,7 +507,7 @@ # order, then the encoded string will be consistent in Python 2&3. if qparams: new_qparams = sorted(qparams.items(), key=lambda x: x[0]) - query_string = "?%s" % urlutils.urlencode(new_qparams) + query_string = "?%s" % parse.urlencode(new_qparams) else: query_string = "" diff -Nru python-novaclient-2.16.0/openstack-common.conf python-novaclient-2.17.0/openstack-common.conf --- python-novaclient-2.16.0/openstack-common.conf 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/openstack-common.conf 2014-03-07 21:11:20.000000000 +0000 @@ -1,12 +1,10 @@ [DEFAULT] # The list of modules to copy from openstack-common -module=install_venv_common module=jsonutils module=strutils module=timeutils module=uuidutils -module=py3kcompat module=apiclient module=importutils module=cliutils diff -Nru python-novaclient-2.16.0/PKG-INFO python-novaclient-2.17.0/PKG-INFO --- python-novaclient-2.16.0/PKG-INFO 2014-02-26 16:29:29.000000000 +0000 +++ python-novaclient-2.17.0/PKG-INFO 2014-03-07 21:11:56.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: python-novaclient -Version: 2.16.0 +Version: 2.17.0 Summary: Client library for OpenStack Compute API Home-page: https://git.openstack.org/cgit/openstack/python-novaclient Author: OpenStack @@ -154,6 +154,7 @@ rate-limits Print a list of rate limits for a user reboot Reboot a server. rebuild Shutdown, re-image, and re-boot a server. + refresh-network Refresh server network information. remove-fixed-ip Remove an IP address from a server. remove-floating-ip Remove a floating IP address from a server. rename Rename a server. @@ -274,3 +275,6 @@ Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.3 diff -Nru python-novaclient-2.16.0/python_novaclient.egg-info/PKG-INFO python-novaclient-2.17.0/python_novaclient.egg-info/PKG-INFO --- python-novaclient-2.16.0/python_novaclient.egg-info/PKG-INFO 2014-02-26 16:29:29.000000000 +0000 +++ python-novaclient-2.17.0/python_novaclient.egg-info/PKG-INFO 2014-03-07 21:11:56.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: python-novaclient -Version: 2.16.0 +Version: 2.17.0 Summary: Client library for OpenStack Compute API Home-page: https://git.openstack.org/cgit/openstack/python-novaclient Author: OpenStack @@ -154,6 +154,7 @@ rate-limits Print a list of rate limits for a user reboot Reboot a server. rebuild Shutdown, re-image, and re-boot a server. + refresh-network Refresh server network information. remove-fixed-ip Remove an IP address from a server. remove-floating-ip Remove a floating IP address from a server. rename Rename a server. @@ -274,3 +275,6 @@ Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.3 diff -Nru python-novaclient-2.16.0/python_novaclient.egg-info/requires.txt python-novaclient-2.17.0/python_novaclient.egg-info/requires.txt --- python-novaclient-2.16.0/python_novaclient.egg-info/requires.txt 2014-02-26 16:29:29.000000000 +0000 +++ python-novaclient-2.17.0/python_novaclient.egg-info/requires.txt 2014-03-07 21:11:56.000000000 +0000 @@ -3,5 +3,5 @@ PrettyTable>=0.7,<0.8 requests>=1.1 simplejson>=2.0.9 -six>=1.4.1 +six>=1.5.2 Babel>=1.3 \ No newline at end of file diff -Nru python-novaclient-2.16.0/python_novaclient.egg-info/SOURCES.txt python-novaclient-2.17.0/python_novaclient.egg-info/SOURCES.txt --- python-novaclient-2.16.0/python_novaclient.egg-info/SOURCES.txt 2014-02-26 16:29:29.000000000 +0000 +++ python-novaclient-2.17.0/python_novaclient.egg-info/SOURCES.txt 2014-03-07 21:11:56.000000000 +0000 @@ -102,6 +102,7 @@ novaclient/tests/v1_1/contrib/test_instance_actions.py novaclient/tests/v1_1/contrib/test_list_extensions.py novaclient/tests/v1_1/contrib/test_migrations.py +novaclient/tests/v1_1/contrib/test_server_external_events.py novaclient/tests/v1_1/contrib/test_tenant_networks.py novaclient/tests/v3/__init__.py novaclient/tests/v3/fakes.py @@ -166,6 +167,7 @@ novaclient/v1_1/contrib/list_extensions.py novaclient/v1_1/contrib/metadata_extensions.py novaclient/v1_1/contrib/migrations.py +novaclient/v1_1/contrib/server_external_events.py novaclient/v1_1/contrib/tenant_networks.py novaclient/v3/__init__.py novaclient/v3/agents.py diff -Nru python-novaclient-2.16.0/README.rst python-novaclient-2.17.0/README.rst --- python-novaclient-2.16.0/README.rst 2014-02-26 16:28:51.000000000 +0000 +++ python-novaclient-2.17.0/README.rst 2014-03-07 21:11:21.000000000 +0000 @@ -146,6 +146,7 @@ rate-limits Print a list of rate limits for a user reboot Reboot a server. rebuild Shutdown, re-image, and re-boot a server. + refresh-network Refresh server network information. remove-fixed-ip Remove an IP address from a server. remove-floating-ip Remove a floating IP address from a server. rename Rename a server. diff -Nru python-novaclient-2.16.0/requirements.txt python-novaclient-2.17.0/requirements.txt --- python-novaclient-2.16.0/requirements.txt 2014-02-26 16:28:52.000000000 +0000 +++ python-novaclient-2.17.0/requirements.txt 2014-03-07 21:11:20.000000000 +0000 @@ -4,5 +4,5 @@ PrettyTable>=0.7,<0.8 requests>=1.1 simplejson>=2.0.9 -six>=1.4.1 +six>=1.5.2 Babel>=1.3 diff -Nru python-novaclient-2.16.0/setup.cfg python-novaclient-2.17.0/setup.cfg --- python-novaclient-2.16.0/setup.cfg 2014-02-26 16:29:29.000000000 +0000 +++ python-novaclient-2.17.0/setup.cfg 2014-03-07 21:11:56.000000000 +0000 @@ -16,6 +16,9 @@ License :: OSI Approved :: Apache Software License Operating System :: OS Independent Programming Language :: Python + Programming Language :: Python :: 2.6 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3.3 [files] packages =