python-glanceclient-0.12.0/0000775000175300017540000000000012241451524016676 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/setup.py0000664000175300017540000000143212241451455020413 0ustar jenkinsjenkins00000000000000#!/usr/bin/env python # Copyright (c) 2013 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. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools setuptools.setup( setup_requires=['pbr>=0.5.21,<1.0'], pbr=True) python-glanceclient-0.12.0/.coveragerc0000664000175300017540000000015112241451455021017 0ustar jenkinsjenkins00000000000000[run] branch = True source = glanceclient omit = glanceclient/openstack/* [report] ignore-errors = True python-glanceclient-0.12.0/HACKING.rst0000664000175300017540000001377312241451455020512 0ustar jenkinsjenkins00000000000000Glance Style Commandments ========================= - Step 1: Read http://www.python.org/dev/peps/pep-0008/ - Step 2: Read http://www.python.org/dev/peps/pep-0008/ again - Step 3: Read on General ------- - Put two newlines between top-level code (funcs, classes, etc) - Put one newline between methods in classes and anywhere else - Do not write "except:", use "except Exception:" at the very least - Include your name with TODOs as in "#TODO(termie)" - Do not name anything the same name as a built-in or reserved word Imports ------- - Do not make relative imports - Order your imports by the full module path - Organize your imports according to the following template Example:: # vim: tabstop=4 shiftwidth=4 softtabstop=4 {{stdlib imports in human alphabetical order}} \n {{third-party lib imports in human alphabetical order}} \n {{glance imports in human alphabetical order}} \n \n {{begin your code}} Human Alphabetical Order Examples --------------------------------- Example:: import httplib import logging import random import StringIO import time import unittest import eventlet import webob.exc import glance.api.middleware from glance.api import images from glance.auth import users import glance.common from glance.endpoint import cloud from glance import test Docstrings ---------- Docstrings are required for all functions and methods. Docstrings should ONLY use triple-double-quotes (``"""``) Single-line docstrings should NEVER have extraneous whitespace between enclosing triple-double-quotes. **INCORRECT** :: """ There is some whitespace between the enclosing quotes :( """ **CORRECT** :: """There is no whitespace between the enclosing quotes :)""" Docstrings that span more than one line should look like this: Example:: """ Start the docstring on the line following the opening triple-double-quote If you are going to describe parameters and return values, use Sphinx, the appropriate syntax is as follows. :param foo: the foo parameter :param bar: the bar parameter :returns: return_type -- description of the return value :returns: description of the return value :raises: AttributeError, KeyError """ **DO NOT** leave an extra newline before the closing triple-double-quote. Dictionaries/Lists ------------------ If a dictionary (dict) or list object is longer than 80 characters, its items should be split with newlines. Embedded iterables should have their items indented. Additionally, the last item in the dictionary should have a trailing comma. This increases readability and simplifies future diffs. Example:: my_dictionary = { "image": { "name": "Just a Snapshot", "size": 2749573, "properties": { "user_id": 12, "arch": "x86_64", }, "things": [ "thing_one", "thing_two", ], "status": "ACTIVE", }, } Calling Methods --------------- Calls to methods 80 characters or longer should format each argument with newlines. This is not a requirement, but a guideline:: unnecessarily_long_function_name('string one', 'string two', kwarg1=constants.ACTIVE, kwarg2=['a', 'b', 'c']) Rather than constructing parameters inline, it is better to break things up:: list_of_strings = [ 'what_a_long_string', 'not as long', ] dict_of_numbers = { 'one': 1, 'two': 2, 'twenty four': 24, } object_one.call_a_method('string three', 'string four', kwarg1=list_of_strings, kwarg2=dict_of_numbers) Internationalization (i18n) Strings ----------------------------------- In order to support multiple languages, we have a mechanism to support automatic translations of exception and log strings. Example:: msg = _("An error occurred") raise HTTPBadRequest(explanation=msg) If you have a variable to place within the string, first internationalize the template string then do the replacement. Example:: msg = _("Missing parameter: %s") % ("flavor",) LOG.error(msg) If you have multiple variables to place in the string, use keyword parameters. This helps our translators reorder parameters when needed. Example:: msg = _("The server with id %(s_id)s has no key %(m_key)s") LOG.error(msg % {"s_id": "1234", "m_key": "imageId"}) Creating Unit Tests ------------------- For every new feature, unit tests should be created that both test and (implicitly) document the usage of said feature. If submitting a patch for a bug that had no unit test, a new passing unit test should be added. If a submitted bug fix does have a unit test, be sure to add a new one that fails without the patch and passes with the patch. Commit Messages --------------- Using a common format for commit messages will help keep our git history readable. Follow these guidelines: First, provide a brief summary of 50 characters or less. Summaries of greater then 72 characters will be rejected by the gate. The first line of the commit message should provide an accurate description of the change, not just a reference to a bug or blueprint. It must be followed by a single blank line. Following your brief summary, provide a more detailed description of the patch, manually wrapping the text at 72 characters. This description should provide enough detail that one does not have to refer to external resources to determine its high-level functionality. Once you use 'git review', two lines will be appended to the commit message: a blank line followed by a 'Change-Id'. This is important to correlate this commit with a specific review in Gerrit, and it should not be modified. For further information on constructing high quality commit messages, and how to split up commits into a series of changes, consult the project wiki: http://wiki.openstack.org/GitCommitMessages python-glanceclient-0.12.0/openstack-common.conf0000664000175300017540000000030012241451455023016 0ustar jenkinsjenkins00000000000000[DEFAULT] # The list of modules to copy from openstack-common module=gettextutils module=importutils module=strutils # The base module to hold the copy of openstack.common base=glanceclient python-glanceclient-0.12.0/test-requirements.txt0000664000175300017540000000017212241451455023142 0ustar jenkinsjenkins00000000000000hacking>=0.5.6,<0.7 coverage>=3.6 discover mox>=0.5.3 mock>=0.8.0 sphinx>=1.1.2 testrepository>=0.0.17 testtools>=0.9.32 python-glanceclient-0.12.0/python_glanceclient.egg-info/0000775000175300017540000000000012241451524024421 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/python_glanceclient.egg-info/SOURCES.txt0000664000175300017540000000371612241451524026314 0ustar jenkinsjenkins00000000000000.coveragerc .testr.conf AUTHORS ChangeLog HACKING.rst LICENSE MANIFEST.in README.rst openstack-common.conf requirements.txt run_tests.sh setup.cfg setup.py test-requirements.txt tox.ini doc/source/conf.py doc/source/index.rst doc/source/man/glance.rst glanceclient/__init__.py glanceclient/client.py glanceclient/exc.py glanceclient/shell.py glanceclient/common/__init__.py glanceclient/common/base.py glanceclient/common/exceptions.py glanceclient/common/http.py glanceclient/common/progressbar.py glanceclient/common/utils.py glanceclient/openstack/__init__.py glanceclient/openstack/common/__init__.py glanceclient/openstack/common/gettextutils.py glanceclient/openstack/common/importutils.py glanceclient/openstack/common/strutils.py glanceclient/v1/__init__.py glanceclient/v1/client.py glanceclient/v1/image_members.py glanceclient/v1/images.py glanceclient/v1/legacy_shell.py glanceclient/v1/shell.py glanceclient/v2/__init__.py glanceclient/v2/client.py glanceclient/v2/image_members.py glanceclient/v2/image_tags.py glanceclient/v2/images.py glanceclient/v2/schemas.py glanceclient/v2/shell.py python_glanceclient.egg-info/PKG-INFO python_glanceclient.egg-info/SOURCES.txt python_glanceclient.egg-info/dependency_links.txt python_glanceclient.egg-info/entry_points.txt python_glanceclient.egg-info/not-zip-safe python_glanceclient.egg-info/requires.txt python_glanceclient.egg-info/top_level.txt tests/__init__.py tests/test_base.py tests/test_exc.py tests/test_http.py tests/test_progressbar.py tests/test_shell.py tests/test_ssl.py tests/test_utils.py tests/utils.py tests/v1/__init__.py tests/v1/test_image_members.py tests/v1/test_images.py tests/v1/test_legacy_shell.py tests/v1/test_shell.py tests/v2/__init__.py tests/v2/test_images.py tests/v2/test_members.py tests/v2/test_schemas.py tests/v2/test_shell_v2.py tests/v2/test_tags.py tests/var/ca.crt tests/var/certificate.crt tests/var/expired-cert.crt tests/var/privatekey.key tests/var/wildcard-certificate.crt tools/with_venv.shpython-glanceclient-0.12.0/python_glanceclient.egg-info/PKG-INFO0000664000175300017540000000265612241451524025527 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: python-glanceclient Version: 0.12.0 Summary: OpenStack Image API Client Library Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Python bindings to the OpenStack Images API ============================================= This is a client library for Glance built on the OpenStack Images API. It provides a Python API (the ``glanceclient`` module) and a command-line tool (``glance``). This library fully supports the v1 Images API, while support for the v2 API is in progress. Development takes place via the usual OpenStack processes as outlined in the `OpenStack wiki `_. The master repository is on `GitHub `_. See release notes and more at ``_. Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 2.6 python-glanceclient-0.12.0/python_glanceclient.egg-info/not-zip-safe0000664000175300017540000000000112241451460026646 0ustar jenkinsjenkins00000000000000 python-glanceclient-0.12.0/python_glanceclient.egg-info/dependency_links.txt0000664000175300017540000000000112241451524030467 0ustar jenkinsjenkins00000000000000 python-glanceclient-0.12.0/python_glanceclient.egg-info/top_level.txt0000664000175300017540000000001512241451524027147 0ustar jenkinsjenkins00000000000000glanceclient python-glanceclient-0.12.0/python_glanceclient.egg-info/requires.txt0000664000175300017540000000013712241451524027022 0ustar jenkinsjenkins00000000000000pbr>=0.5.21,<1.0 PrettyTable>=0.6,<0.8 python-keystoneclient>=0.3.0 pyOpenSSL warlock>=1.0.1,<2python-glanceclient-0.12.0/python_glanceclient.egg-info/entry_points.txt0000664000175300017540000000006412241451524027717 0ustar jenkinsjenkins00000000000000[console_scripts] glance = glanceclient.shell:main python-glanceclient-0.12.0/tox.ini0000664000175300017540000000166512241451455020224 0ustar jenkinsjenkins00000000000000[tox] envlist = py26,py27,py33,pypy,pep8 [testenv] setenv = VIRTUAL_ENV={envdir} LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=C OS_STDOUT_NOCAPTURE=False OS_STDERR_NOCAPTURE=False deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = python setup.py testr --testr-args='{posargs}' [testenv:pep8] commands = flake8 [testenv:venv] commands = {posargs} [testenv:cover] commands = python setup.py testr --coverage --testr-args='{posargs}' [tox:jenkins] downloadcache = ~/cache/pip [flake8] # H233 Python 3.x incompatible use of print operator # H302 import only modules # H303 no wildcard import # H306 imports not in alphabetical orde # H404 multi line docstring should start with a summary # H501 Do not use locals() for string formatting ignore = F403,F841,F812,F821,H233,H302,H303,H306,H404,H501 show-source = True exclude = .venv,.tox,dist,doc,*egg,build python-glanceclient-0.12.0/.testr.conf0000664000175300017540000000023512241451455020767 0ustar jenkinsjenkins00000000000000[DEFAULT] test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list python-glanceclient-0.12.0/PKG-INFO0000664000175300017540000000265612241451524020004 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: python-glanceclient Version: 0.12.0 Summary: OpenStack Image API Client Library Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: Python bindings to the OpenStack Images API ============================================= This is a client library for Glance built on the OpenStack Images API. It provides a Python API (the ``glanceclient`` module) and a command-line tool (``glance``). This library fully supports the v1 Images API, while support for the v2 API is in progress. Development takes place via the usual OpenStack processes as outlined in the `OpenStack wiki `_. The master repository is on `GitHub `_. See release notes and more at ``_. Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 2.6 python-glanceclient-0.12.0/setup.cfg0000664000175300017540000000162512241451524020523 0ustar jenkinsjenkins00000000000000[metadata] name = python-glanceclient summary = OpenStack Image API Client Library description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://www.openstack.org/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 2.6 [files] packages = glanceclient [global] setup-hooks = pbr.hooks.setup_hook [entry_points] console_scripts = glance = glanceclient.shell:main [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 [upload_sphinx] upload-dir = doc/build/html [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 python-glanceclient-0.12.0/requirements.txt0000664000175300017540000000015212241451455022163 0ustar jenkinsjenkins00000000000000pbr>=0.5.21,<1.0 argparse PrettyTable>=0.6,<0.8 python-keystoneclient>=0.3.0 pyOpenSSL warlock>=1.0.1,<2 python-glanceclient-0.12.0/README.rst0000664000175300017540000000124012241451455020365 0ustar jenkinsjenkins00000000000000Python bindings to the OpenStack Images API ============================================= This is a client library for Glance built on the OpenStack Images API. It provides a Python API (the ``glanceclient`` module) and a command-line tool (``glance``). This library fully supports the v1 Images API, while support for the v2 API is in progress. Development takes place via the usual OpenStack processes as outlined in the `OpenStack wiki `_. The master repository is on `GitHub `_. See release notes and more at ``_. python-glanceclient-0.12.0/tests/0000775000175300017540000000000012241451524020040 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/tests/test_progressbar.py0000664000175300017540000000373012241451455024010 0ustar jenkinsjenkins00000000000000# 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 sys import StringIO import testtools from glanceclient.common import progressbar from tests import utils as test_utils class TestProgressBarWrapper(testtools.TestCase): def test_iter_iterator_display_progress_bar(self): size = 100 iterator = iter('X' * 100) saved_stdout = sys.stdout try: sys.stdout = output = test_utils.FakeTTYStdout() # Consume iterator. data = list(progressbar.VerboseIteratorWrapper(iterator, size)) self.assertEqual(data, ['X'] * 100) self.assertEqual( output.getvalue().strip(), '[%s>] 100%%' % ('=' * 29) ) finally: sys.stdout = saved_stdout def test_iter_file_display_progress_bar(self): size = 98304 file_obj = StringIO.StringIO('X' * size) saved_stdout = sys.stdout try: sys.stdout = output = test_utils.FakeTTYStdout() file_obj = progressbar.VerboseFileWrapper(file_obj, size) chunksize = 1024 chunk = file_obj.read(chunksize) while chunk: chunk = file_obj.read(chunksize) self.assertEqual( output.getvalue().strip(), '[%s>] 100%%' % ('=' * 29) ) finally: sys.stdout = saved_stdout python-glanceclient-0.12.0/tests/test_exc.py0000664000175300017540000000202112241451455022226 0ustar jenkinsjenkins00000000000000# 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 collections import testtools from glanceclient import exc FakeResponse = collections.namedtuple('HTTPResponse', ['status']) class TestHTTPExceptions(testtools.TestCase): def test_from_response(self): """exc.from_response should return instance of an HTTP exception.""" out = exc.from_response(FakeResponse(400)) self.assertTrue(isinstance(out, exc.HTTPBadRequest)) python-glanceclient-0.12.0/tests/test_shell.py0000664000175300017540000001530512241451455022567 0ustar jenkinsjenkins00000000000000# Copyright 2013 OpenStack Foundation # Copyright (C) 2013 Yahoo! Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # vim: tabstop=4 shiftwidth=4 softtabstop=4 import argparse import os import tempfile import mock from glanceclient import exc from glanceclient import shell as openstack_shell #NOTE (esheffield) Used for the schema caching tests from glanceclient.v2 import schemas as schemas import json from tests import utils DEFAULT_IMAGE_URL = 'http://127.0.0.1:5000/' DEFAULT_USERNAME = 'username' DEFAULT_PASSWORD = 'password' DEFAULT_TENANT_ID = 'tenant_id' DEFAULT_TENANT_NAME = 'tenant_name' DEFAULT_AUTH_URL = 'http://127.0.0.1:5000/v2.0/' DEFAULT_AUTH_TOKEN = ' 3bcc3d3a03f44e3d8377f9247b0ad155' TEST_SERVICE_URL = 'http://127.0.0.1:5000/' class ShellTest(utils.TestCase): def setUp(self): super(ShellTest, self).setUp() global _old_env fake_env = { 'OS_USERNAME': DEFAULT_USERNAME, 'OS_PASSWORD': DEFAULT_PASSWORD, 'OS_TENANT_NAME': DEFAULT_TENANT_NAME, 'OS_AUTH_URL': DEFAULT_AUTH_URL, 'OS_IMAGE_URL': DEFAULT_IMAGE_URL, 'OS_AUTH_TOKEN': DEFAULT_AUTH_TOKEN} _old_env, os.environ = os.environ, fake_env.copy() global shell, _shell, assert_called, assert_called_anytime _shell = openstack_shell.OpenStackImagesShell() shell = lambda cmd: _shell.main(cmd.split()) def tearDown(self): super(ShellTest, self).tearDown() global _old_env os.environ = _old_env def test_help_unknown_command(self): shell = openstack_shell.OpenStackImagesShell() argstr = 'help foofoo' self.assertRaises(exc.CommandError, shell.main, argstr.split()) def test_help(self): shell = openstack_shell.OpenStackImagesShell() argstr = 'help' actual = shell.main(argstr.split()) self.assertEqual(0, actual) def test_help_on_subcommand_error(self): self.assertRaises(exc.CommandError, shell, 'help bad') def test_get_base_parser(self): test_shell = openstack_shell.OpenStackImagesShell() actual_parser = test_shell.get_base_parser() description = 'Command-line interface to the OpenStack Images API.' expected = argparse.ArgumentParser( prog='glance', usage=None, description=description, version=None, conflict_handler='error', add_help=False, formatter_class=openstack_shell.HelpFormatter,) # NOTE(guochbo): Can't compare ArgumentParser instances directly # Convert ArgumentPaser to string first. self.assertEqual(str(expected), str(actual_parser)) def test_get_image_url_by_ipv6Addr_host(self): fake_args = lambda: None setattr(fake_args, 'os_image_url', None) setattr(fake_args, 'host', '2011:2013:1:f101::1') setattr(fake_args, 'use_ssl', True) setattr(fake_args, 'port', '9292') expected_image_url = 'https://[2011:2013:1:f101::1]:9292/' test_shell = openstack_shell.OpenStackImagesShell() targeted_image_url = test_shell._get_image_url(fake_args) self.assertEqual(expected_image_url, targeted_image_url) class ShellCacheSchemaTest(utils.TestCase): def setUp(self): super(ShellCacheSchemaTest, self).setUp() self.shell = openstack_shell.OpenStackImagesShell() self._mock_client_setup() def _mock_client_setup(self): self.schema_dict = { 'name': 'image', 'properties': { 'name': {'type': 'string', 'description': 'Name of image'}, }, } self.client = mock.Mock() self.client.schemas.get.return_value = schemas.Schema(self.schema_dict) def _make_args(self, args): class Args(): def __init__(self, entries): self.__dict__.update(entries) return Args(args) def _write_file(self, path, text): with file(path, 'w') as f: f.write(text) def _read_file(self, path): with file(path, 'r') as f: text = f.read() return text def test_cache_schema_gets_when_not_exists(self): cache_dir = tempfile.gettempdir() cache_file = cache_dir + '/image_schema.json' if os.path.exists(cache_file): os.remove(cache_file) options = { 'get_schema': False } with mock.patch.object(self.shell, '_get_versioned_client')\ as mocked_get_client: mocked_get_client.return_value = self.client self.shell._cache_schema(self._make_args(options), home_dir=cache_dir) self.assertTrue(os.path.exists(cache_file)) def test_cache_schema_gets_when_forced(self): cache_dir = tempfile.gettempdir() cache_file = cache_dir + '/image_schema.json' dummy_schema = 'my dummy schema' self._write_file(cache_file, dummy_schema) options = { 'get_schema': True } with mock.patch.object(self.shell, '_get_versioned_client') \ as mocked_get_client: mocked_get_client.return_value = self.client self.shell._cache_schema(self._make_args(options), home_dir=cache_dir) self.assertTrue(os.path.exists(cache_file)) text = self._read_file(cache_file) self.assertEqual(text, json.dumps(self.schema_dict)) def test_cache_schema_leaves_when_present_not_forced(self): cache_dir = tempfile.gettempdir() cache_file = cache_dir + '/image_schema.json' dummy_schema = 'my dummy schema' self._write_file(cache_file, dummy_schema) options = { 'get_schema': False } with mock.patch.object(self.shell, '_get_versioned_client') \ as mocked_get_client: mocked_get_client.return_value = self.client self.shell._cache_schema(self._make_args(options), home_dir=cache_dir) self.assertTrue(os.path.exists(cache_file)) text = self._read_file(cache_file) self.assertEqual(text, dummy_schema) python-glanceclient-0.12.0/tests/v2/0000775000175300017540000000000012241451524020367 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/tests/v2/test_members.py0000664000175300017540000000662212241451455023443 0ustar jenkinsjenkins00000000000000# 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 testtools import warlock from glanceclient.v2 import image_members from tests import utils IMAGE = '3a4560a1-e585-443e-9b39-553b46ec92d1' MEMBER = '11223344-5566-7788-9911-223344556677' fixtures = { '/v2/images/{image}/members'.format(image=IMAGE): { 'GET': ( {}, {'members': [ { 'image_id': IMAGE, 'member_id': MEMBER, }, ]}, ), 'POST': ( {}, { 'image_id': IMAGE, 'member_id': MEMBER, 'status': 'pending' } ) }, '/v2/images/{image}/members/{mem}'.format(image=IMAGE, mem=MEMBER): { 'DELETE': ( {}, None, ), 'PUT': ( {}, { 'image_id': IMAGE, 'member_id': MEMBER, 'status': 'accepted' } ), }, } fake_schema = {'name': 'member', 'properties': {'image_id': {}, 'member_id': {}}} FakeModel = warlock.model_factory(fake_schema) class TestController(testtools.TestCase): def setUp(self): super(TestController, self).setUp() self.api = utils.FakeAPI(fixtures) self.controller = image_members.Controller(self.api, FakeModel) def test_list_image_members(self): image_id = IMAGE #NOTE(iccha): cast to list since the controller returns a generator image_members = list(self.controller.list(image_id)) self.assertEqual(image_members[0].image_id, IMAGE) self.assertEqual(image_members[0].member_id, MEMBER) def test_delete_image_member(self): image_id = IMAGE member_id = MEMBER self.controller.delete(image_id, member_id) expect = [ ('DELETE', '/v2/images/{image}/members/{mem}'.format(image=IMAGE, mem=MEMBER), {}, None)] self.assertEqual(self.api.calls, expect) def test_update_image_members(self): image_id = IMAGE member_id = MEMBER status = 'accepted' image_member = self.controller.update(image_id, member_id, status) self.assertEqual(image_member.image_id, IMAGE) self.assertEqual(image_member.member_id, MEMBER) self.assertEqual(image_member.status, status) def test_create_image_members(self): image_id = IMAGE member_id = MEMBER status = 'pending' image_member = self.controller.create(image_id, member_id) self.assertEqual(image_member.image_id, IMAGE) self.assertEqual(image_member.member_id, MEMBER) self.assertEqual(image_member.status, status) python-glanceclient-0.12.0/tests/v2/test_shell_v2.py0000664000175300017540000003723312241451455023531 0ustar jenkinsjenkins00000000000000# Copyright 2013 OpenStack Foundation # Copyright (C) 2013 Yahoo! Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # vim: tabstop=4 shiftwidth=4 softtabstop=4 import StringIO import mock import testtools from glanceclient.common import http from glanceclient.common import progressbar from glanceclient.common import utils from glanceclient.v2 import shell as test_shell from tests import utils as test_utils class ShellV2Test(testtools.TestCase): def setUp(self): super(ShellV2Test, self).setUp() self._mock_utils() self.gc = self._mock_glance_client() def _make_args(self, args): #NOTE(venkatesh): this conversion from a dict to an object # is required because the test_shell.do_xxx(gc, args) methods # expects the args to be attributes of an object. If passed as # dict directly, it throws an AttributeError. class Args(): def __init__(self, entries): self.__dict__.update(entries) return Args(args) def _mock_glance_client(self): my_mocked_gc = mock.Mock() my_mocked_gc.schemas.return_value = 'test' my_mocked_gc.get.return_value = {} return my_mocked_gc def _mock_utils(self): utils.print_list = mock.Mock() utils.print_dict = mock.Mock() utils.save_image = mock.Mock() def _test_with_few_arguments(self, func, func_args, err_msg): with mock.patch.object(utils, 'exit') as mocked_utils_exit: mocked_utils_exit.return_value = '%s' % err_msg func(self.gc, func_args) mocked_utils_exit.assert_called_once_with(err_msg) def test_do_image_list(self): input = { 'page_size': 18, 'visibility': True, 'member_status': 'Fake', 'owner': 'test', 'checksum': 'fake_checksum', 'tag': 'fake tag' } args = self._make_args(input) with mock.patch.object(self.gc.images, 'list') as mocked_list: mocked_list.return_value = {} test_shell.do_image_list(self.gc, args) exp_img_filters = { 'owner': 'test', 'member_status': 'Fake', 'visibility': True, 'checksum': 'fake_checksum', 'tag': 'fake tag' } mocked_list.assert_called_once_with(page_size=18, filters=exp_img_filters) utils.print_list.assert_called_once_with({}, ['ID', 'Name']) def test_do_image_show(self): args = self._make_args({'id': 'pass', 'page_size': 18}) with mock.patch.object(self.gc.images, 'get') as mocked_list: ignore_fields = ['self', 'access', 'file', 'schema'] expect_image = dict([(field, field) for field in ignore_fields]) expect_image['id'] = 'pass' mocked_list.return_value = expect_image test_shell.do_image_show(self.gc, args) mocked_list.assert_called_once_with('pass') utils.print_dict.assert_called_once_with({'id': 'pass'}) def test_do_image_create_no_user_props(self): args = self._make_args({'name': 'IMG-01', 'disk_format': 'vhd', 'container_format': 'bare'}) with mock.patch.object(self.gc.images, 'create') as mocked_create: ignore_fields = ['self', 'access', 'file', 'schema'] expect_image = dict([(field, field) for field in ignore_fields]) expect_image['id'] = 'pass' expect_image['name'] = 'IMG-01' expect_image['disk_format'] = 'vhd' expect_image['container_format'] = 'bare' mocked_create.return_value = expect_image test_shell.do_image_create(self.gc, args) mocked_create.assert_called_once_with(name='IMG-01', disk_format='vhd', container_format='bare') utils.print_dict.assert_called_once_with({ 'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd', 'container_format': 'bare'}) def test_do_image_create_with_user_props(self): args = self._make_args({'name': 'IMG-01', 'property': ['myprop=myval']}) with mock.patch.object(self.gc.images, 'create') as mocked_create: ignore_fields = ['self', 'access', 'file', 'schema'] expect_image = dict([(field, field) for field in ignore_fields]) expect_image['id'] = 'pass' expect_image['name'] = 'IMG-01' expect_image['myprop'] = 'myval' mocked_create.return_value = expect_image test_shell.do_image_create(self.gc, args) mocked_create.assert_called_once_with(name='IMG-01', myprop='myval') utils.print_dict.assert_called_once_with({ 'id': 'pass', 'name': 'IMG-01', 'myprop': 'myval'}) def test_do_image_update_no_user_props(self): args = self._make_args({'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd', 'container_format': 'bare'}) with mock.patch.object(self.gc.images, 'update') as mocked_update: ignore_fields = ['self', 'access', 'file', 'schema'] expect_image = dict([(field, field) for field in ignore_fields]) expect_image['id'] = 'pass' expect_image['name'] = 'IMG-01' expect_image['disk_format'] = 'vhd' expect_image['container_format'] = 'bare' mocked_update.return_value = expect_image test_shell.do_image_update(self.gc, args) mocked_update.assert_called_once_with('pass', None, name='IMG-01', disk_format='vhd', container_format='bare') utils.print_dict.assert_called_once_with({ 'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd', 'container_format': 'bare'}) def test_do_image_update_with_user_props(self): args = self._make_args({'id': 'pass', 'name': 'IMG-01', 'property': ['myprop=myval']}) with mock.patch.object(self.gc.images, 'update') as mocked_update: ignore_fields = ['self', 'access', 'file', 'schema'] expect_image = dict([(field, field) for field in ignore_fields]) expect_image['id'] = 'pass' expect_image['name'] = 'IMG-01' expect_image['myprop'] = 'myval' mocked_update.return_value = expect_image test_shell.do_image_update(self.gc, args) mocked_update.assert_called_once_with('pass', None, name='IMG-01', myprop='myval') utils.print_dict.assert_called_once_with({ 'id': 'pass', 'name': 'IMG-01', 'myprop': 'myval'}) def test_do_image_update_with_remove_props(self): args = self._make_args({'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd', 'remove-property': ['container_format']}) with mock.patch.object(self.gc.images, 'update') as mocked_update: ignore_fields = ['self', 'access', 'file', 'schema'] expect_image = dict([(field, field) for field in ignore_fields]) expect_image['id'] = 'pass' expect_image['name'] = 'IMG-01' expect_image['disk_format'] = 'vhd' mocked_update.return_value = expect_image test_shell.do_image_update(self.gc, args) mocked_update.assert_called_once_with('pass', ['container_format'], name='IMG-01', disk_format='vhd') utils.print_dict.assert_called_once_with({ 'id': 'pass', 'name': 'IMG-01', 'disk_format': 'vhd'}) def test_do_explain(self): input = { 'page_size': 18, 'id': 'pass', 'schemas': 'test', 'model': 'test', } args = self._make_args(input) with mock.patch.object(utils, 'print_list'): test_shell.do_explain(self.gc, args) self.gc.schemas.get.assert_called_once_with('test') def test_image_upload(self): args = self._make_args({'id': 'IMG-01', 'file': 'test'}) with mock.patch.object(self.gc.images, 'upload') as mocked_upload: utils.get_data_file = mock.Mock(return_value='testfile') mocked_upload.return_value = None test_shell.do_image_upload(self.gc, args) mocked_upload.assert_called_once_with('IMG-01', 'testfile') def test_image_download(self): args = self._make_args( {'id': 'pass', 'file': 'test', 'progress': False}) with mock.patch.object(self.gc.images, 'data') as mocked_data: resp = test_utils.FakeResponse({}, StringIO.StringIO('CCC')) ret = mocked_data.return_value = http.ResponseBodyIterator(resp) test_shell.do_image_download(self.gc, args) mocked_data.assert_called_once_with('pass') utils.save_image.assert_called_once_with(ret, 'test') def test_image_download_with_progressbar(self): args = self._make_args( {'id': 'pass', 'file': 'test', 'progress': True}) with mock.patch.object(self.gc.images, 'data') as mocked_data: resp = test_utils.FakeResponse({}, StringIO.StringIO('CCC')) mocked_data.return_value = http.ResponseBodyIterator(resp) test_shell.do_image_download(self.gc, args) mocked_data.assert_called_once_with('pass') utils.save_image.assert_called_once_with(mock.ANY, 'test') self.assertIsInstance( utils.save_image.call_args[0][0], progressbar.VerboseIteratorWrapper ) def test_do_image_delete(self): args = self._make_args({'id': 'pass', 'file': 'test'}) with mock.patch.object(self.gc.images, 'delete') as mocked_delete: mocked_delete.return_value = 0 test_shell.do_image_delete(self.gc, args) mocked_delete.assert_called_once_with('pass') def test_do_member_list(self): args = self._make_args({'image_id': 'IMG-01'}) with mock.patch.object(self.gc.image_members, 'list') as mocked_list: mocked_list.return_value = {} test_shell.do_member_list(self.gc, args) mocked_list.assert_called_once_with('IMG-01') columns = ['Image ID', 'Member ID', 'Status'] utils.print_list.assert_called_once_with({}, columns) def test_do_member_create(self): args = self._make_args({'image_id': 'IMG-01', 'member_id': 'MEM-01'}) with mock.patch.object(self.gc.image_members, 'create') as mock_create: mock_create.return_value = {} test_shell.do_member_create(self.gc, args) mock_create.assert_called_once_with('IMG-01', 'MEM-01') columns = ['Image ID', 'Member ID', 'Status'] utils.print_list.assert_called_once_with([{}], columns) def test_do_member_create_with_few_arguments(self): args = self._make_args({'image_id': None, 'member_id': 'MEM-01'}) msg = 'Unable to create member. Specify image_id and member_id' self._test_with_few_arguments(func=test_shell.do_member_create, func_args=args, err_msg=msg) def test_do_member_update(self): input = { 'image_id': 'IMG-01', 'member_id': 'MEM-01', 'member_status': 'status', } args = self._make_args(input) with mock.patch.object(self.gc.image_members, 'update') as mock_update: mock_update.return_value = {} test_shell.do_member_update(self.gc, args) mock_update.assert_called_once_with('IMG-01', 'MEM-01', 'status') columns = ['Image ID', 'Member ID', 'Status'] utils.print_list.assert_called_once_with([{}], columns) def test_do_member_update_with_few_arguments(self): input = { 'image_id': 'IMG-01', 'member_id': 'MEM-01', 'member_status': None, } args = self._make_args(input) msg = 'Unable to update member. Specify image_id, member_id' \ ' and member_status' self._test_with_few_arguments(func=test_shell.do_member_update, func_args=args, err_msg=msg) def test_do_member_delete(self): args = self._make_args({'image_id': 'IMG-01', 'member_id': 'MEM-01'}) with mock.patch.object(self.gc.image_members, 'delete') as mock_delete: test_shell.do_member_delete(self.gc, args) mock_delete.assert_called_once_with('IMG-01', 'MEM-01') def test_do_member_delete_with_few_arguments(self): args = self._make_args({'image_id': None, 'member_id': 'MEM-01'}) msg = 'Unable to delete member. Specify image_id and member_id' self._test_with_few_arguments(func=test_shell.do_member_delete, func_args=args, err_msg=msg) def test_image_tag_update(self): args = self._make_args({'image_id': 'IMG-01', 'tag_value': 'tag01'}) with mock.patch.object(self.gc.image_tags, 'update') as mocked_update: self.gc.images.get = mock.Mock(return_value={}) mocked_update.return_value = None test_shell.do_image_tag_update(self.gc, args) mocked_update.assert_called_once_with('IMG-01', 'tag01') def test_image_tag_update_with_few_arguments(self): args = self._make_args({'image_id': None, 'tag_value': 'tag01'}) msg = 'Unable to update tag. Specify image_id and tag_value' self._test_with_few_arguments(func=test_shell.do_image_tag_update, func_args=args, err_msg=msg) def test_image_tag_delete(self): args = self._make_args({'image_id': 'IMG-01', 'tag_value': 'tag01'}) with mock.patch.object(self.gc.image_tags, 'delete') as mocked_delete: mocked_delete.return_value = None test_shell.do_image_tag_delete(self.gc, args) mocked_delete.assert_called_once_with('IMG-01', 'tag01') def test_image_tag_delete_with_few_arguments(self): args = self._make_args({'image_id': 'IMG-01', 'tag_value': None}) msg = 'Unable to delete tag. Specify image_id and tag_value' self._test_with_few_arguments(func=test_shell.do_image_tag_delete, func_args=args, err_msg=msg) python-glanceclient-0.12.0/tests/v2/test_images.py0000664000175300017540000005253612241451455023263 0ustar jenkinsjenkins00000000000000# 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 errno import testtools import warlock from glanceclient.v2 import images from tests import utils _CHKSUM = '93264c3edf5972c9f1cb309543d38a5c' _CHKSUM1 = '54264c3edf5972c9f1cb309453d38a46' _TAG1 = 'power' _TAG2 = '64bit' _BOGUS_ID = '63e7f218-29de-4477-abdc-8db7c9533188' _EVERYTHING_ID = '802cbbb7-0379-4c38-853f-37302b5e3d29' _OWNED_IMAGE_ID = 'a4963502-acc7-42ba-ad60-5aa0962b7faf' _OWNER_ID = '6bd473f0-79ae-40ad-a927-e07ec37b642f' _PRIVATE_ID = 'e33560a7-3964-4de5-8339-5a24559f99ab' _PUBLIC_ID = '857806e7-05b6-48e0-9d40-cb0e6fb727b9' _SHARED_ID = '331ac905-2a38-44c5-a83d-653db8f08313' _STATUS_REJECTED_ID = 'f3ea56ff-d7e4-4451-998c-1e3d33539c8e' fixtures = { '/v2/images?limit=%d' % images.DEFAULT_PAGE_SIZE: { 'GET': ( {}, {'images': [ { 'id': '3a4560a1-e585-443e-9b39-553b46ec92d1', 'name': 'image-1', }, { 'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', 'name': 'image-2', }, ]}, ), }, '/v2/images?limit=1': { 'GET': ( {}, { 'images': [ { 'id': '3a4560a1-e585-443e-9b39-553b46ec92d1', 'name': 'image-1', }, ], 'next': ('/v2/images?limit=1&' 'marker=3a4560a1-e585-443e-9b39-553b46ec92d1'), }, ), }, ('/v2/images?limit=1&marker=3a4560a1-e585-443e-9b39-553b46ec92d1'): { 'GET': ( {}, {'images': [ { 'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', 'name': 'image-2', }, ]}, ), }, '/v2/images/3a4560a1-e585-443e-9b39-553b46ec92d1': { 'GET': ( {}, { 'id': '3a4560a1-e585-443e-9b39-553b46ec92d1', 'name': 'image-1', }, ), 'PATCH': ( {}, '', ), }, '/v2/images/e7e59ff6-fa2e-4075-87d3-1a1398a07dc3': { 'GET': ( {}, { 'id': 'e7e59ff6-fa2e-4075-87d3-1a1398a07dc3', 'name': 'image-3', 'barney': 'rubble', 'george': 'jetson', }, ), 'PATCH': ( {}, '', ), }, '/v2/images': { 'POST': ( {}, { 'id': '3a4560a1-e585-443e-9b39-553b46ec92d1', 'name': 'image-1', }, ), }, 'v2/images/87b634c1-f893-33c9-28a9-e5673c99239a': { 'DELETE': ( {}, { 'id': '87b634c1-f893-33c9-28a9-e5673c99239a', }, ), }, '/v2/images/606b0e88-7c5a-4d54-b5bb-046105d4de6f/file': { 'PUT': ( {}, '', ), }, '/v2/images/5cc4bebc-db27-11e1-a1eb-080027cbe205/file': { 'GET': ( {}, 'A', ), }, '/v2/images/66fb18d6-db27-11e1-a1eb-080027cbe205/file': { 'GET': ( { 'content-md5': 'wrong' }, 'BB', ), }, '/v2/images/1b1c6366-dd57-11e1-af0f-02163e68b1d8/file': { 'GET': ( { 'content-md5': 'defb99e69a9f1f6e06f15006b1f166ae' }, 'CCC', ), }, '/v2/images?limit=%d&visibility=public' % images.DEFAULT_PAGE_SIZE: { 'GET': ( {}, {'images': [ { 'id': _PUBLIC_ID, 'harvey': 'lipshitz', }, ]}, ), }, '/v2/images?limit=%d&visibility=private' % images.DEFAULT_PAGE_SIZE: { 'GET': ( {}, {'images': [ { 'id': _PRIVATE_ID, }, ]}, ), }, '/v2/images?limit=%d&visibility=shared' % images.DEFAULT_PAGE_SIZE: { 'GET': ( {}, {'images': [ { 'id': _SHARED_ID, }, ]}, ), }, '/v2/images?limit=%d&member_status=rejected' % images.DEFAULT_PAGE_SIZE: { 'GET': ( {}, {'images': [ { 'id': _STATUS_REJECTED_ID, }, ]}, ), }, '/v2/images?limit=%d&member_status=pending' % images.DEFAULT_PAGE_SIZE: { 'GET': ( {}, {'images': []}, ), }, '/v2/images?owner=%s&limit=%d' % (_OWNER_ID, images.DEFAULT_PAGE_SIZE): { 'GET': ( {}, {'images': [ { 'id': _OWNED_IMAGE_ID, }, ]}, ), }, '/v2/images?owner=%s&limit=%d' % (_BOGUS_ID, images.DEFAULT_PAGE_SIZE): { 'GET': ( {}, {'images': []}, ), }, '/v2/images?owner=%s&limit=%d&member_status=pending&visibility=shared' % (_BOGUS_ID, images.DEFAULT_PAGE_SIZE): { 'GET': ( {}, {'images': [ { 'id': _EVERYTHING_ID, }, ]}, ), }, '/v2/images?checksum=%s&limit=%d' % (_CHKSUM, images.DEFAULT_PAGE_SIZE): { 'GET': ( {}, {'images': [ { 'id': '3a4560a1-e585-443e-9b39-553b46ec92d1', 'name': 'image-1', } ]}, ), }, '/v2/images?checksum=%s&limit=%d' % (_CHKSUM1, images.DEFAULT_PAGE_SIZE): { 'GET': ( {}, {'images': [ { 'id': '2a4560b2-e585-443e-9b39-553b46ec92d1', 'name': 'image-1', }, { 'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', 'name': 'image-2', }, ]}, ), }, '/v2/images?checksum=wrong&limit=%d' % images.DEFAULT_PAGE_SIZE: { 'GET': ( {}, {'images': []}, ), }, '/v2/images?limit=%d&tag=%s' % (images.DEFAULT_PAGE_SIZE, _TAG1): { 'GET': ( {}, {'images': [ { 'id': '3a4560a1-e585-443e-9b39-553b46ec92d1', 'name': 'image-1', } ]}, ), }, '/v2/images?limit=%d&tag=%s' % (images.DEFAULT_PAGE_SIZE, _TAG2): { 'GET': ( {}, {'images': [ { 'id': '2a4560b2-e585-443e-9b39-553b46ec92d1', 'name': 'image-1', }, { 'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', 'name': 'image-2', }, ]}, ), }, '/v2/images?limit=%d&tag=%s&tag=%s' % (images.DEFAULT_PAGE_SIZE, _TAG1, _TAG2): { 'GET': ( {}, {'images': [ { 'id': '2a4560b2-e585-443e-9b39-553b46ec92d1', 'name': 'image-1', } ]}, ), }, '/v2/images?limit=%d&tag=fake' % images.DEFAULT_PAGE_SIZE: { 'GET': ( {}, {'images': []}, ), }, } fake_schema = { 'name': 'image', 'properties': {'id': {}, 'name': {}}, 'additionalProperties': {'type': 'string'} } FakeModel = warlock.model_factory(fake_schema) class TestController(testtools.TestCase): def setUp(self): super(TestController, self).setUp() self.api = utils.FakeAPI(fixtures) self.controller = images.Controller(self.api, FakeModel) def test_list_images(self): #NOTE(bcwaldon): cast to list since the controller returns a generator images = list(self.controller.list()) self.assertEqual(images[0].id, '3a4560a1-e585-443e-9b39-553b46ec92d1') self.assertEqual(images[0].name, 'image-1') self.assertEqual(images[1].id, '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810') self.assertEqual(images[1].name, 'image-2') def test_list_images_paginated(self): #NOTE(bcwaldon): cast to list since the controller returns a generator images = list(self.controller.list(page_size=1)) self.assertEqual(images[0].id, '3a4560a1-e585-443e-9b39-553b46ec92d1') self.assertEqual(images[0].name, 'image-1') self.assertEqual(images[1].id, '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810') self.assertEqual(images[1].name, 'image-2') def test_list_images_visibility_public(self): filters = {'filters': dict([('visibility', 'public')])} images = list(self.controller.list(**filters)) self.assertEqual(images[0].id, _PUBLIC_ID) def test_list_images_visibility_private(self): filters = {'filters': dict([('visibility', 'private')])} images = list(self.controller.list(**filters)) self.assertEqual(images[0].id, _PRIVATE_ID) def test_list_images_visibility_shared(self): filters = {'filters': dict([('visibility', 'shared')])} images = list(self.controller.list(**filters)) self.assertEqual(images[0].id, _SHARED_ID) def test_list_images_member_status_rejected(self): filters = {'filters': dict([('member_status', 'rejected')])} images = list(self.controller.list(**filters)) self.assertEqual(images[0].id, _STATUS_REJECTED_ID) def test_list_images_for_owner(self): filters = {'filters': dict([('owner', _OWNER_ID)])} images = list(self.controller.list(**filters)) self.assertEqual(images[0].id, _OWNED_IMAGE_ID) def test_list_images_for_checksum_single_image(self): fake_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' filters = {'filters': dict([('checksum', _CHKSUM)])} images = list(self.controller.list(**filters)) self.assertEqual(1, len(images)) self.assertEqual(images[0].id, '%s' % fake_id) def test_list_images_for_checksum_multiple_images(self): fake_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1' fake_id2 = '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810' filters = {'filters': dict([('checksum', _CHKSUM1)])} images = list(self.controller.list(**filters)) self.assertEqual(2, len(images)) self.assertEqual(images[0].id, '%s' % fake_id1) self.assertEqual(images[1].id, '%s' % fake_id2) def test_list_images_for_wrong_checksum(self): filters = {'filters': dict([('checksum', 'wrong')])} images = list(self.controller.list(**filters)) self.assertEqual(0, len(images)) def test_list_images_for_bogus_owner(self): filters = {'filters': dict([('owner', _BOGUS_ID)])} images = list(self.controller.list(**filters)) self.assertEqual(images, []) def test_list_images_for_bunch_of_filters(self): filters = {'filters': dict([('owner', _BOGUS_ID), ('visibility', 'shared'), ('member_status', 'pending')])} images = list(self.controller.list(**filters)) self.assertEqual(images[0].id, _EVERYTHING_ID) def test_list_images_filters_encoding(self): filters = {"owner": u"ni\xf1o"} try: list(self.controller.list(filters=filters)) except KeyError: # NOTE(flaper87): It raises KeyError because there's # no fixture supporting this query: # /v2/images?owner=ni%C3%B1o&limit=20 # We just want to make sure filters are correctly encoded. pass self.assertEqual(filters["owner"], "ni\xc3\xb1o") def test_list_images_for_tag_single_image(self): img_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' filters = {'filters': dict([('tag', [_TAG1])])} images = list(self.controller.list(**filters)) self.assertEqual(1, len(images)) self.assertEqual(images[0].id, '%s' % img_id) pass def test_list_images_for_tag_multiple_images(self): img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1' img_id2 = '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810' filters = {'filters': dict([('tag', [_TAG2])])} images = list(self.controller.list(**filters)) self.assertEqual(2, len(images)) self.assertEqual(images[0].id, '%s' % img_id1) self.assertEqual(images[1].id, '%s' % img_id2) def test_list_images_for_multi_tags(self): img_id1 = '2a4560b2-e585-443e-9b39-553b46ec92d1' filters = {'filters': dict([('tag', [_TAG1, _TAG2])])} images = list(self.controller.list(**filters)) self.assertEqual(1, len(images)) self.assertEqual(images[0].id, '%s' % img_id1) def test_list_images_for_non_existent_tag(self): filters = {'filters': dict([('tag', ['fake'])])} images = list(self.controller.list(**filters)) self.assertEqual(0, len(images)) def test_get_image(self): image = self.controller.get('3a4560a1-e585-443e-9b39-553b46ec92d1') self.assertEqual(image.id, '3a4560a1-e585-443e-9b39-553b46ec92d1') self.assertEqual(image.name, 'image-1') def test_create_image(self): properties = { 'name': 'image-1' } image = self.controller.create(**properties) self.assertEqual(image.id, '3a4560a1-e585-443e-9b39-553b46ec92d1') self.assertEqual(image.name, 'image-1') def test_create_bad_additionalProperty_type(self): properties = { 'name': 'image-1', 'bad_prop': True, } with testtools.ExpectedException(TypeError): self.controller.create(**properties) def test_delete_image(self): self.controller.delete('87b634c1-f893-33c9-28a9-e5673c99239a') expect = [ ('DELETE', 'v2/images/87b634c1-f893-33c9-28a9-e5673c99239a', {}, None)] self.assertEqual(self.api.calls, expect) def test_data_upload(self): image_data = 'CCC' image_id = '606b0e88-7c5a-4d54-b5bb-046105d4de6f' self.controller.upload(image_id, image_data) expect = [('PUT', '/v2/images/%s/file' % image_id, {'Content-Type': 'application/octet-stream'}, image_data)] self.assertEqual(self.api.calls, expect) def test_data_without_checksum(self): body = self.controller.data('5cc4bebc-db27-11e1-a1eb-080027cbe205', do_checksum=False) body = ''.join([b for b in body]) self.assertEqual(body, 'A') body = self.controller.data('5cc4bebc-db27-11e1-a1eb-080027cbe205') body = ''.join([b for b in body]) self.assertEqual(body, 'A') def test_data_with_wrong_checksum(self): body = self.controller.data('66fb18d6-db27-11e1-a1eb-080027cbe205', do_checksum=False) body = ''.join([b for b in body]) self.assertEqual(body, 'BB') body = self.controller.data('66fb18d6-db27-11e1-a1eb-080027cbe205') try: body = ''.join([b for b in body]) self.fail('data did not raise an error.') except IOError as e: self.assertEqual(errno.EPIPE, e.errno) msg = 'was 9d3d9048db16a7eee539e93e3618cbe7 expected wrong' self.assertTrue(msg in str(e)) def test_data_with_checksum(self): body = self.controller.data('1b1c6366-dd57-11e1-af0f-02163e68b1d8', do_checksum=False) body = ''.join([b for b in body]) self.assertEqual(body, 'CCC') body = self.controller.data('1b1c6366-dd57-11e1-af0f-02163e68b1d8') body = ''.join([b for b in body]) self.assertEqual(body, 'CCC') def test_update_replace_prop(self): image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' params = {'name': 'pong'} image = self.controller.update(image_id, **params) expect_hdrs = { 'Content-Type': 'application/openstack-images-v2.1-json-patch', } expect_body = '[{"path": "/name", "value": "pong", "op": "replace"}]' expect = [ ('GET', '/v2/images/%s' % image_id, {}, None), ('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body), ('GET', '/v2/images/%s' % image_id, {}, None), ] self.assertEqual(self.api.calls, expect) self.assertEqual(image.id, image_id) #NOTE(bcwaldon): due to limitations of our fake api framework, the name # will not actually change - yet in real life it will... self.assertEqual(image.name, 'image-1') def test_update_add_prop(self): image_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' params = {'finn': 'human'} image = self.controller.update(image_id, **params) expect_hdrs = { 'Content-Type': 'application/openstack-images-v2.1-json-patch', } expect_body = '[{"path": "/finn", "value": "human", "op": "add"}]' expect = [ ('GET', '/v2/images/%s' % image_id, {}, None), ('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body), ('GET', '/v2/images/%s' % image_id, {}, None), ] self.assertEqual(self.api.calls, expect) self.assertEqual(image.id, image_id) #NOTE(bcwaldon): due to limitations of our fake api framework, the name # will not actually change - yet in real life it will... self.assertEqual(image.name, 'image-1') def test_update_remove_prop(self): image_id = 'e7e59ff6-fa2e-4075-87d3-1a1398a07dc3' remove_props = ['barney'] image = self.controller.update(image_id, remove_props) expect_hdrs = { 'Content-Type': 'application/openstack-images-v2.1-json-patch', } expect_body = '[{"path": "/barney", "op": "remove"}]' expect = [ ('GET', '/v2/images/%s' % image_id, {}, None), ('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body), ('GET', '/v2/images/%s' % image_id, {}, None), ] self.assertEqual(self.api.calls, expect) self.assertEqual(image.id, image_id) #NOTE(bcwaldon): due to limitations of our fake api framework, the name # will not actually change - yet in real life it will... self.assertEqual(image.name, 'image-3') def test_update_replace_remove_same_prop(self): image_id = 'e7e59ff6-fa2e-4075-87d3-1a1398a07dc3' # Updating a property takes precedence over removing a property params = {'barney': 'miller'} remove_props = ['barney'] image = self.controller.update(image_id, remove_props, **params) expect_hdrs = { 'Content-Type': 'application/openstack-images-v2.1-json-patch', } expect_body = '[{"path": "/barney", "value": "miller", ' \ '"op": "replace"}]' expect = [ ('GET', '/v2/images/%s' % image_id, {}, None), ('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body), ('GET', '/v2/images/%s' % image_id, {}, None), ] self.assertEqual(self.api.calls, expect) self.assertEqual(image.id, image_id) #NOTE(bcwaldon): due to limitations of our fake api framework, the name # will not actually change - yet in real life it will... self.assertEqual(image.name, 'image-3') def test_update_add_remove_same_prop(self): image_id = 'e7e59ff6-fa2e-4075-87d3-1a1398a07dc3' # Adding a property takes precedence over removing a property params = {'finn': 'human'} remove_props = ['finn'] image = self.controller.update(image_id, remove_props, **params) expect_hdrs = { 'Content-Type': 'application/openstack-images-v2.1-json-patch', } expect_body = '[{"path": "/finn", "value": "human", "op": "add"}]' expect = [ ('GET', '/v2/images/%s' % image_id, {}, None), ('PATCH', '/v2/images/%s' % image_id, expect_hdrs, expect_body), ('GET', '/v2/images/%s' % image_id, {}, None), ] self.assertEqual(self.api.calls, expect) self.assertEqual(image.id, image_id) #NOTE(bcwaldon): due to limitations of our fake api framework, the name # will not actually change - yet in real life it will... self.assertEqual(image.name, 'image-3') def test_update_bad_additionalProperty_type(self): image_id = 'e7e59ff6-fa2e-4075-87d3-1a1398a07dc3' params = {'name': 'pong', 'bad_prop': False} with testtools.ExpectedException(TypeError): self.controller.update(image_id, **params) python-glanceclient-0.12.0/tests/v2/__init__.py0000664000175300017540000000000012241451455022471 0ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/tests/v2/test_tags.py0000664000175300017540000000433712241451455022750 0ustar jenkinsjenkins00000000000000# 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 testtools import warlock from glanceclient.v2 import image_tags from tests import utils IMAGE = '3a4560a1-e585-443e-9b39-553b46ec92d1' TAG = 'tag01' fixtures = { '/v2/images/{image}/tags/{tag_value}'.format(image=IMAGE, tag_value=TAG): { 'DELETE': ( {}, None, ), 'PUT': ( {}, { 'image_id': IMAGE, 'tag_value': TAG } ), }, } fake_schema = {'name': 'image', 'properties': {'image_id': {}, 'tags': {}}} FakeModel = warlock.model_factory(fake_schema) class TestController(testtools.TestCase): def setUp(self): super(TestController, self).setUp() self.api = utils.FakeAPI(fixtures) self.controller = image_tags.Controller(self.api, FakeModel) def test_update_image_tag(self): image_id = IMAGE tag_value = TAG self.controller.update(image_id, tag_value) expect = [ ('PUT', '/v2/images/{image}/tags/{tag_value}'.format(image=IMAGE, tag_value=TAG), {}, None)] self.assertEqual(self.api.calls, expect) def test_delete_image_tag(self): image_id = IMAGE tag_value = TAG self.controller.delete(image_id, tag_value) expect = [ ('DELETE', '/v2/images/{image}/tags/{tag_value}'.format(image=IMAGE, tag_value=TAG), {}, None)] self.assertEqual(self.api.calls, expect) python-glanceclient-0.12.0/tests/v2/test_schemas.py0000664000175300017540000001135512241451455023433 0ustar jenkinsjenkins00000000000000# 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 testtools import warlock from glanceclient.v2 import schemas from tests import utils fixtures = { '/v2/schemas': { 'GET': ( {}, { 'image': '/v2/schemas/image', 'access': '/v2/schemas/image/access', }, ), }, '/v2/schemas/image': { 'GET': ( {}, { 'name': 'image', 'properties': { 'name': {'type': 'string', 'description': 'Name of image'}, }, }, ), }, } _SCHEMA = schemas.Schema({ 'name': 'image', 'properties': { 'name': {'type': 'string'}, 'color': {'type': 'string'}, }, }) class TestSchemaProperty(testtools.TestCase): def test_property_minimum(self): prop = schemas.SchemaProperty('size') self.assertEqual(prop.name, 'size') def test_property_description(self): prop = schemas.SchemaProperty('size', description='some quantity') self.assertEqual(prop.name, 'size') self.assertEqual(prop.description, 'some quantity') class TestSchema(testtools.TestCase): def test_schema_minimum(self): raw_schema = {'name': 'Country', 'properties': {}} schema = schemas.Schema(raw_schema) self.assertEqual(schema.name, 'Country') self.assertEqual(schema.properties, []) def test_schema_with_property(self): raw_schema = {'name': 'Country', 'properties': {'size': {}}} schema = schemas.Schema(raw_schema) self.assertEqual(schema.name, 'Country') self.assertEqual([p.name for p in schema.properties], ['size']) def test_raw(self): raw_schema = {'name': 'Country', 'properties': {}} schema = schemas.Schema(raw_schema) self.assertEqual(schema.raw(), raw_schema) class TestController(testtools.TestCase): def setUp(self): super(TestController, self).setUp() self.api = utils.FakeAPI(fixtures) self.controller = schemas.Controller(self.api) def test_get_schema(self): schema = self.controller.get('image') self.assertEqual(schema.name, 'image') self.assertEqual([p.name for p in schema.properties], ['name']) class TestSchemaBasedModel(testtools.TestCase): def setUp(self): super(TestSchemaBasedModel, self).setUp() self.model = warlock.model_factory(_SCHEMA.raw(), schemas.SchemaBasedModel) def test_patch_should_replace_missing_core_properties(self): obj = { 'name': 'fred' } original = self.model(obj) original['color'] = 'red' patch = original.patch expected = '[{"path": "/color", "value": "red", "op": "replace"}]' self.assertEqual(patch, expected) def test_patch_should_add_extra_properties(self): obj = { 'name': 'fred', } original = self.model(obj) original['weight'] = '10' patch = original.patch expected = '[{"path": "/weight", "value": "10", "op": "add"}]' self.assertEqual(patch, expected) def test_patch_should_replace_extra_properties(self): obj = { 'name': 'fred', 'weight': '10' } original = self.model(obj) original['weight'] = '22' patch = original.patch expected = '[{"path": "/weight", "value": "22", "op": "replace"}]' self.assertEqual(patch, expected) def test_patch_should_remove_extra_properties(self): obj = { 'name': 'fred', 'weight': '10' } original = self.model(obj) del original['weight'] patch = original.patch expected = '[{"path": "/weight", "op": "remove"}]' self.assertEqual(patch, expected) def test_patch_should_remove_core_properties(self): obj = { 'name': 'fred', 'color': 'red' } original = self.model(obj) del original['color'] patch = original.patch expected = '[{"path": "/color", "op": "remove"}]' self.assertEqual(patch, expected) python-glanceclient-0.12.0/tests/test_ssl.py0000664000175300017540000002321712241451455022262 0ustar jenkinsjenkins00000000000000# 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 os from OpenSSL import crypto import testtools from glanceclient import exc from glanceclient.common import http TEST_VAR_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), 'var')) class TestVerifiedHTTPSConnection(testtools.TestCase): def test_ssl_init_ok(self): """ Test VerifiedHTTPSConnection class init """ key_file = os.path.join(TEST_VAR_DIR, 'privatekey.key') cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt') cacert = os.path.join(TEST_VAR_DIR, 'ca.crt') try: conn = http.VerifiedHTTPSConnection('127.0.0.1', 0, key_file=key_file, cert_file=cert_file, cacert=cacert) except exc.SSLConfigurationError: self.fail('Failed to init VerifiedHTTPSConnection.') def test_ssl_init_cert_no_key(self): """ Test VerifiedHTTPSConnection: absense of SSL key file. """ cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt') cacert = os.path.join(TEST_VAR_DIR, 'ca.crt') try: conn = http.VerifiedHTTPSConnection('127.0.0.1', 0, cert_file=cert_file, cacert=cacert) self.fail('Failed to raise assertion.') except exc.SSLConfigurationError: pass def test_ssl_init_key_no_cert(self): """ Test VerifiedHTTPSConnection: absense of SSL cert file. """ key_file = os.path.join(TEST_VAR_DIR, 'privatekey.key') cacert = os.path.join(TEST_VAR_DIR, 'ca.crt') try: conn = http.VerifiedHTTPSConnection('127.0.0.1', 0, key_file=key_file, cacert=cacert) except Exception: self.fail('Failed to init VerifiedHTTPSConnection.') def test_ssl_init_bad_key(self): """ Test VerifiedHTTPSConnection: bad key. """ key_file = os.path.join(TEST_VAR_DIR, 'badkey.key') cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt') cacert = os.path.join(TEST_VAR_DIR, 'ca.crt') try: conn = http.VerifiedHTTPSConnection('127.0.0.1', 0, cert_file=cert_file, cacert=cacert) self.fail('Failed to raise assertion.') except exc.SSLConfigurationError: pass def test_ssl_init_bad_cert(self): """ Test VerifiedHTTPSConnection: bad cert. """ key_file = os.path.join(TEST_VAR_DIR, 'privatekey.key') cert_file = os.path.join(TEST_VAR_DIR, 'badcert.crt') cacert = os.path.join(TEST_VAR_DIR, 'ca.crt') try: conn = http.VerifiedHTTPSConnection('127.0.0.1', 0, cert_file=cert_file, cacert=cacert) self.fail('Failed to raise assertion.') except exc.SSLConfigurationError: pass def test_ssl_init_bad_ca(self): """ Test VerifiedHTTPSConnection: bad CA. """ key_file = os.path.join(TEST_VAR_DIR, 'privatekey.key') cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt') cacert = os.path.join(TEST_VAR_DIR, 'badca.crt') try: conn = http.VerifiedHTTPSConnection('127.0.0.1', 0, cert_file=cert_file, cacert=cacert) self.fail('Failed to raise assertion.') except exc.SSLConfigurationError: pass def test_ssl_cert_cname(self): """ Test certificate: CN match """ cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt') cert = crypto.load_certificate(crypto.FILETYPE_PEM, file(cert_file).read()) # The expected cert should have CN=0.0.0.0 self.assertEqual(cert.get_subject().commonName, '0.0.0.0') try: conn = http.VerifiedHTTPSConnection('0.0.0.0', 0) conn.verify_callback(None, cert, 0, 0, 1) except Exception: self.fail('Unexpected exception.') def test_ssl_cert_cname_wildcard(self): """ Test certificate: wildcard CN match """ cert_file = os.path.join(TEST_VAR_DIR, 'wildcard-certificate.crt') cert = crypto.load_certificate(crypto.FILETYPE_PEM, file(cert_file).read()) # The expected cert should have CN=*.pong.example.com self.assertEqual(cert.get_subject().commonName, '*.pong.example.com') try: conn = http.VerifiedHTTPSConnection('ping.pong.example.com', 0) conn.verify_callback(None, cert, 0, 0, 1) except Exception: self.fail('Unexpected exception.') def test_ssl_cert_subject_alt_name(self): """ Test certificate: SAN match """ cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt') cert = crypto.load_certificate(crypto.FILETYPE_PEM, file(cert_file).read()) # The expected cert should have CN=0.0.0.0 self.assertEqual(cert.get_subject().commonName, '0.0.0.0') try: conn = http.VerifiedHTTPSConnection('alt1.example.com', 0) conn.verify_callback(None, cert, 0, 0, 1) except Exception: self.fail('Unexpected exception.') try: conn = http.VerifiedHTTPSConnection('alt2.example.com', 0) conn.verify_callback(None, cert, 0, 0, 1) except Exception: self.fail('Unexpected exception.') def test_ssl_cert_mismatch(self): """ Test certificate: bogus host """ cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt') cert = crypto.load_certificate(crypto.FILETYPE_PEM, file(cert_file).read()) # The expected cert should have CN=0.0.0.0 self.assertEqual(cert.get_subject().commonName, '0.0.0.0') try: conn = http.VerifiedHTTPSConnection('mismatch.example.com', 0) except Exception: self.fail('Failed to init VerifiedHTTPSConnection.') self.assertRaises(exc.SSLCertificateError, conn.verify_callback, None, cert, 0, 0, 1) def test_ssl_expired_cert(self): """ Test certificate: out of date cert """ cert_file = os.path.join(TEST_VAR_DIR, 'expired-cert.crt') cert = crypto.load_certificate(crypto.FILETYPE_PEM, file(cert_file).read()) # The expected expired cert has CN=openstack.example.com self.assertEqual(cert.get_subject().commonName, 'openstack.example.com') try: conn = http.VerifiedHTTPSConnection('openstack.example.com', 0) except Exception: self.fail('Failed to init VerifiedHTTPSConnection.') self.assertRaises(exc.SSLCertificateError, conn.verify_callback, None, cert, 0, 0, 1) def test_ssl_broken_key_file(self): """ Test verify exception is raised. """ cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt') cacert = os.path.join(TEST_VAR_DIR, 'ca.crt') key_file = 'fake.key' self.assertRaises( exc.SSLConfigurationError, http.VerifiedHTTPSConnection, '127.0.0.1', 0, key_file=key_file, cert_file=cert_file, cacert=cacert) def test_ssl_init_ok_with_insecure_true(self): """ Test VerifiedHTTPSConnection class init """ key_file = os.path.join(TEST_VAR_DIR, 'privatekey.key') cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt') cacert = os.path.join(TEST_VAR_DIR, 'ca.crt') try: conn = http.VerifiedHTTPSConnection( '127.0.0.1', 0, key_file=key_file, cert_file=cert_file, cacert=cacert, insecure=True) except exc.SSLConfigurationError: self.fail('Failed to init VerifiedHTTPSConnection.') def test_ssl_init_ok_with_ssl_compression_false(self): """ Test VerifiedHTTPSConnection class init """ key_file = os.path.join(TEST_VAR_DIR, 'privatekey.key') cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt') cacert = os.path.join(TEST_VAR_DIR, 'ca.crt') try: conn = http.VerifiedHTTPSConnection( '127.0.0.1', 0, key_file=key_file, cert_file=cert_file, cacert=cacert, ssl_compression=False) except exc.SSLConfigurationError: self.fail('Failed to init VerifiedHTTPSConnection.') python-glanceclient-0.12.0/tests/test_utils.py0000664000175300017540000000715112241451455022620 0ustar jenkinsjenkins00000000000000# 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 sys import StringIO import testtools from glanceclient.common import utils class TestUtils(testtools.TestCase): def test_make_size_human_readable(self): self.assertEqual("106B", utils.make_size_human_readable(106)) self.assertEqual("1000kB", utils.make_size_human_readable(1024000)) self.assertEqual("1MB", utils.make_size_human_readable(1048576)) self.assertEqual("1.4GB", utils.make_size_human_readable(1476395008)) self.assertEqual("9.3MB", utils.make_size_human_readable(9761280)) def test_get_new_file_size(self): size = 98304 file_obj = StringIO.StringIO('X' * size) try: self.assertEqual(utils.get_file_size(file_obj), size) # Check that get_file_size didn't change original file position. self.assertEqual(file_obj.tell(), 0) finally: file_obj.close() def test_get_consumed_file_size(self): size, consumed = 98304, 304 file_obj = StringIO.StringIO('X' * size) file_obj.seek(consumed) try: self.assertEqual(utils.get_file_size(file_obj), size) # Check that get_file_size didn't change original file position. self.assertEqual(file_obj.tell(), consumed) finally: file_obj.close() def test_prettytable(self): class Struct: def __init__(self, **entries): self.__dict__.update(entries) # test that the prettytable output is wellformatted (left-aligned) columns = ['ID', 'Name'] val = ['Name1', 'another', 'veeeery long'] images = [Struct(**{'id': i ** 16, 'name': val[i]}) for i in range(len(val))] saved_stdout = sys.stdout try: sys.stdout = output_list = StringIO.StringIO() utils.print_list(images, columns) sys.stdout = output_dict = StringIO.StringIO() utils.print_dict({'K': 'k', 'Key': 'Value'}) finally: sys.stdout = saved_stdout self.assertEqual(output_list.getvalue(), '''\ +-------+--------------+ | ID | Name | +-------+--------------+ | | Name1 | | 1 | another | | 65536 | veeeery long | +-------+--------------+ ''') self.assertEqual(output_dict.getvalue(), '''\ +----------+-------+ | Property | Value | +----------+-------+ | K | k | | Key | Value | +----------+-------+ ''') def test_exception_to_str(self): class FakeException(Exception): def __str__(self): raise UnicodeError() ret = utils.exception_to_str(Exception('error message')) self.assertEqual(ret, 'error message') ret = utils.exception_to_str(Exception('\xa5 error message')) self.assertEqual(ret, ' error message') ret = utils.exception_to_str(FakeException('\xa5 error message')) self.assertEqual(ret, "Caught '%(exception)s' exception." % {'exception': 'FakeException'}) python-glanceclient-0.12.0/tests/test_http.py0000664000175300017540000003150212241451455022434 0ustar jenkinsjenkins00000000000000# 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 errno import socket import StringIO import urlparse import mox import testtools from glanceclient import exc import glanceclient from glanceclient.common import http from six.moves import http_client from tests import utils class TestClient(testtools.TestCase): def setUp(self): super(TestClient, self).setUp() self.mock = mox.Mox() self.mock.StubOutWithMock(http_client.HTTPConnection, 'request') self.mock.StubOutWithMock(http_client.HTTPConnection, 'getresponse') self.endpoint = 'http://example.com:9292' self.client = http.HTTPClient(self.endpoint, token=u'abc123') def tearDown(self): super(TestClient, self).tearDown() self.mock.UnsetStubs() def test_identity_headers_and_token(self): identity_headers = { 'X-Auth-Token': 'auth_token', 'X-User-Id': 'user', 'X-Tenant-Id': 'tenant', 'X-Roles': 'roles', 'X-Identity-Status': 'Confirmed', 'X-Service-Catalog': 'service_catalog', } #with token kwargs = {'token': u'fake-token', 'identity_headers': identity_headers} http_client_object = http.HTTPClient(self.endpoint, **kwargs) self.assertEqual(http_client_object.auth_token, 'auth_token') self.assertTrue(http_client_object.identity_headers. get('X-Auth-Token') is None) def test_identity_headers_and_no_token_in_header(self): identity_headers = { 'X-User-Id': 'user', 'X-Tenant-Id': 'tenant', 'X-Roles': 'roles', 'X-Identity-Status': 'Confirmed', 'X-Service-Catalog': 'service_catalog', } #without X-Auth-Token in identity headers kwargs = {'token': u'fake-token', 'identity_headers': identity_headers} http_client_object = http.HTTPClient(self.endpoint, **kwargs) self.assertEqual(http_client_object.auth_token, u'fake-token') self.assertTrue(http_client_object.identity_headers. get('X-Auth-Token') is None) def test_connection_refused(self): """ Should receive a CommunicationError if connection refused. And the error should list the host and port that refused the connection """ http_client.HTTPConnection.request( mox.IgnoreArg(), mox.IgnoreArg(), headers=mox.IgnoreArg(), ).AndRaise(socket.error()) self.mock.ReplayAll() try: self.client.json_request('GET', '/v1/images/detail?limit=20') #NOTE(alaski) We expect exc.CommunicationError to be raised # so we should never reach this point. try/except is used here # rather than assertRaises() so that we can check the body of # the exception. self.fail('An exception should have bypassed this line.') except glanceclient.exc.CommunicationError as comm_err: fail_msg = ("Exception message '%s' should contain '%s'" % (comm_err.message, self.endpoint)) self.assertTrue(self.endpoint in comm_err.message, fail_msg) def test_request_redirected(self): resp = utils.FakeResponse({'location': 'http://www.example.com'}, status=302, body=StringIO.StringIO()) http_client.HTTPConnection.request( mox.IgnoreArg(), mox.IgnoreArg(), headers=mox.IgnoreArg(), ) http_client.HTTPConnection.getresponse().AndReturn(resp) # The second request should be to the redirected location expected_response = 'Ok' resp2 = utils.FakeResponse({}, StringIO.StringIO(expected_response)) http_client.HTTPConnection.request( 'GET', 'http://www.example.com', headers=mox.IgnoreArg(), ) http_client.HTTPConnection.getresponse().AndReturn(resp2) self.mock.ReplayAll() self.client.json_request('GET', '/v1/images/detail') def test_http_encoding(self): http_client.HTTPConnection.request( mox.IgnoreArg(), mox.IgnoreArg(), headers=mox.IgnoreArg()) # Lets fake the response # returned by httplib expected_response = 'Ok' fake = utils.FakeResponse({}, StringIO.StringIO(expected_response)) http_client.HTTPConnection.getresponse().AndReturn(fake) self.mock.ReplayAll() headers = {"test": u'ni\xf1o'} resp, body = self.client.raw_request('GET', '/v1/images/detail', headers=headers) self.assertEqual(resp, fake) def test_headers_encoding(self): headers = {"test": u'ni\xf1o'} encoded = self.client.encode_headers(headers) self.assertEqual(encoded["test"], "ni\xc3\xb1o") def test_raw_request(self): " Verify the path being used for HTTP requests reflects accurately. " def check_request(method, path, **kwargs): self.assertEqual(method, 'GET') # NOTE(kmcdonald): See bug #1179984 for more details. self.assertEqual(path, '/v1/images/detail') http_client.HTTPConnection.request( mox.IgnoreArg(), mox.IgnoreArg(), headers=mox.IgnoreArg()).WithSideEffects(check_request) # fake the response returned by httplib fake = utils.FakeResponse({}, StringIO.StringIO('Ok')) http_client.HTTPConnection.getresponse().AndReturn(fake) self.mock.ReplayAll() resp, body = self.client.raw_request('GET', '/v1/images/detail') self.assertEqual(resp, fake) def test_customized_path_raw_request(self): """ Verify the customized path being used for HTTP requests reflects accurately """ def check_request(method, path, **kwargs): self.assertEqual(method, 'GET') self.assertEqual(path, '/customized-path/v1/images/detail') # NOTE(yuyangbj): see bug 1230032 to get more info endpoint = 'http://example.com:9292/customized-path' client = http.HTTPClient(endpoint, token=u'abc123') self.assertEqual(client.endpoint_path, '/customized-path') http_client.HTTPConnection.request( mox.IgnoreArg(), mox.IgnoreArg(), headers=mox.IgnoreArg()).WithSideEffects(check_request) # fake the response returned by httplib fake = utils.FakeResponse({}, StringIO.StringIO('Ok')) http_client.HTTPConnection.getresponse().AndReturn(fake) self.mock.ReplayAll() resp, body = client.raw_request('GET', '/v1/images/detail') self.assertEqual(resp, fake) def test_connection_refused_raw_request(self): """ Should receive a CommunicationError if connection refused. And the error should list the host and port that refused the connection """ endpoint = 'http://example.com:9292' client = http.HTTPClient(endpoint, token=u'abc123') http_client.HTTPConnection.request(mox.IgnoreArg(), mox.IgnoreArg(), headers=mox.IgnoreArg() ).AndRaise(socket.error()) self.mock.ReplayAll() try: client.raw_request('GET', '/v1/images/detail?limit=20') self.fail('An exception should have bypassed this line.') except exc.CommunicationError as comm_err: fail_msg = ("Exception message '%s' should contain '%s'" % (comm_err.message, endpoint)) self.assertTrue(endpoint in comm_err.message, fail_msg) def test_parse_endpoint(self): endpoint = 'http://example.com:9292' test_client = http.HTTPClient(endpoint, token=u'adc123') actual = test_client.parse_endpoint(endpoint) expected = urlparse.ParseResult(scheme='http', netloc='example.com:9292', path='', params='', query='', fragment='') self.assertEqual(expected, actual) def test_get_connection_class(self): endpoint = 'http://example.com:9292' test_client = http.HTTPClient(endpoint, token=u'adc123') actual = (test_client.get_connection_class('https')) self.assertEqual(actual, http.VerifiedHTTPSConnection) def test_get_connections_kwargs_http(self): endpoint = 'http://example.com:9292' test_client = http.HTTPClient(endpoint, token=u'adc123') actual = test_client.get_connection_kwargs('http', insecure=True) self.assertEqual({'timeout': 600.0}, actual) def test_get_connections_kwargs_https(self): endpoint = 'http://example.com:9292' test_client = http.HTTPClient(endpoint, token=u'adc123') actual = test_client.get_connection_kwargs('https', insecure=True) expected = {'cacert': None, 'cert_file': None, 'insecure': True, 'key_file': None, 'ssl_compression': True, 'timeout': 600.0} self.assertEqual(expected, actual) class TestHostResolutionError(testtools.TestCase): def setUp(self): super(TestHostResolutionError, self).setUp() self.mock = mox.Mox() self.invalid_host = "example.com.incorrect_top_level_domain" def test_incorrect_domain_error(self): """ Make sure that using a domain which does not resolve causes an exception which mentions that specific hostname as a reason for failure. """ class FailingConnectionClass(object): def __init__(self, *args, **kwargs): pass def putrequest(self, *args, **kwargs): raise socket.gaierror(-2, "Name or service not known") def request(self, *args, **kwargs): raise socket.gaierror(-2, "Name or service not known") self.endpoint = 'http://%s:9292' % (self.invalid_host,) self.client = http.HTTPClient(self.endpoint, token=u'abc123') self.mock.StubOutWithMock(self.client, 'get_connection') self.client.get_connection().AndReturn(FailingConnectionClass()) self.mock.ReplayAll() try: self.client.raw_request('GET', '/example/path') self.fail("gaierror should be raised") except exc.InvalidEndpoint as e: self.assertTrue(self.invalid_host in str(e), "exception should contain the hostname") def tearDown(self): super(TestHostResolutionError, self).tearDown() self.mock.UnsetStubs() class TestResponseBodyIterator(testtools.TestCase): def test_iter_default_chunk_size_64k(self): resp = utils.FakeResponse({}, StringIO.StringIO('X' * 98304)) iterator = http.ResponseBodyIterator(resp) chunks = list(iterator) self.assertEqual(chunks, ['X' * 65536, 'X' * 32768]) def test_integrity_check_with_correct_checksum(self): resp = utils.FakeResponse({}, StringIO.StringIO('CCC')) body = http.ResponseBodyIterator(resp) body.set_checksum('defb99e69a9f1f6e06f15006b1f166ae') list(body) def test_integrity_check_with_wrong_checksum(self): resp = utils.FakeResponse({}, StringIO.StringIO('BB')) body = http.ResponseBodyIterator(resp) body.set_checksum('wrong') try: list(body) self.fail('integrity checked passed with wrong checksum') except IOError as e: self.assertEqual(errno.EPIPE, e.errno) def test_set_checksum_in_consumed_iterator(self): resp = utils.FakeResponse({}, StringIO.StringIO('CCC')) body = http.ResponseBodyIterator(resp) list(body) # Setting checksum for an already consumed iterator should raise an # AttributeError. self.assertRaises( AttributeError, body.set_checksum, 'defb99e69a9f1f6e06f15006b1f166ae') def test_body_size(self): size = 1000000007 resp = utils.FakeResponse( {'content-length': str(size)}, StringIO.StringIO('BB')) body = http.ResponseBodyIterator(resp) self.assertEqual(len(body), size) python-glanceclient-0.12.0/tests/__init__.py0000664000175300017540000000000012241451455022142 0ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/tests/v1/0000775000175300017540000000000012241451524020366 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/tests/v1/test_shell.py0000664000175300017540000003742112241451455023120 0ustar jenkinsjenkins00000000000000# Copyright 2013 OpenStack Foundation # Copyright (C) 2013 Yahoo! Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # vim: tabstop=4 shiftwidth=4 softtabstop=4 import argparse import json import os import subprocess import tempfile import testtools from glanceclient import exc from glanceclient import shell import glanceclient.v1.client as client import glanceclient.v1.images import glanceclient.v1.shell as v1shell from tests import utils fixtures = { '/v1/images/96d2c7e1-de4e-4612-8aa2-ba26610c804e': { 'PUT': ( { 'Location': 'http://fakeaddress.com:9292/v1/images/' '96d2c7e1-de4e-4612-8aa2-ba26610c804e', 'Etag': 'f8a2eeee2dc65b3d9b6e63678955bd83', 'X-Openstack-Request-Id': 'req-b645039d-e1c7-43e5-b27b-2d18a173c42b', 'Date': 'Mon, 29 Apr 2013 10:24:32 GMT' }, json.dumps({ 'image': { 'status': 'active', 'name': 'testimagerename', 'deleted': False, 'container_format': 'ami', 'created_at': '2013-04-25T15:47:43', 'disk_format': 'ami', 'updated_at': '2013-04-29T10:24:32', 'id': '96d2c7e1-de4e-4612-8aa2-ba26610c804e', 'min_disk': 0, 'protected': False, 'min_ram': 0, 'checksum': 'f8a2eeee2dc65b3d9b6e63678955bd83', 'owner': '1310db0cce8f40b0987a5acbe139765a', 'is_public': True, 'deleted_at': None, 'properties': { 'kernel_id': '1b108400-65d8-4762-9ea4-1bf6c7be7568', 'ramdisk_id': 'b759bee9-0669-4394-a05c-fa2529b1c114' }, 'size': 25165824 } }) ), 'HEAD': ( { 'x-image-meta-id': '96d2c7e1-de4e-4612-8aa2-ba26610c804e', 'x-image-meta-status': 'active' }, None ), 'GET': ( { 'x-image-meta-status': 'active', 'x-image-meta-owner': '1310db0cce8f40b0987a5acbe139765a', 'x-image-meta-name': 'cirros-0.3.1-x86_64-uec', 'x-image-meta-container_format': 'ami', 'x-image-meta-created_at': '2013-04-25T15:47:43', 'etag': 'f8a2eeee2dc65b3d9b6e63678955bd83', 'location': 'http://fakeaddress.com:9292/v1/images/' '96d2c7e1-de4e-4612-8aa2-ba26610c804e', 'x-image-meta-min_ram': '0', 'x-image-meta-updated_at': '2013-04-25T15:47:43', 'x-image-meta-id': '96d2c7e1-de4e-4612-8aa2-ba26610c804e', 'x-image-meta-property-ramdisk_id': 'b759bee9-0669-4394-a05c-fa2529b1c114', 'date': 'Mon, 29 Apr 2013 09:25:17 GMT', 'x-image-meta-property-kernel_id': '1b108400-65d8-4762-9ea4-1bf6c7be7568', 'x-openstack-request-id': 'req-842735bf-77e8-44a7-bfd1-7d95c52cec7f', 'x-image-meta-deleted': 'False', 'x-image-meta-checksum': 'f8a2eeee2dc65b3d9b6e63678955bd83', 'x-image-meta-protected': 'False', 'x-image-meta-min_disk': '0', 'x-image-meta-size': '25165824', 'x-image-meta-is_public': 'True', 'content-type': 'text/html; charset=UTF-8', 'x-image-meta-disk_format': 'ami', }, None ) }, '/v1/images/44d2c7e1-de4e-4612-8aa2-ba26610c444f': { 'PUT': ( { 'Location': 'http://fakeaddress.com:9292/v1/images/' '44d2c7e1-de4e-4612-8aa2-ba26610c444f', 'Etag': 'f8a2eeee2dc65b3d9b6e63678955bd83', 'X-Openstack-Request-Id': 'req-b645039d-e1c7-43e5-b27b-2d18a173c42b', 'Date': 'Mon, 29 Apr 2013 10:24:32 GMT' }, json.dumps({ 'image': { 'status': 'queued', 'name': 'testimagerename', 'deleted': False, 'container_format': 'ami', 'created_at': '2013-04-25T15:47:43', 'disk_format': 'ami', 'updated_at': '2013-04-29T10:24:32', 'id': '44d2c7e1-de4e-4612-8aa2-ba26610c444f', 'min_disk': 0, 'protected': False, 'min_ram': 0, 'checksum': 'f8a2eeee2dc65b3d9b6e63678955bd83', 'owner': '1310db0cce8f40b0987a5acbe139765a', 'is_public': True, 'deleted_at': None, 'properties': { 'kernel_id': '1b108400-65d8-4762-9ea4-1bf6c7be7568', 'ramdisk_id': 'b759bee9-0669-4394-a05c-fa2529b1c114' }, 'size': 25165824 } }) ), 'HEAD': ( { 'x-image-meta-id': '44d2c7e1-de4e-4612-8aa2-ba26610c444f', 'x-image-meta-status': 'queued' }, None ), 'GET': ( { 'x-image-meta-status': 'queued', 'x-image-meta-owner': '1310db0cce8f40b0987a5acbe139765a', 'x-image-meta-name': 'cirros-0.3.1-x86_64-uec', 'x-image-meta-container_format': 'ami', 'x-image-meta-created_at': '2013-04-25T15:47:43', 'etag': 'f8a2eeee2dc65b3d9b6e63678955bd83', 'location': 'http://fakeaddress.com:9292/v1/images/' '44d2c7e1-de4e-4612-8aa2-ba26610c444f', 'x-image-meta-min_ram': '0', 'x-image-meta-updated_at': '2013-04-25T15:47:43', 'x-image-meta-id': '44d2c7e1-de4e-4612-8aa2-ba26610c444f', 'x-image-meta-property-ramdisk_id': 'b759bee9-0669-4394-a05c-fa2529b1c114', 'date': 'Mon, 29 Apr 2013 09:25:17 GMT', 'x-image-meta-property-kernel_id': '1b108400-65d8-4762-9ea4-1bf6c7be7568', 'x-openstack-request-id': 'req-842735bf-77e8-44a7-bfd1-7d95c52cec7f', 'x-image-meta-deleted': 'False', 'x-image-meta-checksum': 'f8a2eeee2dc65b3d9b6e63678955bd83', 'x-image-meta-protected': 'False', 'x-image-meta-min_disk': '0', 'x-image-meta-size': '25165824', 'x-image-meta-is_public': 'True', 'content-type': 'text/html; charset=UTF-8', 'x-image-meta-disk_format': 'ami', }, None ) } } class ShellInvalidEndpointTest(utils.TestCase): # Patch os.environ to avoid required auth info. def setUp(self): """Run before each test.""" super(ShellInvalidEndpointTest, self).setUp() self.old_environment = os.environ.copy() os.environ = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_TENANT_ID': 'tenant_id', 'OS_TOKEN_ID': 'test', 'OS_AUTH_URL': 'http://127.0.0.1:5000/v2.0/', 'OS_AUTH_TOKEN': 'pass', 'OS_IMAGE_API_VERSION': '1', 'OS_REGION_NAME': 'test', 'OS_IMAGE_URL': 'http://is.invalid'} self.shell = shell.OpenStackImagesShell() def tearDown(self): super(ShellInvalidEndpointTest, self).tearDown() os.environ = self.old_environment def run_command(self, cmd): self.shell.main(cmd.split()) def assert_called(self, method, url, body=None, **kwargs): return self.shell.cs.assert_called(method, url, body, **kwargs) def assert_called_anytime(self, method, url, body=None): return self.shell.cs.assert_called_anytime(method, url, body) def test_image_list_invalid_endpoint(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'image-list') def test_image_details_invalid_endpoint_legacy(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'details') def test_image_update_invalid_endpoint_legacy(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'update {"name":""test}') def test_image_index_invalid_endpoint_legacy(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'index') def test_image_create_invalid_endpoint(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'image-create') def test_image_delete_invalid_endpoint(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'image-delete ') def test_image_download_invalid_endpoint(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'image-download ') def test_image_members_invalid_endpoint(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'image-members fake_id') def test_members_list_invalid_endpoint(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'member-list --image-id fake') def test_member_replace_invalid_endpoint(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'members-replace image_id member_id') def test_image_show_invalid_endpoint_legacy(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'show image') def test_image_show_invalid_endpoint(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'image-show --human-readable ') def test_member_images_invalid_endpoint_legacy(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'member-images member_id') def test_member_create_invalid_endpoint(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'member-create --can-share ') def test_member_delete_invalid_endpoint(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'member-delete ') def test_member_add_invalid_endpoint(self): self.assertRaises( exc.InvalidEndpoint, self.run_command, 'member-add ') class ShellStdinHandlingTests(testtools.TestCase): def _fake_update_func(self, *args, **kwargs): '''Function to replace glanceclient.images.update, to determine the parameters that would be supplied with the update request ''' # Store passed in args self.collected_args = (args, kwargs) # Return the first arg, which is an image, # as do_image_update expects this. return args[0] def setUp(self): super(ShellStdinHandlingTests, self).setUp() self.api = utils.FakeAPI(fixtures) self.gc = client.Client("http://fakeaddress.com") self.gc.images = glanceclient.v1.images.ImageManager(self.api) # Store real stdin, so it can be restored in tearDown. self.real_sys_stdin_fd = os.dup(0) # Replace stdin with a FD that points to /dev/null. dev_null = open('/dev/null') self.dev_null_fd = dev_null.fileno() os.dup2(dev_null.fileno(), 0) # Replace the image update function with a fake, # so that we can tell if the data field was set correctly. self.real_update_func = self.gc.images.update self.collected_args = [] self.gc.images.update = self._fake_update_func def tearDown(self): """Restore stdin and gc.images.update to their pretest states.""" super(ShellStdinHandlingTests, self).tearDown() def try_close(fd): try: os.close(fd) except OSError: # Already closed pass # Restore stdin os.dup2(self.real_sys_stdin_fd, 0) # Close duplicate stdin handle try_close(self.real_sys_stdin_fd) # Close /dev/null handle try_close(self.dev_null_fd) # Restore the real image update function self.gc.images.update = self.real_update_func def _do_update(self, image='96d2c7e1-de4e-4612-8aa2-ba26610c804e'): """call v1/shell's do_image_update function.""" v1shell.do_image_update( self.gc, argparse.Namespace( image=image, name='testimagerename', property={}, purge_props=False, human_readable=False, file=None, progress=False ) ) def test_image_update_closed_stdin(self): """Supply glanceclient with a closed stdin, and perform an image update to an active image. Glanceclient should not attempt to read stdin. """ # NOTE(hughsaunders) Close stdin, which is repointed to /dev/null by # setUp() os.close(0) self._do_update() self.assertTrue( 'data' not in self.collected_args[1] or self.collected_args[1]['data'] is None ) def test_image_update_data_is_read_from_file(self): """Ensure that data is read from a file.""" try: # NOTE(hughsaunders) Create a tmpfile, write some data to it and # set it as stdin f = open(tempfile.mktemp(), 'w+') f.write('Some Data') f.flush() f.seek(0) os.dup2(f.fileno(), 0) self._do_update('44d2c7e1-de4e-4612-8aa2-ba26610c444f') self.assertTrue('data' in self.collected_args[1]) self.assertIsInstance(self.collected_args[1]['data'], file) self.assertEqual(self.collected_args[1]['data'].read(), 'Some Data') finally: try: f.close() os.remove(f.name) except Exception: pass def test_image_update_data_is_read_from_pipe(self): """Ensure that data is read from a pipe.""" try: # NOTE(hughsaunders): Setup a pipe, duplicate it to stdin # ensure it is read. process = subprocess.Popen(['/bin/echo', 'Some Data'], stdout=subprocess.PIPE) os.dup2(process.stdout.fileno(), 0) self._do_update('44d2c7e1-de4e-4612-8aa2-ba26610c444f') self.assertTrue('data' in self.collected_args[1]) self.assertIsInstance(self.collected_args[1]['data'], file) self.assertEqual(self.collected_args[1]['data'].read(), 'Some Data\n') finally: try: process.stdout.close() except OSError: pass python-glanceclient-0.12.0/tests/v1/test_legacy_shell.py0000664000175300017540000004463412241451455024450 0ustar jenkinsjenkins00000000000000# Copyright 2013 OpenStack Foundation # Copyright (C) 2013 Yahoo! Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # vim: tabstop=4 shiftwidth=4 softtabstop=4 import mock import testtools from glanceclient import client from glanceclient import exc from glanceclient.v1 import legacy_shell as test_shell class LegacyShellV1Test(testtools.TestCase): def test_print_image_formatted(self): class FakeClient(): endpoint = 'http://is.invalid' class FakeImage(): id = 1 name = 'fake_image' is_public = False protected = False status = 'active' size = '1024' min_ram = 512 min_disk = 10 properties = {'a': 'b', 'c': 'd'} created_at = '04.03.2013' owner = 'test' updated_at = '04.03.2013' deleted_at = '04.03.2013' test_shell.print_image_formatted(FakeClient(), FakeImage()) def test_print_image(self): class FakeImage(): id = 1 name = 'fake_image' is_public = False protected = False status = 'active' size = '1024' min_ram = 512 min_disk = 10 properties = {'a': 'b', 'c': 'd'} created_at = '04.03.2013' owner = 'test' updated_at = '04.03.2013' deleted_at = '04.03.2013' gc = client.Client('1', 'http://is.invalid:8080') test_shell.print_image_formatted(gc, FakeImage()) def test_get_image_fields_from_args(self): args = ["field=name"] actual = test_shell.get_image_fields_from_args(args) self.assertEqual({'field': 'name'}, actual) def test_get_image_fields_from_args_exception_raises(self): args = {"filed": "name"} self.assertRaises( RuntimeError, test_shell.get_image_fields_from_args, args) def test_get_filters_from_args(self): args = ["filter=name"] actual = test_shell.get_image_filters_from_args(args) self.assertEqual({'property-filter': 'name'}, actual) def test_get_image_filters_from_args_exception_raises(self): args = {"filter": "name"} actual = test_shell.get_image_filters_from_args(args) self.assertEqual(1, actual) def test_do_add_error(self): class FakeClient(): endpoint = 'http://is.invalid' class args: fields = 'name' actual = test_shell.do_add(FakeClient(), args) self.assertEqual(1, actual) def test_do_add(self): gc = client.Client('1', 'http://is.invalid') class FakeImage(): fields = ['name=test', 'status=active', 'id=test', 'is_public=True', 'protected=False', 'min_disk=10', 'container_format=ovi', 'status=active'] dry_run = True test_args = FakeImage() actual = test_shell.do_add(gc, test_args) self.assertEqual(0, actual) def test_do_add_with_image_meta(self): gc = client.Client('1', 'http://is.invalid') class FakeImage(): fields = ['name=test', 'status=active', 'is_public=True', 'id=test', 'protected=False', 'min_disk=10', 'container_format=ovi', 'status=active', 'size=256', 'location=test', 'checksum=1024', 'owner=test_user'] dry_run = True test_args = FakeImage() actual = test_shell.do_add(gc, test_args) self.assertEqual(0, actual) def test_do_add_without_dry_run(self): gc = client.Client('1', 'http://is.invalid') class FakeImage(): fields = ['name=test', 'status=active', 'is_public=True', 'id=test', 'protected=False', 'min_disk=10', 'container_format=ovi', 'status=active', 'size=256', 'location=test', 'checksum=1024', 'owner=test_user'] dry_run = False id = 'test' verbose = False test_args = FakeImage() with mock.patch.object(gc.images, 'create') as mocked_create: mocked_create.return_value = FakeImage() actual = test_shell.do_add(gc, test_args) self.assertEqual(0, actual) def test_do_clear_force_true_error(self): class FakeImage1(): id = 1 name = 'fake_image' is_public = False protected = False status = 'active' size = '1024' min_ram = 512 min_disk = 10 properties = {'a': 'b', 'c': 'd'} created_at = '04.03.2013' owner = 'test' updated_at = '04.03.2013' deleted_at = '04.03.2013' force = True verbose = True class FakeImages(): def __init__(self): self.id = 'test' self.name = 'test_image_name' def list(self): self.list = [FakeImage1(), FakeImage1()] return self.list class FakeClient(): def __init__(self): self.images = FakeImages() test_args = FakeImage1() actual = test_shell.do_clear(FakeClient(), test_args) self.assertEqual(1, actual) def test_do_clear_force_true(self): class FakeImage1(): def __init__(self): self.id = 1 self.name = 'fake_image' self.is_public = False self.protected = False self.status = 'active' self.size = '1024' self.min_ram = 512 self.min_disk = 10 self.properties = {'a': 'b', 'c': 'd'} self.created_at = '04.03.2013' self.owner = 'test' self.updated_at = '04.03.2013' self.deleted_at = '04.03.2013' self.force = True self.verbose = True def delete(self): pass class FakeImages(): def __init__(self): self.id = 'test' self.name = 'test_image_name' def list(self): self.list = [FakeImage1(), FakeImage1()] return self.list class FakeClient(): def __init__(self): self.images = FakeImages() test_args = FakeImage1() actual = test_shell.do_clear(FakeClient(), test_args) self.assertEqual(0, actual) def test_do_update_error(self): class FakeClient(): endpoint = 'http://is.invalid' class Image(): fields = ['id', 'is_public', 'name'] args = Image() fake_client = FakeClient() actual = test_shell.do_update(fake_client, args) self.assertEqual(1, actual) def test_do_update_invalid_endpoint(self): class Image(): fields = ['id=test_updated', 'is_public=True', 'name=new_name'] dry_run = False id = 'test' args = Image() gc = client.Client('1', 'http://is.invalid') self.assertRaises( exc.InvalidEndpoint, test_shell.do_update, gc, args) def test_do_update(self): class Image(): fields = ['id=test_updated', 'status=active', 'is_public=True', 'name=new_name', 'protected=False'] dry_run = True id = 'test' args = Image() gc = client.Client('1', 'http://is.invalid') actual = test_shell.do_update(gc, args) self.assertEqual(0, actual) def test_do_update_dry_run_false(self): class Image(): fields = ['id=test_updated', 'status=active', 'is_public=True', 'name=new_name', 'protected=False', 'is_public=True'] dry_run = False id = 'test' verbose = True is_public = True protected = False status = 'active' size = 1024 min_ram = 512 min_disk = 512 properties = {'property': 'test'} created_at = '12.09.2013' args = Image() gc = client.Client('1', 'http://is.invalid') with mock.patch.object(gc.images, 'update') as mocked_update: mocked_update.return_value = Image() actual = test_shell.do_update(gc, args) self.assertEqual(0, actual) def test_do_delete(self): class FakeImage1(): def __init__(self): self.id = 1 self.name = 'fake_image' self.is_public = False self.protected = False self.status = 'active' self.size = '1024' self.min_ram = 512 self.min_disk = 10 self.properties = {'a': 'b', 'c': 'd'} self.created_at = '04.03.2013' self.owner = 'test' self.updated_at = '04.03.2013' self.deleted_at = '04.03.2013' self.force = True self.verbose = True def delete(self): pass def get(self, id): return FakeImage1() class FakeClient(): def __init__(self): self.images = FakeImage1() actual = test_shell.do_delete(FakeClient(), FakeImage1()) def test_show(self): class Image(): fields = ['id=test_updated', 'status=active', 'is_public=True', 'name=new_name', 'protected=False'] id = 'test_show' name = 'fake_image' is_public = False protected = False status = 'active' size = '1024' min_ram = 512 min_disk = 10 properties = {'a': 'b', 'c': 'd'} created_at = '04.03.2013' owner = 'test' updated_at = '04.03.2013' gc = client.Client('1', 'http://is.invalid') with mock.patch.object(gc.images, 'get') as mocked_get: mocked_get.return_value = Image() actual = test_shell.do_show(gc, Image()) self.assertEqual(0, actual) def test_index(self): class Image(): id = 'test' filters = {} limit = 18 marker = False sort_key = 'test' kwarg = 'name' sort_dir = 'test' name = 'test' disk_format = 'ovi' container_format = 'ovi' size = 1024 args = Image() gc = client.Client('1', 'http://is.invalid') with mock.patch.object(gc.images, 'list') as mocked_list: mocked_list.return_value = [Image(), Image()] actual = test_shell.do_index(gc, args) def test_index_return_empty(self): class Image(): id = 'test' filters = {} limit = 18 marker = False sort_key = 'test' kwarg = 'name' sort_dir = 'test' name = 'test' disk_format = 'ovi' container_format = 'ovi' size = 1024 args = Image() gc = client.Client('1', 'http://is.invalid') with mock.patch.object(test_shell, '_get_images') as mocked_get: mocked_get.return_value = False actual = test_shell.do_index(gc, args) self.assertEqual(0, actual) def test_do_details(self): class Image(): id = 'test' filters = {} limit = 18 marker = False sort_key = 'test' kwarg = 'name' sort_dir = 'test' name = 'test' disk_format = 'ovi' container_format = 'ovi' size = 1024 is_public = True protected = False status = 'active' min_ram = 512 min_disk = 512 properties = {} created_at = '12.12.12' args = Image() gc = client.Client('1', 'http://is.invalid') with mock.patch.object(gc.images, 'list') as mocked_list: mocked_list.return_value = [Image(), Image()] actual = test_shell.do_details(gc, args) def test_do_image_members(self): class FakeImage1(): def __init__(self): self.image_id = 1 self.name = 'fake_image' self.is_public = False self.protected = False self.status = 'active' self.size = '1024' self.min_ram = 512 self.min_disk = 10 self.properties = {'a': 'b', 'c': 'd'} self.created_at = '04.03.2013' self.owner = 'test' self.updated_at = '04.03.2013' self.deleted_at = '04.03.2013' self.force = True self.verbose = True def delete(self): pass def get(self, id): return FakeImage1() class FakeClient(): def __init__(self): self.image_members = ImageMembers() class ImageMembers(): def __init__(self): self.member_id = 'test' self.can_share = True def list(self, image): return [ImageMembers(), ImageMembers()] actual = test_shell.do_image_members(FakeClient(), FakeImage1()) def test_do_member_add_error(self): class FakeClient(): def __init__(self): self.image_members = ImageMembers() class FakeImage1(): def __init__(self): self.member_id = 'test' self.fields = ["name", "id", "filter"] self.dry_run = True self.image_id = 'fake_image_id' self.can_share = True def delete(self): pass def get(self, id): return FakeImage1() class ImageMembers(): def __init__(self): self.member_id = 'test' self.can_share = True def list(self, image): return [ImageMembers(), ImageMembers()] actual = test_shell.do_member_add(FakeClient(), FakeImage1()) def test_do_member_images_empty_result(self): class FakeImage1(): def __init__(self): self.member_id = 'test' gc = client.Client('1', 'http://is.invalid') with mock.patch.object(gc.image_members, 'list') as mocked_list: mocked_list.return_value = [] actual = test_shell.do_member_images(gc, FakeImage1()) self.assertEqual(0, actual) def test_do_member_replace(self): class FakeClient(): def __init__(self): self.image_members = ImageMembers() class ImageMembers(): def __init__(self): self.member_id = 'test' self.can_share = True self.dry_run = True self.image_id = "fake_image_id" def list(self, image): return [ImageMembers(), ImageMembers()] actual = test_shell.do_member_add(FakeClient(), ImageMembers()) def test_do_members_replace_dry_run_true(self): class Fake(): def __init__(self): self.dry_run = True self.can_share = True self.image_id = 'fake_id' self.member_id = 'test' gc = client.Client('1', 'http://is.invalid') actual = test_shell.do_members_replace(gc, Fake()) def test_do_members_replace_dry_run_false(self): class Fake(): def __init__(self): self.dry_run = False self.can_share = True self.image_id = 'fake_id' self.member_id = 'test' gc = client.Client('1', 'http://is.invalid') with mock.patch.object(gc.image_members, 'list') as mocked_list: mocked_list.return_value = [] with mock.patch.object(gc.image_members, 'create'): actual = test_shell.do_members_replace(gc, Fake()) def test_do_member_images(self): class FakeClient(): def __init__(self): self.image_members = ImageMembers() class ImageMembers(): def __init__(self): self.member_id = 'test' self.can_share = True self.dry_run = True self.image_id = "fake_image_id" def list(self, member): return [ImageMembers(), ImageMembers()] actual = test_shell.do_member_images(FakeClient(), ImageMembers()) def test_create_pretty_table(self): class MyPrettyTable(test_shell.PrettyTable): def __init__(self): self.columns = [] # Test add column my_pretty_table = MyPrettyTable() my_pretty_table.add_column(1, label='test') # Test make header test_res = my_pretty_table.make_header() self.assertEqual('t\n-', test_res) # Test make row result = my_pretty_table.make_row('t') self.assertEqual("t", result) result = my_pretty_table._clip_and_justify( data='test', width=4, just=1) self.assertEqual("test", result) python-glanceclient-0.12.0/tests/v1/test_images.py0000664000175300017540000006320012241451455023250 0ustar jenkinsjenkins00000000000000# 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 errno import json import StringIO import sys import testtools import urlparse from glanceclient.v1 import client from glanceclient.v1 import images from glanceclient.v1 import legacy_shell from glanceclient.v1 import shell from tests import utils fixtures = { '/v1/images': { 'POST': ( { 'location': '/v1/images/1', }, json.dumps( {'image': { 'id': '1', 'name': 'image-1', 'container_format': 'ovf', 'disk_format': 'vhd', 'owner': 'asdf', 'size': '1024', 'min_ram': '512', 'min_disk': '10', 'properties': {'a': 'b', 'c': 'd'}, 'is_public': False, 'protected': False, 'deleted': False, }}, ), ), }, '/v1/images/detail?limit=20': { 'GET': ( {}, {'images': [ { 'id': 'a', 'name': 'image-1', 'properties': {'arch': 'x86_64'}, }, { 'id': 'b', 'name': 'image-2', 'properties': {'arch': 'x86_64'}, }, ]}, ), }, '/v1/images/detail?is_public=None&limit=20': { 'GET': ( {}, {'images': [ { 'id': 'a', 'owner': 'A', 'is_public': 'True', 'name': 'image-1', 'properties': {'arch': 'x86_64'}, }, { 'id': 'b', 'owner': 'B', 'is_public': 'False', 'name': 'image-2', 'properties': {'arch': 'x86_64'}, }, { 'id': 'c', 'is_public': 'False', 'name': 'image-3', 'properties': {'arch': 'x86_64'}, }, ]}, ), }, '/v1/images/detail?is_public=None&limit=5': { 'GET': ( {}, {'images': [ { 'id': 'a', 'owner': 'A', 'name': 'image-1', 'properties': {'arch': 'x86_64'}, }, { 'id': 'b', 'owner': 'B', 'name': 'image-2', 'properties': {'arch': 'x86_64'}, }, { 'id': 'b2', 'owner': 'B', 'name': 'image-3', 'properties': {'arch': 'x86_64'}, }, { 'id': 'c', 'name': 'image-3', 'properties': {'arch': 'x86_64'}, }, ]}, ), }, '/v1/images/detail?limit=5': { 'GET': ( {}, {'images': [ { 'id': 'a', 'owner': 'A', 'is_public': 'False', 'name': 'image-1', 'properties': {'arch': 'x86_64'}, }, { 'id': 'b', 'owner': 'A', 'is_public': 'False', 'name': 'image-2', 'properties': {'arch': 'x86_64'}, }, { 'id': 'b2', 'owner': 'B', 'name': 'image-3', 'properties': {'arch': 'x86_64'}, }, { 'id': 'c', 'is_public': 'True', 'name': 'image-3', 'properties': {'arch': 'x86_64'}, }, ]}, ), }, '/v1/images/detail?marker=a&limit=20': { 'GET': ( {}, {'images': [ { 'id': 'b', 'name': 'image-1', 'properties': {'arch': 'x86_64'}, }, { 'id': 'c', 'name': 'image-2', 'properties': {'arch': 'x86_64'}, }, ]}, ), }, '/v1/images/detail?limit=1': { 'GET': ( {}, {'images': [ { 'id': 'a', 'name': 'image-0', 'properties': {'arch': 'x86_64'}, }, ]}, ), }, '/v1/images/detail?marker=a&limit=1': { 'GET': ( {}, {'images': [ { 'id': 'b', 'name': 'image-1', 'properties': {'arch': 'x86_64'}, }, ]}, ), }, '/v1/images/detail?limit=2': { 'GET': ( {}, {'images': [ { 'id': 'a', 'name': 'image-1', 'properties': {'arch': 'x86_64'}, }, { 'id': 'b', 'name': 'image-2', 'properties': {'arch': 'x86_64'}, }, ]}, ), }, '/v1/images/detail?marker=b&limit=2': { 'GET': ( {}, {'images': [ { 'id': 'c', 'name': 'image-3', 'properties': {'arch': 'x86_64'}, }, ]}, ), }, '/v1/images/detail?limit=20&name=foo': { 'GET': ( {}, {'images': [ { 'id': 'a', 'name': 'image-1', 'properties': {'arch': 'x86_64'}, }, { 'id': 'b', 'name': 'image-2', 'properties': {'arch': 'x86_64'}, }, ]}, ), }, '/v1/images/detail?property-ping=pong&limit=20': { 'GET': ( {}, {'images': [ { 'id': '1', 'name': 'image-1', 'properties': {'arch': 'x86_64'}, }, ]}, ), }, '/v1/images/detail?sort_dir=desc&limit=20': { 'GET': ( {}, {'images': [ { 'id': 'a', 'name': 'image-1', 'properties': {'arch': 'x86_64'}, }, { 'id': 'b', 'name': 'image-2', 'properties': {'arch': 'x86_64'}, }, ]}, ), }, '/v1/images/detail?sort_key=name&limit=20': { 'GET': ( {}, {'images': [ { 'id': 'a', 'name': 'image-1', 'properties': {'arch': 'x86_64'}, }, { 'id': 'b', 'name': 'image-2', 'properties': {'arch': 'x86_64'}, }, ]}, ), }, '/v1/images/1': { 'HEAD': ( { 'x-image-meta-id': '1', 'x-image-meta-name': 'image-1', 'x-image-meta-property-arch': 'x86_64', 'x-image-meta-is_public': 'false', 'x-image-meta-protected': 'false', 'x-image-meta-deleted': 'false', }, None), 'GET': ( {}, 'XXX', ), 'PUT': ( {}, json.dumps( {'image': { 'id': '1', 'name': 'image-2', 'container_format': 'ovf', 'disk_format': 'vhd', 'owner': 'asdf', 'size': '1024', 'min_ram': '512', 'min_disk': '10', 'properties': {'a': 'b', 'c': 'd'}, 'is_public': False, 'protected': False, }}, ), ), 'DELETE': ({}, None), }, '/v1/images/2': { 'HEAD': ( { 'x-image-meta-id': '2' }, None, ), 'GET': ( { 'x-image-meta-checksum': 'wrong' }, 'YYY', ), }, '/v1/images/3': { 'HEAD': ( { 'x-image-meta-id': '3', 'x-image-meta-name': "ni\xc3\xb1o" }, None, ), 'GET': ( { 'x-image-meta-checksum': '0745064918b49693cca64d6b6a13d28a' }, 'ZZZ', ), }, } class ImageManagerTest(testtools.TestCase): def setUp(self): super(ImageManagerTest, self).setUp() self.api = utils.FakeAPI(fixtures) self.mgr = images.ImageManager(self.api) def test_paginated_list(self): images = list(self.mgr.list(page_size=2)) expect = [ ('GET', '/v1/images/detail?limit=2', {}, None), ('GET', '/v1/images/detail?marker=b&limit=2', {}, None), ] self.assertEqual(self.api.calls, expect) self.assertEqual(len(images), 3) self.assertEqual(images[0].id, 'a') self.assertEqual(images[1].id, 'b') self.assertEqual(images[2].id, 'c') def test_list_with_limit_less_than_page_size(self): results = list(self.mgr.list(page_size=2, limit=1)) expect = [('GET', '/v1/images/detail?limit=2', {}, None)] self.assertEqual(1, len(results)) self.assertEqual(self.api.calls, expect) def test_list_with_limit_greater_than_page_size(self): images = list(self.mgr.list(page_size=1, limit=2)) expect = [ ('GET', '/v1/images/detail?limit=1', {}, None), ('GET', '/v1/images/detail?marker=a&limit=1', {}, None), ] self.assertEqual(len(images), 2) self.assertEqual(images[0].id, 'a') self.assertEqual(images[1].id, 'b') self.assertEqual(self.api.calls, expect) def test_list_with_marker(self): list(self.mgr.list(marker='a')) url = '/v1/images/detail?marker=a&limit=20' expect = [('GET', url, {}, None)] self.assertEqual(self.api.calls, expect) def test_list_with_filter(self): list(self.mgr.list(filters={'name': "foo"})) url = '/v1/images/detail?limit=20&name=foo' expect = [('GET', url, {}, None)] self.assertEqual(self.api.calls, expect) def test_list_with_property_filters(self): list(self.mgr.list(filters={'properties': {'ping': 'pong'}})) url = '/v1/images/detail?property-ping=pong&limit=20' expect = [('GET', url, {}, None)] self.assertEqual(self.api.calls, expect) def test_list_with_sort_dir(self): list(self.mgr.list(sort_dir='desc')) url = '/v1/images/detail?sort_dir=desc&limit=20' expect = [('GET', url, {}, None)] self.assertEqual(self.api.calls, expect) def test_list_with_sort_key(self): list(self.mgr.list(sort_key='name')) url = '/v1/images/detail?sort_key=name&limit=20' expect = [('GET', url, {}, None)] self.assertEqual(self.api.calls, expect) def test_get(self): image = self.mgr.get('1') expect = [('HEAD', '/v1/images/1', {}, None)] self.assertEqual(self.api.calls, expect) self.assertEqual(image.id, '1') self.assertEqual(image.name, 'image-1') self.assertEqual(image.is_public, False) self.assertEqual(image.protected, False) self.assertEqual(image.deleted, False) self.assertEqual(image.properties, {u'arch': u'x86_64'}) def test_get_int(self): image = self.mgr.get(1) expect = [('HEAD', '/v1/images/1', {}, None)] self.assertEqual(self.api.calls, expect) self.assertEqual(image.id, '1') self.assertEqual(image.name, 'image-1') self.assertEqual(image.is_public, False) self.assertEqual(image.protected, False) self.assertEqual(image.deleted, False) self.assertEqual(image.properties, {u'arch': u'x86_64'}) def test_get_encoding(self): image = self.mgr.get('3') expect = [('HEAD', '/v1/images/3', {}, None)] self.assertEqual(image.name, u"ni\xf1o") def test_data(self): data = ''.join([b for b in self.mgr.data('1', do_checksum=False)]) expect = [('GET', '/v1/images/1', {}, None)] self.assertEqual(self.api.calls, expect) self.assertEqual(data, 'XXX') expect += [('GET', '/v1/images/1', {}, None)] data = ''.join([b for b in self.mgr.data('1')]) self.assertEqual(self.api.calls, expect) self.assertEqual(data, 'XXX') def test_data_with_wrong_checksum(self): data = ''.join([b for b in self.mgr.data('2', do_checksum=False)]) expect = [('GET', '/v1/images/2', {}, None)] self.assertEqual(self.api.calls, expect) self.assertEqual(data, 'YYY') expect += [('GET', '/v1/images/2', {}, None)] data = self.mgr.data('2') self.assertEqual(self.api.calls, expect) try: data = ''.join([b for b in data]) self.fail('data did not raise an error.') except IOError as e: self.assertEqual(errno.EPIPE, e.errno) msg = 'was fd7c5c4fdaa97163ee4ba8842baa537a expected wrong' self.assertTrue(msg in str(e)) def test_data_with_checksum(self): data = ''.join([b for b in self.mgr.data('3', do_checksum=False)]) expect = [('GET', '/v1/images/3', {}, None)] self.assertEqual(self.api.calls, expect) self.assertEqual(data, 'ZZZ') expect += [('GET', '/v1/images/3', {}, None)] data = ''.join([b for b in self.mgr.data('3')]) self.assertEqual(self.api.calls, expect) self.assertEqual(data, 'ZZZ') def test_delete(self): self.mgr.delete('1') expect = [('DELETE', '/v1/images/1', {}, None)] self.assertEqual(self.api.calls, expect) def test_create_without_data(self): params = { 'id': '1', 'name': 'image-1', 'container_format': 'ovf', 'disk_format': 'vhd', 'owner': 'asdf', 'size': 1024, 'min_ram': 512, 'min_disk': 10, 'copy_from': 'http://example.com', 'properties': {'a': 'b', 'c': 'd'}, } image = self.mgr.create(**params) expect_headers = { 'x-image-meta-id': '1', 'x-image-meta-name': 'image-1', 'x-image-meta-container_format': 'ovf', 'x-image-meta-disk_format': 'vhd', 'x-image-meta-owner': 'asdf', 'x-image-meta-size': '1024', 'x-image-meta-min_ram': '512', 'x-image-meta-min_disk': '10', 'x-glance-api-copy-from': 'http://example.com', 'x-image-meta-property-a': 'b', 'x-image-meta-property-c': 'd', } expect = [('POST', '/v1/images', expect_headers, None)] self.assertEqual(self.api.calls, expect) self.assertEqual(image.id, '1') self.assertEqual(image.name, 'image-1') self.assertEqual(image.container_format, 'ovf') self.assertEqual(image.disk_format, 'vhd') self.assertEqual(image.owner, 'asdf') self.assertEqual(image.size, 1024) self.assertEqual(image.min_ram, 512) self.assertEqual(image.min_disk, 10) self.assertEqual(image.is_public, False) self.assertEqual(image.protected, False) self.assertEqual(image.deleted, False) self.assertEqual(image.properties, {'a': 'b', 'c': 'd'}) def test_create_with_data(self): image_data = StringIO.StringIO('XXX') self.mgr.create(data=image_data) expect_headers = {'x-image-meta-size': '3'} expect = [('POST', '/v1/images', expect_headers, image_data)] self.assertEqual(self.api.calls, expect) def test_update(self): fields = { 'name': 'image-2', 'container_format': 'ovf', 'disk_format': 'vhd', 'owner': 'asdf', 'size': 1024, 'min_ram': 512, 'min_disk': 10, 'copy_from': 'http://example.com', 'properties': {'a': 'b', 'c': 'd'}, 'deleted': False, } image = self.mgr.update('1', **fields) expect_hdrs = { 'x-image-meta-name': 'image-2', 'x-image-meta-container_format': 'ovf', 'x-image-meta-disk_format': 'vhd', 'x-image-meta-owner': 'asdf', 'x-image-meta-size': '1024', 'x-image-meta-min_ram': '512', 'x-image-meta-min_disk': '10', 'x-glance-api-copy-from': 'http://example.com', 'x-image-meta-property-a': 'b', 'x-image-meta-property-c': 'd', 'x-image-meta-deleted': 'False', } expect = [('PUT', '/v1/images/1', expect_hdrs, None)] self.assertEqual(self.api.calls, expect) self.assertEqual(image.id, '1') self.assertEqual(image.name, 'image-2') self.assertEqual(image.size, 1024) self.assertEqual(image.min_ram, 512) self.assertEqual(image.min_disk, 10) def test_update_with_data(self): image_data = StringIO.StringIO('XXX') self.mgr.update('1', data=image_data) expect_headers = {'x-image-meta-size': '3'} expect = [('PUT', '/v1/images/1', expect_headers, image_data)] self.assertEqual(self.api.calls, expect) def test_update_with_purge_props(self): self.mgr.update('1', purge_props=True) expect_headers = {'x-glance-registry-purge-props': 'true'} expect = [('PUT', '/v1/images/1', expect_headers, None)] self.assertEqual(self.api.calls, expect) def test_image_meta_from_headers_encoding(self): fields = {"x-image-meta-name": "ni\xc3\xb1o"} headers = self.mgr._image_meta_from_headers(fields) self.assertEqual(headers["name"], u"ni\xf1o") def test_image_list_with_owner(self): images = self.mgr.list(owner='A', page_size=20) image_list = list(images) self.assertEqual(image_list[0].owner, 'A') self.assertEqual(image_list[0].id, 'a') self.assertEqual(len(image_list), 1) def test_image_list_with_notfound_owner(self): images = self.mgr.list(owner='X', page_size=20) self.assertEqual(len(list(images)), 0) def test_image_list_with_empty_string_owner(self): images = self.mgr.list(owner='', page_size=20) image_list = list(images) self.assertRaises(AttributeError, lambda: image_list[0].owner) self.assertEqual(image_list[0].id, 'c') self.assertEqual(len(image_list), 1) def test_image_list_with_unspecified_owner(self): images = self.mgr.list(owner=None, page_size=5) image_list = list(images) self.assertEqual(image_list[0].owner, 'A') self.assertEqual(image_list[0].id, 'a') self.assertEqual(image_list[1].owner, 'A') self.assertEqual(image_list[1].id, 'b') self.assertEqual(image_list[2].owner, 'B') self.assertEqual(image_list[2].id, 'b2') self.assertRaises(AttributeError, lambda: image_list[3].owner) self.assertEqual(image_list[3].id, 'c') self.assertEqual(len(image_list), 4) def test_image_list_with_owner_and_limit(self): images = self.mgr.list(owner='B', page_size=5, limit=1) image_list = list(images) self.assertEqual(image_list[0].owner, 'B') self.assertEqual(image_list[0].id, 'b') self.assertEqual(len(image_list), 1) def test_image_list_all_tenants(self): images = self.mgr.list(is_public=None, page_size=5) image_list = list(images) self.assertEqual(image_list[0].owner, 'A') self.assertEqual(image_list[0].id, 'a') self.assertEqual(image_list[1].owner, 'B') self.assertEqual(image_list[1].id, 'b') self.assertEqual(image_list[2].owner, 'B') self.assertEqual(image_list[2].id, 'b2') self.assertRaises(AttributeError, lambda: image_list[3].owner) self.assertEqual(image_list[3].id, 'c') self.assertEqual(len(image_list), 4) class ImageTest(testtools.TestCase): def setUp(self): super(ImageTest, self).setUp() self.api = utils.FakeAPI(fixtures) self.mgr = images.ImageManager(self.api) def test_delete(self): image = self.mgr.get('1') image.delete() expect = [ ('HEAD', '/v1/images/1', {}, None), ('DELETE', '/v1/images/1', {}, None), ] self.assertEqual(self.api.calls, expect) def test_update(self): image = self.mgr.get('1') image.update(name='image-5') expect = [ ('HEAD', '/v1/images/1', {}, None), ('PUT', '/v1/images/1', {'x-image-meta-name': 'image-5'}, None), ] self.assertEqual(self.api.calls, expect) def test_data(self): image = self.mgr.get('1') data = ''.join([b for b in image.data()]) expect = [ ('HEAD', '/v1/images/1', {}, None), ('GET', '/v1/images/1', {}, None), ] self.assertEqual(self.api.calls, expect) self.assertEqual(data, 'XXX') data = ''.join([b for b in image.data(do_checksum=False)]) expect += [('GET', '/v1/images/1', {}, None)] self.assertEqual(self.api.calls, expect) self.assertEqual(data, 'XXX') def test_data_with_wrong_checksum(self): image = self.mgr.get('2') data = ''.join([b for b in image.data(do_checksum=False)]) expect = [ ('HEAD', '/v1/images/2', {}, None), ('GET', '/v1/images/2', {}, None), ] self.assertEqual(self.api.calls, expect) self.assertEqual(data, 'YYY') data = image.data() expect += [('GET', '/v1/images/2', {}, None)] self.assertEqual(self.api.calls, expect) try: data = ''.join([b for b in image.data()]) self.fail('data did not raise an error.') except IOError as e: self.assertEqual(errno.EPIPE, e.errno) msg = 'was fd7c5c4fdaa97163ee4ba8842baa537a expected wrong' self.assertTrue(msg in str(e)) def test_data_with_checksum(self): image = self.mgr.get('3') data = ''.join([b for b in image.data(do_checksum=False)]) expect = [ ('HEAD', '/v1/images/3', {}, None), ('GET', '/v1/images/3', {}, None), ] self.assertEqual(self.api.calls, expect) self.assertEqual(data, 'ZZZ') data = ''.join([b for b in image.data()]) expect += [('GET', '/v1/images/3', {}, None)] self.assertEqual(self.api.calls, expect) self.assertEqual(data, 'ZZZ') class ParameterFakeAPI(utils.FakeAPI): image_list = {'images': [ { 'id': 'a', 'name': 'image-1', 'properties': {'arch': 'x86_64'}, }, { 'id': 'b', 'name': 'image-2', 'properties': {'arch': 'x86_64'}, }, ]} def json_request(self, method, url, **kwargs): self.url = url return utils.FakeResponse({}), ParameterFakeAPI.image_list class FakeArg(object): def __init__(self, arg_dict): self.arg_dict = arg_dict self.fields = arg_dict.keys() def __getattr__(self, name): if name in self.arg_dict: return self.arg_dict[name] else: return None class UrlParameterTest(testtools.TestCase): def setUp(self): super(UrlParameterTest, self).setUp() self.api = ParameterFakeAPI({}) self.gc = client.Client("http://fakeaddress.com") self.gc.images = images.ImageManager(self.api) def test_is_public_list(self): shell.do_image_list(self.gc, FakeArg({"is_public": "True"})) parts = urlparse.urlparse(self.api.url) qs_dict = urlparse.parse_qs(parts.query) self.assertTrue('is_public' in qs_dict) self.assertTrue(qs_dict['is_public'][0].lower() == "true") def test_copy_from_used(self): class LegacyFakeArg(object): def __init__(self, fields): self.fields = fields self.dry_run = False self.verbose = False def images_create(**kwargs): class FakeImage(): id = "ThisiSanID" self.assertNotEqual(kwargs['data'], sys.stdin) return FakeImage() self.gc.images.create = images_create args = LegacyFakeArg(["copy_from=http://somehost.com/notreal.qcow"]) legacy_shell.do_add(self.gc, args) python-glanceclient-0.12.0/tests/v1/test_image_members.py0000664000175300017540000001012512241451455024575 0ustar jenkinsjenkins00000000000000# 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 testtools import glanceclient.v1.images import glanceclient.v1.image_members from tests import utils fixtures = { '/v1/images/1/members': { 'GET': ( {}, {'members': [ {'member_id': '1', 'can_share': False}, ]}, ), 'PUT': ({}, None), }, '/v1/images/1/members/1': { 'GET': ( {}, {'member': { 'member_id': '1', 'can_share': False, }}, ), 'PUT': ({}, None), 'DELETE': ({}, None), }, '/v1/shared-images/1': { 'GET': ( {}, {'shared_images': [ {'image_id': '1', 'can_share': False}, ]}, ), }, } class ImageMemberManagerTest(testtools.TestCase): def setUp(self): super(ImageMemberManagerTest, self).setUp() self.api = utils.FakeAPI(fixtures) self.mgr = glanceclient.v1.image_members.ImageMemberManager(self.api) self.image = glanceclient.v1.images.Image(self.api, {'id': '1'}, True) def test_list_by_image(self): members = self.mgr.list(image=self.image) expect = [('GET', '/v1/images/1/members', {}, None)] self.assertEqual(self.api.calls, expect) self.assertEqual(len(members), 1) self.assertEqual(members[0].member_id, '1') self.assertEqual(members[0].image_id, '1') self.assertEqual(members[0].can_share, False) def test_list_by_member(self): resource_class = glanceclient.v1.image_members.ImageMember member = resource_class(self.api, {'member_id': '1'}, True) self.mgr.list(member=member) expect = [('GET', '/v1/shared-images/1', {}, None)] self.assertEqual(self.api.calls, expect) def test_get(self): member = self.mgr.get(self.image, '1') expect = [('GET', '/v1/images/1/members/1', {}, None)] self.assertEqual(self.api.calls, expect) self.assertEqual(member.member_id, '1') self.assertEqual(member.image_id, '1') self.assertEqual(member.can_share, False) def test_delete(self): self.mgr.delete('1', '1') expect = [('DELETE', '/v1/images/1/members/1', {}, None)] self.assertEqual(self.api.calls, expect) def test_create(self): self.mgr.create(self.image, '1', can_share=True) expect_body = {'member': {'can_share': True}} expect = [('PUT', '/v1/images/1/members/1', {}, expect_body)] self.assertEqual(self.api.calls, expect) def test_replace(self): body = [ {'member_id': '2', 'can_share': False}, {'member_id': '3'}, ] self.mgr.replace(self.image, body) expect = [('PUT', '/v1/images/1/members', {}, {'memberships': body})] self.assertEqual(self.api.calls, expect) def test_replace_objects(self): body = [ glanceclient.v1.image_members.ImageMember( self.mgr, {'member_id': '2', 'can_share': False}, True), glanceclient.v1.image_members.ImageMember( self.mgr, {'member_id': '3', 'can_share': True}, True), ] self.mgr.replace(self.image, body) expect_body = { 'memberships': [ {'member_id': '2', 'can_share': False}, {'member_id': '3', 'can_share': True}, ], } expect = [('PUT', '/v1/images/1/members', {}, expect_body)] self.assertEqual(self.api.calls, expect) python-glanceclient-0.12.0/tests/v1/__init__.py0000664000175300017540000000000012241451455022470 0ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/tests/var/0000775000175300017540000000000012241451524020630 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/tests/var/ca.crt0000664000175300017540000000410612241451455021731 0ustar jenkinsjenkins00000000000000-----BEGIN CERTIFICATE----- MIIF7jCCA9YCCQDbl9qx7iIeJDANBgkqhkiG9w0BAQUFADCBuDEZMBcGA1UEChMQ T3BlbnN0YWNrIENBIE9yZzEaMBgGA1UECxMRT3BlbnN0YWNrIFRlc3QgQ0ExIzAh BgkqhkiG9w0BCQEWFGFkbWluQGNhLmV4YW1wbGUuY29tMREwDwYDVQQHEwhTdGF0 ZSBDQTELMAkGA1UECBMCQ0ExCzAJBgNVBAYTAkFVMS0wKwYDVQQDEyRPcGVuc3Rh Y2sgVGVzdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTIxMTE2MTI1MDE2WhcN NDAwNDAzMTI1MDE2WjCBuDEZMBcGA1UEChMQT3BlbnN0YWNrIENBIE9yZzEaMBgG A1UECxMRT3BlbnN0YWNrIFRlc3QgQ0ExIzAhBgkqhkiG9w0BCQEWFGFkbWluQGNh LmV4YW1wbGUuY29tMREwDwYDVQQHEwhTdGF0ZSBDQTELMAkGA1UECBMCQ0ExCzAJ BgNVBAYTAkFVMS0wKwYDVQQDEyRPcGVuc3RhY2sgVGVzdCBDZXJ0aWZpY2F0ZSBB dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC94cpBjwj2 MD0w5j1Jlcy8Ljmk3r7CRaoV5vhWUrAWpT7Thxr/Ti0qAfZZRSIVpvBM0RlseH0Q toUJixuYMoNRPUQ74r/TRoO8HfjQDJfnXtWg2L7DRP8p4Zgj3vByBUCU+rKsbI/H Nssl/AronADbZXCoL5hJRN8euMYZGrt/Gh1ZotKE5gQlEjylDFlA3s3pn+ABLgzf 7L7iufwV3zLdPRHCb6Ve8YvUmKfI6gy+WwTRhNhLz4Nj0uBthnj6QhnRXtxkNT7A aAStqKH6TtYRnk2Owh8ITFbtLQ0/MSV8jHAxMXx9AloBhEKxv3cIpgLH6lOCnj// Ql+H6/QWtmTUHzP1kBfMhTQnWTfR92QTcgEMiZ7a07VyVtLh+kp/G5IUqpM6Pyz/ O6QDs7FF69bTpws7Ce916PPrGFZ9Gqvo/P0jXge8kYqO+a8QnTRldAxdUzPJCK9+ Dyi2LWeHf8nPFYdwW9Ov6Jw1CKDYxjJg6KIwnrMPa2eUdPB6/OKkqr9/KemOoKQu 4KSaYadFZbaJwt7JPZaHy6TpkGxW7Af8RqGrW6a6nWEFcfO2POuHcAHWL5LiRmni unm60DBF3b3itDTqCvER3mZE9pN8dqtxdpB8SUX8eq0UJJK2K8mJQS+oE9crbqYb 1kQbYjhhPLlvOQru+/m/abqZrC04u2OtYQIDAQABMA0GCSqGSIb3DQEBBQUAA4IC AQA8wGVBbzfpQ3eYpchiHyHF9N5LIhr6Bt4jYDKLz8DIbElLtoOlgH/v7hLGJ7wu R9OteonwQ1qr9umMmnp61bKXOEBJLBJbGKEt0MNLmmX89+M/h3rdMVZEz/Hht/xK Xm4di8pjkHfmdhqsbiFW81lAt9W1r74lnH7wQHr9ueALGKDx0hi8pAZ27itgQVHL eA1erhw0kjr9BqWpDIskVwePcD7pFoZ48GQlST0uIEq5U+1AWq7AbOABsqODygKi Ri5pmTasNFT7nEX3ti4VN214MNy0JnPzTRNWR2rD0I30AebM3KkzTprbLVfnGkm4 7hOPV+Wc8EjgbbrUAIp2YpOfO/9nbgljTOUsqfjqxzvHx/09XOo2M6NIE5UiHqIq TXN7CeGIhBoYbvBAH2QvtveFXv41IYL4zFFXo4wTBSzCCOUGeDDv0U4hhsNaCkDQ G2TcubNA4g/FAtqLvPj/6VbIIgFE/1/6acsT+W0O+kkVAb7ej2dpI7J+jKXDXuiA PDCMn9dVQ7oAcaQvVdvvRphLdIZ9wHgqKhxKsMwzIMExuDKL0lWe/3sueFyol6nv xRCSgzr5MqSObbO3EnWgcUocBvlPyYLnTM2T8C5wh3BGnJXqJSRETggNn8PXBVIm +c5o+Ic0mYu4v8P1ZSozFdgf+HLriVPwzJU5dHvvTEu7sw== -----END CERTIFICATE----- python-glanceclient-0.12.0/tests/var/wildcard-certificate.crt0000664000175300017540000000645412241451455025427 0ustar jenkinsjenkins00000000000000#Certificate: # Data: # Version: 1 (0x0) # Serial Number: 13493453254446411258 (0xbb42603e589dedfa) # Signature Algorithm: sha1WithRSAEncryption # Issuer: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=*.pong.example.com/emailAddress=admin@example.com # Validity # Not Before: Aug 21 17:29:18 2013 GMT # Not After : Jul 28 17:29:18 2113 GMT # Subject: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=*.pong.example.com/emailAddress=admin@example.com # Subject Public Key Info: # Public Key Algorithm: rsaEncryption # Public-Key: (4096 bit) # Modulus: # 00:d4:bb:3a:c4:a0:06:54:31:23:5d:b0:78:5a:be: # 45:44:ae:a1:89:86:11:d8:ca:a8:33:b0:4f:f3:e1: # 46:1e:85:a3:2a:9c:a4:e0:c2:14:34:4f:91:df:dc: # . # . # . # Exponent: 65537 (0x10001) # Signature Algorithm: sha1WithRSAEncryption # 9f:cc:08:5d:19:ee:54:31:a3:57:d7:3c:89:89:c0:69:41:dd: # 46:f8:73:68:ec:46:b9:fa:f5:df:f6:d9:58:35:d8:53:94:88: # bd:36:a6:23:9e:0c:0d:89:62:35:91:49:b6:14:f4:43:69:3c: # . # . # . -----BEGIN CERTIFICATE----- MIIFyjCCA7ICCQC7QmA+WJ3t+jANBgkqhkiG9w0BAQUFADCBpTELMAkGA1UEBhMC VVMxCzAJBgNVBAgMAkNBMQ8wDQYDVQQHDAZTdGF0ZTExGzAZBgNVBAoMEk9wZW5z dGFjayBUZXN0IE9yZzEcMBoGA1UECwwTT3BlbnN0YWNrIFRlc3QgVW5pdDEbMBkG A1UEAwwSKi5wb25nLmV4YW1wbGUuY29tMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBl eGFtcGxlLmNvbTAgFw0xMzA4MjExNzI5MThaGA8yMTEzMDcyODE3MjkxOFowgaUx CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEPMA0GA1UEBwwGU3RhdGUxMRswGQYD VQQKDBJPcGVuc3RhY2sgVGVzdCBPcmcxHDAaBgNVBAsME09wZW5zdGFjayBUZXN0 IFVuaXQxGzAZBgNVBAMMEioucG9uZy5leGFtcGxlLmNvbTEgMB4GCSqGSIb3DQEJ ARYRYWRtaW5AZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK AoICAQDUuzrEoAZUMSNdsHhavkVErqGJhhHYyqgzsE/z4UYehaMqnKTgwhQ0T5Hf 3GmlIBt4I96/3cxj0qSLrdR81fM+5Km8lIlVHwVn1y6LKcMlaUC4K+sgDLcjhZfb f9+fMkcur3WlNzKpAEaIosWwsu6YvYc+W/nPBpKxMbOZ4fZiPMEo8Pxmw7sl/6hn lBOJj7dpZOZpHhVPZgzYNVoyfKCZiwgdxH4JEYa+EQos87+2Nwhs7bCgrTLLppCU vpobwZV5w4O0D6INpUfBmsr4IAuXeFWZa61vZYqhaVbAbTTlUzOLGh7Z2uz9gt75 iSR2J0e2xntVaUIYLIAUNOO2edk8NMAuIOGr2EIyC7i2O/BTti2YjGNO7SsEClxi IFKjYahylHmNrS1Q/oMAcJppmhz+oOCmKOMmAZXYAH1A3gs/sWphJpgv/MWt6Ji2 4VpFaJ+o4bHILlqIpuvL4GLIOkmxVP639khaumgKtgNIUTKJ/V6t/J31WARfxKxl BQTTzV/Be+84YJiiddx8eunU8AorPyAJFzsDPTJpFUB4Q5BwAeDGCySgxJpUqM2M TETBycdiVToM4SWkRsOZgZxQ+AVfkkqDct2Bat2lg9epcIez8PrsohQjQbmiqUUL 2c3de4kLYzIWF8EN3P2Me/7b06jbn4c7Fly/AN6tJOG23BzhHQIDAQABMA0GCSqG SIb3DQEBBQUAA4ICAQCfzAhdGe5UMaNX1zyJicBpQd1G+HNo7Ea5+vXf9tlYNdhT lIi9NqYjngwNiWI1kUm2FPRDaTwC0kLxk5zBPzF7bcf0SwJCeDjmlUpY7YenS0DA XmIbg8FvgOlp69Ikrqz98Y4pB9H4O81WdjxNBBbHjrufAXxZYnh5rXrVsXeSJ8jN MYGWlSv4xwFGfRX53b8VwXFjGjAkH8SQGtRV2w9d0jF8OzFwBA4bKk4EplY0yBPR 2d7Y3RVrDnOVfV13F8CZxJ5fu+6QamUwIaTjpyqflE1L52KTy+vWPYR47H2u2bhD IeZRufJ8adNIOtH32EcENkusQjLrb3cTXGW00TljhFXd22GqL5d740u+GEKHtWh+ 9OKPTMZK8yK7d5EyS2agTVWmXU6HfpAKz9+AEOnVYErpnggNZjkmJ9kD185rGlSZ Vvo429hXoUAHNbd+8zda3ufJnJf5q4ZEl8+hp8xsvraUy83XLroVZRsKceldmAM8 swt6n6w5gRKg4xTH7KFrd+KNptaoY3SsVrnJuaSOPenrUXbZzaI2Q35CId93+8NP mXVIWdPO1msdZNiCYInRIGycK+oifUZPtAaJdErg8rt8NSpHzYKQ0jfjAGiVHBjK s0J2TjoKB3jtlrw2DAmFWKeMGNp//1Rm6kfQCCXWftn+TA7XEJhcjyDBVciugA== -----END CERTIFICATE----- python-glanceclient-0.12.0/tests/var/expired-cert.crt0000664000175300017540000000417312241451455023745 0ustar jenkinsjenkins00000000000000-----BEGIN CERTIFICATE----- MIIGFTCCA/2gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBuDEZMBcGA1UEChMQT3Bl bnN0YWNrIENBIE9yZzEaMBgGA1UECxMRT3BlbnN0YWNrIFRlc3QgQ0ExIzAhBgkq hkiG9w0BCQEWFGFkbWluQGNhLmV4YW1wbGUuY29tMREwDwYDVQQHEwhTdGF0ZSBD QTELMAkGA1UECBMCQ0ExCzAJBgNVBAYTAkFVMS0wKwYDVQQDEyRPcGVuc3RhY2sg VGVzdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTIxMTE1MTcwNjMzWhcNMTIx MTE2MTcwNjMzWjCBqDEbMBkGA1UEChMST3BlbnN0YWNrIFRlc3QgT3JnMRwwGgYD VQQLExNPcGVuc3RhY2sgVGVzdCBVbml0MSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBl eGFtcGxlLmNvbTEPMA0GA1UEBxMGU3RhdGUxMQswCQYDVQQIEwJDQTELMAkGA1UE BhMCVVMxHjAcBgNVBAMTFW9wZW5zdGFjay5leGFtcGxlLmNvbTCCAiIwDQYJKoZI hvcNAQEBBQADggIPADCCAgoCggIBANn9w82sGN+iALSlZ5/Odd5iJ3MAJ5BoalMG kfUECGMewd7lE5+6ok1+vqVbYjd+F56aSkIJFR/ck51EYG2diGM5E5zjdiLcyB9l dKB5PmaB2P9dHyomy+sMONqhw5uEsWKIfPbtjzGRhjJL0bIYwptGr4JPraZy8R3d HWbTO3SlnFkjHHtfoKuZtRJq5OD1hXM8J9IEsBC90zw7RWCTw1iKllLfKITPUi7O i8ITjUyTVKR2e56XRtmxGgGsGyZpcYrmhRuLo9jyL9m3VuNzsfwDvCqn7cnZIOQa VO4hNZdO+33PINCC+YVNOGYwqfBuKxYvHJSbMfOZ6JDK98v65pWLBN7PObYIjQFH uJyK5DuQMqvyRIcrtfLUalepD+PQaCn4ajgXjpqBz4t0pMte8jh0i4clLwvT0elT PtA+MMos3hIGjJgEHTvLdCff9qlkjHlW7lg45PYn7S0Z7dqtBWD7Ys2B+AWp/skt hRr7YZeegLfHVJVkMFL6Ojs98161W2FLmEA+5nejzjx7kWlJsg9aZPbBnN87m6iK RHI+VkqSpBHm10iMlp4Nn30RtOj0wQhxoZjtEouGeRobHN5ULwpAfNEpKMMZf5bt 604JjOP9Pn+WzsvzGDeXjgxUP55PIR+EpHkvS5h1YQ+9RV5J669e2J9T4gnc0Abg t3jJvtp1AgMBAAGjODA2MDQGA1UdEQQtMCuCEGFsdDEuZXhhbXBsZS5jb22BDm9z QGV4YW1wbGUuY29tggcwLjAuMC4wMA0GCSqGSIb3DQEBBQUAA4ICAQBkKUA4lhsS zjcuh77wtAIP9SN5Se4CheTRDXKDeuwWB6VQDzdJdtqSnWNF6sVEA97vhNTSjaBD hfrtX9FZ+ImADlOf01t4Dakhsmje/DEPiQHaCy9P5fGtGIGRlWUyTmyQoV1LDLM5 wgB1V5Oz2iDat2AdvUb0OFP0O1M887OgPpfUDQJEUTVAs5JS+6P/6RPyFh/dHWiX UGoM0nMvTwsLWT4CZ9NdIChecVwBFqXjNytPY53tKbCWp77d/oGUg5Pb6EBD3xSW AeMJ6PuafDRgm/He8nOtZnUd+53Ha59yzSGnSopu5WqrUa/xD+ZiK6dX7LsH/M8y Hz0rh7w22qNHUxNaC3hrhx1BxX4au6z4kpKXIlAWH7ViRzVZ8XkwqqrndqWPWOFk 1emLLJ1dfT8FXdgpHenkUiktAf5qZhUWbF6nr9at+c4T7ZrLHSekux2r29kD9BJw O2gSSclxKlMPwirUC0P4J/2WP72kCbf6AEfKU2siT12E6/xOmgen9lVYKckBiLbb rJ97L1ieJI8GZTGExjtE9Lo+XVsv28D2XLU8vNCODs0xPZCr2TLNS/6YcnVy6594 vpvU7fbNFAyxG4sjQC0wHoN6rn+kd1kzfprmBHKTx3W7y+hzjb+W7iS2EZn20k+N l3+dFHnWayuCdqcFwIl3m8i8FupFihz9+A== -----END CERTIFICATE----- python-glanceclient-0.12.0/tests/var/privatekey.key0000664000175300017540000000625312241451455023536 0ustar jenkinsjenkins00000000000000-----BEGIN RSA PRIVATE KEY----- MIIJKQIBAAKCAgEA1Ls6xKAGVDEjXbB4Wr5FRK6hiYYR2MqoM7BP8+FGHoWjKpyk 4MIUNE+R39xppSAbeCPev93MY9Kki63UfNXzPuSpvJSJVR8FZ9cuiynDJWlAuCvr IAy3I4WX23/fnzJHLq91pTcyqQBGiKLFsLLumL2HPlv5zwaSsTGzmeH2YjzBKPD8 ZsO7Jf+oZ5QTiY+3aWTmaR4VT2YM2DVaMnygmYsIHcR+CRGGvhEKLPO/tjcIbO2w oK0yy6aQlL6aG8GVecODtA+iDaVHwZrK+CALl3hVmWutb2WKoWlWwG005VMzixoe 2drs/YLe+YkkdidHtsZ7VWlCGCyAFDTjtnnZPDTALiDhq9hCMgu4tjvwU7YtmIxj Tu0rBApcYiBSo2GocpR5ja0tUP6DAHCaaZoc/qDgpijjJgGV2AB9QN4LP7FqYSaY L/zFreiYtuFaRWifqOGxyC5aiKbry+BiyDpJsVT+t/ZIWrpoCrYDSFEyif1erfyd 9VgEX8SsZQUE081fwXvvOGCYonXcfHrp1PAKKz8gCRc7Az0yaRVAeEOQcAHgxgsk oMSaVKjNjExEwcnHYlU6DOElpEbDmYGcUPgFX5JKg3LdgWrdpYPXqXCHs/D67KIU I0G5oqlFC9nN3XuJC2MyFhfBDdz9jHv+29Oo25+HOxZcvwDerSThttwc4R0CAwEA AQKCAgEAqnwqSu4cZFjFCQ6mRcL67GIvn3FM2DsBtfr0+HRvp4JeE4ZaNK4VVx71 vzx7hhRHL28/0vBEHzPvHun+wtUMDjlfNnyr2wXzZRb0fB7KAC9r6K15z8Og+dzU qNrAMmsu1OFVHUUxWnOYE2Svnj6oLMynmHhJqXqREWTNlOOce3pJKzCGdy0hzQAo zGnFhpcg3Fw6s7+iQHF+lb+cO53Zb3QW2xRgFZBwNd6eEwx9deCA5htPVFW5wbAJ asud4eSwkFb6M9Hbg6gT67rMMzIrWAbeQwgihIYSJe2v0qMyox6czjvuwZVMHJdH byBTkkVEmdxTd03V5F21f3wrik/4oWqytjmjvMIY1gGTMo7aBnvPoKpgc2fqJub9 cdAfGiJnFqo4Ae55mL4sgJPUCP7UATaDNAOCgt0zStmHMH8ACwk0dh1pzjyjpSR3 OQfFs8QCAl9cvzxwux1tzG/uYxOrr+Rj2JlZKW/ljbWOeE0Gnjca73F40uGkEIbZ 5i6YEuiPE6XGH0TP62Sdu2t5OlaKnZT12Tf6E8xNDsdaLuvAIz5sXyhoxvOmVd9w V4+uN1bZ10c5k/4uGRsHiXjX6IyYZEj8rKz6ryNikCdi6OzxWE3pCXmfBlVaXtO6 EIubzk6dgjWcsPoqOsIl5Ywz4RWu0YUk4ZxRts54jCn14bPQpoECggEBAPiLTN8Z I0GQXMQaq9sN8kVsM/6AG/vWbc+IukPDYEC6Prk79jzkxMpDP8qK9C71bh39U1ky Kz4gSsLi9v3rM1gZwNshkZJ/zdQJ1NiCkzJVJX48DGeyYqUBjVt8Si37V2vzblBN RvM7U3rDN0xGiannyWnBC/jed+ZFCo97E9yOxIAs2ekwsl+ED3j1cARv8pBTGWnw Zhh4AD/Osk5U038oYcWHaIzUuNhEpv46bFLjVT11mGHfUY51Db3jBn0HYRlOPEV/ F0kE5F+6rRg2tt7n0PO3UbzSNFyDRwtknJ2Nh4EtZZe93domls8SMR/kEHXcPLiQ ytEFyIAzsxfUwrECggEBANsc54N/LPmX1XuC643ZsDobH5/ALKc8W7wE7e82oSTD 7cKBgdgB71DupJ7m81LHaDgT2RIzjl+lR3VVYLR/ukMcW+47JWrHyrsinu6itOdt ruhw0UPksoJGsB4KxUdRioFVT7m45GpnseJL0tjYaTCW01swae4QL4skNjjphPrb b/heMz9n79TK2ePlw1BvJKH0fnOJRuh/v63pD9SymB8EPsazjloKZ5qTrqVi3Obs F8WTSdl8KB1JSgeppdvHRcZQY1J+UfdCAlGD/pP7/zCKkRYcetre7fGMKVyPIDzO GAWz0xA2jnrgg7UqIh74oRHe0lZVMdMQ7FoJbRa7KC0CggEAJreEbQh8bn0vhjjl ZoVApUHaw51vPobDql2RLncj6lFY7gACNrAoW52oNUP6D8qZscBBmJZxGAdtvfgf I6Tc5a91VG1hQOH5zTsO1f9ZMLEE2yo9gHXQWgXo4ER3RbxufNl56LZxA/jM40W/ unkOftIllPzGgakeIlfE8l7o1CXFRHY4J9Q3JRvsURpirb5GmeboAZG6RbuDxmzL Z9pc6+T9fgi+55lHhiEDpnyxXSQepilIaI6iJL/lORxBaX6ZyJhgWS8YEH7bmHH6 /tefGxAfg6ed6v0PvQ2SJpswrnZakmvg9IdWJOJ4AZ/C2UXsrn91Ugb0ISV2e0oS bvbssQKCAQBjstc04h0YxJmCxaNgu/iPt9+/1LV8st4awzNwcS8Jh40bv8nQ+7Bk 5vFIzFVTCSDGw2E2Avd5Vb8aCGskNioOd0ztLURtPdNlKu+eLbKayzGW2h6eAeWn mXpxcP0q4lNfXe4U16g3Mk+iZFXgDThvv3EUQQcyJ3M6oJN7eeXkLwzXuiUfaK+b 52EVbWpdovTMLG+NKp11FQummjF12n2VP11BFFplZe6WSzRgVIenGy4F3Grx5qhq CvsAWZT6V8XL4rAOzSOGmiZr6N9hfnwzHhm+Md9Ez8L88YWwc/97K1uK3LPg4LIb /yRuvmkgJolDlFuopMMzArRIk5lrimVRAoIBAQDZmXk/VMA7fsI1/2sgSME0xt1A jkJZMZSnVD0UDWFkbyK6E5jDnwVUyqBDYe+HJyT4UnPDNCj++BchCQcG0Jih04RM jwGqxkfTF9K7kfouINSSXPRw/BtHkqMhV/g324mWcifCFVkDQghuslfmey8BKumo 2KPyGnF9Q8CvTSQ0VlK1ZAKRf/zish49PMm7vD1KGkjRPliS3tgAmXPEpwijPGse 4dSUeTfw5wCKAoq9DHjyHdO5fnfkOvA5PMQ4JZAzOCzJak8ET+tw4wB/dBeYiLVi l00GHLYAr5Nv/WqVnl/VLMd9rOCnLck+pxBNSa6dTrp3FuY00son6hneIvkv -----END RSA PRIVATE KEY----- python-glanceclient-0.12.0/tests/var/certificate.crt0000664000175300017540000000661412241451455023636 0ustar jenkinsjenkins00000000000000# Certificate: # Data: # Version: 3 (0x2) # Serial Number: 1 (0x1) # Signature Algorithm: sha1WithRSAEncryption # Issuer: O=Openstack CA Org, OU=Openstack Test CA/emailAddress=admin@ca.example.com, # L=State CA, ST=CA, C=AU, CN=Openstack Test Certificate Authority # Validity # Not Before: Nov 16 12:50:19 2012 GMT # Not After : Apr 3 12:50:19 2040 GMT # Subject: O=Openstack Test Org, OU=Openstack Test Unit/emailAddress=admin@example.com, # L=State1, ST=CA, C=US, CN=0.0.0.0 # Subject Public Key Info: # Public Key Algorithm: rsaEncryption # RSA Public Key: (4096 bit) # Modulus (4096 bit): # 00:d4:bb:3a:c4:a0:06:54:31:23:5d:b0:78:5a:be: # 45:44:ae:a1:89:86:11:d8:ca:a8:33:b0:4f:f3:e1: # . # . # . # Exponent: 65537 (0x10001) # X509v3 extensions: # X509v3 Subject Alternative Name: # DNS:alt1.example.com, DNS:alt2.example.com # Signature Algorithm: sha1WithRSAEncryption # 2c:fc:5c:87:24:bd:4a:fa:40:d2:2e:35:a4:2a:f3:1c:b3:67: # b0:e4:8a:cd:67:6b:55:50:d4:cb:dd:2d:26:a5:15:62:90:a3: # . # . # . -----BEGIN CERTIFICATE----- MIIGADCCA+igAwIBAgIBATANBgkqhkiG9w0BAQUFADCBuDEZMBcGA1UEChMQT3Bl bnN0YWNrIENBIE9yZzEaMBgGA1UECxMRT3BlbnN0YWNrIFRlc3QgQ0ExIzAhBgkq hkiG9w0BCQEWFGFkbWluQGNhLmV4YW1wbGUuY29tMREwDwYDVQQHEwhTdGF0ZSBD QTELMAkGA1UECBMCQ0ExCzAJBgNVBAYTAkFVMS0wKwYDVQQDEyRPcGVuc3RhY2sg VGVzdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTIxMTE2MTI1MDE5WhcNNDAw NDAzMTI1MDE5WjCBmjEbMBkGA1UEChMST3BlbnN0YWNrIFRlc3QgT3JnMRwwGgYD VQQLExNPcGVuc3RhY2sgVGVzdCBVbml0MSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBl eGFtcGxlLmNvbTEPMA0GA1UEBxMGU3RhdGUxMQswCQYDVQQIEwJDQTELMAkGA1UE BhMCVVMxEDAOBgNVBAMTBzAuMC4wLjAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw ggIKAoICAQDUuzrEoAZUMSNdsHhavkVErqGJhhHYyqgzsE/z4UYehaMqnKTgwhQ0 T5Hf3GmlIBt4I96/3cxj0qSLrdR81fM+5Km8lIlVHwVn1y6LKcMlaUC4K+sgDLcj hZfbf9+fMkcur3WlNzKpAEaIosWwsu6YvYc+W/nPBpKxMbOZ4fZiPMEo8Pxmw7sl /6hnlBOJj7dpZOZpHhVPZgzYNVoyfKCZiwgdxH4JEYa+EQos87+2Nwhs7bCgrTLL ppCUvpobwZV5w4O0D6INpUfBmsr4IAuXeFWZa61vZYqhaVbAbTTlUzOLGh7Z2uz9 gt75iSR2J0e2xntVaUIYLIAUNOO2edk8NMAuIOGr2EIyC7i2O/BTti2YjGNO7SsE ClxiIFKjYahylHmNrS1Q/oMAcJppmhz+oOCmKOMmAZXYAH1A3gs/sWphJpgv/MWt 6Ji24VpFaJ+o4bHILlqIpuvL4GLIOkmxVP639khaumgKtgNIUTKJ/V6t/J31WARf xKxlBQTTzV/Be+84YJiiddx8eunU8AorPyAJFzsDPTJpFUB4Q5BwAeDGCySgxJpU qM2MTETBycdiVToM4SWkRsOZgZxQ+AVfkkqDct2Bat2lg9epcIez8PrsohQjQbmi qUUL2c3de4kLYzIWF8EN3P2Me/7b06jbn4c7Fly/AN6tJOG23BzhHQIDAQABozEw LzAtBgNVHREEJjAkghBhbHQxLmV4YW1wbGUuY29tghBhbHQyLmV4YW1wbGUuY29t MA0GCSqGSIb3DQEBBQUAA4ICAQAs/FyHJL1K+kDSLjWkKvMcs2ew5IrNZ2tVUNTL 3S0mpRVikKOQbNLh5B6Q7eQIvilCdkuit7o2HrpxQHsRor5b4+LyjSLoltyE7dgr ioP5nkKH+ujw6PtMxJCiKvvI+6cVHh6EV2ZkddvbJLVBVVZmB4H64xocS3rrQj19 SXFYVrEjqdLzdGPNIBR+XVnTCeofXg1rkMaU7JuY8nRztee8PRVcKYX6scPfZJb8 +Ea2dsTmtQP4H9mk+JiKGYhEeMLVmjiv3q7KIFownTKZ88K6QbpW2Nj66ItvphoT QqI3rs6E8N0BhftiCcxXtXg+o4utfcnp8jTXX5tVnv44FqtWx7Gzg8XTLPri+ZEB 5IbgU4Q3qFicenBfjwZhH3+GNe52/wLVZLYjal5RPVSRdu9UEDeDAwTCMZSLF4lC rc9giQCMnJ4ISi6C7xH+lDZGFqcJd4oXg/ue9aOJJAFTwhd83fdCHhUu431iPrts NubfrHLMeUjluFgIWmhEZg+XTjB1SQeQzNaZiMODaAv4/40ZVKxvNpDFwIIsPUDf +uC+fv1Q8+alqVMl2ouVyr8ut43HWNV6CJHXODvFp5irjxzVSgLtYDVUInkDFJEs tFpTY21/zVAHIvsj2n4F1231nILR6vBp/WbwBY7r7j0oRtbaO3B1Q6tsbCZQRkKU tdc5rw== -----END CERTIFICATE----- python-glanceclient-0.12.0/tests/test_base.py0000664000175300017540000000432212241451455022367 0ustar jenkinsjenkins00000000000000# Copyright 2013 OpenStack Foundation # Copyright (C) 2013 Yahoo! Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # vim: tabstop=4 shiftwidth=4 softtabstop=4 import testtools from glanceclient.common import base class TestBase(testtools.TestCase): def test_resource_repr(self): r = base.Resource(None, dict(foo="bar", baz="spam")) self.assertEqual(repr(r), "") def test_getid(self): self.assertEqual(base.getid(4), 4) class TmpObject(object): id = 4 self.assertEqual(base.getid(TmpObject), 4) def test_two_resources_with_same_id_are_equal(self): # Two resources of the same type with the same id: equal r1 = base.Resource(None, {'id': 1, 'name': 'hi'}) r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) self.assertEqual(r1, r2) def test_two_resources_with_eq_info_are_equal(self): # Two resources with no ID: equal if their info is equal r1 = base.Resource(None, {'name': 'joe', 'age': 12}) r2 = base.Resource(None, {'name': 'joe', 'age': 12}) self.assertEqual(r1, r2) def test_two_resources_with_diff_id_are_not_equal(self): # Two resources with diff ID: not equal r1 = base.Resource(None, {'id': 1, 'name': 'hi'}) r2 = base.Resource(None, {'id': 2, 'name': 'hello'}) self.assertNotEqual(r1, r2) def test_two_resources_with_not_eq_info_are_not_equal(self): # Two resources with no ID: not equal if their info is not equal r1 = base.Resource(None, {'name': 'bill', 'age': 21}) r2 = base.Resource(None, {'name': 'joe', 'age': 12}) self.assertNotEqual(r1, r2) python-glanceclient-0.12.0/tests/utils.py0000664000175300017540000000652312241451455021563 0ustar jenkinsjenkins00000000000000# 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 copy import requests import StringIO import testtools from glanceclient.common import http class FakeAPI(object): def __init__(self, fixtures): self.fixtures = fixtures self.calls = [] def _request(self, method, url, headers=None, body=None): call = (method, url, headers or {}, body) self.calls.append(call) return self.fixtures[url][method] def raw_request(self, *args, **kwargs): fixture = self._request(*args, **kwargs) resp = FakeResponse(fixture[0], StringIO.StringIO(fixture[1])) body_iter = http.ResponseBodyIterator(resp) return resp, body_iter def json_request(self, *args, **kwargs): fixture = self._request(*args, **kwargs) return FakeResponse(fixture[0]), fixture[1] class FakeResponse(object): def __init__(self, headers, body=None, version=1.0, status=200, reason="Ok"): """ :param headers: dict representing HTTP response headers :param body: file-like object :param version: HTTP Version :param status: Response status code :param reason: Status code related message. """ self.body = body self.status = status self.reason = reason self.version = version self.headers = headers def getheaders(self): return copy.deepcopy(self.headers).items() def getheader(self, key, default): return self.headers.get(key, default) def read(self, amt): return self.body.read(amt) class TestCase(testtools.TestCase): TEST_REQUEST_BASE = { 'config': {'danger_mode': False}, 'verify': True} class TestResponse(requests.Response): """ Class used to wrap requests.Response and provide some convenience to initialize with a dict """ def __init__(self, data): self._text = None super(TestResponse, self) if isinstance(data, dict): self.status_code = data.get('status_code', None) self.headers = data.get('headers', None) # Fake the text attribute to streamline Response creation self._text = data.get('text', None) else: self.status_code = data def __eq__(self, other): return self.__dict__ == other.__dict__ @property def text(self): return self._text class FakeTTYStdout(StringIO.StringIO): """A Fake stdout that try to emulate a TTY device as much as possible.""" def isatty(self): return True def write(self, data): # When a CR (carriage return) is found reset file. if data.startswith('\r'): self.seek(0) data = data[1:] return StringIO.StringIO.write(self, data) python-glanceclient-0.12.0/run_tests.sh0000775000175300017540000000211512241451455021265 0ustar jenkinsjenkins00000000000000#!/bin/bash function usage { echo "Usage: $0 [OPTION]..." echo "Run python-glanceclient's test suite(s)" echo "" echo " -p, --pep8 Just run flake8" echo " -h, --help Print this usage message" echo "" echo "This script is deprecated and currently retained for compatibility." echo 'You can run the full test suite for multiple environments by running "tox".' echo 'You can run tests for only python 2.7 by running "tox -e py27", or run only' echo 'the flake8 tests with "tox -e pep8".' exit } command -v tox > /dev/null 2>&1 if [ $? -ne 0 ]; then echo 'This script requires "tox" to run.' echo 'You can install it with "pip install tox".' exit 1; fi just_pep8=0 function process_option { case "$1" in -h|--help) usage;; -p|--pep8) let just_pep8=1;; esac } for arg in "$@"; do process_option $arg done if [ $just_pep8 -eq 1 ]; then tox -e pep8 exit fi tox -e py27 $toxargs 2>&1 | tee run_tests.err.log || exit if [ ${PIPESTATUS[0]} -ne 0 ]; then exit ${PIPESTATUS[0]} fi if [ -z "$toxargs" ]; then tox -e pep8 fi python-glanceclient-0.12.0/tools/0000775000175300017540000000000012241451524020036 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/tools/with_venv.sh0000775000175300017540000000030312241451455022405 0ustar jenkinsjenkins00000000000000#!/bin/bash command -v tox > /dev/null 2>&1 if [ $? -ne 0 ]; then echo 'This script requires "tox" to run.' echo 'You can install it with "pip install tox".' exit 1; fi tox -evenv -- $@ python-glanceclient-0.12.0/AUTHORS0000664000175300017540000000536012241451524017752 0ustar jenkinsjenkins00000000000000Adam Gandelman Alessandro Pilotti Alessio Ababilov Alex Gaynor Alex Meade Andre Naehring Andrew Laski Andy McCrae Anita Kuno Bhuvan Arumugam Brian Lamar Brian Rosmaita Brian Waldon Chang Bo Guo Chris Behrens Christian Berendt Chuck Short Clark Boylan Dan Prince Davanum Srinivas David Peraza David Wittman Dazhao Dean Troyer Diego Parrilla Dirk Mueller Doug Hellmann Fei Long Wang Flaper Fesp Florian Haas Gabe Westmaas Gabriel Hurley Ghe Rivero Hugh Saunders Jakub Ruzicka James E. Blair James Li Jared Culp Jay Pipes John Bresnahan Justin Santa Barbara Ken'ichi Ohmichi Kevin McDonald Lars Gellrich Mark J. Washenberger Mark McLoughlin MattieuPuel Michael Basnight Monty Taylor Russell Bryant Sascha Peilicke Sean Dague Stanislaw Pitucha Stuart McLaren Sulochan Acharya Tatyana Leontovich Thierry Carrez Thomas Leaman Unmesh Gurjar Venkatesh Sampath Vincent Untz Vishvananda Ishaya Yang Yu Zhenguo Niu Zhi Yan Liu ZhiQiang Fan amalaba eddie-sheffield iccha-sethi iccha.sethi isethi jaypipes lrqrun mouad benchchaoui python-glanceclient-0.12.0/glanceclient/0000775000175300017540000000000012241451524021326 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/glanceclient/common/0000775000175300017540000000000012241451524022616 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/glanceclient/common/base.py0000664000175300017540000000747712241451455024124 0ustar jenkinsjenkins00000000000000# 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. """ Base utilities to build API operation managers and objects on top of. """ import copy # Python 2.4 compat try: all except NameError: def all(iterable): return True not in (not x for x in iterable) def getid(obj): """ Abstracts the common pattern of allowing both an object or an object's ID (UUID) as a parameter when dealing with relationships. """ try: return obj.id except AttributeError: return obj class Manager(object): """ Managers interact with a particular type of API (servers, flavors, images, etc.) and provide CRUD operations for them. """ resource_class = None def __init__(self, api): self.api = api def _list(self, url, response_key, obj_class=None, body=None): resp, body = self.api.json_request('GET', url) if obj_class is None: obj_class = self.resource_class data = body[response_key] return [obj_class(self, res, loaded=True) for res in data if res] def _delete(self, url): self.api.raw_request('DELETE', url) def _update(self, url, body, response_key=None): resp, body = self.api.json_request('PUT', url, body=body) # PUT requests may not return a body if body: return self.resource_class(self, body[response_key]) class Resource(object): """ A resource represents a particular instance of an object (tenant, user, etc). This is pretty much just a bag for attributes. :param manager: Manager object :param info: dictionary representing resource attributes :param loaded: prevent lazy-loading if set to True """ def __init__(self, manager, info, loaded=False): self.manager = manager self._info = info self._add_details(info) self._loaded = loaded def _add_details(self, info): for (k, v) in info.iteritems(): setattr(self, k, v) def __getattr__(self, k): if k not in self.__dict__: #NOTE(bcwaldon): disallow lazy-loading if already loaded once if not self.is_loaded(): self.get() return self.__getattr__(k) raise AttributeError(k) else: return self.__dict__[k] def __repr__(self): reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) return "<%s %s>" % (self.__class__.__name__, info) def get(self): # set_loaded() first ... so if we have to bail, we know we tried. self.set_loaded(True) if not hasattr(self.manager, 'get'): return new = self.manager.get(self.id) if new: self._add_details(new._info) def __eq__(self, other): if not isinstance(other, self.__class__): return False if hasattr(self, 'id') and hasattr(other, 'id'): return self.id == other.id return self._info == other._info def is_loaded(self): return self._loaded def set_loaded(self, val): self._loaded = val def to_dict(self): return copy.deepcopy(self._info) python-glanceclient-0.12.0/glanceclient/common/exceptions.py0000664000175300017540000000025612241451455025357 0ustar jenkinsjenkins00000000000000# This is here for compatability purposes. Once all known OpenStack clients # are updated to use glanceclient.exc, this file should be removed from glanceclient.exc import * python-glanceclient-0.12.0/glanceclient/common/progressbar.py0000664000175300017540000000562012241451455025527 0ustar jenkinsjenkins00000000000000# 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 sys class _ProgressBarBase(object): """ Base abstract class used by specific class wrapper to show a progress bar when the wrapped object are consumed. :param wrapped: Object to wrap that hold data to be consumed. :param totalsize: The total size of the data in the wrapped object. :note: The progress will be displayed only if sys.stdout is a tty. """ def __init__(self, wrapped, totalsize): self._wrapped = wrapped self._totalsize = float(totalsize) self._show_progress = sys.stdout.isatty() and self._totalsize != 0 self._percent = 0 def _display_progress_bar(self, size_read): if self._show_progress: self._percent += size_read / self._totalsize # Output something like this: [==========> ] 49% sys.stdout.write('\r[{0:<30}] {1:.0%}'.format( '=' * int(round(self._percent * 29)) + '>', self._percent )) sys.stdout.flush() def __getattr__(self, attr): # Forward other attribute access to the wrapped object. return getattr(self._wrapped, attr) class VerboseFileWrapper(_ProgressBarBase): """ A file wrapper that show and advance a progress bar whenever file's read method is called. """ def read(self, *args, **kwargs): data = self._wrapped.read(*args, **kwargs) if data: self._display_progress_bar(len(data)) else: # Break to a new line from the progress bar for incoming output. sys.stdout.write('\n') return data class VerboseIteratorWrapper(_ProgressBarBase): """ An iterator wrapper that show and advance a progress bar whenever data is consumed from the iterator. :note: Use only with iterator that yield strings. """ def __iter__(self): return self def next(self): try: data = self._wrapped.next() # NOTE(mouad): Assuming that data is a string b/c otherwise calling # len function will not make any sense. self._display_progress_bar(len(data)) return data except StopIteration: # Break to a new line from the progress bar for incoming output. sys.stdout.write('\n') raise python-glanceclient-0.12.0/glanceclient/common/__init__.py0000664000175300017540000000000012241451455024720 0ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/glanceclient/common/http.py0000664000175300017540000004553612241451455024167 0ustar jenkinsjenkins00000000000000# 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 copy import errno import hashlib import logging import posixpath import socket import StringIO import struct import urlparse from six.moves import http_client try: import json except ImportError: import simplejson as json # Python 2.5 compat fix if not hasattr(urlparse, 'parse_qsl'): import cgi urlparse.parse_qsl = cgi.parse_qsl import OpenSSL from glanceclient import exc from glanceclient.common import utils from glanceclient.openstack.common import strutils try: from eventlet import patcher # Handle case where we are running in a monkey patched environment if patcher.is_monkey_patched('socket'): from eventlet.green.httplib import HTTPSConnection from eventlet.green.OpenSSL.SSL import GreenConnection as Connection from eventlet.greenio import GreenSocket # TODO(mclaren): A getsockopt workaround: see 'getsockopt' doc string GreenSocket.getsockopt = utils.getsockopt else: raise ImportError except ImportError: HTTPSConnection = http_client.HTTPSConnection from OpenSSL.SSL import Connection as Connection LOG = logging.getLogger(__name__) USER_AGENT = 'python-glanceclient' CHUNKSIZE = 1024 * 64 # 64kB class HTTPClient(object): def __init__(self, endpoint, **kwargs): self.endpoint = endpoint endpoint_parts = self.parse_endpoint(self.endpoint) self.endpoint_scheme = endpoint_parts.scheme self.endpoint_hostname = endpoint_parts.hostname self.endpoint_port = endpoint_parts.port self.endpoint_path = endpoint_parts.path self.connection_class = self.get_connection_class(self.endpoint_scheme) self.connection_kwargs = self.get_connection_kwargs( self.endpoint_scheme, **kwargs) self.identity_headers = kwargs.get('identity_headers') self.auth_token = kwargs.get('token') if self.identity_headers: if self.identity_headers.get('X-Auth-Token'): self.auth_token = self.identity_headers.get('X-Auth-Token') del self.identity_headers['X-Auth-Token'] @staticmethod def parse_endpoint(endpoint): return urlparse.urlparse(endpoint) @staticmethod def get_connection_class(scheme): if scheme == 'https': return VerifiedHTTPSConnection else: return http_client.HTTPConnection @staticmethod def get_connection_kwargs(scheme, **kwargs): _kwargs = {'timeout': float(kwargs.get('timeout', 600))} if scheme == 'https': _kwargs['cacert'] = kwargs.get('cacert', None) _kwargs['cert_file'] = kwargs.get('cert_file', None) _kwargs['key_file'] = kwargs.get('key_file', None) _kwargs['insecure'] = kwargs.get('insecure', False) _kwargs['ssl_compression'] = kwargs.get('ssl_compression', True) return _kwargs def get_connection(self): _class = self.connection_class try: return _class(self.endpoint_hostname, self.endpoint_port, **self.connection_kwargs) except http_client.InvalidURL: raise exc.InvalidEndpoint() def log_curl_request(self, method, url, kwargs): curl = ['curl -i -X %s' % method] for (key, value) in kwargs['headers'].items(): header = '-H \'%s: %s\'' % (key, value) curl.append(header) conn_params_fmt = [ ('key_file', '--key %s'), ('cert_file', '--cert %s'), ('cacert', '--cacert %s'), ] for (key, fmt) in conn_params_fmt: value = self.connection_kwargs.get(key) if value: curl.append(fmt % value) if self.connection_kwargs.get('insecure'): curl.append('-k') if kwargs.get('body') is not None: curl.append('-d \'%s\'' % kwargs['body']) curl.append('%s%s' % (self.endpoint, url)) LOG.debug(strutils.safe_encode(' '.join(curl))) @staticmethod def log_http_response(resp, body=None): status = (resp.version / 10.0, resp.status, resp.reason) dump = ['\nHTTP/%.1f %s %s' % status] dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()]) dump.append('') if body: dump.extend([body, '']) LOG.debug(strutils.safe_encode('\n'.join(dump))) @staticmethod def encode_headers(headers): """Encodes headers. Note: This should be used right before sending anything out. :param headers: Headers to encode :returns: Dictionary with encoded headers' names and values """ to_str = strutils.safe_encode return dict([(to_str(h), to_str(v)) for h, v in headers.iteritems()]) def _http_request(self, url, method, **kwargs): """Send an http request with the specified characteristics. Wrapper around httplib.HTTP(S)Connection.request to handle tasks such as setting headers and error handling. """ # Copy the kwargs so we can reuse the original in case of redirects kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {})) kwargs['headers'].setdefault('User-Agent', USER_AGENT) if self.auth_token: kwargs['headers'].setdefault('X-Auth-Token', self.auth_token) if self.identity_headers: for k, v in self.identity_headers.iteritems(): kwargs['headers'].setdefault(k, v) self.log_curl_request(method, url, kwargs) conn = self.get_connection() # Note(flaper87): Before letting headers / url fly, # they should be encoded otherwise httplib will # complain. If we decide to rely on python-request # this wont be necessary anymore. kwargs['headers'] = self.encode_headers(kwargs['headers']) try: if self.endpoint_path: # NOTE(yuyangbj): this method _http_request could either be # called by API layer, or be called recursively with # redirection. For example, url would be '/v1/images/detail' # from API layer, but url would be 'https://example.com:92/ # v1/images/detail' from recursion. # See bug #1230032 and bug #1208618. if url is not None: all_parts = urlparse.urlparse(url) if not (all_parts.scheme and all_parts.netloc): norm_parse = posixpath.normpath url = norm_parse('/'.join([self.endpoint_path, url])) else: url = self.endpoint_path conn_url = urlparse.urlsplit(url).geturl() # Note(flaper87): Ditto, headers / url # encoding to make httplib happy. conn_url = strutils.safe_encode(conn_url) if kwargs['headers'].get('Transfer-Encoding') == 'chunked': conn.putrequest(method, conn_url) for header, value in kwargs['headers'].items(): conn.putheader(header, value) conn.endheaders() chunk = kwargs['body'].read(CHUNKSIZE) # Chunk it, baby... while chunk: conn.send('%x\r\n%s\r\n' % (len(chunk), chunk)) chunk = kwargs['body'].read(CHUNKSIZE) conn.send('0\r\n\r\n') else: conn.request(method, conn_url, **kwargs) resp = conn.getresponse() except socket.gaierror as e: message = "Error finding address for %s: %s" % ( self.endpoint_hostname, e) raise exc.InvalidEndpoint(message=message) except (socket.error, socket.timeout) as e: endpoint = self.endpoint message = ("Error communicating with %(endpoint)s %(e)s" % {'endpoint': endpoint, 'e': e}) raise exc.CommunicationError(message=message) body_iter = ResponseBodyIterator(resp) # Read body into string if it isn't obviously image data if resp.getheader('content-type', None) != 'application/octet-stream': body_str = ''.join([chunk for chunk in body_iter]) self.log_http_response(resp, body_str) body_iter = StringIO.StringIO(body_str) else: self.log_http_response(resp) if 400 <= resp.status < 600: LOG.error("Request returned failure status.") raise exc.from_response(resp, body_str) elif resp.status in (301, 302, 305): # Redirected. Reissue the request to the new location. return self._http_request(resp.getheader('location', None), method, **kwargs) elif resp.status == 300: raise exc.from_response(resp) return resp, body_iter def json_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/json') if 'body' in kwargs: kwargs['body'] = json.dumps(kwargs['body']) resp, body_iter = self._http_request(url, method, **kwargs) if 'application/json' in resp.getheader('content-type', ''): body = ''.join([chunk for chunk in body_iter]) try: body = json.loads(body) except ValueError: LOG.error('Could not decode response body as JSON') else: body = None return resp, body def raw_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/octet-stream') if 'body' in kwargs: if (hasattr(kwargs['body'], 'read') and method.lower() in ('post', 'put')): # We use 'Transfer-Encoding: chunked' because # body size may not always be known in advance. kwargs['headers']['Transfer-Encoding'] = 'chunked' return self._http_request(url, method, **kwargs) class OpenSSLConnectionDelegator(object): """ An OpenSSL.SSL.Connection delegator. Supplies an additional 'makefile' method which httplib requires and is not present in OpenSSL.SSL.Connection. Note: Since it is not possible to inherit from OpenSSL.SSL.Connection a delegator must be used. """ def __init__(self, *args, **kwargs): self.connection = Connection(*args, **kwargs) def __getattr__(self, name): return getattr(self.connection, name) def makefile(self, *args, **kwargs): # Making sure socket is closed when this file is closed # since we now avoid closing socket on connection close # see new close method under VerifiedHTTPSConnection kwargs['close'] = True return socket._fileobject(self.connection, *args, **kwargs) class VerifiedHTTPSConnection(HTTPSConnection): """ Extended HTTPSConnection which uses the OpenSSL library for enhanced SSL support. Note: Much of this functionality can eventually be replaced with native Python 3.3 code. """ def __init__(self, host, port=None, key_file=None, cert_file=None, cacert=None, timeout=None, insecure=False, ssl_compression=True): HTTPSConnection.__init__(self, host, port, key_file=key_file, cert_file=cert_file) self.key_file = key_file self.cert_file = cert_file self.timeout = timeout self.insecure = insecure self.ssl_compression = ssl_compression self.cacert = cacert self.setcontext() @staticmethod def host_matches_cert(host, x509): """ Verify that the the x509 certificate we have received from 'host' correctly identifies the server we are connecting to, ie that the certificate's Common Name or a Subject Alternative Name matches 'host'. """ common_name = x509.get_subject().commonName # First see if we can match the CN if common_name == host: return True # Support single wildcard matching if common_name.startswith('*.') and host.find('.') > 0: if common_name[2:] == host.split('.', 1)[1]: return True # Also try Subject Alternative Names for a match san_list = None for i in xrange(x509.get_extension_count()): ext = x509.get_extension(i) if ext.get_short_name() == 'subjectAltName': san_list = str(ext) for san in ''.join(san_list.split()).split(','): if san == "DNS:%s" % host: return True # Server certificate does not match host msg = ('Host "%s" does not match x509 certificate contents: ' 'CommonName "%s"' % (host, common_name)) if san_list is not None: msg = msg + ', subjectAltName "%s"' % san_list raise exc.SSLCertificateError(msg) def verify_callback(self, connection, x509, errnum, depth, preverify_ok): # NOTE(leaman): preverify_ok may be a non-boolean type preverify_ok = bool(preverify_ok) if x509.has_expired(): msg = "SSL Certificate expired on '%s'" % x509.get_notAfter() raise exc.SSLCertificateError(msg) if depth == 0 and preverify_ok: # We verify that the host matches against the last # certificate in the chain return self.host_matches_cert(self.host, x509) else: # Pass through OpenSSL's default result return preverify_ok def setcontext(self): """ Set up the OpenSSL context. """ self.context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) if self.ssl_compression is False: self.context.set_options(0x20000) # SSL_OP_NO_COMPRESSION if self.insecure is not True: self.context.set_verify(OpenSSL.SSL.VERIFY_PEER, self.verify_callback) else: self.context.set_verify(OpenSSL.SSL.VERIFY_NONE, lambda *args: True) if self.cert_file: try: self.context.use_certificate_file(self.cert_file) except Exception as e: msg = 'Unable to load cert from "%s" %s' % (self.cert_file, e) raise exc.SSLConfigurationError(msg) if self.key_file is None: # We support having key and cert in same file try: self.context.use_privatekey_file(self.cert_file) except Exception as e: msg = ('No key file specified and unable to load key ' 'from "%s" %s' % (self.cert_file, e)) raise exc.SSLConfigurationError(msg) if self.key_file: try: self.context.use_privatekey_file(self.key_file) except Exception as e: msg = 'Unable to load key from "%s" %s' % (self.key_file, e) raise exc.SSLConfigurationError(msg) if self.cacert: try: self.context.load_verify_locations(self.cacert) except Exception as e: msg = 'Unable to load CA from "%s"' % (self.cacert, e) raise exc.SSLConfigurationError(msg) else: self.context.set_default_verify_paths() def connect(self): """ Connect to an SSL port using the OpenSSL library and apply per-connection parameters. """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.timeout is not None: # '0' microseconds sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, struct.pack('fL', self.timeout, 0)) self.sock = OpenSSLConnectionDelegator(self.context, sock) self.sock.connect((self.host, self.port)) def close(self): if self.sock: # Removing reference to socket but don't close it yet. # Response close will close both socket and associated # file. Closing socket too soon will cause response # reads to fail with socket IO error 'Bad file descriptor'. self.sock = None # Calling close on HTTPConnection to continue doing that cleanup. HTTPSConnection.close(self) class ResponseBodyIterator(object): """ A class that acts as an iterator over an HTTP response. This class will also check response body integrity when iterating over the instance and if a checksum was supplied using `set_checksum` method, else by default the class will not do any integrity check. """ def __init__(self, resp): self._resp = resp self._checksum = None self._size = int(resp.getheader('content-length', 0)) self._end_reached = False def set_checksum(self, checksum): """ Set checksum to check against when iterating over this instance. :raise: AttributeError if iterator is already consumed. """ if self._end_reached: raise AttributeError("Can't set checksum for an already consumed" " iterator") self._checksum = checksum def __len__(self): return int(self._size) def __iter__(self): md5sum = hashlib.md5() while True: try: chunk = self.next() except StopIteration: self._end_reached = True # NOTE(mouad): Check image integrity when the end of response # body is reached. md5sum = md5sum.hexdigest() if self._checksum is not None and md5sum != self._checksum: raise IOError(errno.EPIPE, 'Corrupted image. Checksum was %s ' 'expected %s' % (md5sum, self._checksum)) raise else: yield chunk md5sum.update(chunk) def next(self): chunk = self._resp.read(CHUNKSIZE) if chunk: return chunk else: raise StopIteration() python-glanceclient-0.12.0/glanceclient/common/utils.py0000664000175300017540000002261112241451455024335 0ustar jenkinsjenkins00000000000000# 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 __future__ import print_function import errno import os import sys import uuid if os.name == 'nt': import msvcrt else: msvcrt = None import prettytable from glanceclient import exc from glanceclient.openstack.common import importutils from glanceclient.openstack.common import strutils # Decorator for cli-args def arg(*args, **kwargs): def _decorator(func): # Because of the sematics of decorator composition if we just append # to the options list positional options will appear to be backwards. func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs)) return func return _decorator def schema_args(schema_getter, omit=[]): typemap = { 'string': str, 'integer': int, 'boolean': string_to_bool, 'array': list } def _decorator(func): schema = schema_getter() if schema is None: param = '' kwargs = { 'help': ("Please run with connection parameters set to " "retrieve the schema for generating help for this " "command") } func.__dict__.setdefault('arguments', []).insert(0, ((param, ), kwargs)) else: properties = schema.get('properties', {}) for name, property in properties.iteritems(): if name in omit: continue param = '--' + name.replace('_', '-') kwargs = {} type_str = property.get('type', 'string') if type_str == 'array': items = property.get('items') kwargs['type'] = typemap.get(items.get('type')) kwargs['nargs'] = '+' else: kwargs['type'] = typemap.get(type_str) if type_str == 'boolean': kwargs['metavar'] = '[True|False]' else: kwargs['metavar'] = '<%s>' % name.upper() description = property.get('description', "") if 'enum' in property: if len(description): description += " " description += ("Valid values: " + ', '.join(property.get('enum'))) kwargs['help'] = description func.__dict__.setdefault('arguments', []).insert(0, ((param, ), kwargs)) return func return _decorator def pretty_choice_list(l): return ', '.join("'%s'" % i for i in l) def print_list(objs, fields, formatters={}): pt = prettytable.PrettyTable([f for f in fields], caching=False) pt.align = 'l' for o in objs: row = [] for field in fields: if field in formatters: row.append(formatters[field](o)) else: field_name = field.lower().replace(' ', '_') data = getattr(o, field_name, None) or '' row.append(data) pt.add_row(row) print(strutils.safe_encode(pt.get_string())) def print_dict(d): pt = prettytable.PrettyTable(['Property', 'Value'], caching=False) pt.align = 'l' [pt.add_row(list(r)) for r in d.iteritems()] print(strutils.safe_encode(pt.get_string(sortby='Property'))) def find_resource(manager, name_or_id): """Helper for the _find_* methods.""" # first try to get entity as integer id try: if isinstance(name_or_id, int) or name_or_id.isdigit(): return manager.get(int(name_or_id)) except exc.NotFound: pass # now try to get entity as uuid try: uuid.UUID(strutils.safe_encode(name_or_id)) return manager.get(name_or_id) except (ValueError, exc.NotFound): pass # finally try to find entity by name matches = list(manager.list(filters={'name': name_or_id})) num_matches = len(matches) if num_matches == 0: msg = "No %s with a name or ID of '%s' exists." % \ (manager.resource_class.__name__.lower(), name_or_id) raise exc.CommandError(msg) elif num_matches > 1: msg = ("Multiple %s matches found for '%s', use an ID to be more" " specific." % (manager.resource_class.__name__.lower(), name_or_id)) raise exc.CommandError(msg) else: return matches[0] def skip_authentication(f): """Function decorator used to indicate a caller may be unauthenticated.""" f.require_authentication = False return f def is_authentication_required(f): """Checks to see if the function requires authentication. Use the skip_authentication decorator to indicate a caller may skip the authentication step. """ return getattr(f, 'require_authentication', True) def string_to_bool(arg): return arg.strip().lower() in ('t', 'true', 'yes', '1') def env(*vars, **kwargs): """Search for the first defined of possibly many env vars Returns the first environment variable defined in vars, or returns the default defined in kwargs. """ for v in vars: value = os.environ.get(v, None) if value: return value return kwargs.get('default', '') def import_versioned_module(version, submodule=None): module = 'glanceclient.v%s' % version if submodule: module = '.'.join((module, submodule)) return importutils.import_module(module) def exit(msg=''): if msg: print(strutils.safe_encode(msg), file=sys.stderr) sys.exit(1) def save_image(data, path): """ Save an image to the specified path. :param data: binary data of the image :param path: path to save the image to """ if path is None: image = sys.stdout else: image = open(path, 'wb') try: for chunk in data: image.write(chunk) finally: if path is not None: image.close() def make_size_human_readable(size): suffix = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB'] base = 1024.0 index = 0 while size >= base: index = index + 1 size = size / base padded = '%.1f' % size stripped = padded.rstrip('0').rstrip('.') return '%s%s' % (stripped, suffix[index]) def getsockopt(self, *args, **kwargs): """ A function which allows us to monkey patch eventlet's GreenSocket, adding a required 'getsockopt' method. TODO: (mclaren) we can remove this once the eventlet fix (https://bitbucket.org/eventlet/eventlet/commits/609f230) lands in mainstream packages. """ return self.fd.getsockopt(*args, **kwargs) def exception_to_str(exc): try: error = unicode(exc) except UnicodeError: try: error = str(exc) except UnicodeError: error = ("Caught '%(exception)s' exception." % {"exception": exc.__class__.__name__}) return strutils.safe_encode(error, errors='ignore') def get_file_size(file_obj): """ Analyze file-like object and attempt to determine its size. :param file_obj: file-like object. :retval The file's size or None if it cannot be determined. """ if hasattr(file_obj, 'seek') and hasattr(file_obj, 'tell'): try: curr = file_obj.tell() file_obj.seek(0, os.SEEK_END) size = file_obj.tell() file_obj.seek(curr) return size except IOError as e: if e.errno == errno.ESPIPE: # Illegal seek. This means the file object # is a pipe (e.g the user is trying # to pipe image data to the client, # echo testdata | bin/glance add blah...), or # that file object is empty, or that a file-like # object which doesn't support 'seek/tell' has # been supplied. return else: raise def get_data_file(args): if args.file: return open(args.file, 'rb') else: # distinguish cases where: # (1) stdin is not valid (as in cron jobs): # glance ... <&- # (2) image data is provided through standard input: # glance ... < /tmp/file or cat /tmp/file | glance ... # (3) no image data provided: # glance ... try: os.fstat(0) except OSError: # (1) stdin is not valid (closed...) return None if not sys.stdin.isatty(): # (2) image data is provided through standard input if msvcrt: msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) return sys.stdin else: # (3) no image data provided return None python-glanceclient-0.12.0/glanceclient/v2/0000775000175300017540000000000012241451524021655 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/glanceclient/v2/image_tags.py0000664000175300017540000000267012241451455024337 0ustar jenkinsjenkins00000000000000# 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. class Controller(object): def __init__(self, http_client, model): self.http_client = http_client self.model = model def update(self, image_id, tag_value): """ Update an image with the given tag. :param image_id: image to be updated with the given tag. :param tag_value: value of the tag. """ url = '/v2/images/%s/tags/%s' % (image_id, tag_value) self.http_client.json_request('PUT', url) def delete(self, image_id, tag_value): """ Delete the tag associated with the given image. :param image_id: Image whose tag to be deleted. :param tag_value: tag value to be deleted. """ url = '/v2/images/%s/tags/%s' % (image_id, tag_value) self.http_client.json_request('DELETE', url) python-glanceclient-0.12.0/glanceclient/v2/schemas.py0000664000175300017540000000535512241451455023665 0ustar jenkinsjenkins00000000000000# 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 copy import jsonpatch import warlock.model as warlock class SchemaBasedModel(warlock.Model): """Glance specific subclass of the warlock Model This implementation alters the function of the patch property to take into account the schema's core properties. With this version undefined properties which are core will generated 'replace' operations rather than 'add' since this is what the Glance API expects. """ @warlock.Model.patch.getter def patch(self): """Return a jsonpatch object representing the delta.""" original = copy.deepcopy(self.__dict__['__original__']) new = dict(self) if self.__dict__['schema']: for prop in self.schema['properties']: if prop not in original and prop in new: original[prop] = None return jsonpatch.make_patch(original, dict(self)).to_string() class SchemaProperty(object): def __init__(self, name, **kwargs): self.name = name self.description = kwargs.get('description') def translate_schema_properties(schema_properties): """Parse the properties dictionary of a schema document :returns list of SchemaProperty objects """ properties = [] for (name, prop) in schema_properties.items(): properties.append(SchemaProperty(name, **prop)) return properties class Schema(object): def __init__(self, raw_schema): self._raw_schema = raw_schema self.name = raw_schema['name'] raw_properties = raw_schema['properties'] self.properties = translate_schema_properties(raw_properties) def is_core_property(self, property_name): for prop in self.properties: if property_name == prop.name: return True return False def raw(self): return copy.deepcopy(self._raw_schema) class Controller(object): def __init__(self, http_client): self.http_client = http_client def get(self, schema_name): uri = '/v2/schemas/%s' % schema_name _, raw_schema = self.http_client.json_request('GET', uri) return Schema(raw_schema) python-glanceclient-0.12.0/glanceclient/v2/__init__.py0000664000175300017540000000000012241451455023757 0ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/glanceclient/v2/image_members.py0000664000175300017540000000353212241451455025031 0ustar jenkinsjenkins00000000000000# 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. class Controller(object): def __init__(self, http_client, model): self.http_client = http_client self.model = model def list(self, image_id): url = '/v2/images/%s/members' % image_id resp, body = self.http_client.json_request('GET', url) for member in body['members']: yield self.model(member) def delete(self, image_id, member_id): self.http_client.json_request('DELETE', '/v2/images/%s/members/%s' % (image_id, member_id)) def update(self, image_id, member_id, member_status): url = '/v2/images/%s/members/%s' % (image_id, member_id) body = {'status': member_status} resp, updated_member = self.http_client.json_request('PUT', url, body=body) return self.model(updated_member) def create(self, image_id, member_id): url = '/v2/images/%s/members' % image_id body = {'member': member_id} resp, created_member = self.http_client.json_request('POST', url, body=body) return self.model(created_member) python-glanceclient-0.12.0/glanceclient/v2/images.py0000664000175300017540000001364312241451455023506 0ustar jenkinsjenkins00000000000000# 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 import warlock from glanceclient.openstack.common import strutils DEFAULT_PAGE_SIZE = 20 class Controller(object): def __init__(self, http_client, model): self.http_client = http_client self.model = model def list(self, **kwargs): """Retrieve a listing of Image objects :param page_size: Number of images to request in each paginated request :returns generator over list of Images """ def paginate(url): resp, body = self.http_client.json_request('GET', url) for image in body['images']: yield image try: next_url = body['next'] except KeyError: return else: for image in paginate(next_url): yield image filters = kwargs.get('filters', {}) if not kwargs.get('page_size'): filters['limit'] = DEFAULT_PAGE_SIZE else: filters['limit'] = kwargs['page_size'] tags = filters.pop('tag', []) tags_url_params = [] for tag in tags: if isinstance(tag, basestring): tags_url_params.append({'tag': strutils.safe_encode(tag)}) for param, value in filters.iteritems(): if isinstance(value, basestring): filters[param] = strutils.safe_encode(value) url = '/v2/images?%s' % urllib.urlencode(filters) for param in tags_url_params: url = '%s&%s' % (url, urllib.urlencode(param)) for image in paginate(url): #NOTE(bcwaldon): remove 'self' for now until we have an elegant # way to pass it into the model constructor without conflict image.pop('self', None) yield self.model(**image) def get(self, image_id): url = '/v2/images/%s' % image_id resp, body = self.http_client.json_request('GET', url) #NOTE(bcwaldon): remove 'self' for now until we have an elegant # way to pass it into the model constructor without conflict body.pop('self', None) return self.model(**body) def data(self, image_id, do_checksum=True): """ Retrieve data of an image. :param image_id: ID of the image to download. :param do_checksum: Enable/disable checksum validation. """ url = '/v2/images/%s/file' % image_id resp, body = self.http_client.raw_request('GET', url) checksum = resp.getheader('content-md5', None) if do_checksum and checksum is not None: body.set_checksum(checksum) return body def upload(self, image_id, image_data): """ Upload the data for an image. :param image_id: ID of the image to upload data for. :param image_data: File-like object supplying the data to upload. """ url = '/v2/images/%s/file' % image_id hdrs = {'Content-Type': 'application/octet-stream'} self.http_client.raw_request('PUT', url, headers=hdrs, body=image_data) def delete(self, image_id): """Delete an image.""" self.http_client.json_request('DELETE', 'v2/images/%s' % image_id) def create(self, **kwargs): """Create an image.""" url = '/v2/images' image = self.model() for (key, value) in kwargs.items(): try: setattr(image, key, value) except warlock.InvalidOperation as e: raise TypeError(unicode(e)) resp, body = self.http_client.json_request('POST', url, body=image) #NOTE(esheffield): remove 'self' for now until we have an elegant # way to pass it into the model constructor without conflict body.pop('self', None) return self.model(**body) def update(self, image_id, remove_props=None, **kwargs): """ Update attributes of an image. :param image_id: ID of the image to modify. :param remove_props: List of property names to remove :param **kwargs: Image attribute names and their new values. """ image = self.get(image_id) for (key, value) in kwargs.items(): try: setattr(image, key, value) except warlock.InvalidOperation as e: raise TypeError(unicode(e)) if remove_props is not None: cur_props = image.keys() new_props = kwargs.keys() #NOTE(esheffield): Only remove props that currently exist on the # image and are NOT in the properties being updated / added props_to_remove = set(cur_props).intersection( set(remove_props).difference(new_props)) for key in props_to_remove: delattr(image, key) url = '/v2/images/%s' % image_id hdrs = {'Content-Type': 'application/openstack-images-v2.1-json-patch'} self.http_client.raw_request('PATCH', url, headers=hdrs, body=image.patch) #NOTE(bcwaldon): calling image.patch doesn't clear the changes, so # we need to fetch the image again to get a clean history. This is # an obvious optimization for warlock return self.get(image_id) python-glanceclient-0.12.0/glanceclient/v2/shell.py0000664000175300017540000002355012241451455023346 0ustar jenkinsjenkins00000000000000# 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 glanceclient.common import progressbar from glanceclient.common import utils from glanceclient import exc import json import os from os.path import expanduser IMAGE_SCHEMA = None def get_image_schema(): global IMAGE_SCHEMA if IMAGE_SCHEMA is None: schema_path = expanduser("~/.glanceclient/image_schema.json") if os.path.exists(schema_path) and os.path.isfile(schema_path): with file(schema_path, "r") as f: schema_raw = f.read() IMAGE_SCHEMA = json.loads(schema_raw) return IMAGE_SCHEMA @utils.schema_args(get_image_schema) @utils.arg('--property', metavar="", action='append', default=[], help=('Arbitrary property to associate with image.' ' May be used multiple times.')) def do_image_create(gc, args): """Create a new image.""" schema = gc.schemas.get("image") _args = [(x[0].replace('-', '_'), x[1]) for x in vars(args).items()] fields = dict(filter(lambda x: x[1] is not None and (x[0] == 'property' or schema.is_core_property(x[0])), _args)) raw_properties = fields.pop('property', []) for datum in raw_properties: key, value = datum.split('=', 1) fields[key] = value image = gc.images.create(**fields) ignore = ['self', 'access', 'file', 'schema'] image = dict([item for item in image.iteritems() if item[0] not in ignore]) utils.print_dict(image) @utils.arg('id', metavar='', help='ID of image to update.') @utils.schema_args(get_image_schema, omit=['id']) @utils.arg('--property', metavar="", action='append', default=[], help=('Arbitrary property to associate with image.' ' May be used multiple times.')) @utils.arg('--remove-property', metavar="key", action='append', default=[], help="Name of arbitrary property to remove from the image") def do_image_update(gc, args): """Update an existing image.""" schema = gc.schemas.get("image") _args = [(x[0].replace('-', '_'), x[1]) for x in vars(args).items()] fields = dict(filter(lambda x: x[1] is not None and (x[0] in ['property', 'remove_property'] or schema.is_core_property(x[0])), _args)) raw_properties = fields.pop('property', []) for datum in raw_properties: key, value = datum.split('=', 1) fields[key] = value remove_properties = fields.pop('remove_property', None) image_id = fields.pop('id') image = gc.images.update(image_id, remove_properties, **fields) ignore = ['self', 'access', 'file', 'schema'] image = dict([item for item in image.iteritems() if item[0] not in ignore]) utils.print_dict(image) @utils.arg('--page-size', metavar='', default=None, type=int, help='Number of images to request in each paginated request.') @utils.arg('--visibility', metavar='', help='The visibility of the images to display.') @utils.arg('--member-status', metavar='', help='The status of images to display.') @utils.arg('--owner', metavar='', help='Display images owned by .') @utils.arg('--checksum', metavar='', help='Display images matching the checksum') @utils.arg('--tag', metavar='', action='append', help="Filter images by an user-defined tag.") def do_image_list(gc, args): """List images you can access.""" filter_keys = ['visibility', 'member_status', 'owner', 'checksum', 'tag'] filter_items = [(key, getattr(args, key)) for key in filter_keys] filters = dict([item for item in filter_items if item[1] is not None]) kwargs = {'filters': filters} if args.page_size is not None: kwargs['page_size'] = args.page_size images = gc.images.list(**kwargs) columns = ['ID', 'Name'] utils.print_list(images, columns) @utils.arg('id', metavar='', help='ID of image to describe.') def do_image_show(gc, args): """Describe a specific image.""" image = gc.images.get(args.id) ignore = ['self', 'access', 'file', 'schema'] image = dict([item for item in image.iteritems() if item[0] not in ignore]) utils.print_dict(image) @utils.arg('--image-id', metavar='', required=True, help='Image to display members of.') def do_member_list(gc, args): """Describe sharing permissions by image.""" members = gc.image_members.list(args.image_id) columns = ['Image ID', 'Member ID', 'Status'] utils.print_list(members, columns) @utils.arg('image_id', metavar='', help='Image from which to remove member') @utils.arg('member_id', metavar='', help='Tenant to remove as member') def do_member_delete(gc, args): """Delete image member""" if not (args.image_id and args.member_id): utils.exit('Unable to delete member. Specify image_id and member_id') else: gc.image_members.delete(args.image_id, args.member_id) @utils.arg('image_id', metavar='', help='Image from which to update member') @utils.arg('member_id', metavar='', help='Tenant to update') @utils.arg('member_status', metavar='', help='Updated status of member') def do_member_update(gc, args): """Update the status of a member for a given image.""" if not (args.image_id and args.member_id and args.member_status): utils.exit('Unable to update member. Specify image_id, member_id and' ' member_status') else: member = gc.image_members.update(args.image_id, args.member_id, args.member_status) member = [member] columns = ['Image ID', 'Member ID', 'Status'] utils.print_list(member, columns) @utils.arg('image_id', metavar='', help='Image on which to create member') @utils.arg('member_id', metavar='', help='Tenant to add as member') def do_member_create(gc, args): """Create member for a given image.""" if not (args.image_id and args.member_id): utils.exit('Unable to create member. Specify image_id and member_id') else: member = gc.image_members.create(args.image_id, args.member_id) member = [member] columns = ['Image ID', 'Member ID', 'Status'] utils.print_list(member, columns) @utils.arg('model', metavar='', help='Name of model to describe.') def do_explain(gc, args): """Describe a specific model.""" try: schema = gc.schemas.get(args.model) except exc.HTTPNotFound: utils.exit('Unable to find requested model \'%s\'' % args.model) else: formatters = {'Attribute': lambda m: m.name} columns = ['Attribute', 'Description'] utils.print_list(schema.properties, columns, formatters) @utils.arg('--file', metavar='', help='Local file to save downloaded image data to. ' 'If this is not specified the image data will be ' 'written to stdout.') @utils.arg('id', metavar='', help='ID of image to download.') @utils.arg('--progress', action='store_true', default=False, help='Show download progress bar.') def do_image_download(gc, args): """Download a specific image.""" body = gc.images.data(args.id) if args.progress: body = progressbar.VerboseIteratorWrapper(body, len(body)) utils.save_image(body, args.file) @utils.arg('--file', metavar='', help=('Local file that contains disk image to be uploaded' ' during creation. Alternatively, images can be passed' ' to the client via stdin.')) @utils.arg('id', metavar='', help='ID of image to upload data to.') def do_image_upload(gc, args): """Upload data for a specific image.""" image_data = utils.get_data_file(args) gc.images.upload(args.id, image_data) @utils.arg('id', metavar='', help='ID of image to delete.') def do_image_delete(gc, args): """Delete specified image.""" gc.images.delete(args.id) @utils.arg('image_id', metavar='', help='Image to be updated with the given tag') @utils.arg('tag_value', metavar='', help='Value of the tag') def do_image_tag_update(gc, args): """Update an image with the given tag.""" if not (args.image_id and args.tag_value): utils.exit('Unable to update tag. Specify image_id and tag_value') else: gc.image_tags.update(args.image_id, args.tag_value) image = gc.images.get(args.image_id) image = [image] columns = ['ID', 'Tags'] utils.print_list(image, columns) @utils.arg('image_id', metavar='', help='Image whose tag to be deleted') @utils.arg('tag_value', metavar='', help='Value of the tag') def do_image_tag_delete(gc, args): """Delete the tag associated with the given image.""" if not (args.image_id and args.tag_value): utils.exit('Unable to delete tag. Specify image_id and tag_value') else: gc.image_tags.delete(args.image_id, args.tag_value) python-glanceclient-0.12.0/glanceclient/v2/client.py0000664000175300017540000000402412241451455023510 0ustar jenkinsjenkins00000000000000# 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 warlock from glanceclient.common import http from glanceclient.v2 import images from glanceclient.v2 import image_members from glanceclient.v2 import image_tags from glanceclient.v2 import schemas class Client(object): """Client for the OpenStack Images v2 API. :param string endpoint: A user-supplied endpoint URL for the glance service. :param string token: Token for authentication. :param integer timeout: Allows customization of the timeout for client http requests. (optional) """ def __init__(self, *args, **kwargs): self.http_client = http.HTTPClient(*args, **kwargs) self.schemas = schemas.Controller(self.http_client) image_model = self._get_image_model() self.images = images.Controller(self.http_client, image_model) self.image_tags = image_tags.Controller(self.http_client, image_model) self.image_members = image_members.Controller(self.http_client, self._get_member_model()) def _get_image_model(self): schema = self.schemas.get('image') return warlock.model_factory(schema.raw(), schemas.SchemaBasedModel) def _get_member_model(self): schema = self.schemas.get('member') return warlock.model_factory(schema.raw(), schemas.SchemaBasedModel) python-glanceclient-0.12.0/glanceclient/__init__.py0000664000175300017540000000223112241451455023440 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack Foundation # # 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. #NOTE(bcwaldon): this try/except block is needed to run setup.py due to # its need to import local code before installing required dependencies try: import glanceclient.client Client = glanceclient.client.Client except ImportError: import warnings warnings.warn("Could not import glanceclient.client", ImportWarning) import pbr.version version_info = pbr.version.VersionInfo('python-glanceclient') try: __version__ = version_info.version_string() except AttributeError: __version__ = None python-glanceclient-0.12.0/glanceclient/v1/0000775000175300017540000000000012241451524021654 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/glanceclient/v1/__init__.py0000664000175300017540000000126212241451455023771 0ustar jenkinsjenkins00000000000000# 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 glanceclient.v1.client import Client # noqa python-glanceclient-0.12.0/glanceclient/v1/image_members.py0000664000175300017540000000703212241451455025027 0ustar jenkinsjenkins00000000000000# 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 glanceclient.common import base class ImageMember(base.Resource): def __repr__(self): return "" % self._info @property def id(self): return self.member_id def delete(self): self.manager.delete(self) class ImageMemberManager(base.Manager): resource_class = ImageMember def get(self, image, member_id): image_id = base.getid(image) url = '/v1/images/%s/members/%s' % (image_id, member_id) resp, body = self.api.json_request('GET', url) member = body['member'] member['image_id'] = image_id return ImageMember(self, member, loaded=True) def list(self, image=None, member=None): out = [] if image and member: try: out.append(self.get(image, member)) #TODO(bcwaldon): narrow this down to 404 except Exception: pass elif image: out.extend(self._list_by_image(image)) elif member: out.extend(self._list_by_member(member)) else: #TODO(bcwaldon): figure out what is appropriate to do here as we # are unable to provide the requested response pass return out def _list_by_image(self, image): image_id = base.getid(image) url = '/v1/images/%s/members' % image_id resp, body = self.api.json_request('GET', url) out = [] for member in body['members']: member['image_id'] = image_id out.append(ImageMember(self, member, loaded=True)) return out def _list_by_member(self, member): member_id = base.getid(member) url = '/v1/shared-images/%s' % member_id resp, body = self.api.json_request('GET', url) out = [] for member in body['shared_images']: member['member_id'] = member_id out.append(ImageMember(self, member, loaded=True)) return out def delete(self, image_id, member_id): self._delete("/v1/images/%s/members/%s" % (image_id, member_id)) def create(self, image, member_id, can_share=False): """Creates an image.""" url = '/v1/images/%s/members/%s' % (base.getid(image), member_id) body = {'member': {'can_share': can_share}} self._update(url, body=body) def replace(self, image, members): memberships = [] for member in members: try: obj = { 'member_id': member.member_id, 'can_share': member.can_share, } except AttributeError: obj = {'member_id': member['member_id']} if 'can_share' in member: obj['can_share'] = member['can_share'] memberships.append(obj) url = '/v1/images/%s/members' % base.getid(image) self.api.json_request('PUT', url, {}, {'memberships': memberships}) python-glanceclient-0.12.0/glanceclient/v1/images.py0000664000175300017540000002530012241451455023476 0ustar jenkinsjenkins00000000000000# 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 copy import json import urllib from glanceclient.common import base from glanceclient.common import utils from glanceclient.openstack.common import strutils UPDATE_PARAMS = ('name', 'disk_format', 'container_format', 'min_disk', 'min_ram', 'owner', 'size', 'is_public', 'protected', 'location', 'checksum', 'copy_from', 'properties', #NOTE(bcwaldon: an attempt to update 'deleted' will be # ignored, but we need to support it for backwards- # compatibility with the legacy client library 'deleted') CREATE_PARAMS = UPDATE_PARAMS + ('id', 'store') DEFAULT_PAGE_SIZE = 20 SORT_DIR_VALUES = ('asc', 'desc') SORT_KEY_VALUES = ('name', 'status', 'container_format', 'disk_format', 'size', 'id', 'created_at', 'updated_at') class Image(base.Resource): def __repr__(self): return "" % self._info def update(self, **fields): self.manager.update(self, **fields) def delete(self): return self.manager.delete(self) def data(self, **kwargs): return self.manager.data(self, **kwargs) class ImageManager(base.Manager): resource_class = Image def _image_meta_from_headers(self, headers): meta = {'properties': {}} safe_decode = strutils.safe_decode for key, value in headers.iteritems(): value = safe_decode(value, incoming='utf-8') if key.startswith('x-image-meta-property-'): _key = safe_decode(key[22:], incoming='utf-8') meta['properties'][_key] = value elif key.startswith('x-image-meta-'): _key = safe_decode(key[13:], incoming='utf-8') meta[_key] = value for key in ['is_public', 'protected', 'deleted']: if key in meta: meta[key] = utils.string_to_bool(meta[key]) return self._format_image_meta_for_user(meta) def _image_meta_to_headers(self, fields): headers = {} fields_copy = copy.deepcopy(fields) # NOTE(flaper87): Convert to str, headers # that are not instance of basestring. All # headers will be encoded later, before the # request is sent. def to_str(value): if not isinstance(value, basestring): return str(value) return value for key, value in fields_copy.pop('properties', {}).iteritems(): headers['x-image-meta-property-%s' % key] = to_str(value) for key, value in fields_copy.iteritems(): headers['x-image-meta-%s' % key] = to_str(value) return headers @staticmethod def _format_image_meta_for_user(meta): for key in ['size', 'min_ram', 'min_disk']: if key in meta: try: meta[key] = int(meta[key]) except ValueError: pass return meta def get(self, image): """Get the metadata for a specific image. :param image: image object or id to look up :rtype: :class:`Image` """ image_id = base.getid(image) resp, body = self.api.raw_request('HEAD', '/v1/images/%s' % urllib.quote(str(image_id))) meta = self._image_meta_from_headers(dict(resp.getheaders())) return Image(self, meta) def data(self, image, do_checksum=True): """Get the raw data for a specific image. :param image: image object or id to look up :param do_checksum: Enable/disable checksum validation :rtype: iterable containing image data """ image_id = base.getid(image) resp, body = self.api.raw_request('GET', '/v1/images/%s' % urllib.quote(str(image_id))) checksum = resp.getheader('x-image-meta-checksum', None) if do_checksum and checksum is not None: body.set_checksum(checksum) return body def list(self, **kwargs): """Get a list of images. :param page_size: number of items to request in each paginated request :param limit: maximum number of images to return :param marker: begin returning images that appear later in the image list than that represented by this image id :param filters: dict of direct comparison filters that mimics the structure of an image object :param owner: If provided, only images with this owner (tenant id) will be listed. An empty string ('') matches ownerless images. :rtype: list of :class:`Image` """ absolute_limit = kwargs.get('limit') def paginate(qp, seen=0): def filter_owner(owner, image): # If client side owner 'filter' is specified # only return images that match 'owner'. if owner is None: # Do not filter based on owner return False if (not hasattr(image, 'owner')) or image.owner is None: # ownerless image return not (owner == '') else: return not (image.owner == owner) owner = qp.pop('owner', None) for param, value in qp.iteritems(): if isinstance(value, basestring): # Note(flaper87) Url encoding should # be moved inside http utils, at least # shouldn't be here. # # Making sure all params are str before # trying to encode them qp[param] = strutils.safe_encode(value) url = '/v1/images/detail?%s' % urllib.urlencode(qp) images = self._list(url, "images") for image in images: if filter_owner(owner, image): continue seen += 1 if absolute_limit is not None and seen > absolute_limit: return yield image page_size = qp.get('limit') if (page_size and len(images) == page_size and (absolute_limit is None or 0 < seen < absolute_limit)): qp['marker'] = image.id for image in paginate(qp, seen): yield image params = {'limit': kwargs.get('page_size', DEFAULT_PAGE_SIZE)} if 'marker' in kwargs: params['marker'] = kwargs['marker'] sort_key = kwargs.get('sort_key') if sort_key is not None: if sort_key in SORT_KEY_VALUES: params['sort_key'] = sort_key else: raise ValueError('sort_key must be one of the following: %s.' % ', '.join(SORT_KEY_VALUES)) sort_dir = kwargs.get('sort_dir') if sort_dir is not None: if sort_dir in SORT_DIR_VALUES: params['sort_dir'] = sort_dir else: raise ValueError('sort_dir must be one of the following: %s.' % ', '.join(SORT_DIR_VALUES)) filters = kwargs.get('filters', {}) properties = filters.pop('properties', {}) for key, value in properties.items(): params['property-%s' % key] = value params.update(filters) if kwargs.get('owner') is not None: params['owner'] = kwargs['owner'] params['is_public'] = None if 'is_public' in kwargs: params['is_public'] = kwargs['is_public'] return paginate(params) def delete(self, image): """Delete an image.""" self._delete("/v1/images/%s" % base.getid(image)) def create(self, **kwargs): """Create an image TODO(bcwaldon): document accepted params """ image_data = kwargs.pop('data', None) if image_data is not None: image_size = utils.get_file_size(image_data) if image_size is not None: kwargs.setdefault('size', image_size) fields = {} for field in kwargs: if field in CREATE_PARAMS: fields[field] = kwargs[field] else: msg = 'create() got an unexpected keyword argument \'%s\'' raise TypeError(msg % field) copy_from = fields.pop('copy_from', None) hdrs = self._image_meta_to_headers(fields) if copy_from is not None: hdrs['x-glance-api-copy-from'] = copy_from resp, body_iter = self.api.raw_request( 'POST', '/v1/images', headers=hdrs, body=image_data) body = json.loads(''.join([c for c in body_iter])) return Image(self, self._format_image_meta_for_user(body['image'])) def update(self, image, **kwargs): """Update an image TODO(bcwaldon): document accepted params """ image_data = kwargs.pop('data', None) if image_data is not None: image_size = utils.get_file_size(image_data) if image_size is not None: kwargs.setdefault('size', image_size) hdrs = {} try: purge_props = 'true' if kwargs.pop('purge_props') else 'false' except KeyError: pass else: hdrs['x-glance-registry-purge-props'] = purge_props fields = {} for field in kwargs: if field in UPDATE_PARAMS: fields[field] = kwargs[field] else: msg = 'update() got an unexpected keyword argument \'%s\'' raise TypeError(msg % field) copy_from = fields.pop('copy_from', None) hdrs.update(self._image_meta_to_headers(fields)) if copy_from is not None: hdrs['x-glance-api-copy-from'] = copy_from url = '/v1/images/%s' % base.getid(image) resp, body_iter = self.api.raw_request( 'PUT', url, headers=hdrs, body=image_data) body = json.loads(''.join([c for c in body_iter])) return Image(self, self._format_image_meta_for_user(body['image'])) python-glanceclient-0.12.0/glanceclient/v1/shell.py0000664000175300017540000004034212241451455023343 0ustar jenkinsjenkins00000000000000# 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 __future__ import print_function import argparse import copy import sys from glanceclient import exc from glanceclient.common import utils from glanceclient.common import progressbar from glanceclient.openstack.common import strutils import glanceclient.v1.images #NOTE(bcwaldon): import deprecated cli functions from glanceclient.v1.legacy_shell import * CONTAINER_FORMATS = 'Acceptable formats: ami, ari, aki, bare, and ovf.' DISK_FORMATS = ('Acceptable formats: ami, ari, aki, vhd, vmdk, raw, ' 'qcow2, vdi, and iso.') @utils.arg('--name', metavar='', help='Filter images to those that have this name.') @utils.arg('--status', metavar='', help='Filter images to those that have this status.') @utils.arg('--container-format', metavar='', help='Filter images to those that have this container format. ' + CONTAINER_FORMATS) @utils.arg('--disk-format', metavar='', help='Filter images to those that have this disk format. ' + DISK_FORMATS) @utils.arg('--size-min', metavar='', help='Filter images to those with a size greater than this.') @utils.arg('--size-max', metavar='', help='Filter images to those with a size less than this.') @utils.arg('--property-filter', metavar='', help="Filter images by a user-defined image property.", action='append', dest='properties', default=[]) @utils.arg('--page-size', metavar='', default=None, type=int, help='Number of images to request in each paginated request.') @utils.arg('--human-readable', action='store_true', default=False, help='Print image size in a human-friendly format.') @utils.arg('--sort-key', default='name', choices=glanceclient.v1.images.SORT_KEY_VALUES, help='Sort image list by specified field.') @utils.arg('--sort-dir', default='asc', choices=glanceclient.v1.images.SORT_DIR_VALUES, help='Sort image list in specified direction.') @utils.arg('--is-public', type=utils.string_to_bool, metavar='{True,False}', help=('Allows the user to select a listing of public or non ' 'public images.')) @utils.arg('--owner', default=None, metavar='', help='Display only images owned by this tenant id. Filtering ' 'occurs on the client side so may be inefficient. This option ' 'is mainly intended for admin use. Use an empty string (\'\') ' 'to list images with no owner. Note: This option overrides ' 'the --is-public argument if present. Note: the v2 API ' 'supports more efficient server-side owner based filtering.') @utils.arg('--all-tenants', action='store_true', default=False, help=('Allows the admin user to list all images ' 'irrespective of the image\'s owner or is_public value.')) def do_image_list(gc, args): """List images you can access.""" filter_keys = ['name', 'status', 'container_format', 'disk_format', 'size_min', 'size_max', 'is_public'] filter_items = [(key, getattr(args, key)) for key in filter_keys] filters = dict([item for item in filter_items if item[1] is not None]) if args.properties: property_filter_items = [p.split('=', 1) for p in args.properties] filters['properties'] = dict(property_filter_items) kwargs = {'filters': filters} if args.page_size is not None: kwargs['page_size'] = args.page_size kwargs['sort_key'] = args.sort_key kwargs['sort_dir'] = args.sort_dir kwargs['owner'] = args.owner if args.all_tenants is True: kwargs['is_public'] = None images = gc.images.list(**kwargs) if args.human_readable: def convert_size(image): image.size = utils.make_size_human_readable(image.size) return image images = (convert_size(image) for image in images) columns = ['ID', 'Name', 'Disk Format', 'Container Format', 'Size', 'Status'] utils.print_list(images, columns) def _image_show(image, human_readable=False): # Flatten image properties dict for display info = copy.deepcopy(image._info) if human_readable: info['size'] = utils.make_size_human_readable(info['size']) for (k, v) in info.pop('properties').iteritems(): info['Property \'%s\'' % k] = v utils.print_dict(info) def _set_data_field(fields, args): if 'location' not in fields and 'copy_from' not in fields: fields['data'] = utils.get_data_file(args) @utils.arg('image', metavar='', help='Name or ID of image to describe.') @utils.arg('--human-readable', action='store_true', default=False, help='Print image size in a human-friendly format.') def do_image_show(gc, args): """Describe a specific image.""" image_id = utils.find_resource(gc.images, args.image).id image = gc.images.get(image_id) _image_show(image, args.human_readable) @utils.arg('--file', metavar='', help='Local file to save downloaded image data to. ' 'If this is not specified the image data will be ' 'written to stdout.') @utils.arg('image', metavar='', help='Name or ID of image to download.') @utils.arg('--progress', action='store_true', default=False, help='Show download progress bar.') def do_image_download(gc, args): """Download a specific image.""" image = utils.find_resource(gc.images, args.image) body = image.data() if args.progress: body = progressbar.VerboseIteratorWrapper(body, len(body)) utils.save_image(body, args.file) @utils.arg('--id', metavar='', help='ID of image to reserve.') @utils.arg('--name', metavar='', help='Name of image.') @utils.arg('--store', metavar='', help='Store to upload image to.') @utils.arg('--disk-format', metavar='', help='Disk format of image. ' + DISK_FORMATS) @utils.arg('--container-format', metavar='', help='Container format of image. ' + CONTAINER_FORMATS) @utils.arg('--owner', metavar='', help='Tenant who should own image.') @utils.arg('--size', metavar='', help=('Size of image data (in bytes). Only used with' ' \'--location\' and \'--copy_from\'.')) @utils.arg('--min-disk', metavar='', help='Minimum size of disk needed to boot image (in gigabytes).') @utils.arg('--min-ram', metavar='', help='Minimum amount of ram needed to boot image (in megabytes).') @utils.arg('--location', metavar='', help=('URL where the data for this image already resides. For ' 'example, if the image data is stored in swift, you could ' 'specify \'swift://account:key@example.com/container/obj\'.')) @utils.arg('--file', metavar='', help=('Local file that contains disk image to be uploaded during' ' creation. Alternatively, images can be passed to the client' ' via stdin.')) @utils.arg('--checksum', metavar='', help=('Hash of image data used Glance can use for verification.' ' Provide a md5 checksum here.')) @utils.arg('--copy-from', metavar='', help=('Similar to \'--location\' in usage, but this indicates that' ' the Glance server should immediately copy the data and' ' store it in its configured image store.')) #NOTE(bcwaldon): This will be removed once devstack is updated # to use --is-public @utils.arg('--public', action='store_true', default=False, help=argparse.SUPPRESS) @utils.arg('--is-public', type=utils.string_to_bool, metavar='{True,False}', help='Make image accessible to the public.') @utils.arg('--is-protected', type=utils.string_to_bool, metavar='{True,False}', help='Prevent image from being deleted.') @utils.arg('--property', metavar="", action='append', default=[], help=("Arbitrary property to associate with image. " "May be used multiple times.")) @utils.arg('--human-readable', action='store_true', default=False, help='Print image size in a human-friendly format.') @utils.arg('--progress', action='store_true', default=False, help='Show upload progress bar.') def do_image_create(gc, args): """Create a new image.""" # Filter out None values fields = dict(filter(lambda x: x[1] is not None, vars(args).items())) fields['is_public'] = fields.get('is_public') or fields.pop('public') if 'is_protected' in fields: fields['protected'] = fields.pop('is_protected') raw_properties = fields.pop('property') fields['properties'] = {} for datum in raw_properties: key, value = datum.split('=', 1) fields['properties'][key] = value # Filter out values we can't use CREATE_PARAMS = glanceclient.v1.images.CREATE_PARAMS fields = dict(filter(lambda x: x[0] in CREATE_PARAMS, fields.items())) _set_data_field(fields, args) if args.progress: filesize = utils.get_file_size(fields['data']) fields['data'] = progressbar.VerboseFileWrapper( fields['data'], filesize ) image = gc.images.create(**fields) _image_show(image, args.human_readable) @utils.arg('image', metavar='', help='Name or ID of image to modify.') @utils.arg('--name', metavar='', help='Name of image.') @utils.arg('--disk-format', metavar='', help='Disk format of image. ' + DISK_FORMATS) @utils.arg('--container-format', metavar='', help='Container format of image. ' + CONTAINER_FORMATS) @utils.arg('--owner', metavar='', help='Tenant who should own image.') @utils.arg('--size', metavar='', help='Size of image data (in bytes).') @utils.arg('--min-disk', metavar='', help='Minimum size of disk needed to boot image (in gigabytes).') @utils.arg('--min-ram', metavar='', help='Minimum amount of ram needed to boot image (in megabytes).') @utils.arg('--location', metavar='', help=('URL where the data for this image already resides. For ' 'example, if the image data is stored in swift, you could ' 'specify \'swift://account:key@example.com/container/obj\'.')) @utils.arg('--file', metavar='', help=('Local file that contains disk image to be uploaded during' ' update. Alternatively, images can be passed to the client' ' via stdin.')) @utils.arg('--checksum', metavar='', help='Hash of image data used Glance can use for verification.') @utils.arg('--copy-from', metavar='', help=('Similar to \'--location\' in usage, but this indicates that' ' the Glance server should immediately copy the data and' ' store it in its configured image store.')) @utils.arg('--is-public', type=utils.string_to_bool, metavar='{True,False}', help='Make image accessible to the public.') @utils.arg('--is-protected', type=utils.string_to_bool, metavar='{True,False}', help='Prevent image from being deleted.') @utils.arg('--property', metavar="", action='append', default=[], help=("Arbitrary property to associate with image. " "May be used multiple times.")) @utils.arg('--purge-props', action='store_true', default=False, help=("If this flag is present, delete all image properties " "not explicitly set in the update request. Otherwise, " "those properties not referenced are preserved.")) @utils.arg('--human-readable', action='store_true', default=False, help='Print image size in a human-friendly format.') @utils.arg('--progress', action='store_true', default=False, help='Show upload progress bar.') def do_image_update(gc, args): """Update a specific image.""" # Filter out None values fields = dict(filter(lambda x: x[1] is not None, vars(args).items())) image_arg = fields.pop('image') image = utils.find_resource(gc.images, image_arg) if 'is_protected' in fields: fields['protected'] = fields.pop('is_protected') raw_properties = fields.pop('property') fields['properties'] = {} for datum in raw_properties: key, value = datum.split('=', 1) fields['properties'][key] = value # Filter out values we can't use UPDATE_PARAMS = glanceclient.v1.images.UPDATE_PARAMS fields = dict(filter(lambda x: x[0] in UPDATE_PARAMS, fields.items())) if image.status == 'queued': _set_data_field(fields, args) if args.progress: filesize = utils.get_file_size(fields['data']) fields['data'] = progressbar.VerboseFileWrapper( fields['data'], filesize ) image = gc.images.update(image, purge_props=args.purge_props, **fields) _image_show(image, args.human_readable) @utils.arg('images', metavar='', nargs='+', help='Name or ID of image(s) to delete.') def do_image_delete(gc, args): """Delete specified image(s).""" for args_image in args.images: image = utils.find_resource(gc.images, args_image) try: if args.verbose: print('Requesting image delete for %s ...' % strutils.safe_encode(args_image), end=' ') gc.images.delete(image) if args.verbose: print('[Done]') except exc.HTTPException as e: if args.verbose: print('[Fail]') print('%s: Unable to delete image %s' % (e, args_image)) @utils.arg('--image-id', metavar='', help='Filter results by an image ID.') @utils.arg('--tenant-id', metavar='', help='Filter results by a tenant ID.') def do_member_list(gc, args): """Describe sharing permissions by image or tenant.""" if args.image_id and args.tenant_id: print('Unable to filter members by both --image-id and --tenant-id.') sys.exit(1) elif args.image_id: kwargs = {'image': args.image_id} elif args.tenant_id: kwargs = {'member': args.tenant_id} else: print('Unable to list all members. Specify --image-id or --tenant-id') sys.exit(1) members = gc.image_members.list(**kwargs) columns = ['Image ID', 'Member ID', 'Can Share'] utils.print_list(members, columns) @utils.arg('image', metavar='', help='Image to add member to.') @utils.arg('tenant_id', metavar='', help='Tenant to add as member') @utils.arg('--can-share', action='store_true', default=False, help='Allow the specified tenant to share this image.') def do_member_create(gc, args): """Share a specific image with a tenant.""" image = utils.find_resource(gc.images, args.image) gc.image_members.create(image, args.tenant_id, args.can_share) @utils.arg('image', metavar='', help='Image from which to remove member') @utils.arg('tenant_id', metavar='', help='Tenant to remove as member') def do_member_delete(gc, args): """Remove a shared image from a tenant.""" image_id = utils.find_resource(gc.images, args.image).id if not args.dry_run: gc.image_members.delete(image_id, args.tenant_id) else: print("Dry run. We would have done the following:") print('Remove "%s" from the member list of image ' '"%s"' % (args.tenant_id, args.image)) python-glanceclient-0.12.0/glanceclient/v1/legacy_shell.py0000664000175300017540000004141712241451455024673 0ustar jenkinsjenkins00000000000000# 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. """ DEPRECATED functions that implement the same command line interface as the legacy glance client. """ from __future__ import print_function import argparse import sys import urlparse from glanceclient.common import utils SUCCESS = 0 FAILURE = 1 def get_image_fields_from_args(args): """ Validate the set of arguments passed as field name/value pairs and return them as a mapping. """ fields = {} for arg in args: pieces = arg.strip(',').split('=') if len(pieces) != 2: msg = ("Arguments should be in the form of field=value. " "You specified %s." % arg) raise RuntimeError(msg) fields[pieces[0]] = pieces[1] return fields def get_image_filters_from_args(args): """Build a dictionary of query filters based on the supplied args.""" try: fields = get_image_fields_from_args(args) except RuntimeError as e: print(e) return FAILURE SUPPORTED_FILTERS = ['name', 'disk_format', 'container_format', 'status', 'min_ram', 'min_disk', 'size_min', 'size_max', 'changes-since'] filters = {} for (key, value) in fields.items(): if key not in SUPPORTED_FILTERS: key = 'property-%s' % (key,) filters[key] = value return filters def print_image_formatted(client, image): """ Formatted print of image metadata. :param client: The Glance client object :param image: The image metadata """ uri_parts = urlparse.urlparse(client.endpoint) if uri_parts.port: hostbase = "%s:%s" % (uri_parts.hostname, uri_parts.port) else: hostbase = uri_parts.hostname print("URI: %s://%s/v1/images/%s" % (uri_parts.scheme, hostbase, image.id)) print("Id: %s" % image.id) print("Public: " + (image.is_public and "Yes" or "No")) print("Protected: " + (image.protected and "Yes" or "No")) print("Name: %s" % getattr(image, 'name', '')) print("Status: %s" % image.status) print("Size: %d" % int(image.size)) print("Disk format: %s" % getattr(image, 'disk_format', '')) print("Container format: %s" % getattr(image, 'container_format', '')) print("Minimum Ram Required (MB): %s" % image.min_ram) print("Minimum Disk Required (GB): %s" % image.min_disk) if hasattr(image, 'owner'): print("Owner: %s" % image.owner) if len(image.properties) > 0: for k, v in image.properties.items(): print("Property '%s': %s" % (k, v)) print("Created at: %s" % image.created_at) if hasattr(image, 'deleted_at'): print("Deleted at: %s" % image.deleted_at) if hasattr(image, 'updated_at'): print("Updated at: %s" % image.updated_at) @utils.arg('--silent-upload', action="store_true", help="DEPRECATED! Animations are always off.") @utils.arg('fields', default=[], nargs='*', help=argparse.SUPPRESS) def do_add(gc, args): """DEPRECATED! Use image-create instead.""" try: fields = get_image_fields_from_args(args.fields) except RuntimeError as e: print(e) return FAILURE image_meta = { 'is_public': utils.string_to_bool( fields.pop('is_public', 'False')), 'protected': utils.string_to_bool( fields.pop('protected', 'False')), 'min_disk': fields.pop('min_disk', 0), 'min_ram': fields.pop('min_ram', 0), } #NOTE(bcwaldon): Use certain properties only if they are explicitly set optional = ['id', 'name', 'disk_format', 'container_format'] for field in optional: if field in fields: image_meta[field] = fields.pop(field) # Strip any args that are not supported unsupported_fields = ['status', 'size'] for field in unsupported_fields: if field in fields.keys(): print('Found non-settable field %s. Removing.' % field) fields.pop(field) # We need either a location or image data/stream to add... image_data = None if 'location' in fields.keys(): image_meta['location'] = fields.pop('location') if 'checksum' in fields.keys(): image_meta['checksum'] = fields.pop('checksum') elif 'copy_from' in fields.keys(): image_meta['copy_from'] = fields.pop('copy_from') else: # Grab the image data stream from stdin or redirect, # otherwise error out image_data = sys.stdin image_meta['data'] = image_data # allow owner to be set when image is created if 'owner' in fields.keys(): image_meta['owner'] = fields.pop('owner') # Add custom attributes, which are all the arguments remaining image_meta['properties'] = fields if not args.dry_run: image = gc.images.create(**image_meta) print("Added new image with ID: %s" % image.id) if args.verbose: print("Returned the following metadata for the new image:") for k, v in sorted(image.to_dict().items()): print(" %(k)30s => %(v)s" % {'k': k, 'v': v}) else: print("Dry run. We would have done the following:") def _dump(dict): for k, v in sorted(dict.items()): print(" %(k)30s => %(v)s" % {'k': k, 'v': v}) print("Add new image with metadata:") _dump(image_meta) return SUCCESS @utils.arg('id', metavar='', help='ID of image to describe.') @utils.arg('fields', default=[], nargs='*', help=argparse.SUPPRESS) def do_update(gc, args): """DEPRECATED! Use image-update instead.""" try: fields = get_image_fields_from_args(args.fields) except RuntimeError as e: print(e) return FAILURE image_meta = {} # Strip any args that are not supported nonmodifiable_fields = ['created_at', 'deleted_at', 'deleted', 'updated_at', 'size', 'status'] for field in nonmodifiable_fields: if field in fields.keys(): print('Found non-modifiable field %s. Removing.' % field) fields.pop(field) base_image_fields = ['disk_format', 'container_format', 'name', 'min_disk', 'min_ram', 'location', 'owner', 'copy_from'] for field in base_image_fields: fvalue = fields.pop(field, None) if fvalue is not None: image_meta[field] = fvalue # Have to handle "boolean" values specially... if 'is_public' in fields: image_meta['is_public'] = utils.string_to_bool(fields.pop('is_public')) if 'protected' in fields: image_meta['protected'] = utils.string_to_bool(fields.pop('protected')) # Add custom attributes, which are all the arguments remaining image_meta['properties'] = fields if not args.dry_run: image = gc.images.update(args.id, **image_meta) print("Updated image %s" % args.id) if args.verbose: print("Updated image metadata for image %s:" % args.id) print_image_formatted(gc, image) else: def _dump(dict): for k, v in sorted(dict.items()): print(" %(k)30s => %(v)s" % {'k': k, 'v': v}) print("Dry run. We would have done the following:") print("Update existing image with metadata:") _dump(image_meta) return SUCCESS @utils.arg('id', metavar='', help='ID of image to describe.') def do_delete(gc, args): """DEPRECATED! Use image-delete instead.""" if not (args.force or user_confirm("Delete image %s?" % args.id, default=False)): print('Not deleting image %s' % args.id) return FAILURE gc.images.get(args.id).delete() @utils.arg('id', metavar='', help='ID of image to describe.') def do_show(gc, args): """DEPRECATED! Use image-show instead.""" image = gc.images.get(args.id) print_image_formatted(gc, image) return SUCCESS def _get_images(gc, args): parameters = { 'filters': get_image_filters_from_args(args.filters), 'page_size': args.limit, } optional_kwargs = ['marker', 'sort_key', 'sort_dir'] for kwarg in optional_kwargs: value = getattr(args, kwarg, None) if value is not None: parameters[kwarg] = value return gc.images.list(**parameters) @utils.arg('--limit', dest="limit", metavar="LIMIT", default=10, type=int, help="Page size to use while requesting image metadata") @utils.arg('--marker', dest="marker", metavar="MARKER", default=None, help="Image index after which to begin pagination") @utils.arg('--sort_key', dest="sort_key", metavar="KEY", help="Sort results by this image attribute.") @utils.arg('--sort_dir', dest="sort_dir", metavar="[desc|asc]", help="Sort results in this direction.") @utils.arg('filters', default=[], nargs='*', help=argparse.SUPPRESS) def do_index(gc, args): """DEPRECATED! Use image-list instead.""" images = _get_images(gc, args) if not images: return SUCCESS pretty_table = PrettyTable() pretty_table.add_column(36, label="ID") pretty_table.add_column(30, label="Name") pretty_table.add_column(20, label="Disk Format") pretty_table.add_column(20, label="Container Format") pretty_table.add_column(14, label="Size", just="r") print(pretty_table.make_header()) for image in images: print(pretty_table.make_row(image.id, image.name, image.disk_format, image.container_format, image.size)) @utils.arg('--limit', dest="limit", metavar="LIMIT", default=10, type=int, help="Page size to use while requesting image metadata") @utils.arg('--marker', dest="marker", metavar="MARKER", default=None, help="Image index after which to begin pagination") @utils.arg('--sort_key', dest="sort_key", metavar="KEY", help="Sort results by this image attribute.") @utils.arg('--sort_dir', dest="sort_dir", metavar="[desc|asc]", help="Sort results in this direction.") @utils.arg('filters', default='', nargs='*', help=argparse.SUPPRESS) def do_details(gc, args): """DEPRECATED! Use image-list instead.""" images = _get_images(gc, args) for i, image in enumerate(images): if i == 0: print("=" * 80) print_image_formatted(gc, image) print("=" * 80) def do_clear(gc, args): """DEPRECATED!""" if not (args.force or user_confirm("Delete all images?", default=False)): print('Not deleting any images') return FAILURE images = gc.images.list() for image in images: if args.verbose: print('Deleting image %s "%s" ...' % (image.id, image.name), end=' ') try: image.delete() if args.verbose: print('done') except Exception as e: print('Failed to delete image %s' % image.id) print(e) return FAILURE return SUCCESS @utils.arg('image_id', help='Image ID to filters members with.') def do_image_members(gc, args): """DEPRECATED! Use member-list instead.""" members = gc.image_members.list(image=args.image_id) sharers = 0 # Output the list of members for memb in members: can_share = '' if memb.can_share: can_share = ' *' sharers += 1 print("%s%s" % (memb.member_id, can_share)) # Emit a footnote if sharers > 0: print("\n(*: Can share image)") @utils.arg('--can-share', default=False, action="store_true", help="Allow member to further share image.") @utils.arg('member_id', help='ID of member (typically tenant) to grant access.') def do_member_images(gc, args): """DEPRECATED! Use member-list instead.""" members = gc.image_members.list(member=args.member_id) if not len(members): print("No images shared with member %s" % args.member_id) return SUCCESS sharers = 0 # Output the list of images for memb in members: can_share = '' if memb.can_share: can_share = ' *' sharers += 1 print("%s%s" % (memb.image_id, can_share)) # Emit a footnote if sharers > 0: print("\n(*: Can share image)") @utils.arg('--can-share', default=False, action="store_true", help="Allow member to further share image.") @utils.arg('image_id', help='ID of image to describe.') @utils.arg('member_id', help='ID of member (typically tenant) to grant access.') def do_members_replace(gc, args): """DEPRECATED!""" if not args.dry_run: for member in gc.image_members.list(image=args.image_id): gc.image_members.delete(args.image_id, member.member_id) gc.image_members.create(args.image_id, args.member_id, args.can_share) else: print("Dry run. We would have done the following:") print('Replace members of image %s with "%s"' % (args.image_id, args.member_id)) if args.can_share: print("New member would have been able to further share image.") @utils.arg('--can-share', default=False, action="store_true", help="Allow member to further share image.") @utils.arg('image_id', help='ID of image to describe.') @utils.arg('member_id', help='ID of member (typically tenant) to grant access.') def do_member_add(gc, args): """DEPRECATED! Use member-create instead.""" if not args.dry_run: gc.image_members.create(args.image_id, args.member_id, args.can_share) else: print("Dry run. We would have done the following:") print('Add "%s" to membership of image %s' % (args.member_id, args.image_id)) if args.can_share: print("New member would have been able to further share image.") def user_confirm(prompt, default=False): """ Yes/No question dialog with user. :param prompt: question/statement to present to user (string) :param default: boolean value to return if empty string is received as response to prompt """ if default: prompt_default = "[Y/n]" else: prompt_default = "[y/N]" # for bug 884116, don't issue the prompt if stdin isn't a tty if not (hasattr(sys.stdin, 'isatty') and sys.stdin.isatty()): return default answer = raw_input("%s %s " % (prompt, prompt_default)) if answer == "": return default else: return answer.lower() in ("yes", "y") class PrettyTable(object): """Creates an ASCII art table Example: ID Name Size Hits --- ----------------- ------------ ----- 122 image 22 0 """ def __init__(self): self.columns = [] def add_column(self, width, label="", just='l'): """Add a column to the table :param width: number of characters wide the column should be :param label: column heading :param just: justification for the column, 'l' for left, 'r' for right """ self.columns.append((width, label, just)) def make_header(self): label_parts = [] break_parts = [] for width, label, _ in self.columns: # NOTE(sirp): headers are always left justified label_part = self._clip_and_justify(label, width, 'l') label_parts.append(label_part) break_part = '-' * width break_parts.append(break_part) label_line = ' '.join(label_parts) break_line = ' '.join(break_parts) return '\n'.join([label_line, break_line]) def make_row(self, *args): row = args row_parts = [] for data, (width, _, just) in zip(row, self.columns): row_part = self._clip_and_justify(data, width, just) row_parts.append(row_part) row_line = ' '.join(row_parts) return row_line @staticmethod def _clip_and_justify(data, width, just): # clip field to column width clipped_data = str(data)[:width] if just == 'r': # right justify justified = clipped_data.rjust(width) else: # left justify justified = clipped_data.ljust(width) return justified python-glanceclient-0.12.0/glanceclient/v1/client.py0000664000175300017540000000257112241451455023514 0ustar jenkinsjenkins00000000000000# 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 glanceclient.common import http from glanceclient.v1 import images from glanceclient.v1 import image_members class Client(http.HTTPClient): """Client for the OpenStack Images v1 API. :param string endpoint: A user-supplied endpoint URL for the glance service. :param string token: Token for authentication. :param integer timeout: Allows customization of the timeout for client http requests. (optional) """ def __init__(self, *args, **kwargs): """Initialize a new client for the Images v1 API.""" super(Client, self).__init__(*args, **kwargs) self.images = images.ImageManager(self) self.image_members = image_members.ImageMemberManager(self) python-glanceclient-0.12.0/glanceclient/exc.py0000664000175300017540000000707012241451455022466 0ustar jenkinsjenkins00000000000000# 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 sys class BaseException(Exception): """An error occurred.""" def __init__(self, message=None): self.message = message def __str__(self): return self.message or self.__class__.__doc__ class CommandError(BaseException): """Invalid usage of CLI.""" class InvalidEndpoint(BaseException): """The provided endpoint is invalid.""" class CommunicationError(BaseException): """Unable to communicate with server.""" class ClientException(Exception): """DEPRECATED!""" class HTTPException(ClientException): """Base exception for all HTTP-derived exceptions.""" code = 'N/A' def __init__(self, details=None): self.details = details or self.__class__.__name__ def __str__(self): return "%s (HTTP %s)" % (self.details, self.code) class HTTPMultipleChoices(HTTPException): code = 300 def __str__(self): self.details = ("Requested version of OpenStack Images API is not" "available.") return "%s (HTTP %s) %s" % (self.__class__.__name__, self.code, self.details) class BadRequest(HTTPException): """DEPRECATED!""" code = 400 class HTTPBadRequest(BadRequest): pass class Unauthorized(HTTPException): """DEPRECATED!""" code = 401 class HTTPUnauthorized(Unauthorized): pass class Forbidden(HTTPException): """DEPRECATED!""" code = 403 class HTTPForbidden(Forbidden): pass class NotFound(HTTPException): """DEPRECATED!""" code = 404 class HTTPNotFound(NotFound): pass class HTTPMethodNotAllowed(HTTPException): code = 405 class Conflict(HTTPException): """DEPRECATED!""" code = 409 class HTTPConflict(Conflict): pass class OverLimit(HTTPException): """DEPRECATED!""" code = 413 class HTTPOverLimit(OverLimit): pass class HTTPInternalServerError(HTTPException): code = 500 class HTTPNotImplemented(HTTPException): code = 501 class HTTPBadGateway(HTTPException): code = 502 class ServiceUnavailable(HTTPException): """DEPRECATED!""" code = 503 class HTTPServiceUnavailable(ServiceUnavailable): pass #NOTE(bcwaldon): Build a mapping of HTTP codes to corresponding exception # classes _code_map = {} for obj_name in dir(sys.modules[__name__]): if obj_name.startswith('HTTP'): obj = getattr(sys.modules[__name__], obj_name) _code_map[obj.code] = obj def from_response(response, body=None): """Return an instance of an HTTPException based on httplib response.""" cls = _code_map.get(response.status, HTTPException) if body: details = body.replace('\n\n', '\n') return cls(details=details) return cls() class NoTokenLookupException(Exception): """DEPRECATED!""" pass class EndpointNotFound(Exception): """DEPRECATED!""" pass class SSLConfigurationError(BaseException): pass class SSLCertificateError(BaseException): pass python-glanceclient-0.12.0/glanceclient/openstack/0000775000175300017540000000000012241451524023315 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/glanceclient/openstack/common/0000775000175300017540000000000012241451524024605 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/glanceclient/openstack/common/importutils.py0000664000175300017540000000421212241451455027554 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 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 related utilities and helper functions. """ import sys import traceback def import_class(import_str): """Returns a class from a string including module and class.""" mod_str, _sep, class_str = import_str.rpartition('.') try: __import__(mod_str) return getattr(sys.modules[mod_str], class_str) except (ValueError, AttributeError): raise ImportError('Class %s cannot be found (%s)' % (class_str, traceback.format_exception(*sys.exc_info()))) def import_object(import_str, *args, **kwargs): """Import a class and return an instance of it.""" return import_class(import_str)(*args, **kwargs) def import_object_ns(name_space, import_str, *args, **kwargs): """ Import a class and return an instance of it, first by trying to find the class in a default namespace, then failing back to a full path if not found in the default namespace. """ import_value = "%s.%s" % (name_space, import_str) try: return import_class(import_value)(*args, **kwargs) except ImportError: return import_class(import_str)(*args, **kwargs) def import_module(import_str): """Import a module.""" __import__(import_str) return sys.modules[import_str] def try_import(import_str, default=None): """Try to import a module and if it fails return default.""" try: return import_module(import_str) except ImportError: return default python-glanceclient-0.12.0/glanceclient/openstack/common/gettextutils.py0000664000175300017540000000306312241451455027731 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ gettext for openstack-common modules. Usual usage in an openstack.common module: from glanceclient.openstack.common.gettextutils import _ """ import gettext import os _localedir = os.environ.get('glanceclient'.upper() + '_LOCALEDIR') _t = gettext.translation('glanceclient', localedir=_localedir, fallback=True) def _(msg): return _t.ugettext(msg) def install(domain): """Install a _() function using the given translation domain. Given a translation domain, install a _() function using gettext's install() function. The main difference from gettext.install() is that we allow overriding the default localedir (e.g. /usr/share/locale) using a translation-domain-specific environment variable (e.g. NOVA_LOCALEDIR). """ gettext.install(domain, localedir=os.environ.get(domain.upper() + '_LOCALEDIR'), unicode=True) python-glanceclient-0.12.0/glanceclient/openstack/common/__init__.py0000664000175300017540000000000012241451455026707 0ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/glanceclient/openstack/common/strutils.py0000664000175300017540000001164612241451455027063 0ustar jenkinsjenkins00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 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. """ System-level utilities and helper functions. """ import sys from glanceclient.openstack.common.gettextutils import _ TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes') FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no') def int_from_bool_as_string(subject): """ Interpret a string as a boolean and return either 1 or 0. Any string value in: ('True', 'true', 'On', 'on', '1') is interpreted as a boolean True. Useful for JSON-decoded stuff and config file parsing """ return bool_from_string(subject) and 1 or 0 def bool_from_string(subject, strict=False): """ Interpret a string as a boolean. A case-insensitive match is performed such that strings matching 't', 'true', 'on', 'y', 'yes', or '1' are considered True and, when `strict=False`, anything else is considered False. Useful for JSON-decoded stuff and config file parsing. If `strict=True`, unrecognized values, including None, will raise a ValueError which is useful when parsing values passed in from an API call. Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'. """ if not isinstance(subject, basestring): subject = str(subject) lowered = subject.strip().lower() if lowered in TRUE_STRINGS: return True elif lowered in FALSE_STRINGS: return False elif strict: acceptable = ', '.join( "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS)) msg = _("Unrecognized value '%(val)s', acceptable values are:" " %(acceptable)s") % {'val': subject, 'acceptable': acceptable} raise ValueError(msg) else: return False def safe_decode(text, incoming=None, errors='strict'): """ Decodes incoming str using `incoming` if they're not already unicode. :param incoming: Text's current encoding :param errors: Errors handling policy. See here for valid values http://docs.python.org/2/library/codecs.html :returns: text or a unicode `incoming` encoded representation of it. :raises TypeError: If text is not an isntance of basestring """ if not isinstance(text, basestring): raise TypeError("%s can't be decoded" % type(text)) if isinstance(text, unicode): return text if not incoming: incoming = (sys.stdin.encoding or sys.getdefaultencoding()) try: return text.decode(incoming, errors) except UnicodeDecodeError: # Note(flaper87) If we get here, it means that # sys.stdin.encoding / sys.getdefaultencoding # didn't return a suitable encoding to decode # text. This happens mostly when global LANG # var is not set correctly and there's no # default encoding. In this case, most likely # python will use ASCII or ANSI encoders as # default encodings but they won't be capable # of decoding non-ASCII characters. # # Also, UTF-8 is being used since it's an ASCII # extension. return text.decode('utf-8', errors) def safe_encode(text, incoming=None, encoding='utf-8', errors='strict'): """ Encodes incoming str/unicode using `encoding`. If incoming is not specified, text is expected to be encoded with current python's default encoding. (`sys.getdefaultencoding`) :param incoming: Text's current encoding :param encoding: Expected encoding for text (Default UTF-8) :param errors: Errors handling policy. See here for valid values http://docs.python.org/2/library/codecs.html :returns: text or a bytestring `encoding` encoded representation of it. :raises TypeError: If text is not an isntance of basestring """ if not isinstance(text, basestring): raise TypeError("%s can't be encoded" % type(text)) if not incoming: incoming = (sys.stdin.encoding or sys.getdefaultencoding()) if isinstance(text, unicode): return text.encode(encoding, errors) elif text and encoding != incoming: # Decode text before encoding it with `encoding` text = safe_decode(text, incoming, errors) return text.encode(encoding, errors) return text python-glanceclient-0.12.0/glanceclient/openstack/__init__.py0000664000175300017540000000000012241451455025417 0ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/glanceclient/shell.py0000664000175300017540000005154712241451455023026 0ustar jenkinsjenkins00000000000000# 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. """ Command-line interface to the OpenStack Images API. """ from __future__ import print_function import argparse import json import logging import os from os.path import expanduser import re import sys from keystoneclient.v2_0 import client as ksclient import netaddr import glanceclient from glanceclient import exc from glanceclient.common import utils from glanceclient.openstack.common import strutils class OpenStackImagesShell(object): def get_base_parser(self): parser = argparse.ArgumentParser( prog='glance', description=__doc__.strip(), epilog='See "glance help COMMAND" ' 'for help on a specific command.', add_help=False, formatter_class=HelpFormatter, ) # Global arguments parser.add_argument('-h', '--help', action='store_true', help=argparse.SUPPRESS, ) parser.add_argument('--version', action='version', version=glanceclient.__version__) parser.add_argument('-d', '--debug', default=bool(utils.env('GLANCECLIENT_DEBUG')), action='store_true', help='Defaults to env[GLANCECLIENT_DEBUG]') parser.add_argument('-v', '--verbose', default=False, action="store_true", help="Print more verbose output") parser.add_argument('--get-schema', default=False, action="store_true", dest='get_schema', help='Force retrieving the schema used to generate' ' portions of the help text rather than using' ' a cached copy. Ignored with api version 1') parser.add_argument('-k', '--insecure', default=False, action='store_true', help='Explicitly allow glanceclient to perform ' '\"insecure SSL\" (https) requests. The server\'s ' 'certificate will not be verified against any ' 'certificate authorities. This option should ' 'be used with caution.') parser.add_argument('--cert-file', help='Path of certificate file to use in SSL ' 'connection. This file can optionally be ' 'prepended with the private key.') parser.add_argument('--key-file', help='Path of client key to use in SSL ' 'connection. This option is not necessary ' 'if your key is prepended to your cert file.') parser.add_argument('--os-cacert', metavar='', dest='os_cacert', default=utils.env('OS_CACERT'), help='Path of CA TLS certificate(s) used to ' 'verify the remote server\'s certificate. ' 'Without this option glance looks for the ' 'default system CA certificates.') parser.add_argument('--ca-file', dest='os_cacert', help='DEPRECATED! Use --os-cacert.') parser.add_argument('--timeout', default=600, help='Number of seconds to wait for a response') parser.add_argument('--no-ssl-compression', dest='ssl_compression', default=True, action='store_false', help='Disable SSL compression when using https.') parser.add_argument('-f', '--force', dest='force', default=False, action='store_true', help='Prevent select actions from requesting ' 'user confirmation.') #NOTE(bcwaldon): DEPRECATED parser.add_argument('--dry-run', default=False, action='store_true', help='DEPRECATED! Only used for deprecated ' 'legacy commands.') #NOTE(bcwaldon): DEPRECATED parser.add_argument('--ssl', dest='use_ssl', default=False, action='store_true', help='DEPRECATED! Send a fully-formed endpoint ' 'using --os-image-url instead.') #NOTE(bcwaldon): DEPRECATED parser.add_argument('-H', '--host', metavar='ADDRESS', help='DEPRECATED! Send a fully-formed endpoint ' 'using --os-image-url instead.') #NOTE(bcwaldon): DEPRECATED parser.add_argument('-p', '--port', dest='port', metavar='PORT', type=int, default=9292, help='DEPRECATED! Send a fully-formed endpoint ' 'using --os-image-url instead.') parser.add_argument('--os-username', default=utils.env('OS_USERNAME'), help='Defaults to env[OS_USERNAME]') parser.add_argument('--os_username', help=argparse.SUPPRESS) #NOTE(bcwaldon): DEPRECATED parser.add_argument('-I', dest='os_username', help='DEPRECATED! Use --os-username.') parser.add_argument('--os-password', default=utils.env('OS_PASSWORD'), help='Defaults to env[OS_PASSWORD]') parser.add_argument('--os_password', help=argparse.SUPPRESS) #NOTE(bcwaldon): DEPRECATED parser.add_argument('-K', dest='os_password', help='DEPRECATED! Use --os-password.') parser.add_argument('--os-tenant-id', default=utils.env('OS_TENANT_ID'), help='Defaults to env[OS_TENANT_ID]') parser.add_argument('--os_tenant_id', help=argparse.SUPPRESS) parser.add_argument('--os-tenant-name', default=utils.env('OS_TENANT_NAME'), help='Defaults to env[OS_TENANT_NAME]') parser.add_argument('--os_tenant_name', help=argparse.SUPPRESS) #NOTE(bcwaldon): DEPRECATED parser.add_argument('-T', dest='os_tenant_name', help='DEPRECATED! Use --os-tenant-name.') parser.add_argument('--os-auth-url', default=utils.env('OS_AUTH_URL'), help='Defaults to env[OS_AUTH_URL]') parser.add_argument('--os_auth_url', help=argparse.SUPPRESS) #NOTE(bcwaldon): DEPRECATED parser.add_argument('-N', dest='os_auth_url', help='DEPRECATED! Use --os-auth-url.') parser.add_argument('--os-region-name', default=utils.env('OS_REGION_NAME'), help='Defaults to env[OS_REGION_NAME]') parser.add_argument('--os_region_name', help=argparse.SUPPRESS) #NOTE(bcwaldon): DEPRECATED parser.add_argument('-R', dest='os_region_name', help='DEPRECATED! Use --os-region-name.') parser.add_argument('--os-auth-token', default=utils.env('OS_AUTH_TOKEN'), help='Defaults to env[OS_AUTH_TOKEN]') parser.add_argument('--os_auth_token', help=argparse.SUPPRESS) #NOTE(bcwaldon): DEPRECATED parser.add_argument('-A', '--auth_token', dest='os_auth_token', help='DEPRECATED! Use --os-auth-token.') parser.add_argument('--os-image-url', default=utils.env('OS_IMAGE_URL'), help='Defaults to env[OS_IMAGE_URL]') parser.add_argument('--os_image_url', help=argparse.SUPPRESS) #NOTE(bcwaldon): DEPRECATED parser.add_argument('-U', '--url', dest='os_image_url', help='DEPRECATED! Use --os-image-url.') parser.add_argument('--os-image-api-version', default=utils.env('OS_IMAGE_API_VERSION', default='1'), help='Defaults to env[OS_IMAGE_API_VERSION] or 1') parser.add_argument('--os_image_api_version', help=argparse.SUPPRESS) parser.add_argument('--os-service-type', default=utils.env('OS_SERVICE_TYPE'), help='Defaults to env[OS_SERVICE_TYPE]') parser.add_argument('--os_service_type', help=argparse.SUPPRESS) parser.add_argument('--os-endpoint-type', default=utils.env('OS_ENDPOINT_TYPE'), help='Defaults to env[OS_ENDPOINT_TYPE]') parser.add_argument('--os_endpoint_type', help=argparse.SUPPRESS) #NOTE(bcwaldon): DEPRECATED parser.add_argument('-S', '--os_auth_strategy', help='DEPRECATED! This option is ' 'completely ignored.') return parser def get_subcommand_parser(self, version): parser = self.get_base_parser() self.subcommands = {} subparsers = parser.add_subparsers(metavar='') submodule = utils.import_versioned_module(version, 'shell') self._find_actions(subparsers, submodule) self._find_actions(subparsers, self) return parser def _find_actions(self, subparsers, actions_module): for attr in (a for a in dir(actions_module) if a.startswith('do_')): # I prefer to be hypen-separated instead of underscores. command = attr[3:].replace('_', '-') callback = getattr(actions_module, attr) desc = callback.__doc__ or '' help = desc.strip().split('\n')[0] arguments = getattr(callback, 'arguments', []) subparser = subparsers.add_parser(command, help=help, description=desc, add_help=False, formatter_class=HelpFormatter ) subparser.add_argument('-h', '--help', action='help', help=argparse.SUPPRESS, ) self.subcommands[command] = subparser for (args, kwargs) in arguments: subparser.add_argument(*args, **kwargs) subparser.set_defaults(func=callback) # TODO(dtroyer): move this into the common client support? # Compatibility check to remove API version as the trailing component # in a service endpoint; also removes a trailing '/' def _strip_version(self, endpoint): """Strip version from the last component of endpoint if present.""" # Get rid of trailing '/' if present if endpoint.endswith('/'): endpoint = endpoint[:-1] url_bits = endpoint.split('/') # regex to match 'v1' or 'v2.0' etc if re.match('v\d+\.?\d*', url_bits[-1]): endpoint = '/'.join(url_bits[:-1]) return endpoint def _get_ksclient(self, **kwargs): """Get an endpoint and auth token from Keystone. :param username: name of user :param password: user's password :param tenant_id: unique identifier of tenant :param tenant_name: name of tenant :param auth_url: endpoint to authenticate against """ return ksclient.Client(username=kwargs.get('username'), password=kwargs.get('password'), tenant_id=kwargs.get('tenant_id'), tenant_name=kwargs.get('tenant_name'), auth_url=kwargs.get('auth_url'), cacert=kwargs.get('cacert'), insecure=kwargs.get('insecure')) def _get_endpoint(self, client, **kwargs): """Get an endpoint using the provided keystone client.""" endpoint_kwargs = { 'service_type': kwargs.get('service_type') or 'image', 'endpoint_type': kwargs.get('endpoint_type') or 'publicURL', } if kwargs.get('region_name'): endpoint_kwargs['attr'] = 'region' endpoint_kwargs['filter_value'] = kwargs.get('region_name') endpoint = client.service_catalog.url_for(**endpoint_kwargs) return self._strip_version(endpoint) def _get_image_url(self, args): """Translate the available url-related options into a single string. Return the endpoint that should be used to talk to Glance if a clear decision can be made. Otherwise, return None. """ if args.os_image_url: return args.os_image_url elif args.host: # Check if it is legal ipv6 address, if so, need wrap it with '[]' if netaddr.valid_ipv6(args.host): args.host = '[%s]' % args.host scheme = 'https' if args.use_ssl else 'http' return '%s://%s:%s/' % (scheme, args.host, args.port) else: return None def _get_endpoint_and_token(self, args, force_auth=False): image_url = self._get_image_url(args) auth_token = args.os_auth_token auth_reqd = force_auth or (utils.is_authentication_required(args.func) and not (auth_token and image_url)) if not auth_reqd: endpoint = image_url token = args.os_auth_token else: if not args.os_username: raise exc.CommandError("You must provide a username via" " either --os-username or " "env[OS_USERNAME]") if not args.os_password: raise exc.CommandError("You must provide a password via" " either --os-password or " "env[OS_PASSWORD]") if not (args.os_tenant_id or args.os_tenant_name): raise exc.CommandError("You must provide a tenant_id via" " either --os-tenant-id or " "via env[OS_TENANT_ID]") if not args.os_auth_url: raise exc.CommandError("You must provide an auth url via" " either --os-auth-url or " "via env[OS_AUTH_URL]") kwargs = { 'username': args.os_username, 'password': args.os_password, 'tenant_id': args.os_tenant_id, 'tenant_name': args.os_tenant_name, 'auth_url': args.os_auth_url, 'service_type': args.os_service_type, 'endpoint_type': args.os_endpoint_type, 'cacert': args.os_cacert, 'insecure': args.insecure, 'region_name': args.os_region_name, } _ksclient = self._get_ksclient(**kwargs) token = args.os_auth_token or _ksclient.auth_token endpoint = args.os_image_url or self._get_endpoint(_ksclient, **kwargs) return endpoint, token def _get_versioned_client(self, api_version, args, force_auth=False): endpoint, token = self._get_endpoint_and_token(args, force_auth=force_auth) kwargs = { 'token': token, 'insecure': args.insecure, 'timeout': args.timeout, 'cacert': args.os_cacert, 'cert_file': args.cert_file, 'key_file': args.key_file, 'ssl_compression': args.ssl_compression } client = glanceclient.Client(api_version, endpoint, **kwargs) return client def _cache_schema(self, options, home_dir='~/.glanceclient'): homedir = expanduser(home_dir) if not os.path.exists(homedir): os.makedirs(homedir) schema_file_path = homedir + os.sep + "image_schema.json" if (not os.path.exists(schema_file_path)) or options.get_schema: try: client = self._get_versioned_client('2', options, force_auth=True) schema = client.schemas.get("image") with file(schema_file_path, 'w') as f: f.write(json.dumps(schema.raw())) except Exception as e: #NOTE(esheffield) do nothing here, we'll get a message later #if the schema is missing pass def main(self, argv): # Parse args once to find version parser = self.get_base_parser() (options, args) = parser.parse_known_args(argv) # build available subcommands based on version api_version = options.os_image_api_version if api_version == '2': self._cache_schema(options) subcommand_parser = self.get_subcommand_parser(api_version) self.parser = subcommand_parser # Handle top-level --help/-h before attempting to parse # a command off the command line if options.help or not argv: self.do_help(options) return 0 # Parse args again and call whatever callback was selected args = subcommand_parser.parse_args(argv) # Short-circuit and deal with help command right away. if args.func == self.do_help: self.do_help(args) return 0 LOG = logging.getLogger('glanceclient') LOG.addHandler(logging.StreamHandler()) LOG.setLevel(logging.DEBUG if args.debug else logging.INFO) client = self._get_versioned_client(api_version, args, force_auth=False) try: args.func(client, args) except exc.Unauthorized: raise exc.CommandError("Invalid OpenStack Identity credentials.") @utils.arg('command', metavar='', nargs='?', help='Display help for ') def do_help(self, args): """ Display help about this program or one of its subcommands. """ if getattr(args, 'command', None): if args.command in self.subcommands: self.subcommands[args.command].print_help() else: raise exc.CommandError("'%s' is not a valid subcommand" % args.command) else: self.parser.print_help() class HelpFormatter(argparse.HelpFormatter): def start_section(self, heading): # Title-case the headings heading = '%s%s' % (heading[0].upper(), heading[1:]) super(HelpFormatter, self).start_section(heading) def main(): try: OpenStackImagesShell().main(map(strutils.safe_decode, sys.argv[1:])) except KeyboardInterrupt: print('... terminating glance client', file=sys.stderr) sys.exit(1) except Exception as e: print(utils.exception_to_str(e), file=sys.stderr) sys.exit(1) python-glanceclient-0.12.0/glanceclient/client.py0000664000175300017540000000153612241451455023166 0ustar jenkinsjenkins00000000000000# 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 glanceclient.common import utils def Client(version, *args, **kwargs): module = utils.import_versioned_module(version, 'client') client_class = getattr(module, 'Client') return client_class(*args, **kwargs) python-glanceclient-0.12.0/MANIFEST.in0000664000175300017540000000011012241451455020427 0ustar jenkinsjenkins00000000000000include AUTHORS include ChangeLog exclude .gitignore exclude .gitreview python-glanceclient-0.12.0/doc/0000775000175300017540000000000012241451524017443 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/doc/source/0000775000175300017540000000000012241451524020743 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/doc/source/conf.py0000664000175300017540000000435512241451455022254 0ustar jenkinsjenkins00000000000000# -*- coding: utf-8 -*- # import os import sys import pbr.version sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = 'python-glanceclient' copyright = u'OpenStack Foundation' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # Grouping the document tree for man pages. # List of tuples 'sourcefile', 'target', u'title', u'Authors name', 'manual' man_pages = [ ('man/glance', 'glance', u'Client for OpenStack Images API', [u'OpenStack Foundation'], 1), ] # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'nature' # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % project # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ( 'index', '%s.tex' % project, u'%s Documentation' % project, u'OpenStack Foundation', 'manual' ), ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} python-glanceclient-0.12.0/doc/source/man/0000775000175300017540000000000012241451524021516 5ustar jenkinsjenkins00000000000000python-glanceclient-0.12.0/doc/source/man/glance.rst0000664000175300017540000000413112241451455023503 0ustar jenkinsjenkins00000000000000============================== :program:`glance` CLI man page ============================== .. program:: glance .. highlight:: bash SYNOPSIS ======== :program:`glance` [options] [command-options] :program:`glance help` :program:`glance help` DESCRIPTION =========== The :program:`glance` command line utility interacts with OpenStack Images Service (Glance). In order to use the CLI, you must provide your OpenStack username, password, project (historically called tenant), and auth endpoint. You can use configuration options :option:`--os-username`, :option:`--os-password`, :option:`--os-tenant-id`, and :option:`--os-auth-url` or set corresponding environment variables:: export OS_USERNAME=user export OS_PASSWORD=pass export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b export OS_AUTH_URL=http://auth.example.com:5000/v2.0 The command line tool will attempt to reauthenticate using provided credentials for every request. You can override this behavior by manually supplying an auth token using :option:`--os-image-url` and :option:`--os-auth-token` or by setting corresponding environment variables:: export OS_IMAGE_URL=http://glance.example.org:9292/ export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155 You can select an API version to use by :option:`--os-image-api-version` option or by setting corresponding environment variable:: export OS_IMAGE_API_VERSION=2 OPTIONS ======= To get a list of available commands and options run:: glance help To get usage and options of a command:: glance help EXAMPLES ======== Get information about image-create command:: glance help image-create See available images:: glance image-list Create new image:: glance image-create --name foo --disk-format=qcow2 \ --container-format=bare --is-public=True \ --copy-from http://somewhere.net/foo.img Describe a specific image:: glance image-show foo BUGS ==== Glance client is hosted in Launchpad so you can view current bugs at https://bugs.launchpad.net/python-glanceclient/. python-glanceclient-0.12.0/doc/source/index.rst0000664000175300017540000001704612241451455022617 0ustar jenkinsjenkins00000000000000Python API ========== In order to use the python api directly, you must first obtain an auth token and identify which endpoint you wish to speak to. Once you have done so, you can use the API like so:: >>> from glanceclient import Client >>> glance = Client('1', endpoint=OS_IMAGE_ENDPOINT, token=OS_AUTH_TOKEN) >>> image = glance.images.create(name="My Test Image") >>> print image.status 'queued' >>> image.update(data=open('/tmp/myimage.iso', 'rb')) >>> print image.status 'active' >>> with open('/tmp/copyimage.iso', 'wb') as f: for chunk in image.data: f.write(chunk) >>> image.delete() Command-line Tool ================= In order to use the CLI, you must provide your OpenStack username, password, tenant, and auth endpoint. Use the corresponding configuration options (``--os-username``, ``--os-password``, ``--os-tenant-id``, and ``--os-auth-url``) or set them in environment variables:: export OS_USERNAME=user export OS_PASSWORD=pass export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b export OS_AUTH_URL=http://auth.example.com:5000/v2.0 The command line tool will attempt to reauthenticate using your provided credentials for every request. You can override this behavior by manually supplying an auth token using ``--os-image-url`` and ``--os-auth-token``. You can alternatively set these environment variables:: export OS_IMAGE_URL=http://glance.example.org:9292/ export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155 Once you've configured your authentication parameters, you can run ``glance help`` to see a complete listing of available commands. See also :doc:`/man/glance`. Release Notes ============= 0.12.0 ------ * Add command line support for V2 image create, update, and upload * Enable querying for images by tag * 1230032_, 1231524_: Fix several issues with handling redirects * 1206095_: Use openstack-images-v2.1-json-patch for update method .. _1230032: http://bugs.launchpad.net/python-glanceclient/+bug/1230032 .. _1231524: http://bugs.launchpad.net/python-glanceclient/+bug/1231524 .. _1206095: http://bugs.launchpad.net/python-glanceclient/+bug/1206095 0.11.0 ------ * 1212463_: Allow single-wildcard SSL common name matching * 1208618_: Support absolute redirects for endpoint urls * 1190606_: Properly handle integer-like image ids * Support removing properties from images in the v2 library .. _1212463: http://bugs.launchpad.net/python-glanceclient/+bug/1212463 .. _1208618: http://bugs.launchpad.net/python-glanceclient/+bug/1208618 .. _1190606: http://bugs.launchpad.net/python-glanceclient/+bug/1190606 0.10.0 ------ * 1192229_: Security Update! Fix SSL certificate CNAME checking to handle ip addresses correctly * Add an optional progress bar for image downloads * Additional v2 api functionality, including image creation and uploads * Allow v1 admin clients to list all users' images, and to list the images of specific tenants. * Add a --checksum option to the v2 CLI for selecting images by checksum * Added support for image creation and uploads to the v2 library * Added support for updating and deleting v2 image tags to the v2 library and CLI * Added support for managing image memberships to the v2 library and CLI * Added a cli man page. * 1184566_: Fix support for unix pipes when uploading images in the v1 CLI * 1157864_: Fix an issue where glanceclient would fail with eventlet. .. _1192229: http://bugs.launchpad.net/python-glanceclient/+bug/1192229 .. _1184566: http://bugs.launchpad.net/python-glanceclient/+bug/1184566 .. _1157864: http://bugs.launchpad.net/python-glanceclient/+bug/1157864 0.9.0 ----- * Implement 'visibility', 'owner' and 'member_status' filters for v2 CLI and library * Relax prettytable dependency to v0.7.X * 1118799_: Implement filter on 'is_public' attribute in v1 API * 1157905_, 1130390_: Improve handling of SIGINT (CTRL-C) .. _1118799: http://bugs.launchpad.net/python-glanceclient/+bug/1118799 .. _1157905: http://bugs.launchpad.net/python-glanceclient/+bug/1157905 .. _1130390: http://bugs.launchpad.net/python-glanceclient/+bug/1130390 0.8.0 ----- * Implement image-delete for Image API v2 * Update warlock dependency to >= 0.7.0 and < 1 * 1061150_: Support non-ASCII characters * 1102944_: The port option is configurable when using HTTPS * 1093380_: Support image names in place of IDs for CLI commands * 1094917_: Better representation of errors through CLI .. _1061150: http://bugs.launchpad.net/python-glanceclient/+bug/1061150 .. _1102944: http://bugs.launchpad.net/python-glanceclient/+bug/1102944 .. _1093380: http://bugs.launchpad.net/python-glanceclient/+bug/1093380 .. _1094917: http://bugs.launchpad.net/python-glanceclient/+bug/1094917 0.7.0 ----- * Add ``--store`` option to ``image-create`` command * Deprecate ``--ca-file`` in favor of ``--os-cacert`` * 1082957_: Add ``--sort-key`` and ``--sort-dir`` CLI options to ``image-list`` command * 1081542_: Change default ``image-list`` CLI sort to order by image name ascending * 1079692_: Verify SSL certification hostnames when using HTTPS * 1080739_: Use ``--os-region-name`` in service catalog lookup .. _1082957: http://bugs.launchpad.net/python-glanceclient/+bug/1082957 .. _1081542: http://bugs.launchpad.net/python-glanceclient/+bug/1081542 .. _1079692: http://bugs.launchpad.net/python-glanceclient/+bug/1079692 .. _1080739: http://bugs.launchpad.net/python-glanceclient/+bug/1080739 0.6.0 ----- * Multiple image ID can be passed to ``glance image-delete`` * ``glance --version`` and glanceclient.__version__ expose the current library version * Use ``--human-readable`` with ``image-list`` and ``image-show`` to display image sizes in human-friendly formats * Use OpenSSL for HTTPS connections * 1056220_: Always use 'Transfer-Encoding: chunked' when transferring image data * 1052846_: Padded endpoints enabled (e.g. glance.example.com/padding/v1) * 1050345_: ``glance image-create`` and ``glance image-update`` now work on Windows .. _1056220: http://bugs.launchpad.net/python-glanceclient/+bug/1056220 .. _1052846: http://bugs.launchpad.net/python-glanceclient/+bug/1052846 .. _1050345: http://bugs.launchpad.net/python-glanceclient/+bug/1050345 0.5.1 ----- * 1045824_: Always send Content-Length when updating image with image data * 1046607_: Handle 300 Multiple Choices nicely in the CLI * 1035931_: Properly display URI in legacy 'show' command * 1048698_: Catch proper httplib InvalidURL exception .. _1045824: http://bugs.launchpad.net/python-glanceclient/+bug/1045824 .. _1046607: http://bugs.launchpad.net/python-glanceclient/+bug/1046607 .. _1035931: http://bugs.launchpad.net/python-glanceclient/+bug/1035931 .. _1048698: http://bugs.launchpad.net/python-glanceclient/+bug/1048698 0.5.0 ----- * Add 'image-download' command to CLI * Relax dependency on warlock to anything less than v2 0.4.2 ----- * 1037233_: Fix v1 image list where limit kwarg is less than page_size .. _1037233: https://bugs.launchpad.net/python-glanceclient/+bug/1037233 0.4.1 ----- * Default to system CA cert if one is not provided while using SSL * 1036315_: Allow 'deleted' to be provided in v1 API image update * 1036299_: Fix case where boolean values were treated as strings in v1 API * 1036297_: Fix case where int values were treated as strings in v1 API .. _1036315: https://bugs.launchpad.net/python-glanceclient/+bug/1036315 .. _1036299: https://bugs.launchpad.net/python-glanceclient/+bug/1036299 .. _1036297: https://bugs.launchpad.net/python-glanceclient/+bug/1036297 0.4.0 ----- * Send client SSL certificate to server for self-identification * Properly validate server SSL certificates * Images API v2 image data download python-glanceclient-0.12.0/LICENSE0000664000175300017540000002363612241451455017720 0ustar jenkinsjenkins00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. python-glanceclient-0.12.0/ChangeLog0000664000175300017540000037316112241451524020463 0ustar jenkinsjenkins00000000000000commit c22b88d9b1a7542d4ace31b792d3a24613e8d0f1 Merge: 1076464 7dfd205 Author: Jenkins Date: Fri Nov 15 09:14:34 2013 +0000 Merge "Add release notes for 0.12.0" commit 10764641f8e14795fb900d32ccbed79588e20028 Merge: a7c33d5 fb31223 Author: Jenkins Date: Fri Nov 15 08:47:38 2013 +0000 Merge "Fix Pep8 errors found by Pep8 1.4.6" commit 7dfd205eb27e8b676d2e9ef240ee4eda45dd930f Author: Mark J. Washenberger Date: Thu Nov 14 16:52:36 2013 -0800 Add release notes for 0.12.0 Change-Id: I7642635ba0326d97ca44f08ca54c1d613f1925f1 commit a7c33d58028bf31541fe6a024dbf77f6de9a7d5c Merge: 9a649d4 7f8292f Author: Jenkins Date: Fri Nov 15 06:33:29 2013 +0000 Merge "change assertEquals to assertEqual" commit 9a649d46ead921d6792e5c4e04bd3e53fb8b4746 Merge: 861bc83 2517203 Author: Jenkins Date: Fri Nov 15 06:33:28 2013 +0000 Merge "Replace OpenStack LLC with OpenStack Foundation" commit 861bc832cb64fc50c759a8de107294daf9412e84 Merge: 12a1d71 b331cb6 Author: Jenkins Date: Tue Nov 12 04:47:31 2013 +0000 Merge "Added support for running the tests under PyPy with tox" commit 12a1d719b2938ed7c6e4e17a4ae19251671e9fd3 Merge: 518cb25 69fa91b Author: Jenkins Date: Tue Nov 12 04:38:33 2013 +0000 Merge "Fix python 3.x related Hacking warnings" commit 7f8292f39da0221c0c5dbe1f31190f552d6c14d6 Author: Christian Berendt Date: Thu Oct 24 07:54:16 2013 +0200 change assertEquals to assertEqual According to http://docs.python.org/2/library/unittest.html assertEquals is a deprecated alias of assertEqual. Change-Id: I22f4702f41537c05684dfacb3f305627b36849c5 commit fb31223291d30cb2c5f2150233707f077cf6de26 Author: Dirk Mueller Date: Fri Oct 18 11:32:25 2013 +0200 Fix Pep8 errors found by Pep8 1.4.6 These missed E128 warnings were missed by 1.4.5, adjust the indentation. Change-Id: I7b459113a6f5143012acf959639b725238ca8a11 commit 518cb2508d6557f1e8f1c8c480720e46fef4bae9 Author: Chuck Short Date: Tue Oct 15 14:47:30 2013 -0400 python3: use six.moves for httplib imports This adds six to the requirements.txt file in order to make some HTTP-related imports work across Python 2's httplib and Python 3's http.client modules. Tests were updated, including one change to the location of HTTPConnection - moving it from being accessed where it was imported rather than its canonical location inside of six.moves.http_client. Change-Id: Ibc4932b37dfdf195cd5091066914513af1876955 Signed-off-by: Chuck Short commit cd11833cffa306516704e871fad23699e21339f3 Merge: 5ec5e7b a0715e9 Author: Jenkins Date: Wed Oct 9 17:46:01 2013 +0000 Merge "Fix regression bug after removing posixpath in http.py" commit 5ec5e7baec4deb5ee427e1ee8402d46f810cdde7 Merge: ad12bcf 50266ee Author: Jenkins Date: Wed Oct 9 17:36:54 2013 +0000 Merge "Fix getting header in redirect processing" commit ad12bcf23dd22d70b7d34335fadd18b94230754a Merge: 7bf2725 4718635 Author: Jenkins Date: Wed Oct 9 05:06:39 2013 +0000 Merge "Fix default value for a header" commit 7bf27252e57779252fd0ab1cf7f0062f9c27995b Author: Chang Bo Guo Date: Tue Oct 8 19:04:08 2013 -0700 Fix misused assertTrue in unit tests Refactored unit tests to use assertEqual instead of assertTrue where needed. Closes-Bug: #1226374 Change-Id: I5f3b582e19f3461a04b5df05960095779ec8aa1a commit 32d9c42816b608220ae5692e573142dab6534604 Author: eddie-sheffield Date: Mon Aug 19 17:27:07 2013 -0400 Add CLI for V2 image create, update, and upload Provides command line support for image-create, image-update, and image-upload using the Glance V2 API. This includes building help text for create and update based on the image jsonschema as fetched from the server. Also fixes bug caused by default warlock patch generation not matching what Glance expects when updating a core property which had not originally been set when the image was created. Related to bp glance-client-v2 Change-Id: I841f9e3d05802f4b794cb6f4849abe03ff0324d9 commit a0715e966dbdeda18f6c80b2ebf1a8997e0fbb0f Author: Yang Yu Date: Wed Sep 25 10:39:36 2013 -0500 Fix regression bug after removing posixpath in http.py After removing posixpath.normpath(url) in http.py, the code has a regression bug that the url like 'http://example.com:80/test' can not work. The code urlparse.urljoin() can not work as '%s%s' % (self.endpoint_path, url). Fixes bug #1230032 Change-Id: Ie7266fc3a067b92dfeed169086b4bf6a87dedbd6 commit 50266eec2b86aaa0a7717adf004576d444f4fce9 Author: Russell Bryant Date: Thu Sep 26 11:43:29 2013 -0400 Fix getting header in redirect processing The code for processing an HTTP redirect included an incorrect method of getting the Location header from an HTTPResponse. It needs to use the getheader() method on the response, instead. This patch fixes that and includes a unit test that covers this code path. Change-Id: I0952fabad581b020dee07bdc4007b55b47c906aa Closes-Bug: #1231524 commit 471863581114a5cabc50392f17033ed04a79f37d Author: Russell Bryant Date: Thu Sep 26 11:42:16 2013 -0400 Fix default value for a header This code specified None as the default value, but it would raise an exception if that was ever returned. An empty string causes the code to work as intended. This came up while I was writing a unit test for another bug fix. Change-Id: I658bb8a9b5124f281988fb60b645182ea0ccf99f Related-bug: #1231524 commit 7a4a8a0979fd76203f0cb81622a7f06ee42bb615 Merge: 360a29a 061da46 Author: Jenkins Date: Wed Sep 25 20:01:58 2013 +0000 Merge "Support glance client can get ipv6 image url correctly" commit 25172039759ab1013ccf00a2c1bec93db76a23ff Author: ZhiQiang Fan Date: Fri Sep 20 04:05:51 2013 +0800 Replace OpenStack LLC with OpenStack Foundation Change-Id: I38dcbcf1a6c8efe540fcf5f29e782cb3826e583d Fixes-Bug: #1214176 commit 360a29a763ba89323fb2d501ac1edd77185f418f Merge: 897ae3d 3307549 Author: Jenkins Date: Thu Sep 19 17:55:45 2013 +0000 Merge "Use openstack-images-v2.1-json-patch for update method" commit 061da46202759f55866d224f9ab8013e747637c7 Author: Dazhao Date: Wed Aug 21 12:54:19 2013 +0800 Support glance client can get ipv6 image url correctly This patch is for fix bug 1208784. In openstack ipv6 environment, if the os image url is not provided, need use the provided host to generate literal ipv6 image url. Fixes bug 1208784 Change-Id: Icb71241a639db02d079348f086bd7bd5f0412609 commit b331cb63a4b9636c4ee992684bd56cb2c9d3ca4a Author: Alex Gaynor Date: Thu Sep 5 09:57:31 2013 -0700 Added support for running the tests under PyPy with tox This is a precursor to having them run under check and gate. Change-Id: I9c2f8218f457aeaa03e06b4a87554fcdc40e4a0a commit 897ae3d795423a0f1d92f0738ad4dff99c28c6de Author: Fei Long Wang Date: Sun Aug 11 11:02:32 2013 +0800 Enable query image by tag This patch will enable Glance client to query images by user defined tags. Implement bp image-query-by-tag Implement bp glance-client-v2 Change-Id: I6f54630c5b7c9c567d85485ad4289284e5486814 commit 69fa91b4322395b125b8e50821ad88d0e0d2a313 Author: Dirk Mueller Date: Sun Aug 4 16:12:23 2013 +0200 Fix python 3.x related Hacking warnings Convert print operator usages to print functions. Fix one instance of outdated "except x,y:" syntactical construct. Remove usages of local() in string formatting alongway. Change-Id: Id0673a9183a6ea6bd9bf3f5c6d8e7c5f114ebf01 commit b6e117f151e6d3ed8dc4849c4138d7fb28652e75 Author: Gabe Westmaas Date: Sun Aug 25 01:14:07 2013 +0000 Fix glanceclient usage inconsistences for options Enumerated options should have the same format for all enumerated options. This commit moves all options to the {option1,option2} format. fixes bug: #1155171 Change-Id: I8e0ecf3896c76021cb027cbbbb3b5564a04aacec commit b15c57c4a2cd7566072fcf8adb1422cb3e21b1de Author: Mark J. Washenberger Date: Thu Aug 22 16:01:29 2013 -0700 Add 0.11.0 doc notes Change-Id: Ic76e872e78ec77b731ad9dced833803da3fa9aca commit 3307549a0f66294972c8b5ebff59ab01d18a1d91 Author: Ghe Rivero Date: Mon Jul 29 11:41:10 2013 +0000 Use openstack-images-v2.1-json-patch for update method image.patch returns a JSON schema Draft 10 (application/openstack-images-v2.1-json-patch) while the glaceclient update method specify a Content-Type header application/openstack-images-v2.0-json-patch with correspond to a JSON schemea Draft 4. Fixes bug 1206095 Change-Id: I8c5a96f0e117a81b5b527a96ef45758fc69b518d commit 835638fd8ad4fb39be2161ee79057565809082ab Merge: fe9a62b a30981e Author: Jenkins Date: Thu Aug 22 21:12:47 2013 +0000 Merge "Don't use posixpath for URLs" commit fe9a62b5b5681f3f8d467b24b8aca7ab646d1366 Merge: 53d3a0e 683e40f Author: Jenkins Date: Thu Aug 22 21:12:36 2013 +0000 Merge "Allow single-wildcard SSL common name matching" commit 683e40fd31d791683e272555485b9eef1400752a Author: Brian Waldon Date: Wed Aug 14 15:37:45 2013 -0700 Allow single-wildcard SSL common name matching Fix bug 1212463 Change-Id: I168601fd9847497c2261c77ce6c856bca187c6c8 commit 53d3a0e129d085b3cb21e8827b321132f3b5f48e Author: Dean Troyer Date: Mon Aug 19 19:04:17 2013 +0000 Revert "removed deprecated parameter --public" While trunk devstack was updated for this, stable/folsom and stable/grizzly are not and grenade is now broken. I'm not sure how long --public was undocumented but it may be that certain cli args may need to remain deprecated but supported for longer than we wish. This reverts commit ce8636b6b3c4b606483cfb2bc405d86224f309bc Change-Id: I91d4884e470c8fcc611dae62a30fa22d08dbec03 commit 20bd548d71097ca96d4e9f841ff68547e477323d Merge: 7e4ba22 ce8636b Author: Jenkins Date: Mon Aug 19 15:34:56 2013 +0000 Merge "removed deprecated parameter --public" commit 7e4ba229c3d89392b52eb9dfb260c745cdb821ae Author: eddie-sheffield Date: Tue Aug 6 16:56:59 2013 -0400 \Allow removal of properties using glance v2 api This adds support to the glance client library for removing properties from an image. Properties to be removed must be explicitly listed. There is no equivalent of the 'purge_props' functionality in v1. Also beefed up testing around create since some of the functionality is the same as update. Fixes bug 1206472 Change-Id: I16f4c39cdfc8a0cd07bede600461b7a289fbb080 commit 3de64660a90f82bc146a14afda0c444d29f0b295 Merge: 14a9d27 ec1dc92 Author: Jenkins Date: Mon Aug 12 22:15:33 2013 +0000 Merge "Raise warlock requirement" commit 14a9d27796f4b23536d2aa1a37a41ac3bc942056 Merge: 0c2d2a9 1f2eef5 Author: Jenkins Date: Mon Aug 12 17:14:42 2013 +0000 Merge "Updated from global requirements" commit 1f2eef510f61ef90be4868c0e67c1e0861613b7d Author: Monty Taylor Date: Mon Aug 5 18:03:37 2013 -0300 Updated from global requirements Change-Id: I2e2bd3a38458e1307bcc0410da74dc76c0a5987a commit 0c2d2a982d322957116d9d1fae45e6d5efed6152 Merge: eb47b55 f629692 Author: Jenkins Date: Fri Aug 9 20:34:10 2013 +0000 Merge "Cast image_id to string before calling urllib.quote" commit eb47b55dbd078aea4eedcc70ad44d189510c6644 Author: Mark J. Washenberger Date: Thu Aug 8 22:31:40 2013 -0700 Revert 02116565d358a4fa254217779fef82b14b38d8ca A patch slipped in that modified the default image list limit in a backwards-incompatible way. This change reverts that patch, but preserves some of the formatting improvements. Change-Id: I17ae5024896ca7b1064be66b9e47653e953771d6 commit 5cdbcdf3fc20c9136739f0dfac2db63d670e2303 Author: Mark J. Washenberger Date: Thu Aug 8 19:17:51 2013 -0700 Add 0.10.0 docs update Change-Id: Ib2e75abc7d696f89d97918738bfc960cf3d1da5b commit 1d7da740b22945c43a9f36affca3c1b3fbad91fa Author: mouad benchchaoui Date: Mon Jul 8 21:18:16 2013 +0200 Show a pretty progressbar when uploading and downloading an image. Add a new module that contain generic wrapper for file and iterator, which are used to wrap image to upload and the request body iterator in upload and download cases repectively, to show and advance a pretty progress bar when this laters are consumed, The progress bar is triggered by adding a --progress command line argument to commands: image-create, image-download or image-update. Change-Id: I2ba42fd0c58f4fa087adb568ec3f08246cae3759 bug fix: LP#1112309 blueprint: progressbar-when-uploading commit ec1dc92a2a4a96115a914a72517212752e237373 Author: Zhi Yan Liu Date: Thu Aug 8 12:22:41 2013 +0800 Raise warlock requirement Glance PATCH API need use new 'require' condition for schema to validate income requests. Raise warlock requirement to >=1.0.1,<2 as older warlock requires a too old jsonschema to be useful. See bug #1202391 for details. Change-Id: I0ac148850a5b1254bf0827c7de2db1464a6703d8 Signed-off-by: Zhi Yan Liu commit f629692917b0411850f709998beedf7dcc36c4a1 Author: Justin Santa Barbara Date: Thu Jul 18 18:38:34 2013 -0700 Cast image_id to string before calling urllib.quote We can't pass a string in; see Bug #1178233 Change-Id: I040c667e122d792fdcb47ee172463339068af48c commit a30981e7f03317b1be06c05ff68833ffe3719fe0 Author: Justin Santa Barbara Date: Mon Aug 5 14:42:19 2013 -0700 Don't use posixpath for URLs Use URL functions instead. Fixes bug #1208618 Change-Id: I27bb29a6422200a1a522c50335e5d93d495ec429 commit 43e71e399372102f8ef4a3b7ad836fe16ace63a3 Merge: 5c4fb2f e827c37 Author: Jenkins Date: Wed Jul 31 19:10:12 2013 +0000 Merge "Changes to allow image upload with V2 api" commit e827c37a05687d4fdd9b81a4b241e199a790ef1a Author: eddie-sheffield Date: Wed Jul 31 12:21:41 2013 -0400 Changes to allow image upload with V2 api Related to bp glance-client-v2 Change-Id: I72a1d2825dd5c1ff4890e8be477cb4447d59f136 commit 5c4fb2f7a1a8943018dcad21ae6f8a0ed310d4cc Merge: 937b65b 060a3fe Author: Jenkins Date: Wed Jul 31 15:22:04 2013 +0000 Merge "Fix test assertions & test cases for V2 Shell Unit test" commit 937b65b513ae53193c0c731a6d8e89ec310f1882 Merge: 2d86c5d 92e4ee2 Author: Jenkins Date: Tue Jul 30 22:08:19 2013 +0000 Merge "Enable client library V2 to create an image." commit 2d86c5dcdefc133dde59ac9b1ffa3b8c88e29d74 Merge: e0e6030 d1aded8 Author: Jenkins Date: Tue Jul 30 20:42:08 2013 +0000 Merge "Provide glance CLI man page." commit e0e6030a09cd7589875bf681a8b621b25f8885b2 Merge: e34dc38 bb47812 Author: Jenkins Date: Tue Jul 30 17:31:08 2013 +0000 Merge "Allow v1 client to list all users' images" commit e34dc38a703334edb88e18a0b838df159a79c10a Merge: fd0e117 12feedb Author: Jenkins Date: Tue Jul 30 17:31:06 2013 +0000 Merge "Add v1 client side owner based filtering" commit ce8636b6b3c4b606483cfb2bc405d86224f309bc Author: Christian Berendt Date: Tue Jul 30 18:26:58 2013 +0200 removed deprecated parameter --public As noted by bcwaldon the parameter can be removed after updating Devstack. Should be done after merging the following change: https://review.openstack.org/#/c/39323/ Change-Id: I8d0f7ab4cccccf022446374a31e03ac913cfb136 commit fd0e1175795ac3a5497dcf72ed250077abaf2ed1 Author: Flaper Fesp Date: Thu Jul 11 15:28:49 2013 +0200 Encode error messages before sending them to stdout When an error with non-ascii characters is caught by glanceclient, it fails at printing it and exists with a UnicodeEncodedError. This patch encodes errors' messages using strutils before sending them to stdout. Fixes bug: #1200206 Change-Id: I4dabcd76ffb258840bd6a66ad23c030f34960e86 commit bb47812765414df87eb0a1481ba65127de7b3000 Author: Stuart McLaren Date: Tue Jul 16 12:44:25 2013 +0000 Allow v1 client to list all users' images Add a '--all-tenants' option to the image-list command. This adds 'is_public=None' to the query string passed to the server, and for an admin user results in a listing of all images present on the server irrespective of owner or public values. Addresses bug 1201787. Change-Id: I38dd0752a31ebea84f16b786d205e82eba1a96bc commit 12feedb2cfe087f7126f250850f35ebb1cf3de7a Author: Stuart McLaren Date: Tue Jul 16 10:58:57 2013 +0000 Add v1 client side owner based filtering Add the --owner option to the v1 client's image-list command to support filtering images based on the owner (tenant id). Allows administrators to more easily list a particular user's images. Note that this is far less efficient than v2 server-side owner based filtering. Addresses bug 1201765. Change-Id: I4ffa522b96c91e659c87f5452f2f1f44e47e806b commit 92e4ee201a5b5f7a9cd210bf34f59e2efaca134b Author: eddie-sheffield Date: Tue Jul 2 16:34:00 2013 -0400 Enable client library V2 to create an image. Adds support for creating an image to the client library only, not the CLI. Replaced reference to deprecated BaseException.message Related to bp glance-client-v2 Change-Id: I8e3d09d89493368d22f7b1f69f79ebd2518e289d commit d1aded801f427c50509a2d0e8dd1b1fd3e7fccb9 Author: Jakub Ruzicka Date: Thu Jun 20 22:15:15 2013 +0200 Provide glance CLI man page. Provide basic but hopefully useful man page. Also update OpenStack LCC to OpenStack Foundation in docs. Resolves: bug 1193111 Implements blueprint: clients-man-pages Change-Id: I9502b3d5ca75149d747d523934a6401e435924b7 commit 060a3fe2c0be2ff6be1f9ba81474d628dee94b95 Author: Venkatesh Sampath Date: Fri Jun 28 16:48:27 2013 +0530 Fix test assertions & test cases for V2 Shell Unit test Fixes bug 1195674 Change-Id: I46c10040ea10415498dd1eed69e45139cfa37eb4 commit 8d7411d78b87020a7c5765dc294118f24449db0c Merge: 49a1207 5d90740 Author: Jenkins Date: Wed Jul 24 22:11:08 2013 +0000 Merge "HTTPS response issues" commit 49a120726690bb434d57e9c39fcf0aad3af8ed9e Merge: 093064c 822cd64 Author: Jenkins Date: Mon Jul 22 21:50:07 2013 +0000 Merge "Fix SSL certificate CNAME checking" commit 5d90740f33d80db5559becc0cc619d965a4bb292 Author: David Peraza Date: Thu Jun 20 20:53:08 2013 +0000 HTTPS response issues Fixes bug 1179392 glanceclient.common.http.HTTPClient.get_connection_kwargs method explicitly sets the timeout to be a float, while struc.pack is still using LL as the format string which means 2 Long Integers. Setting format string to fL which mean a float followed by Long Integer. Also, Bad file descriptor error is caused by socket being closed to soon. Added a close() implementation to VerifiedHTTPSConnection which will remove reference to socket before returning call to base HTTPConnection.close(). This will avoid socket to be closed before response body is read. Socket will close when response close is called. Change-Id: I3a973da3b962c7572ae0f61f6996bdd1f0048339 commit 093064caa8335e5190170bf6b1d6af60841485cd Merge: 46ce865 0211656 Author: Jenkins Date: Fri Jul 19 09:45:42 2013 +0000 Merge "Increase default page_size value" commit 46ce8655066508fb8f701cb2f2e9642c879a34f1 Merge: eb6ed51 95810ef Author: Jenkins Date: Thu Jul 18 17:45:33 2013 +0000 Merge "Pass all identity headers received to glance" commit eb6ed51647ca2a4f6dfcf52fc603f2c5d4aa0e9e Merge: 29270bb 09b29aa Author: Jenkins Date: Wed Jul 17 19:00:48 2013 +0000 Merge "Expose checksum index image property in client" commit 02116565d358a4fa254217779fef82b14b38d8ca Author: Jared Culp Date: Thu Jul 11 14:04:15 2013 -0400 Increase default page_size value This is a temporary solution. Increasing the default page size (which is being used everytime, since the client is ignoring nova's --limit param). This should decrease the number of queries that glance does when nova requests an image detail list. Bug 1200257 Change-Id: I7ed4521698570cbae9c20e69ddca8b11b57c65ad commit 95810ef1d2184b33902a56dfe7d14c12cb0869ef Author: iccha.sethi Date: Fri Jul 12 20:23:54 2013 +0000 Pass all identity headers received to glance There is an upcoming patch in nova which passes identity headers to glance client. We want to ensure that these get passed to glance, which in turn with help the no auth option in glance. Resolves bug 1200761 Change-Id: Ifbef582aa4e64a2e7a46db43a9cc6cf8c3531dbd commit 822cd64c0718b46a065abbb8709f6b466d12e708 Author: Thomas Leaman Date: Tue Jun 18 15:34:45 2013 +0000 Fix SSL certificate CNAME checking Currently, accessing a host via ip address will pass SSL verification; the CNAME is not checked as intended as part of verify_callback. 'preverify_ok is True' will always return false (int/bool comparison). preverify_ok will be 1 if preverification has passed. Fixes bug 1192229 Change-Id: Ib651548ab4289295a9b92ee039b2aff2d08aba5f commit 29270bb853d1fa5e05139197c77fb13f3898ead4 Author: Sean Dague Date: Fri Jul 12 17:13:15 2013 -0400 uncap python-keystoneclient version requirement if any of the projects specify a capped client, it has the potential for preventing that client from being tested in the gate. To fix this we have to uncap maximum versions of all openstack client code in all openstack projects. Fixes bug #1200214 Change-Id: I664d2f030972a3bbb08ae1c4b1710816b54b44b2 commit 09b29aac12b2988db41834798f509b8a54500ab6 Author: amalaba Date: Tue Jul 2 19:13:24 2013 +0530 Expose checksum index image property in client Implement checksum image index property in the python-glanceclient Change-Id: If1426b7938457014ef27a86d3902d53854161627 Implements: blueprint index-using-checksum-image-property commit 842720801550ff335122b2f2e4837a18aed25081 Merge: aa2ff9a bd0880c Author: Jenkins Date: Mon Jul 8 16:16:21 2013 +0000 Merge "Rename invalid domain name to be RFC compliant." commit aa2ff9a2b76e537b74f488a693e3569e2a713fb6 Author: Alex Meade Date: Fri Jul 5 17:05:49 2013 -0400 Flake8 should ignore build folder This adds 'build' to the exclude list for flake8 Fixes bug 1198329 Change-Id: Ia662f93db6c86c0aedfbc44632a56cd8667df469 commit b9c1df8dfc6e6520b1a9ba407b4321bd199b134c Author: Venkatesh Sampath Date: Tue Jun 25 17:58:42 2013 +0530 Enable client V2 to update/delete tags for a given image. Added the CLI option image-tag-update to associate a tag to an image via API V2. Added the CLI option image-tag-delete to delete a tag associated with an image via API V2. Related to bp glance-client-v2 Change-Id: I76060e1982223770a6c2c0bd9376d568af0df456 commit bd0880cc940a3dde2ac9b09d67591982a548d466 Author: Monty Taylor Date: Sun Jun 30 23:19:32 2013 -0400 Rename invalid domain name to be RFC compliant. http://tools.ietf.org/html/rfc6761 and http://tools.ietf.org/html/rfc2606 define invalid domain names to be used in contexts such as this. It's good to be compliant with RFCs. Change-Id: Ibb7f9ee12c0c4331f8a33470def74c3a136ef6d9 commit 62579fbb217f4c1a4668e793ebaafaf668619206 Author: Dirk Mueller Date: Sun Jun 9 11:07:27 2013 +0200 Start using Pyflakes and Hacking Instead of globally ignoring pyflakes and hacking warnings, only blacklist those that trigger very frequently so far, in order to clean them up in followup commits. Fix and start gating on the rest already. Change-Id: Ied7c7250061e3bf379e8286e8ce3b9e4af817faf commit d8a537c7fe9b1e455831d05d9ba0ab67e03fb3a5 Author: Kevin McDonald Date: Tue May 14 10:31:45 2013 -0500 Removes extra slash on endpoints without a path Change-Id: I5ce54117c5ac276fb9629c71eb16db88e078c947 Fixes: bug #1179984 commit c120aff87fa3d7ee8a53ec7a655389d069d88187 Merge: 2c84b58 9fda0dc Author: Jenkins Date: Tue Jun 18 12:47:31 2013 +0000 Merge "Fix problem where image data is not read from a pipe." commit 2c84b58a3d87f2dedbd53c66fa98fb1011dad6d8 Author: Monty Taylor Date: Tue Jun 11 11:29:06 2013 -0700 Remove explicit distribute depend. Causes issues with the recent re-merge with setuptools. Advice from upstream is to stop doing explicit depends. Change-Id: I75916a4fe2f7cf2eb8db2187c03895438aa58efd commit a7bc8fe9f6860e8573f02482372cabbae993e492 Merge: 34d6c2f b586957 Author: Jenkins Date: Tue Jun 11 00:27:17 2013 +0000 Merge "python3: Introduce py33 to tox.ini" commit 34d6c2f4dd2af15218d49cb6acc5d0a642a310fe Merge: 554f938 7818387 Author: Jenkins Date: Thu Jun 6 07:28:08 2013 +0000 Merge "Replace utils.ensure_(str|unicode) with strutils.safe(decode|encode)" commit 554f938dce70af481d382b2fc1b17455d44c099d Merge: e796c1b 7daa976 Author: Jenkins Date: Wed Jun 5 23:09:07 2013 +0000 Merge "Do not decode headers in v1/images.py" commit e796c1be9eb0d766c21fee93532660fc6d07420e Merge: 03f1286 81e8834 Author: Jenkins Date: Wed Jun 5 04:23:49 2013 +0000 Merge "Add tests for encodings" commit 03f12869409fc734c85cebcb67932fc2c2c495f8 Merge: 7d48783 40460cb Author: Jenkins Date: Mon Jun 3 23:32:41 2013 +0000 Merge "Rename requires files to standard names." commit 7818387d4ac4c4a20899fc4470da86c5bcabf183 Author: Flaper Fesp Date: Wed May 22 11:31:25 2013 +0200 Replace utils.ensure_(str|unicode) with strutils.safe(decode|encode) Glanceclient implemented both functions before they landed into oslo. Since both functions are already in oslo, it is now possible to pull them in. There's a small difference between glance's implementation and oslo's, that is the later does not convert non-str objects - int, bool - to str before trying to decode / encode them. This patch takes care of that where necessary, more precisely, while encoding headers before doing a new request. Fixes bug: #1172253 Change-Id: I9a0dca31140bae28d8ec6aede515c5bb852b701b commit 7daa976d14102d3e2aca06d57d82f6aa92c84da2 Author: Flaper Fesp Date: Mon Jun 3 16:46:49 2013 +0200 Do not decode headers in v1/images.py v1.images._image_meta_to_headers currently encodes headers as a way to ensure they're an instance of basestring. This is not necessary since headers will be encoded later during the request. Also, all data within the client should be already decoded. Fixes bug: #1187013 Change-Id: I80525adbc6e9e576cfad5b576090ef9ee574c1cf commit 9fda0dc815f042272c7c2b8ae18424cf1b6505e8 Author: Hugh Saunders Date: Mon Jun 3 15:24:51 2013 +0100 Fix problem where image data is not read from a pipe. For image-updae and image-create commands, glanceclient attempts to determine whether image data should be uploaded based on the presence of data on stdin. Unforunately it is difficult to determine if data is available, especially when standard in is from a pipe. This is especially problematic for update operations, where data must only be uploaded if the image is in queued state. For example data may be uploaded when the user only wants to rename an image, but the rename will be rejected because data cannot be uploaded to an unqueued image. This patch removes the check that attempts to determine if data is available to read as it didn't work for pipes. It also re-introduces a check for image state in the update operation, so that glanceclient only attempts to read data if the image being updated is in queued state. The image state check is part of the original patchset that was removed so the patchset could have a single focus [1] This patch also removes a test for handling empty stdin, and adds a test for reading stdin from a pipe. [1] https://review.openstack.org/#/c/27536/3/glanceclient/v1/shell.py Fixes: bug 1184566 Related to: bug 1173044 Change-Id: I8d37f6412a0bf9ca21cbd75cde6a4d5a174e5545 commit 81e88344fbd20b792e03eb1002fa5d68e5af212b Author: Flaper Fesp Date: Mon Jun 3 16:36:07 2013 +0200 Add tests for encodings The patch adds tests for headers encodings, incoming data decoding, outgoing meta encoding and filters encoding. Fixes bug: #1187013 Change-Id: I7e59bf6c44b9d188bd3faf32fc09f309f3ad67d3 commit b586957e1cc40bf401e5efb807fc58248eb66139 Author: Chuck Short Date: Sat Jun 1 19:57:29 2013 -0500 python3: Introduce py33 to tox.ini Introduce py33 to tox.ini to make testing with python3 easier. Change-Id: I11801a5b3fd736d8e647a8a7152e498f505108f6 Signed-off-by: Chuck Short commit 7d487834344e812c812dab0b14b9a51b5816554b Merge: a3585ef 9e51524 Author: Jenkins Date: Fri May 31 08:05:25 2013 +0000 Merge "Update importutils and openstack-common.conf format" commit 40460cbeb1f0b08b82fdf405b3341eebf5612cb0 Author: Zhenguo Niu Date: Wed May 29 17:23:30 2013 +0800 Rename requires files to standard names. Rename tools/pip-requires to requirements.txt and tools/test-requires to test-requirements.txt. These are standard files, and tools in the general world are growing intelligence about them. Change-Id: Ic220b54de5ce7c15f442b8ffcb97cd03c2344f9a Fixes: bug #1179008 commit a3585ef62dbaa2c67f5b400f41e02cc686be5e7c Author: Hugh Saunders Date: Fri Apr 26 08:26:11 2013 +0100 Don't attempt to read stdin if it is empty. * Check for available data size in v1/shell.py/_set_data_field, don't read if 0. * Add test_shell.py including tests for 3x stdin scenarios: * closed * open and empty * open with data Change-Id: I6ff65b0e226be509de9cd3f021560081529283b0 Fixes: bug #1173044 commit 9e515245a30ea14f343ab54444f51dbf2d75d8a2 Author: Flaper Fesp Date: Wed May 22 11:00:00 2013 +0200 Update importutils and openstack-common.conf format THe patch updates the importutils module and uses a per-line module specification instead of the old modules= options. Change-Id: Ieb28780bb9034fd61942305ff1eec21b3637027a commit 2a56a32edb9ce2ee8df8e467e3c938224accaf6d Merge: 52418e5 4f9d4d0 Author: Jenkins Date: Wed May 22 23:40:31 2013 +0000 Merge "Migrate to pbr." commit 52418e5dd1b973df0550d659f97e9cdf7e3b5af6 Merge: 985c06b ca17541 Author: Jenkins Date: Wed May 22 23:40:28 2013 +0000 Merge "Migrate to flake8." commit 985c06bea146d12665bc999d80228a15a0f5ede4 Merge: 8c70c5b ab36778 Author: Jenkins Date: Wed May 22 23:40:25 2013 +0000 Merge "Improve unit tests for python-glanceclient.glanceclient.common.base" commit 8c70c5b08d6ecae90c2df1ee5c7ba995e3329926 Author: Zhi Yan Liu Date: Wed May 15 22:45:08 2013 +0800 Convert non-ascii characters within image property to unicode Convert non-ascii characters within image property (key/value pair) to unicode but utf-8 to prevent provisioning failure when cloud using qpid backend. This change also make the image property encoding consistency between the image updating and the receiving. Before this, image property updating use unicode, but receiving (get) result is utf-8. Fixes: Bug #1180377 Change-Id: I010760c598a7e008c79f1240255708265352cdb5 Signed-off-by: Zhi Yan Liu commit 4f9d4d051ac4bed9f4bd4f431491af4a75dd308b Author: Monty Taylor Date: Sat May 18 09:02:07 2013 -0700 Migrate to pbr. Fixes bug 1179007. Change-Id: I99d571bbf37ef53366a96de088c249cb6fd23b0e commit ca17541f33b4c65af7fe43e4de856334a8a866cd Author: Monty Taylor Date: Sat May 18 08:29:02 2013 -0700 Migrate to flake8. Fixes bug 1172444. Change-Id: Icec0820d0f780ed9473218b7e46e29c1e3db6541 commit 8451a9482202fee5a4302bb3d7c62cff55365b0c Author: Tatyana Leontovich Date: Thu Feb 28 18:53:58 2013 +0200 Add test for glanceclient shells Add unittests for the following modules: * glanceclient.shell * glanceclient.v1.shell * glanceclient.v1.legacy_shell * glanceclient.v2.shell Also add mock library to the tools/test-requires Implements: blueprint glanceclient-shells-unittests Change-Id: I5ec527c5efff3726932d234d7c67e08149643f89 commit ab36778ec3c8ed69ce798816161ee35a368e2dc2 Author: Tatyana Leontovich Date: Mon Mar 4 13:15:06 2013 +0200 Improve unit tests for python-glanceclient.glanceclient.common.base Add several tests for glanceclient.common.base module Fixes: bug #1144158 Change-Id: Ifc288075c79849ee1384f09f513874ee08cd0248 commit de5f2eea025a5ba926bff376f85ce4ad7ca364dd Merge: 03a49e2 18795f5 Author: Jenkins Date: Fri May 10 17:07:43 2013 +0000 Merge "Image Members for glance v2 api" commit 03a49e2da98dbe0194e645a5466978a75454611b Merge: a3223b9 e750e9b Author: Jenkins Date: Thu May 9 21:19:38 2013 +0000 Merge "Expand HACKING with commit message guidelines" commit 18795f590ae3d63bb6c796ec9d69a2ceac61606e Author: iccha-sethi Date: Sat Mar 2 03:12:04 2013 +0000 Image Members for glance v2 api Lists, creates, deletes, updates image members using glance v2 api. Related to bp glance-api-v2-image-members Change-Id: Ic018a265a1676bb0a5638a55e70a527ce6b447fc commit a3223b99724eb04701b8d1d87d6f8d03bd581521 Author: Andy McCrae Date: Thu Apr 25 13:59:54 2013 +0100 Fix inconsistent --debug messages on image-update image-update --debug message no longer adds '-d "None"' to the curl command that is output. This keeps the curl output consistent with the client request. Fixes bug #1172702 Change-Id: I34ceeb6f4a67c753ca3a805ec11240a99ce38ec4 commit e750e9b0ed7de080f72060844e0cbc2911aa682a Author: Alex Meade Date: Fri Apr 26 15:25:17 2013 -0400 Expand HACKING with commit message guidelines Add a 'Commit Messages' section to HACKING describing how a commit message should be formed. Change-Id: Ife0d6eab18cc0f07ad8e8c79cb2e4198513236d2 commit addd3c3be383d9db363415096d5dcc4fe384af97 Merge: 4bbc7b8 2f33f5f Author: Jenkins Date: Wed Apr 24 19:07:03 2013 +0000 Merge "Prevent WantReadError when using https" commit 4bbc7b8a9b1ce63c052c3dedaf9726916bc37b91 Merge: 42757e8 45feb67 Author: Jenkins Date: Wed Apr 24 16:38:25 2013 +0000 Merge "Improve Python 3.x compatibility" commit 2f33f5f283d921ce1db595a89c36eefb8b8fcd29 Author: Stuart McLaren Date: Wed Mar 20 18:00:39 2013 +0000 Prevent WantReadError when using https If the glance client is instantiated when the socket module has been monkey patched requests to the server can yield a WantReadError because the socket has been set to non-blocking but this is not being handled correctly. When this is the case use eventlet's GreenConnection which handles non-blocking sockets correctly. Also, for now, add a required getsockopt method to GreenConnection. This can be removed once the eventlet fix (https://bitbucket.org/eventlet/eventlet/commits/609f230) lands. Fixes bug 1157864. Change-Id: I187b69f75b8bcfe16facd41e69b1cd0490dae605 commit 42757e8a3cea2cdad3f44c6901ed50586455d81e Merge: 1b5c323 2178120 Author: Jenkins Date: Mon Apr 22 23:33:45 2013 +0000 Merge "Test that copy_from is used properly in old API" commit 1b5c3231ec0de60f90f73e47147d9771593b11f4 Merge: e22d37d 5ab8837 Author: Jenkins Date: Mon Apr 22 20:11:10 2013 +0000 Merge "bug 1166263 image-update handling for closed stdin" commit 45feb672af7cab42898fc0f249c6fbe5a4a36e12 Author: Dirk Mueller Date: Mon Apr 22 16:38:55 2013 +0200 Improve Python 3.x compatibility Some mechanical translation of the deprecated except x,y construct. Should work with Python >= 2.6 just fine Change-Id: I394f9956b9e3e3d9f5f1e9ad50c35b13200af2a1 commit e22d37d6f95df252f3ebc2e6b5901ac66b6ae15f Author: Davanum Srinivas Date: Sun Apr 7 20:43:37 2013 -0400 Sync with oslo-incubator copy of setup.py and version.py Keep up with the changes to the master copy Change-Id: Id938cc593a61b56a06c05069034bd958fc6aa8b3 commit 11c2c40d46ce5efaaa9ff388347ad82c72307bec Merge: b0ce15b c98432a Author: Jenkins Date: Mon Apr 22 00:05:29 2013 +0000 Merge "Fix "glance add" parsing of "copy_from" option." commit 5ab88370d0f682ac27a0483c87fd25a54bdb624d Author: MattieuPuel Date: Sun Apr 14 20:39:19 2013 +0200 bug 1166263 image-update handling for closed stdin handles the case where an image-update command is issued from a cron job with an invalid standard input file descriptor: consider no image data is provided when no --file option present. Change-Id: I5eb3433311e5faf0a3fb7eb36f6a01e5df7efe4c commit 2178120e9a84612f737505ab7b7f2c57e75a5892 Author: John Bresnahan Date: Fri Apr 12 10:59:28 2013 -1000 Test that copy_from is used properly in old API This adds a test to verify that the copy from attribute is used properly. It tests for bug 1167899. This submission depends on the review here: https://review.openstack.org/#/c/26740/ Change-Id: Ied14fb29887b7cbbecbab51bd384cf0f640c9e18 commit c98432a6d4d13f44eb00720a67153715d81a5d78 Author: Jakub Ruzicka Date: Thu Apr 11 14:38:35 2013 +0200 Fix "glance add" parsing of "copy_from" option. copy_from was ignored which resulted in "glance add" attempting to read from stdout. Fixes bug 1167899 Change-Id: I57fd85f7bb655bef69222d4fdf6b8274088ca827 commit b0ce15be3e9e24a5540215e9931ffbddc2ae42f7 Author: Davanum Srinivas Date: Fri Apr 5 09:46:19 2013 -0400 Fix problem running glance --version __version__ should point to a string and not VersionInfo Fixes LP# 1164760 Change-Id: I27d366af5ed89d0931ef46eb1507e6ba0eec0b6e commit 0995045f2a6c6179b9d3daf7e8c994e30e4e2d8c Author: Tatyana Leontovich Date: Wed Mar 6 15:01:44 2013 +0200 Improve unit tests for python-glanceclient.glanceclient.common.http Add several tests for glanceclient.common.http module Fixes: bug #1149445 Change-Id: I6a47c64e11cefea276163777dcd559316fc8e0ad commit 741c15f9633f642aa2e52de10b8eb65d6a8c9ecb Author: Brian Waldon Date: Tue Apr 2 14:49:59 2013 -0700 Add docs for 0.9.0 Change-Id: Ic4645b8d83eec6577b67cc17d805c6b12b1bacda commit 6a8a66120ccc75897fd144767f3a6c92f364db90 Merge: 1620848 a8f7de2 Author: Jenkins Date: Tue Apr 2 16:11:27 2013 +0000 Merge "Filter images list by public=True|False" commit a8f7de2afde8496256727d2ae08ee520e50b4066 Author: John Bresnahan Date: Tue Mar 12 14:24:58 2013 -1000 Filter images list by public=True|False When running the image-list command the user should have the option to list images according to whether or not the image is was set to public. A new test is included to verify this behavior. Change-Id: If645e7390fcf850648cda780a04ea37a26d855a2 Fixes bug: 1118799 commit 16208485bbab13e5869f7a8e5048c62315315dc7 Merge: edf9ae5 31960f0 Author: Jenkins Date: Thu Mar 21 22:48:35 2013 +0000 Merge "Allow for prettytable 0.7.x as well" commit edf9ae509763a70c7cd72e79c8c3896b903981e3 Author: John Bresnahan Date: Wed Mar 20 08:51:59 2013 -1000 Trapping KeyboardInterrupt sooner. Currently a KeyboardInterrupt can be triggered by the user while the client is communicating with keystone. This patch moves the trap higher up in the stack. Fixes bug: 1157905 Change-Id: I16889c2d97bc4694ab27c863c62c27333e264b60 commit 31960f0a8584802852d97ec8ce27b5516b32d63b Author: Dirk Mueller Date: Fri Feb 22 16:11:12 2013 +0100 Allow for prettytable 0.7.x as well Relax requirements to >= 0.6, < 0.8, as 0.7.x seems to work as well. Added testcase to ensure this. Change-Id: I1a1a709e6053451b1256a0d78f8fe8562fb10e62 commit c7c8e92e3c64b0f81c380548deb48f92adc2e7d4 Merge: 8787c82 552a68d Author: Jenkins Date: Tue Mar 12 14:43:23 2013 +0000 Merge "Implements filters: visibility, owner, member_status. Includes tests." commit 8787c82f118e8c7bed66193270cf307fc84ac6be Merge: d810019 1a3d40c Author: Jenkins Date: Mon Mar 11 21:54:47 2013 +0000 Merge "Control C does not cancel the CLI cleanly" commit 552a68d2bfedd996bb11eca97150b5728a452b3d Author: Brian Rosmaita Date: Sat Mar 2 18:59:34 2013 +0000 Implements filters: visibility, owner, member_status. Includes tests. Related to bp glance-api-v2-image-members Change-Id: Ic48f54639fec4dc9b48819a8ffb1f0097001894c commit d810019d0e70c98bc15b12b833b4e7762c318936 Author: James Li Date: Wed Mar 6 23:14:46 2013 +0000 Add missing spaces in help msg Change-Id: Ida1dcd9c75d1c36ea1b4bfefcd1d1292b565ab6b commit 1a3d40ccb4f532cad2ae0e1ad642ba56b0e2f707 Author: John Bresnahan Date: Tue Feb 19 11:58:54 2013 -1000 Control C does not cancel the CLI cleanly When a user attempts to terminate the CLI with CTL+C a stack trace is printed to the screen. When downloading files the partial amount downloaded will not be cleaned up. In most cases the user redirects to stdout, thus this program cannot clean that up. Fixes Bug: 1130390 Change-Id: If9f8ffc72b422d5dbd5969eecde8904238dd8860 commit 0ca43be6fd3eacc1f03b8376ac92ddb79992b2ff Merge: 8ec7468 fbb858a Author: Jenkins Date: Wed Feb 27 01:26:42 2013 +0000 Merge "Use getattr properly in legacy shell" commit 8ec7468134492b9066bd20b99ef66f716b01503c Author: Brian Waldon Date: Fri Feb 22 10:21:00 2013 -0800 Replace SchemaNotFound with HTTPNotFound Fixes bug 1131682 Change-Id: I615acbef0411677cae5d30262702babd900c0c81 commit fbb858aa7704bdbfbf8986a5a33c07fefffd6498 Author: Brian Waldon Date: Fri Feb 22 10:15:50 2013 -0800 Use getattr properly in legacy shell Fixes bug 1131703 Change-Id: If97f422af170c29785d2bf8884fafff979031e14 commit 6c7fb0e46cd34b1f9b90d0de052c5fe01fc7b725 Merge: 51bbf74 7f7c9a1 Author: Jenkins Date: Thu Feb 21 22:09:04 2013 +0000 Merge "Report name resolution errors properly" commit 51bbf74264b75a0b98bbb8de7611ccc935f49980 Author: Brian Waldon Date: Thu Feb 21 12:47:53 2013 -0800 Add docs for v0.8.0 Change-Id: I86683a88466f96a8bd7e443c436b3bed19f5cfc4 commit 7f7c9a1d8596cff87f576cf45318cf96c5d33262 Author: Stanislaw Pitucha Date: Tue Feb 19 15:40:16 2013 +0000 Report name resolution errors properly Errors in name resolution have been logged previously with the url path rather than the hostname. That resulted in incorrect errors like: InvalidEndpoint: Error finding address for /v1/images/detail?is_public=none&limit=20: [Errno -2] Name or service not known rather than one mentioning hostname itself. This patch changes the log message to fit the situation. Change-Id: I1eecbcb22d41b1341c214937b9cbfd046fd301a0 commit d831b5eb278a2e6830601044cea00cf1e2ffdde5 Merge: 9f1cd2b 55cb4f4 Author: Jenkins Date: Mon Feb 18 18:58:14 2013 +0000 Merge "Decode input and encode output" commit 9f1cd2b16bd800d3c76a8796dc2c65cf4162cdd0 Merge: ed644d7 d20d9d1 Author: Jenkins Date: Mon Feb 18 03:55:59 2013 +0000 Merge "Update to latest oslo-version." commit ed644d7228cf03e0b463b191675025eab72f7fe7 Merge: c994c3e 00ba179 Author: Jenkins Date: Sat Feb 16 00:17:28 2013 +0000 Merge "Add library support for v2 image update" commit c994c3ec8b9aef5d27da32e6f450dd0e008e44f8 Merge: 541a560 96cd98d Author: Jenkins Date: Fri Feb 15 23:48:51 2013 +0000 Merge "Expect minumum warlock version of 0.7.0" commit 541a560f0257c19391b5b7c242fc04cbb142b1a9 Merge: 882a7f5 2514ec8 Author: Jenkins Date: Thu Feb 14 01:14:49 2013 +0000 Merge "Update .coveragerc" commit 55cb4f4473a6fc429524e7c4848379013a4d2d1d Author: Flaper Fesp Date: Wed Jan 30 15:18:44 2013 +0100 Decode input and encode output Currently glanceclient doesn't support non-ASCII characters for images names and properties (names and values as well). This patch introduces 2 functions (utils.py) that will help encoding and decoding strings in a more "secure" way. About the ensure_(str|unicode) functions: They both try to use first the encoding used in stdin (or python's default encoding if that's None) and fallback to utf-8 if those encodings fail to decode a given text. About the changes in glanceclient: The major change is that all inputs will be decoded and will kept as such inside the client's functions and will then be encoded before being printed / sent out the client. There are other small changes, all related to encoding to str, around in order to avoid fails during some conversions. i.e: quoting url encoded parameters. Fixes bug: 1061150 Change-Id: I5c3ea93a716edfe284d19f6291d4e36028f91eb2 commit 00ba17965ad90d6ebed38f867c093aa6c0d4bb2d Author: Brian Waldon Date: Sun Jan 20 16:29:32 2013 -0800 Add library support for v2 image update Related to bp glance-client-v2 Change-Id: Ia6fe16e462ce8827175577cbed8e15c326bf8ad3 commit 96cd98d00b91433620737a17bd1be722236f2e70 Author: Brian Waldon Date: Sun Jan 20 15:18:39 2013 -0800 Expect minumum warlock version of 0.7.0 Change-Id: Ic2ef17f29ebd4c157a8ccf7a52b9c74e0c17a54e commit 882a7f514cdb59e092e01d608b254dba247c46bc Merge: 60cfebc 118a0f7 Author: Jenkins Date: Wed Feb 13 02:03:49 2013 +0000 Merge "Make effective ssl callback behaviour more obvious" commit d20d9d1ca42365700c00b4404e1445068e2964d4 Author: Monty Taylor Date: Mon Feb 4 11:57:45 2013 +1100 Update to latest oslo-version. Remove the need for versioninfo file and just use python's PKG-INFO. Change-Id: I4765141e9bf3fa075dfbbc6a07e495c29e12177b commit 2514ec84e5a7b992d2bc1c39c37bf9a5b04eeeb9 Author: Alessio Ababilov Date: Wed Feb 6 16:47:06 2013 +0200 Update .coveragerc Set up proper source and omit options. Change-Id: I2590006f922b2163f9c913d4ce3c349f0e49c68b Implements: blueprint update-coveragerc commit 118a0f7a3048faf56d68745ee7c74c2c20d6c479 Author: Stuart McLaren Date: Fri Feb 1 10:02:01 2013 +0000 Make effective ssl callback behaviour more obvious When using 'insecure' no callback is executed. Make it more obvious that the set_verify callback won't be called by replacing it with a lambda. Fixes bug 1112361. Change-Id: Ib5d43a8883f7ed76383971d8154e2111f5ab2869 commit 60cfebc31b101cfaf794a78dda74ef644179bd43 Author: Vishvananda Ishaya Date: Thu Jan 31 16:20:08 2013 -0800 Quote image ids before passing them to glance If the image id contains a space it needs to be quoted or else it will construct an improper header. Fixes bug 1111948 Change-Id: I4db2e74401b57de9666d40b38151182b2a76cd1b commit ed67c320144c74bd56f64e421ca069c6555771d6 Author: David Wittman Date: Wed Jan 30 22:04:11 2013 -0600 Fix typo in image-update help page The image-update help page reversed the DISK_FORMAT and CONTAINER_FORMAT metavars. Fixes bug #1111054 Change-Id: Iec8374782d00e8e9102141fb1e1c16d7f6ac136c commit 542a45bd2881b84d845ab167095e40d562d274a2 Merge: 8df58ff a6ef8ed Author: Jenkins Date: Tue Jan 29 20:03:58 2013 +0000 Merge "Adds image-delete functionality." commit a6ef8ed7da5c3914d7d537e1591fa6239ef5d039 Author: Anita Kuno Date: Fri Jan 25 20:54:21 2013 +0000 Adds image-delete functionality. Change-Id: Ic10433f6eb484a4760f3e61f040e903e86b95d91 commit 8df58ff8c2abd3030ddbd63be79bc4c375757229 Merge: 3597599 8d0d4b9 Author: Jenkins Date: Mon Jan 28 23:33:42 2013 +0000 Merge "Change https port to be an optional parameter" commit 359759919ce078c9f36ac67d223d3ab231fc53d5 Merge: a5f996c c5e9426 Author: Jenkins Date: Fri Jan 25 23:22:43 2013 +0000 Merge "Migrate to testr." commit 8d0d4b90f3c177d40a884fc562c51764cf5045f5 Author: Stuart McLaren Date: Tue Jan 22 11:42:42 2013 +0000 Change https port to be an optional parameter VerifiedHTTPSConnection inherits from HTTPSConnection so 'port' should be an optional argument. If not present it will be set by HTTPSConnection in the usual way: by parsing the host string (eg 'localhost:8443') or setting to the default of '443'. Addresses bug 1102944. Change-Id: I2c2cb92f824acf15b0ff54590b5614cf206b57e0 commit c5e9426bb588faacac8b8a661afc560241640e41 Author: Monty Taylor Date: Mon Dec 24 22:50:58 2012 -0600 Migrate to testr. Part of blueprint grizzly-testtools Change-Id: I9a12f0300e2731a6ee0a82e1f9737a6b83aa5d85 commit a5f996ce9bb8e0cb20f7a3b5b39c7ed90da19cdb Merge: 70d8ebb 0aba81a Author: Jenkins Date: Wed Jan 9 00:32:11 2013 +0000 Merge "Add image names to glance command arguments." commit 70d8ebb654cf9587c4cb9ed3565f5c82ce7b0d8a Merge: 19d542e 1d461a6 Author: Jenkins Date: Tue Jan 8 04:08:48 2013 +0000 Merge "Use testtools instead of unittest." commit 0aba81ac1f11af283b4aca5a6ec65899200f6d5b Author: Ken'ichi Ohmichi Date: Sat Jan 5 07:32:25 2013 +0900 Add image names to glance command arguments. Now a user should specify ID as an image by glance command, and I feel it is easy-use that a user can specify name also as an image like nova command(ex. "nova boot"). By applying this patch, a user can specify name as image like the following examples: $ glance image-show cirros-0.3.0-x86_64-uec $ glance image-update --name root-fs cirros-0.3.0-x86_64-uec $ glance image-delete cirros-0.3.0-x86_64-uec $ glance image-download cirros-0.3.0-x86_64-uec $ glance member-create cirros-0.3.0-x86_64-uec 94b0e63a27ca43348fe056622fe3fe94 $ glance member-delete cirros-0.3.0-x86_64-uec 94b0e63a27ca43348fe056622fe3fe94 Fixes bug 1093380 Change-Id: Ia0a070eed6ae3853ef02032f479087edb1d75a67 commit 1d461a6496059b0d429ebe9f360b1836ac918f31 Author: Monty Taylor Date: Mon Dec 24 22:37:01 2012 -0600 Use testtools instead of unittest. Part of blueprint grizzly-testtools Change-Id: Ie914fd8f59cddb1a480566ec4eff908bfb51921c commit 19d542ef5f570d98e08008898a0ee31a3186d5a9 Author: Ken'ichi Ohmichi Date: Tue Jan 1 22:33:15 2013 +0900 Add details to stdout error message. Current glance command does not show the details of error message. For example, the glance command shows HTTPBadRequest only if some necessary parameter is not specified. $ glance image-create --file root-fs.img --name cirros-0.3.0-x86_64-uec Request returned failure status. HTTPBadRequest (HTTP 400) $ By only the above message, it is not easy that a user understand the reason of an error. glance-api server returns the details of reason, but glance command does not show it. This patch adds details, which is gotten from glance-api server, to error message. And a user will be able to understand the reason of a error like the following: $ glance image-create --file root-fs.img --name cirros-0.3.0-x86_64-uec Request returned failure status. 400 Bad Request Invalid disk format 'None' for image. (HTTP 400) $ Fixes bug 1094917 Change-Id: I49192c3ebbc8a70b63dcfcede9fd13f1688388cf commit c057fe47d6889912b7675aa54102509cd464171a Author: Brian Waldon Date: Tue Dec 11 15:33:35 2012 -0800 Document v0.7.0 release Change-Id: I3ee1c2c558d77341e0f50415cf13a85281c4aa5d commit 4781da7007b69e63b18083ea58d46316201c6477 Author: Dean Troyer Date: Fri Dec 7 11:21:11 2012 -0600 Support --os-cacert * Rename --ca-file to --os-cacert (--ca-file deprecated for backward compatibility) * Add cacert to keystoneclient initialization to verify the keystone server certificate This aligns glanceclient with keystoneclient for option naming and the use of TLS for the keystone auth connection. It does not change the use of TLS/SSL for the glance connection. Change-Id: If8b05655aea5f3c62612d77bf947dd790f77eddf commit 2500e69b22c7291d1a01e86145ddfca87277ecd0 Author: Brian Waldon Date: Mon Dec 3 15:00:46 2012 -0800 Update --location help to reference swift store. Fixes bug 1085575. Change-Id: I6b325874a21e63fa02fe261f19e3d071f2b220d9 commit 7be3395c6c7df3a8404eef199a145d8955382e67 Merge: b66e6fa 4fa6805 Author: Jenkins Date: Fri Nov 30 21:27:15 2012 +0000 Merge "Change default image sort to use name" commit b66e6faac0cb47333fd9ec2687458aaa3c0e154d Merge: 09c8216 4da8b28 Author: Jenkins Date: Fri Nov 30 21:22:23 2012 +0000 Merge "Add --sort-key and --sort-dir to image-list" commit 4fa68052423c475d25cdb45610f4827aed13d509 Author: Brian Waldon Date: Wed Nov 28 09:50:46 2012 -0800 Change default image sort to use name Fixes bug 1081542. Change-Id: I9e09c3fe3b6c2bfce7e37df1d29b37a42168e878 commit 4da8b287b40cf0f0de0fd77998e3e66d8bac7999 Author: Brian Waldon Date: Sun Nov 25 14:26:02 2012 -0500 Add --sort-key and --sort-dir to image-list The --sort-key and --sort-dir CLI options allow users to control the field and direction by which their images are sorted in an image-list operation. The previous default sort behavior of sorting by ID asc has been preserved. Fixes bug 1082957. Change-Id: I1d3664219c275b0379fe176f8288d6ffae0dffbe commit 09c821617770a792b92859acb6f0076bf80307e5 Merge: 59b8ac1 c2ba627 Author: Jenkins Date: Mon Nov 26 23:20:19 2012 +0000 Merge "Allow setting x-image-meta-store through shell on image creation" commit 59b8ac198b1af823ae7a5aa61556e87db2ee6bb0 Merge: c0ec97f 7a5946f Author: Jenkins Date: Mon Nov 26 23:20:16 2012 +0000 Merge "Verify that host matches certificate" commit c0ec97f310bc56c69c22e49928b4aea4cd8458f1 Author: Chuck Short Date: Wed Nov 21 12:03:07 2012 -0600 Pin pep8 to 1.3.3 Standardize pep8 to 1.3.3 and cleared up any errors found by pep8 tests. Change-Id: Ib7eb97d0789556d1676ccad58b5d3364065b7d15 Signed-off-by: Chuck Short commit c2ba627ec1425e307fb73cf1d28690eb85a161db Author: Florian Haas Date: Sat Nov 24 11:22:41 2012 +0100 Allow setting x-image-meta-store through shell on image creation Add a command line arg "--store" to "glance image-create" so users can specify a store other than the default on image creation. Change-Id: Icf9a894b08e405d6884964b3cfaa80250e85ed71 commit e1955434b761e0f0b55f76fe7c6042c2d6bf727b Merge: ae16750 3003ed8 Author: Jenkins Date: Wed Nov 21 20:36:49 2012 +0000 Merge "Hook up region_name argument" commit 7a5946fd87bc78b9d302ba6b665283e9744c8cd8 Author: Stuart McLaren Date: Fri Nov 16 13:46:59 2012 +0000 Verify that host matches certificate When using https verify that the Common Name (CN) or the Subject Alternative Name listed in the server's certificate match the host we are connected to. Addresses LP bug 1079692. Change-Id: I24ea1511a2cbdb7c34ce72ac704d7b5e7d57cec2 commit ae16750c48bc8d4b2b8d4de87913572a276f4f1e Author: Brian Waldon Date: Mon Nov 19 14:56:38 2012 -0800 Document bugs/features for v0.6.0 Change-Id: I8966d74eb86e52a222ddac5bc6d52d1dd699fb3d commit 3003ed8ef8078e97a2deeaf1f561292a3708755c Author: Brian Waldon Date: Mon Nov 19 12:47:54 2012 -0800 Hook up region_name argument Connect the --os-region-name option passed through the CLI to the call to service_catalog.url_for. Fixes bug 1080739. Change-Id: I2d19d62a999a82a91de3883db12bbc24e900de25 commit 046d34c89185c31b79f580dd2cafa9f790920426 Merge: e40580b fe17d35 Author: Jenkins Date: Mon Nov 19 19:39:19 2012 +0000 Merge "Simplify human-readable size output" commit e40580b77a513cdfb89da15852ea73de0d9a6eea Merge: 882c13a b24832c Author: Jenkins Date: Mon Nov 19 18:56:52 2012 +0000 Merge "Make image sizes more readable for humans" commit 882c13ab8cb4bd57a61ae3918f4c3ab1e3185a5d Merge: 0192e14 0e90f8e Author: Jenkins Date: Mon Nov 19 18:52:55 2012 +0000 Merge "Set useful boolean flag metavars" commit 0192e14d5652500185aa9410fe6e39cd25c1ec6b Merge: 1899ac2 e20ff23 Author: Jenkins Date: Mon Nov 19 18:48:11 2012 +0000 Merge "added --version as new parameter" commit fe17d3517482525527d4af9b24df64e7651ebe3a Author: Brian Waldon Date: Mon Nov 19 10:35:04 2012 -0800 Simplify human-readable size output * Limit human-readable sizes to a single decimal * Drop trailing zero * Step one suffix further in the case of a size being 1024 Change-Id: I2eb8ac0571d3d08b52f62155912863870573a37c commit b24832c22aa44d2f8b5ecddaf12e7878653af28f Author: Christian Berendt Date: Wed Nov 7 19:39:43 2012 +0100 Make image sizes more readable for humans By introducing the parameter --human-readable for several functions (image-list, image-show, image-update, image-create) it's possible to convert the size in bytes to something more readable like 9.309MB or 1.375GB. Change-Id: I4e2654994361dcf330ed6d681dbed73388f159cb commit 0e90f8ef230eebd421175d637d7c9df7d149a155 Author: Brian Waldon Date: Mon Nov 19 09:08:39 2012 -0800 Set useful boolean flag metavars The boolean flags --is-protected and --is-public now communicate that they must be set to True or False. Fixes bug 1056501. Change-Id: I23094ea556eb71d6eb977a64c171119738ed792b commit 1899ac29637923ad8ff18f863a9d7081a34593d0 Merge: c6b9712 8d81623 Author: Jenkins Date: Sat Nov 17 00:57:15 2012 +0000 Merge "Unpin keystoneclient dependency" commit 8d81623d5f1891a19f58d66cc28f7d4033293354 Author: Vishvananda Ishaya Date: Fri Nov 16 16:29:43 2012 -0800 Unpin keystoneclient dependency The new 0.2.0 keystoneclient was released with no api changes, and glanceclients dependency breaks gating so unpin it. Change-Id: I9cbe2ebb462005ebfea3b7a0e68ca39069a0765f commit c6b9712482389a36102141aecb36a0199291092b Merge: 00eff28 6c201e6 Author: Jenkins Date: Thu Nov 15 21:18:44 2012 +0000 Merge "Fixes bug on Windows related to a wrong API url" commit 6c201e63ea76032cbdd65211382b6266e6a767de Author: Alessandro Pilotti Date: Thu Nov 15 20:25:26 2012 +0200 Fixes bug on Windows related to a wrong API url Fixes Bug #1079323 python-glanceclient (latest repository code) fails on Windows due to a malformed API url. This error is due to the usage of os.path.normpath(), which should not be used for URLs as it swaps "/" with "\" on Windows. The fix consists in using posixpath.normpath(). Please see also https://bugs.launchpad.net/nova/+bug/1077125 and related commit. Change-Id: Iaa643bd579963ad9ffbf10674973cbca75d435ac commit 00eff28f28f57fa3f786629dbf20c19b558188ef Author: Andre Naehring Date: Wed Nov 14 15:37:31 2012 +0100 Enhance --checksum help with algorithm Fixes bug 1056499. Added a line to the help text of --checksum which enhances the help text to show what checksum algorithm is expected. Change-Id: Ie6604022dd9f398c639afe647b2d94b5179dbb61 commit e20ff231587e9d3985602cf8df755e3f24459cda Author: Christian Berendt Date: Tue Nov 13 11:59:17 2012 +0100 added --version as new parameter fixes bug 1056504 Change-Id: Ib28e3941006b46553001d7895d5ddf4b0f9c540d commit 16aafa728e4b8309b16bcc120b10bc20372883f4 Author: Alessandro Pilotti Date: Mon Nov 5 18:19:13 2012 +0200 Fixes setup compatibility issue on Windows Fixes Bug #1052161 "python setup.py build" fails on Windows due to a hardcoded shell path: /bin/sh setup.py updated using openstack-common/update.py Change-Id: If0ae835aeada8769e46dddf4f3c2f2edfdfbc5fe commit a8e88aa340c2d1ad9d937e0f5c2c60c65d7e5962 Merge: 256dcba 8b2c227 Author: Jenkins Date: Thu Oct 25 02:23:49 2012 +0000 Merge "Allow deletion of multiple images through CLI" commit 8b2c227f27be649a4a3e371ad99157ee464ecc1d Author: Sulochan Acharya Date: Mon Oct 22 17:01:46 2012 -0500 Allow deletion of multiple images through CLI Add nargs to argparse for image-delete command to allow muliple (optional) positional image-id arguments. For example: image-delete xxx aaa yyy will delete valid images xxx and yyy and print error message for invalid image aaa. Also with --verbose you can see some extra text on delete request for each image. Fixes bug1056498. Change-Id: I6e804700ed24d16f90ec92569c0893cad4aaa26f commit 256dcbae1c8565284cb54f5c5693b9cfd7103425 Merge: c420fa1 1e14e82 Author: Jenkins Date: Wed Oct 24 02:53:46 2012 +0000 Merge "Fixes shell command for member-delete" commit 1e14e82c815b06dfd8a370f1f97c61a256b28a9a Author: Sulochan Acharya Date: Mon Oct 22 15:46:55 2012 -0500 Fixes shell command for member-delete Fixes the member-delete cli command and string formatting for dry-run option. Fixes bug1064320 Change-Id: I338f03d53da5c9b7656ae4d1335de9623b774dd8 commit c420fa10fe25fd671b2ca48dd86d80b499856ac6 Author: Doug Hellmann Date: Mon Oct 22 18:43:53 2012 -0400 Add OpenStack trove classifier for PyPI Add trove classifier to have the client listed among the other OpenStack-related projets on PyPI. Change-Id: I2bb290f529fd2cd08d0093f495074d8e1683d91f Signed-off-by: Doug Hellmann commit 9004ee40df67705d6e0f07df65763e7ae2c44b13 Merge: 3576336 b5d46e2 Author: Jenkins Date: Sat Oct 13 02:54:09 2012 +0000 Merge "Display acceptable disk/container formats in help text" commit 3576336cb993d88b32638ed1f2bcab5dc31653fe Merge: 556082c 727aadb Author: Jenkins Date: Sat Oct 13 02:17:31 2012 +0000 Merge "Handle create/update of images with unknown size" commit 556082cd6632dbce52ccb67ace57410d61057d66 Author: Stuart McLaren Date: Fri Sep 21 14:18:22 2012 +0000 Implement blueprint ssl-connect-rework Use pyOpenSSL for HTTPS connections. This allows: * Neater loading of system CA files * Optional disabling of SSL compression The performance gain from disabling SSL compression is significant in cases where the image being uploaded/downloaded is in an already compressed format (eg qcow2). Related to bp ssl-connect-rework. Change-Id: I0568b6c95c5fc7b8eafdbd0284e24c453660a55a commit 727aadbc257ec3c99dd1621202948d288d45c8cc Author: Stuart McLaren Date: Wed Sep 26 12:56:51 2012 +0000 Handle create/update of images with unknown size It may not be possible to know in advance the total size of image data which is to be uploaded, for example if the data is being piped to stdin. To handle this we use HTTP Transfer-Encoding: chunked and do not set any image size headers. Various subtly different cases needed to be handled for both image-create and image-update, including: * input from named pipe * piped input of zero size * regular file of zero length Fix for bug 1056220. Change-Id: I0c7f0a64d883e058993b954a1c465c5b057f2bcf commit b5d46e2e0d1e5df7f953787987b88880eb844b9d Author: Brian Waldon Date: Wed Oct 3 13:52:55 2012 -0700 Display acceptable disk/container formats in help text Fixes bug #1056497 This patch provides more information in the help text. Originally the text provided the trivial definitions of the arguments disk_format and container_format. This patch updates the text to display the acceptable formats. Change-Id: I893b52c9f72a34c75e8bea522820863592300302 commit cdc06d9fdb15cd19bd5d26304dfebf092c6c8df8 Author: Brian Waldon Date: Wed Oct 3 13:52:55 2012 -0700 Simplify http(s) connection instantiation The endpoint parsing and connection instantiation code was too complicated and easily broken. This assigns human-readable names to instance variables and breaks up the parsing into more understandable chunks. Fixes bug 1060316. Change-Id: I5c5236f90d88b9e797cf0a476aabe8cc7cfa1cc9 commit 11e6aadf190122d6a4776fc00e660b71a89dffb3 Author: Brian Waldon Date: Tue Sep 11 16:22:56 2012 -0700 Add happy path tests for ResponseBodyIterator Change-Id: I5e971b57a0591752e7fca76d0df78ce139308db5 commit ff3060c067bb2a860642a1c2e01ef151df8c5243 Author: Diego Parrilla Date: Fri Sep 21 00:51:20 2012 +0200 Use full URI path from Glance endpoint in HTTP requests Fixes bug 1052846 Now the connection uses host, port and path to connect to Glance. So proxied connections to Glance are allowed. Change-Id: I53a890e6532adb8168961d1d09f938bf439e895c commit e140dbb0c779de74e4ae063971660ac2d234365a Merge: cdc94af 91896ff Author: Jenkins Date: Tue Sep 18 20:44:58 2012 +0000 Merge "Fixes glance add / update / image-create / image-update on Windows" commit cdc94af297fe56341dfe0484d62f2e69d9aa5e9b Author: Stuart McLaren Date: Mon Sep 17 13:39:33 2012 +0000 Typo in image-create help page The image-create help page reversed the DISK_FORMAT and CONTAINER_FORMAT metavars. Fixes bug 1051968. Change-Id: I385cb0912ad87a62fd10742b5da23a5ea8bc9bb8 commit 91896ff51861e8d90bdf0f7c54cab0f2b3e3c277 Author: Alessandro Pilotti Date: Thu Sep 13 14:02:20 2012 +0300 Fixes glance add / update / image-create / image-update on Windows Fixes Bug #1050345 The image upload hangs if the file contains a byte with value 0x1A (EOF), due to the fact that the file or stdin streams are treated as text and not binary streams. This fix sets the proper binary mode. Change-Id: I3425cb9729a8da4d1b73fbfba06fd6f2c7e8833e commit 902bff79bbe52e831da947bb5ac5fce2330d810e Author: Vincent Untz Date: Thu Sep 13 11:12:00 2012 +0200 Fix weird "None" displayed on some errors logging.exception() should only be called from an exception handler, which is not the case here. Part of bug 1050260. Change-Id: I591a68c458cd733c04cea7d2d640afdbb7dd19f6 commit 8cee48b1ddf480d182bbc33ec684dcfd195b038c Author: Andrew Laski Date: Wed Sep 12 09:40:04 2012 -0400 Make ConnectionRefused error more informative. When the server refuses the connection the error message displayed now lists the endpoint that refused the connection. Fixes: bug 1043067 Change-Id: I62797106732bbb6eec8c99e491fd38850ad58ff8 commit 3f67c461da236bf603cf4812f81f51200573f51f Author: Brian Waldon Date: Mon Sep 10 18:25:20 2012 -0700 Document remaining bug for v0.5.1 Change-Id: I97144b22e2040441e6507ff1810ab7a3da9b1ae2 commit 522317784a294923cb1d94cca744cd9cac242088 Author: Brian Waldon Date: Mon Sep 10 16:53:32 2012 -0700 Update docs for v0.5.1 release Change-Id: I377caf14379ebffe3bbc70c67e9378fc0ebcea95 commit 92d87c0f7cb0fdd5e5c8798fa52d81e81758e64c Merge: ff972fb 30d8e1b Author: Jenkins Date: Mon Sep 10 23:38:06 2012 +0000 Merge "Specified Content-Length in update request header" commit ff972fb02f1992e8036606a03fa0e331c4804f75 Merge: 2f1e029 5acd5a6 Author: Jenkins Date: Mon Sep 10 23:36:21 2012 +0000 Merge "Catches HTTP 300 while printing responses" commit 2f1e0299e84b7e3acfdb0c927025a84ae4c0704c Author: Brian Rosmaita Date: Mon Sep 10 19:19:20 2012 +0000 Corrects URI to display hostname, port properly Fixes bug 1035931 Change-Id: I1b4e8a226c21d137b24bc5b75299bcf4ab4efefb commit 5acd5a6a4adc430989ae05c434d0ac5a9a448b84 Author: isethi Date: Fri Sep 7 20:54:09 2012 +0000 Catches HTTP 300 while printing responses If glance v1 api is not enabled, and a request is made to it, it gives a KeyError. This patch catches the 300 error and displays error message. Fixes bug 1046607 Change-Id: I0009a5deca3b5dd5ccaeaea90feee21274bfe090 commit 61b359efa836b5e25d85892e2772fd9856062103 Author: Stuart McLaren Date: Mon Sep 10 14:57:45 2012 +0000 get_connection should raise httplib.InvalidURL In http.py the exception raised in get_connection should be httplib.InvalidURL rather than httplib.InvalidUrl. Fix for bug 1048698. Change-Id: I7f18321fe7d8669b3b95bf823273ee8ae6961661 commit def324ec5284d6cb8f33620961879e25fa7382d5 Merge: 7402590 61567ba Author: Jenkins Date: Mon Sep 10 15:20:46 2012 +0000 Merge "Fix PEP8 issues." commit 61567bad1ef4d78e8c362b571576e066a7138087 Author: lrqrun Date: Wed Aug 29 19:06:36 2012 +0800 Fix PEP8 issues. Fix some pep8 issues in doc/source/conf.py make the code looks pretty. Change-Id: I44f8152de76ae217d1dd81c9c0c1b1e075ec8792 commit 30d8e1b97ce0621a0ce9aae97ad8ab35101860e6 Author: Unmesh Gurjar Date: Thu Sep 6 00:36:38 2012 -0700 Specified Content-Length in update request header While uploading a Volume to an image, the HTTPConnection's request method does not set the Content-Length header (since the volume file is a sym link i.e. the os.fstat call returns a st_size of 0). This causes Volume uploads to Glance fail (since the image data is ignored as no content-length is specified). Therefore setting the Content-Length from update( ) method if the image data is provided. Fixes LP: #1045824 Change-Id: If259fc5a338e3e90214a52b773132ed901691c0f commit 740259025528836c4bc7a1d3bc840cdebf70192a Author: Mark McLoughlin Date: Wed Sep 5 12:55:55 2012 +0100 Sync importutils changes from openstack-common Syncs the following changes from stable/folsom: 769ec65 Don't trap then re-raise ImportError. 8c74b37 Improve exception from importutils.import_class(). 1fb2361 add import_object_ns function Change-Id: Ib6046181ec4712702c30c8a8e938fc9a21b1a594 commit e233f66ecd0f30523226246bec1c2e62223344ec Merge: 1e8cf71 df7b82d Author: Jenkins Date: Wed Aug 22 00:52:33 2012 +0000 Merge "Add nosehtmloutput as a test dependency." commit 1e8cf7102f292e7d2224faf50fc078990acb2fda Author: Brian Waldon Date: Tue Aug 21 16:44:22 2012 -0700 Update release notes for v0.5.0 Change-Id: Icc8e1d9a4a06448b27afe6e4bb0976eee20dcc83 commit 01ec6d942dc56d25b32ccbb370029bde5370f3ff Merge: 575c591 5069d66 Author: Jenkins Date: Tue Aug 21 23:07:57 2012 +0000 Merge "Update command descriptions" commit 575c591e4b684db4068330c5f7bd0aa2701f378c Merge: 156e6f9 27350e5 Author: Jenkins Date: Tue Aug 21 23:07:23 2012 +0000 Merge "Update pip-requires with warlock<2." commit 156e6f949d6b2663b19ddbb8c32e838b6755ddea Merge: 8a64e0f c24fc93 Author: Jenkins Date: Tue Aug 21 23:06:30 2012 +0000 Merge "Enable client V1 to download images" commit df7b82d2f22a4c3f801cdefeb5c92764e4809694 Author: Clark Boylan Date: Tue Aug 21 14:34:22 2012 -0700 Add nosehtmloutput as a test dependency. Adding nosehtmloutput as a test dependency allows nose to output its results to an html file. This will be used by Jenkins to save logs on a different server. Change-Id: Ib7f07dbe7e81d17d42a191a664c7f844f58fcb94 commit 5069d66d518c407fdf9e7fdcc011bc69608148d0 Author: Brian Waldon Date: Tue Aug 21 13:07:08 2012 -0700 Update command descriptions Several commands did not have descriptions or the descriptions they had were insufficient. This adds mission descriptions and fattens up those that were too lean. Change-Id: I091ae70cdae5d3f72f273519d88873cb5392ba3b commit 27350e50e0255591447b65aa94570479d3d3cb71 Author: Dan Prince Date: Fri Aug 17 11:50:33 2012 -0400 Update pip-requires with warlock<2. Allow warlock to be used up to version 2 (the next major version of the library). Change-Id: I0c5a47f9ebfa0145dfab7310a22982d5d8e9aa52 commit c24fc93142263e34e50ea81a5fdb406b074c2831 Author: Lars Gellrich Date: Mon Aug 13 09:21:58 2012 +0000 Enable client V1 to download images Added the CLI option image-download to download an image via API V1. Based on commit 137b3cf975d73437943e100065c76b83acfa7dd3 Related to bp glance-client-v2 Change-Id: Ie587e208ad7433e468798cd9b1846b4a21e1c4ec commit 8a64e0ff0e0f85a6cbfd8a03b7d04a12f4c53ab1 Author: Brian Waldon Date: Wed Aug 15 18:06:35 2012 -0400 Simplify docs and provide 'News' on index.rst * The index page is now a set of release notes and a quickstart guide * Shrink the README (pypi doc page) and move it into the doc site * Drop the link to the autoindex module docs Change-Id: I276b1228ba4006279c112eb487dcde9e45c4f344 commit d64876424e87b3a7f76a9bf4d29fdacbc5ad4bc4 Author: Brian Lamar Date: Wed Aug 15 14:39:39 2012 -0400 Ensure v1 'limit' query parameter works correctly. The tests were present but were not asserting list results. page_size was overriding the absolute limit so limits were not working if they were less than the page_size. Fixes bug 1037233 Change-Id: If102824212e3846bc65d3f7928cf7aa2e48aaa63 commit a5b8165d7de5edd15a616e2ff97c1f8b72b53e8c Author: Brian Waldon Date: Mon Aug 13 10:56:27 2012 -0700 Allow 'deleted' to be passed through image update The legacy client allowed users to pass 'deleted' through an update call. This is breaking some clients of this library because they expect to be be able to still do that. Fixes bug 1036315 Change-Id: I9ae20a5e4579240c7d5e86316d6d1e927755dbf5 commit 8f0d5c4f2cea6e5dae827b5f728c54dab4a4386b Author: Brian Waldon Date: Mon Aug 13 10:30:43 2012 -0700 Cast is_public, protected, deleted to bool To keep a consistent view of an image, is_public, protected, and deleted need to be cast to a bool when being parsed from headers. Fix bug 1036299 Change-Id: I2730a0f2d705d26ebc0ba883e99c1caf44d70b51 commit 1e539dfdbecfdcf33d57ef79b9f845b90f547506 Author: Brian Waldon Date: Mon Aug 13 10:18:51 2012 -0700 Return known int values as int, not str Cast size, min_ram, min_disk to integers before returning them to the user from the v1 API. Fixes bug 1036297 Change-Id: Ib1e2a3bf931e433b6311cc8a1a5219168b50be97 commit 37caf870ac0c4afb80176b2357f5562a21725ccb Author: Stuart McLaren Date: Fri Aug 10 18:32:07 2012 +0000 Use system CA certificate file When SSL is being used and the --ca-file option is not specified use an available system CA file to verify the server's certificate. Change-Id: Id5c9fda6fd9bd05cde3c2a9160a6e72cef086a44 commit a214d983c2ae115c5c4965808f2bd5912c71e4c3 Author: Chris Behrens Date: Fri Aug 10 21:06:44 2012 +0000 socket errors and timeouts should be CommunicationErrors Also include extra information about socket errors within the exceptions. Change-Id: I9464a484460d40be5727e18ca6f057df9076766e commit 3997f977fa0df34f72670e0e4a4e9e87d932b2af Author: Brian Waldon Date: Wed Aug 8 13:45:38 2012 -0700 Handle communication failures cleanly Expand exceptions to cover more failures cases. This adds CommunicationFailure to represent any failures while attempting to communicate with the remote endpoint. This also adds a new base exception class BaseException which should be used for all non-HTTP related failures. Change-Id: Ie3e1d45c520d816a3f491a85fde94a6c4edf295e commit 4b59f6649498dea4334c400f07e909592b01012e Merge: 392dfd6 227d166 Author: Jenkins Date: Fri Aug 10 19:40:09 2012 +0000 Merge "Client-side SSL Connection" commit 392dfd6d0db33659ec3665513f42a100fea4a3de Merge: 137b3cf ff34cfc Author: Jenkins Date: Fri Aug 10 18:35:33 2012 +0000 Merge "SSL Certificate Validation" commit 137b3cf975d73437943e100065c76b83acfa7dd3 Author: Lars Gellrich Date: Wed Aug 1 16:04:37 2012 +0000 Enable client V2 to download images Added the CLI option image-download to download an image via API V2. Added utility function to save an image. Added common iterator to validate the checksum. Related to bp glance-client-v2 Change-Id: I0247f5a3462142dc5e9f3dc16cbe00c8e3d42f42 commit 354c98b087515dc4303a07d1ff0d9a9d7b4dd48b Author: Brian Waldon Date: Wed Aug 8 11:14:18 2012 -0700 Refactor HTTP-related exceptions * Refactor helper function that builds the map of http status codes to local http exceptions - now we don't have to explicitly list every single exception name * Add several exceptions to represent http status codes that were not previously represented * Improve consistency of exceptions naming by prepending 'HTTP' to necessary exception names * Use HTTPException instead of ClientException * Deprecate old http exceptions (those that aren't prefixed with HTTP) * Deprecate ClientException * Deprecate unused NoTokenLookupException and EndpointNotFound * Add test module to spot-check the from_response helper Change-Id: Ibc7fef9e2a5b24bd001d183d377901f302d650a9 commit 3a68f75b95ed7807aee6cc245ea175ac35a09b11 Author: Brian Waldon Date: Wed Aug 8 10:08:33 2012 -0700 Simplify v2 schema lookup We don't need to look at the container of available schemas in order to get the one we want. Remove glanceclient.exc.SchemaNotFound as it can no longer be raised. Change-Id: Ib49ad58c4fdfc9bc9f535115674d92040a97db65 commit 127463e14dd61507f97b2bf19aa318e3a6e5d591 Merge: 6209852 13d80a7 Author: Jenkins Date: Wed Aug 8 17:55:22 2012 +0000 Merge "Add missing copyright headers" commit 62098527b932bd8b8cdd5328269ec0c89a0bd74d Author: Brian Waldon Date: Tue Aug 7 20:30:20 2012 -0700 legacy_shell.py shouldn't be executable Change-Id: Ife6e9e47ece3971a3872e781a39e30bcb38e3bf7 commit 227d166109d6b35f44a1247c1127b2593fc1b9ec Author: Brian Waldon Date: Thu Aug 2 15:30:50 2012 -0700 Client-side SSL Connection This allows a user to pass a cert and a key to use in HTTPS connections. The flags --cert-file and --key-file are added to the CLI. Addiionally, update the debug curl logging to print --cacert and -k when ca_file and insecure are set. Related to bp glance-client-parity. Change-Id: Ibaea51419a903afb7939a6b5b848f7a6667893bf commit ff34cfc50f3e030671362f42fbba58e11e5fb23d Author: Brian Waldon Date: Thu Aug 2 14:16:13 2012 -0700 SSL Certificate Validation This adds support for validation of ssl certs returned by remote servers over SSL. The --ca-file param represents the CA cert used to sign the remote server's cert. Use --insecure if the remote server is using a self-signed cert or you don't have the CA cert. Related to bp glance-client-parity Change-Id: I45253a6e2d88da599addfcc464571e62ae920166 commit 18543b1a46cad3e01613c4b5c933b7dfb68e048b Merge: 61fdefb 2541f3c Author: Jenkins Date: Fri Aug 3 00:29:25 2012 +0000 Merge "Allow CLI opts to override auth token and endpoint" commit 13d80a7e8f9e3d05c3ef6e4812c8daee027fbfdc Author: Brian Waldon Date: Thu Aug 2 14:22:27 2012 -0700 Add missing copyright headers A few files were missing copyright headers: * glanceclient/common/http.py * glanceclient/v1/__init__.py * glanceclient/exc.py Change-Id: Ibbd53cd49f9367994de66a30601b3aefe1a8d6ee commit 61fdefbdd89284b53f60a172d27f8cde6f9223e7 Merge: a511e6a ba83562 Author: Jenkins Date: Thu Aug 2 18:12:43 2012 +0000 Merge "Update python-keystoneclient version dependency" commit a511e6a05e0f8763fb57dea3af86602a7883604c Author: Brian Waldon Date: Wed Aug 1 19:43:58 2012 -0700 Add legacy compat layer to v1 shell All legacy CLI commands should work as expected with a few minor exceptions: - no upload animation - no interactive pagination - help/usage output has changed Deprecated options are indicated as such in their usage info. Deprecated commands have descriptions that label them as such. Related to bp glance-client-parity Change-Id: I584b2447361967228bea332e14880e18db12fca8 commit 2541f3ce840492555160f8f31d775f443628fe9a Author: Brian Waldon Date: Thu Jul 26 16:10:42 2012 -0700 Allow CLI opts to override auth token and endpoint Previously, both --os-auth-token and --os-image-url had to be provided in order for either of them to be used in any API requests. This breaks the tie between them and allows a user to override either the auth token or the endpoint returned by Keystone independently of one another. Fixes bug 1029586 Change-Id: I8b81be723286c546d9cbd97c8b7d7aa89c03b2d4 commit ba83562b24f735c1ae9b9e5edefe8f2f1466b233 Author: Brian Waldon Date: Wed Aug 1 16:22:23 2012 -0700 Update python-keystoneclient version dependency We need version v0.1.2 for the 'insecure' keyword to work in keystoneclient.v2_0.client.Client Change-Id: I0f8564d5e9067f8e7fcc2f3fb48cd09757977f38 commit 5f5d8ee212bce06f1e964ff1db6c1c4ca41a351b Merge: dc7f781 6c8e034 Author: Jenkins Date: Thu Aug 2 00:19:01 2012 +0000 Merge "Refactor http request/response logging" commit dc7f7817ca694ad0072024d3412122066254139a Merge: c24ea3f 1f44aff Author: Jenkins Date: Wed Aug 1 23:58:19 2012 +0000 Merge "Add exceptions for 500 and 503 HTTP status codes" commit c24ea3f84876ca4087710fc15a94c1a82ec1d5f0 Author: Brian Waldon Date: Mon Jul 30 20:50:27 2012 -0700 Stop looking for v2 image in container The v2 API no longer returns images in JSON containers like '{"image": {...}}', so stop trying to decode the responses as if it does. Fix bug 1031185 Change-Id: I5209fe76445d4195b12944146a0ef190883f363f commit 5189d58d6363f2f73ef2bd43eb56fd34ea611e28 Merge: bb0b59b d004b1a Author: Jenkins Date: Tue Jul 31 03:39:33 2012 +0000 Merge "Fix coverage reporting test." commit bb0b59bda131c9f1a65ffe99d95822fc29c5e59d Merge: 158f7cc bb28293 Author: Jenkins Date: Tue Jul 31 03:38:44 2012 +0000 Merge "Honor '--insecure' commandline flag also for keystone authentication" commit 1f44aff399e9ed6008d96b047a7c82623b980e0a Author: Brian Waldon Date: Sun Jul 29 23:08:02 2012 -0700 Add exceptions for 500 and 503 HTTP status codes Add glance.exc.InternalServerError and .ServiceUnavailable to represent HTTP statuses 500 and 503. Users will definitely see these from Glance, so let's be nice and map them to handy exception classes. Change-Id: I8e8fcda532455793ea4d0f08a23f7c92b68c186c commit 6c8e0342c0a7c285dab2606cab1b2cc9c3b5b90a Author: Brian Waldon Date: Sun Jul 29 22:12:37 2012 -0700 Refactor http request/response logging Using the --debug flag or the GLANCECLIENT_DEBUG env var, a user will see http requests and responses in great detail. Requests are formed into proper curl commands while responses are printed just as they would as if the curl request provided were executed. Response bodies will not be printed if they are application/octet-stream. Change-Id: I9c9c5d6ec9f481091c944e596d073da3739795b6 commit 158f7ccd743b9fee12d60b10cf0fe696d5d4ed34 Author: Brian Waldon Date: Sun Jul 29 21:08:26 2012 -0700 Fix --debug CLI option The --debug argument has been ignored since httplib2 was replaced with httplib. This re-enables the --debug flag as an equivalent to the env var GLANCECLIENT_DEBUG. Fixes bug 1030700 Change-Id: Ib653049eea2f18c4cc2f8f8aac7884245afd0f04 commit d004b1a73a395379846464950153f7cc04cdc918 Author: Clark Boylan Date: Fri Jul 27 14:39:58 2012 -0700 Fix coverage reporting test. A change was committed that modified the glanceclient.common.__init__.py file that seems to conflict with the NOSE_WITH_COVERAGE environment variable. Run the coverage tests like novaclient and swiftclient to get around this problem. Change-Id: Id9a655a0207d3b16a619972ebaecc87387cf784e commit bb282936a01221821988d51a3896907f8c3404ff Author: Sascha Peilicke Date: Thu Jul 26 15:48:23 2012 +0200 Honor '--insecure' commandline flag also for keystone authentication Currently, keystone auth fails with self-signed certificates. Change-Id: Ice89bcd0662038260bc4bd12058972bb35e61e3b commit 1e744f162ece85f14120a16180ed0f83fe9f1e09 Author: Brian Waldon Date: Tue Jul 10 20:51:00 2012 -0700 Replace httplib2 with httplib as http driver * This allows us to send truly chunked responses to users * Handle bad connection url schemes with a new InvalidEndpoint exception * Fixes bug 1023240 Change-Id: I34500987f51d4e0c6e1f89ecf93853de3fcbb1c3 commit 71a0caece87727e07bc34ae265dda58ca3e1e6d2 Author: Sascha Peilicke Date: Fri Jul 20 10:18:02 2012 +0200 Clarify usage of --insecure flag Change-Id: If19a7aab92350fb68e447a0ffe8a97e079d762e4 commit 070f176abf0f3f592469be385c3c530de19eba16 Merge: 0f628b1 c201f24 Author: Jenkins Date: Thu Jul 19 21:26:04 2012 +0000 Merge "Relax prettytable dependency to v0.6.X from v0.6" commit 0f628b19cbbe53ea29de02f4ae84bc893138820f Author: Brian Waldon Date: Sat Jul 14 04:39:27 2012 +0000 Add pagination to v1 image-list * Use recursive generator function to make subsequent requests to the v1 detailed images resource * 'limit' continues to act as the absolute limit of images to return from a list call * 'page_size' indicates how many images to ask for in each subsequent pagination request * Expose --page-size through the cli * Convert v1 images tests to use strict url comparison * Drop strict_url_check from FakeAPI kwargs - now the functionality is always active and tests must directly match fixture urls * Fix bug 1024614 Change-Id: Ifa7874d88360e03b5c8aa95bfb9d5e6dc6dc927e commit 570e64d91f3b85ef6c4586f7b3a59183e5bb1d3e Merge: 5f380c1 da36046 Author: Jenkins Date: Thu Jul 19 20:19:30 2012 +0000 Merge "Wrap image data in iterator" commit 5f380c1f91debaa8aa000f221af4ff02bce7bc98 Merge: a8a3f4d d88d8fc Author: Jenkins Date: Wed Jul 18 01:56:22 2012 +0000 Merge changes Ib98e912a,Ib98e912a * changes: Add pagination to v2 image-list Prevent links from being printed in v2 CLI commit a8a3f4d67b02225cd3831ae10fb52504b8a0c5ac Merge: 6206f42 8bf9e11 Author: Jenkins Date: Wed Jul 18 01:02:07 2012 +0000 Merge changes Ib98e912a,Ib98e912a,Ib98e912a,Ib98e912a,Ib98e912a * changes: Align print_dict to the left Convert v2 images list method to generator Replace static v2 Image model with warlock model Add support for viewing a single image through v2 Rewrite link parsing for finding v2 schemas commit 6206f420284c77f6c0b2d46b05c5ddc3df99d785 Author: Brian Waldon Date: Tue Jul 10 16:32:01 2012 -0700 Update README usage examples * Update the python snippet to reflect reality * Fix broken links * Remove superfluous text Change-Id: I4b7e9aae35cc49e5fa89ca33d2399784c2afd029 commit c201f24d5ad0a4ad008921664d956b7d0ac2dd61 Author: Brian Waldon Date: Tue Jul 17 15:29:51 2012 -0700 Relax prettytable dependency to v0.6.X from v0.6 Change-Id: Ide7247ba444b60179d9c76c43dfaa43c025b69c9 commit d88d8fc46286cf542f8b0553734ac9ff1b192773 Author: Brian Waldon Date: Sat Jul 14 03:06:03 2012 +0000 Add pagination to v2 image-list * Use a recursive generator function to iterate over the image container. The presence of next links are indicators to continue pagination while their value drives the location of the next page. * A user can pass in --page-size on the command line, or page_size when using the controller directly, to control how many images are requested with each subsequent paginated request. Default page size is 20. * Add a flag (strict_url_check) for the FakeAPI class to control whether it chops off query params when trying to match a request to a fixture. * Related to bp glance-client-v2. Change-Id: Ib98e912a7af0bb570b4fd738733edd9b837d1a12 commit 95a7f9dffeae6d108c7e22475efca7df9d5ffd97 Author: Brian Waldon Date: Sat Jul 14 02:02:58 2012 +0000 Prevent links from being printed in v2 CLI Nobody wants to see links in a human interface. This prevents the file, access, self and schema links from being printed when calling image-show or explain. Related to bp glance-client-v2 Change-Id: Ib98e912a7af0bb570b4fd738733edd9b837d1a11 commit 8bf9e112447f97b57d744465cef99b823f544c03 Author: Brian Waldon Date: Sat Jul 14 01:54:29 2012 +0000 Align print_dict to the left Change-Id: Ib98e912a7af0bb570b4fd738733edd9b837d1a10 commit e5f038b62a9edf3f19390f2437c8a10616426903 Author: Brian Waldon Date: Sat Jul 14 01:16:31 2012 +0000 Convert v2 images list method to generator We will want this to be a generator as soon as we implement pagination. Let's establish the interface now. Related to bp glance-client-v2 Change-Id: Ib98e912a7af0bb570b4fd738733edd9b837d1a07 commit c398af18b0b8fb5fb075be22563812e179290b2a Author: Brian Waldon Date: Sat Jul 14 01:11:22 2012 +0000 Replace static v2 Image model with warlock model * Add warlock v0.1.0 as a dependency * Generate a pythonic, self-validating Image model using warlock * Add raw method to Schema model * Related to bp glance-client-v2 Change-Id: Ib98e912a7af0bb570b4fd738733edd9b837d1a04 commit b6cef9d145f870dd717843751f0c5d68867e07d5 Author: Brian Waldon Date: Fri Jul 13 23:05:38 2012 +0000 Add support for viewing a single image through v2 * Add image-create command * Add tests for Image model, Controller.get, and Controller.list * Related to bp glance-client-v2 Change-Id: Ib98e912a7af0bb570b4fd738733edd9b837d1a06 commit f0445a1b443182cf77e27038cc5f90c424e00c62 Author: Brian Waldon Date: Fri Jul 13 22:03:22 2012 +0000 Rewrite link parsing for finding v2 schemas What we called 'links' are no longer returned in a container of objects, they are top-level entity attribtues. This fixes the parsing of the entities to look in the correct place when trying to locate a specific schema. Add a helper for printing to stderr and exiting with a non-zero exit code. Map 'name' to 'Attribute' when explaining a schema. Related to bp glance-client-v2 Change-Id: Ib98e912a7af0bb570b4fd738733edd9b837d1a05 commit 53acf1a0ca70c900267286a249e476fffe078a9f Author: Brian Waldon Date: Thu Jul 12 18:30:54 2012 -0700 Establish the supported importable interface * Consumers of this client should not depend on being able to import any module other than glanceclient and glanceclient * The only attributs of the glanceclient module are Client and __version__ * The attributes of the glanceclient.exc modules have yet to be locked down * glanceclient.common.exceptions was replaced with a placeholder module until consumers of it are updated Change-Id: Iea9648cd06906d65764987c1f2ee5a88ebeee748 commit 49bc6f94f29ee7286601be0451ab3dafc82a0750 Author: Brian Waldon Date: Wed Jul 11 15:28:46 2012 -0700 Add --is-public to image-create This moves image-create closer to image-update by adding --is-public and hiding the help output of --public. The --public option will be removed once devstack no longer depends on it. Fix bug 1023632 Change-Id: I2c58655ba56eef1fa486246618c4fb5bd3c6c8cf commit c0974328284a99b3c674ca0612277f201a6a1ab8 Merge: 1a8ab3c b7f476e Author: Jenkins Date: Thu Jul 12 22:55:10 2012 +0000 Merge "Translate is_protected to protected" commit 1a8ab3c3f258d644f7ee1808ebb411ec2a5a2ef3 Merge: 4e9bac2 d8433ee Author: Jenkins Date: Thu Jul 12 22:55:02 2012 +0000 Merge changes I02ddeb59,Ife231377 * changes: Change --protected to --is-protected in create Properly map boolean-like arguments to True/False commit 4e9bac2d0a8dc3a5dbdec51854621cf57df5730c Merge: a814c15 d2ab652 Author: Jenkins Date: Thu Jul 12 19:42:38 2012 +0000 Merge "Remove AuthorizationFailure exception" commit da360462a54501178753571cdfec800e899f38ae Author: Brian Waldon Date: Wed Jul 11 19:34:28 2012 -0700 Wrap image data in iterator This is establishing the API for a future optimization. We want to be able to offer true socket-level caching, but can't do that with httplib2 right now. For now, we will just fake the optimization by returning an iterator over the image body, which happens to already be fully loaded into a string. Change-Id: I2d36e3cdd45b26d7c7c27ba050bf6a4b5765df6c commit b7f476e2112c7d272bbf1e508f2aa8c4641efe68 Author: Brian Waldon Date: Wed Jul 11 16:32:53 2012 -0700 Translate is_protected to protected When creating or updating an image, translate the 'is_protected' argument into the proper 'protected' image attribute. Fix bug 1023653 Change-Id: Icfe6c38e4fda098ce3f90fd94c8fbbc18be2f4a8 commit d8433ee40a6a8ea3af70572ad1928edd09636309 Author: Brian Waldon Date: Wed Jul 11 16:19:08 2012 -0700 Change --protected to --is-protected in create Make image-create match image-update when specifying a specific value for 'protected'. Fix bug 1023650 Change-Id: I02ddeb59c1f6882b206279a71f7af8889ce4602c commit db1fabed118a771dfb85024c67ae348119aec412 Author: Brian Waldon Date: Wed Jul 11 16:25:13 2012 -0700 Properly map boolean-like arguments to True/False --is-public and --is-protected are now evaluated as True and False Fix bug 1023652 Change-Id: Ife2313770eebc176e7744711956aed20f16576a5 commit a814c154652312e6c5f40674933275a0a6d2c647 Author: Monty Taylor Date: Wed Jul 11 11:08:58 2012 -0500 Add ability to get version information in python * A user can access glanceclient.__version__ to get a string representing the version of the installed library. * Add openstack-common's 'version' module. Change-Id: Ib14c561d8ac0b126617a20acfbd5fdb61c54f2c7 commit c315c5274f8271c83550420236cd28fc1ffa2dd0 Author: Monty Taylor Date: Wed Jul 11 10:44:36 2012 -0500 Latest setup goodness. Upgrade the common setup code to the latest versions, and use setuptools-git for sdist tarball generation. Change-Id: I81eca9199b7d330ef8ec80482565a75f8475a78c commit d2ab65255f6b9ee0b35fd669f4b35348ba107dd3 Author: Brian Waldon Date: Tue Jul 10 21:10:40 2012 -0700 Remove AuthorizationFailure exception The AuthorizationFailure exception isn't used anywhere, so remove it. Fix bug 1015940. Change-Id: Ie6da74b63e3d1658c8ae26c272222f00f1209e38 commit cf8613e76d4682c924a900a17b43197d569d7ad3 Author: Brian Waldon Date: Mon Jul 9 15:39:29 2012 -0700 Preserve image properties on update * By default, image properties should not be deleted on image update. * A user can specify --purge-props through the CLI or purge_props as a keyword argument to ImageManager.update to override the default behavior and force properties to be deleted. * Fixes bug 1022758 Change-Id: Ib079378cb765552fc29a66913aefbbcd934d2065 commit 0e8ab72357b5caf9850a6a49eda718311cefe269 Author: Brian Waldon Date: Mon Jul 9 15:18:44 2012 -0700 Add --file to image-update and correct bad name * Add the --file option to the image-update action, as it was left out of a previous patch * Additionally, change a bad reference (args.fields) to args.file * Fix bug 1022750 Change-Id: Idde127ec3f138f718d671b2133d50debec26236e commit 88a039488bcaa9e06f7eee0fada123ae24b5ba4a Merge: 7a2b397 a1691dd Author: Jenkins Date: Mon Jul 9 17:42:13 2012 +0000 Merge "Allow image filtering by custom properties" commit 7a2b39727b883d5a0bb6d33ef1e8fb0d2265f268 Merge: 12c51bb e7db533 Author: Jenkins Date: Mon Jul 9 17:42:11 2012 +0000 Merge "Expand v1 image-list filters" commit 12c51bbcad9fadf289792d39ee510b8a1221d6fd Merge: ce806d6 b05a9c0 Author: Jenkins Date: Mon Jul 9 17:42:10 2012 +0000 Merge "Add --timeout option to cli" commit ce806d615b5e9c1ac4b7e3c42ee9fa08092069a1 Merge: cc9e28a 9aad246 Author: Jenkins Date: Mon Jul 9 17:34:41 2012 +0000 Merge "Add size filtering to image-list action" commit cc9e28a4c16686a2ea29640bcc7d04c3de1388f0 Merge: 2713ca1 ac7121f Author: Jenkins Date: Thu Jul 5 20:56:15 2012 +0000 Merge "Use PyPI for keystoneclient." commit a1691ddc10b7fa4df0ff8a434d060ee1762773c0 Author: Brian Waldon Date: Wed Jul 4 15:46:29 2012 -0700 Allow image filtering by custom properties A user can filter a list of images by passing in a 'properties' sub-dictionary inside of the 'filters' keyword argumen to ImageManager.list(). The same functionality can be used through the CLI through the use of one or more'--property-filter' options. Related to bp glance-client-parity. Change-Id: I7d119174d83faa894dde557e1944289de296a02c commit e7db533bc0222f912ec0405e101e3dafbd49ece3 Author: Brian Waldon Date: Wed Jul 4 15:24:08 2012 -0700 Expand v1 image-list filters Add comparison filters for the v1 image-list command: --name, --status, --container-format, --disk-format. Related to bp glance-client-parity. Change-Id: I27377764ea5543a4bef593f0a731b09a914a9265 commit b05a9c0200f68426b162a23ca6e6612381a1d1b7 Author: Brian Waldon Date: Wed Jul 4 14:32:52 2012 -0700 Add --timeout option to cli Create a --timeout option to allow users to provide a custom timeout for requests from the command line. The timeout functonality already exists in both v1 and v2 client classes. Related to bp glance-client-parity Change-Id: Ic91de5eae2824b37f6aad3adc5fda28b9674250e commit 9aad246f0e97942bdfd58b755855af3b8453734a Author: Brian Waldon Date: Wed Jul 4 14:24:38 2012 -0700 Add size filtering to image-list action Add --size-min and --size-max options to image-list to represent the size_min and size_max filters passed to the ImageManager.list method. Related to bp glance-client-parity Change-Id: Icb5458c3ed26ea754cff6360b741b3af99d1beb5 commit 2713ca1bdce9ae5d01fa04722bb4d55b3fa1b12d Author: Adam Gandelman Date: Mon Jun 25 19:04:28 2012 -0700 Allow image upload from local file to v1 API Allow an image to be read from a local file as an alternative to stdin (which remains the default). Change-Id: I81070ded9c505df7924c4efd5ae54cf3c0fa534d commit ac7121fc0e7ad437858cdf7dfe7a529961406adc Author: Monty Taylor Date: Mon Jul 2 17:46:16 2012 -0400 Use PyPI for keystoneclient. Change-Id: Ib1ce43cde3e6848a873778dd9fc6aa4709df6452 commit e9b8f8ec2fddb07d5d5e8cf1745e8e4720dd78da Author: Dan Prince Date: Wed Jun 27 12:04:01 2012 -0400 Switch CLI to support underscores and dashes. Update the glanceclient CLI to support both underscores and dashes. Dashes are prefered and show up in help. This will hopefully keep the CLI more consistent with the other OpenStack client projects like Nova, Swift, Keystone in addition to the old Glance client which all seem to prefer underscores to dashes. Fixes LP Bug #1018467. Change-Id: I80d7a19f94033554f7f639166911726de4a5159f commit 089d509f350b01c8345cfb7b9b33510a60043861 Author: Monty Taylor Date: Tue Jun 26 13:58:07 2012 -0500 Split reading of versioninfo out into a method Make the read_versioninfo to match write_versioninfo. Additionally, there is an edge case where if the code is installed from a github zipball, versioning info is missing. Now that we're using this, there should be virtual no instances where a zipball will be easier or less cost than an sdist created tarball, all of which should be public and accessible, but during the transition, we need to account for the codepath. Change-Id: Icd3fe97c6341bb04da27adc55a85f1ab6b525c46 commit 993a7b7dd670f9ea9ade230bd48519b83af85dbf Merge: d6e0a03 03efd16 Author: Jenkins Date: Tue Jun 26 16:58:08 2012 +0000 Merge "Add support for tag-based version numbers." commit 03efd1689616ada606cb4cd7c0b51d2e1935ecfa Author: Monty Taylor Date: Mon Jun 25 09:49:59 2012 -0500 Add support for tag-based version numbers. Change-Id: I9b0e24f65e9b79c39bdf279b7af9c1040ded7952 commit d6e0a03a937841ee509c37003762fd92c9b762ef Author: Bhuvan Arumugam Date: Sat Jun 23 22:27:11 2012 -0700 Support --os-endpoint-type in glanceclient. Bug: 993993 * glanceclient/shellp.py OpenStackImagesShell.get_base_parser(): Parse --os-endpoint-type argument. Default to one defined in os.environ. OpenStackImagesShell._authenticate(): Define endpoint type based on command line argument. Use 'publicURL', if it's not specified in command line and also not defined in os.environ. OpenStackImagesShell.main(): Pass this value to authenticate. Change-Id: I0c0cde5212198eec2a7d75fb2a7cad1cde048c7c commit 99e872a57bf20390949534e94c3929851e827d82 Author: Brian Waldon Date: Thu Jun 21 13:06:18 2012 -0700 Hook up GET /v1/images/ This allows us to get raw image data out of the API! Related to bp glance-client-parity Change-Id: Id5f55553d2ff3b7bf58515062afdfd4b9b183a54 commit 6e1157059efdaed3a0d638efc6b4a1e4db955832 Author: Monty Taylor Date: Wed Jun 13 15:55:21 2012 -0400 Add initial docs. Change-Id: I1f8407597105a914945c932ff55945c8005e273c commit a8d7043266a5cb070b602b13cf7a7421cffe8467 Author: Clark Boylan Date: Fri Jun 8 15:22:23 2012 -0700 Edit build_sphinx options. To better facilitate the building and publishing of sphinx documentation by Jenkins we are moving all openstack projects with sphinx documentation to a common doc tree structure. Documentation goes in project/doc/source and build results go in project/doc/build. Change-Id: Idf3e4472f91a1f5ae36e64b339bef99d4d960b88 commit 0d53efe930045bf8dbcb3083ddd0c5871aa1a06b Merge: 0246e99 7f48506 Author: Jenkins Date: Fri Jun 8 21:29:59 2012 +0000 Merge "Add 'explain' command to v2 that describes schemas" commit 0246e99817701a25392042ec3b2cb9ce6e2b27f3 Merge: 2822748 1c3db6c Author: Jenkins Date: Thu Jun 7 22:43:34 2012 +0000 Merge "Minimize tox.ini" commit 1c3db6c756aee53d1acbdaff3c45b144d6d45d8b Author: Brian Waldon Date: Thu Jun 7 14:51:29 2012 -0700 Minimize tox.ini Change-Id: I77d0873cbd789726b93b059429d742f1cbdc57c1 commit 7f48506781ea1a50ed7760c41a34474f91979f06 Author: Brian Waldon Date: Thu Jun 7 14:01:50 2012 -0700 Add 'explain' command to v2 that describes schemas At its core, this is adding the ability to finx a schema through published links and convert it to a usable object. Related to bp glance-client-v2 Change-Id: I7b38ad091c6b0ad80197eb789503cf73989893e5 commit 2822748bc134724c43f327041428557b7bfc3e3a Author: Brian Waldon Date: Thu Jun 7 14:41:14 2012 -0700 Stick prettytable at v0.6 Doing this so we can align columns reliably Change-Id: Ibdbc6f3df08d6f9c45b6e0a0f5a2440ba2637dbd commit 0935e38113b5db8f2611afd0cff66a1d74a52c0e Author: Brian Waldon Date: Thu Jun 7 14:37:56 2012 -0700 Add tests dir to pep8 command Change-Id: I28120c67e157277202976696eea37023fb719d6d commit 2e81493192ac367211d3fa04feab7d161075409e Merge: bcff183 a1f8ea1 Author: Jenkins Date: Wed Jun 6 20:48:14 2012 +0000 Merge "Auto generate AUTHORS file for glanceclient component." commit bcff183f9a14d255b778a4b8af5869f85bf171b2 Author: Brian Waldon Date: Mon Jun 4 09:01:39 2012 -0700 Set pep8 dependency at v1.2 Additionally, remove the pep8 dep override in tox.ini so the dep we set in tools/test-requires is actually used Change-Id: Iceb6d7a5dd8eceeebc1408e8a5edfabb48462d3e commit 4b62848a4cbfe8d270a3386fcd43393bbefb973f Author: Brian Waldon Date: Thu May 17 14:33:43 2012 -0700 Add minimal support for the v2 API This only allows you to run image-list, but sets up a framework that we can use to fill in the rest of the v2 functionality. * Related to bp glance-client-v2 Change-Id: I8827e36fdcf79fe402990a6d05898ec00cbd54c6 commit a1f8ea1c7f9849e0fb64c547d6456d2c3c38c5e8 Author: Bhuvan Arumugam Date: Fri Jun 1 23:38:12 2012 -0700 Auto generate AUTHORS file for glanceclient component. Bug: 976267 Now that git commits are gated by CLA, we shouldn't enforce committers to add an entry in AUTHORS file. The AUTHORS file should be generated automatically, based on git commits. This commit fixes the problem. * AUTHORS Remove this file. * .gitignore Add AUTHORS file. * glanceclient/openstack/common/setup.py Sync changes from openstack-common. * setup.py Generate AUTHORS file before creating the package. * glanceclient/shell.py Pep8 fix. * tests/test_authors.py Remove this test case. Change-Id: I9e9d4da5ca3b147b483250dcf25a3b2a840123c2 commit b9b897252868732763de60d829b5c8de188adf38 Author: Chuck Short Date: Tue May 29 08:31:48 2012 -0400 Include ChangeLog in tarball Include ChangeLog when generating the tarball. Change-Id: I4a65b82928e3f55775cfa5d3d51b3c3b4e1ef079 Signed-off-by: Chuck Short commit 32ba1385ef907eba02a77ba1c3ea3ef1b60b8da6 Author: Monty Taylor Date: Fri May 25 11:30:01 2012 -0400 Properly install from zipball. Change-Id: I505a42ec11b388e3ee5c818a1aadf0f70d5565c5 commit 405d2494e382d9fcd6735883fcfd9dd13bf943e0 Author: Michael Basnight Date: Thu May 24 22:35:14 2012 -0500 Adds support for --insecure. fixes lp#1004281. Change-Id: I464e39515a7172bfb72921a92f46d31baac466d8 commit a0f6ef9c6bf0469c6b1328184f3fe0a97884f6cc Author: Monty Taylor Date: Thu May 24 09:46:38 2012 -0400 Fix the zipball change. -f downloads things from a url. -e does them for vcs. Change-Id: I14c5edd0ca163112baea08c81771f35f13930264 commit ee7ea1648183215e10736a42c736ac02ed9bee8a Author: Monty Taylor Date: Wed May 23 11:09:43 2012 -0400 Replace git url with github zipball. distribute does not grok git urls, but auto-generated zipfiles are a short-term workaround until we've got pypi uploads sorted. Fixes bug 1003328 Change-Id: Iaded7245cea7a30c2f421c7b48ece9823aaf152f commit 995372fff77677991fe50adf3e9a35deac867f7b Merge: 78cd3d9 3943699 Author: Jenkins Date: Tue May 22 23:55:35 2012 +0000 Merge "Refactor HTTPClient to use two request methods" commit 394369942700567da767b85920e0b3890119a764 Author: Brian Waldon Date: Tue May 15 10:01:47 2012 -0700 Refactor HTTPClient to use two request methods Rather than depend on magic, I would prefer that we explicitly call two different request methods: json_request and raw_request. The former will encode/decode request bodies to and from JSON, while the latter will not. Change-Id: I6a429a5975993f71df85df55f11c5d51c050c289 commit 78cd3d9e162cf8a9d3f50433fad094fbc9e75e3a Author: Thierry Carrez Date: Tue May 22 11:37:58 2012 +0200 Add missing files to MANIFEST.in Some files were missing from tarballs generated using 'python setup.py sdist', this adds them to MANIFEST.in. Fixes bug 1001217. Change-Id: I0c99deba3f24989b34cff27b5d3cd1c999cd08fa commit 623a0898f8d36beeb1cb7523fde7339bbe10e0bd Author: Brian Waldon Date: Thu May 17 14:37:21 2012 -0700 Add importutils from openstack-common As a side-effect, openstack.common.setup is getting updated Change-Id: I9f8696ec5c82ef32872e1a974102dcd179eb70f9 commit 399d0bbdefd66cf47fa1e21402adc2fad6b40bbf Author: Michael Basnight Date: Thu May 10 11:38:11 2012 -0500 Adding service type as configurable shell option fixes lp#997698 Change-Id: I5179a2ed5f32a8e7253806f6f9b02de3c06913ed commit b31c272ae12e82d0ecc6d568be421a99f8014365 Merge: 93c1755 332858d Author: Jenkins Date: Mon May 7 23:36:46 2012 +0000 Merge "Remove printt" commit 332858d56b34a0bac41fc48484680f3660a2a0e5 Author: Dean Troyer Date: Mon May 7 10:25:54 2012 -0500 Remove printt prettyprint 0.6 removed printt at the last minute, replace with get_string Fixes bug 995826 Change-Id: I9a25efc3d723ab0208ea88fc6431a95cc9176acb commit 93c1755acdaa6166ab0d6e27f89673a9a0b4e5ee Author: Chuck Short Date: Mon Apr 30 08:50:02 2012 -0400 Added condition requirement to simplejson simplejson is a part of python 2.6. Change-Id: I3a1d776918c8707f21532fe3b043a039b72d6704 Signed-off-by: Chuck Short commit ae58edcba737a78fcae299e0bf76e30ba5492e60 Author: James E. Blair Date: Thu Apr 26 17:40:10 2012 +0000 Use tox for running tests locally. See: http://wiki.openstack.org/ProjectTestingInterface Tox can manage virtualenvs, and is currently doing so for running tests in Jenkins. It's just as, or more, useful for running tests locally, so this starts the migration from the run_tests system to tox. The goal is to reduce duplicate testing infrastructure, and get what's running locally on developer workstations as close to what is run by Jenkins as possible. This patch removes run_tests.py, and the scripts that manage .venv. It makes run_tests.sh call tox to facilitate the transition for developers used to typing "run_tests.sh". Developers will need tox installed on their workstations. It can be installed from PyPI with "pip install tox". run_tests.sh outputs those instructions if tox is not present. New facilities are available using tox directly, including: tox -e py26 # run tests under python 2.6 tox -e py27 # run tests under python 2.7 tox -e pep8 # run pep8 tests tox # run all of the above tox -e venv foo # run the command "foo" inside a virtualenv The configuration of the openstack nose plugin is removed from setup.cfg and added to the nosetests command line arguments in tox. It is used when running tox from the command line, so the enhanced, colorized output is visible to developers running the test suite locally. However, when Jenkins runs tox, the xunit plugin will be used instead, providing output natively understood by jenkins which is much more readable in that context. Change-Id: Id678c2fb8a5a7d79c680d3d1f2f12141f73dc8a6 commit 3344eac545dc9224dca49fa937e727666f7cee2d Author: Gabriel Hurley Date: Thu Apr 12 16:35:36 2012 -0700 Adds filter support to images.list(). This existed in glance.client, and should exist in the new client as well! :-) Change-Id: Iea8c55fcb0f0d30d6dc138072354c3f20d1288cf commit 1f106a3bd919b5b656b14ccd22c2c23b991ad4ea Author: Brian Waldon Date: Wed Apr 25 20:11:56 2012 -0700 Add '.tox' to .gitignore Change-Id: Ie29752e7e4194fca4cbe82c161d7cac9ebd0848c commit a8ae91b5dfc40463b56f4302987af0800047bec5 Merge: a88bfee 877f144 Author: Jenkins Date: Wed Apr 25 03:49:52 2012 +0000 Merge "Add fields to image-list" commit a88bfee9c5208aaa0613d5f06b941621deeda5e8 Merge: 87e0948 e293676 Author: Jenkins Date: Wed Apr 25 03:41:40 2012 +0000 Merge "Strip version from service catalog endpoint" commit 877f1440136d8c33cd42fb10790b025d7e1c105e Author: Dean Troyer Date: Fri Apr 13 17:12:47 2012 -0500 Add fields to image-list Fields were missing from image-list that were present in the old index: 'Disk Format', 'Container Format', 'Size' Change-Id: Ia86caec1938560c56292c0f3028ee48e774d0057 commit e2936766f730018aef502e61d753ac6ab0a66085 Author: Dean Troyer Date: Mon Apr 23 17:00:39 2012 -0500 Strip version from service catalog endpoint This client includes the API version in the URL directly where the former practice was to include it in the service catalog endpoint. This change removes the version from the last component of the SC endpoint (if present) for transition purposes. Note that this does not generalize to the other APIs where the version is not the last component of the SC endpoint. Change-Id: Ie04c38d80b17a171482e195aa1c633b6b6974042 commit 87e0948349ef257e23f8625ec3afa6275f2afa99 Author: Dean Troyer Date: Fri Apr 13 14:30:09 2012 -0500 Fix image-create using pipelines The return value of ImageManager._get_file_size() is passed on to the glance server in the create api call. Returning None causes the server to reject the request as malformed. Add dtroyer to AUTHORS Change-Id: I02c90e2db5fbd1e49d1516550c806f26dae83fb9 commit d3185dd3b4d01a24025840c2dac166a84ba359d3 Author: Brian Waldon Date: Tue Apr 10 09:40:36 2012 -0700 Allow tenant name to be used in authentication Change-Id: If0bdf67143d64172db0b665fc07c165fabc9486a commit 73ea207a390f8026b371e1037caec8cbc5d912ef Merge: c80e264 cc47971 Author: Jenkins Date: Mon Apr 9 21:57:22 2012 +0000 Merge "Updated depend processing to norms." commit c80e2640c69be06b8eb0a8205868081b15231f29 Author: Monty Taylor Date: Thu Apr 5 19:06:57 2012 -0700 Make tox cover output coverage.xml. Change-Id: I8719be2f627c9f2bf33e7752cdd7348c14943f7a commit 292085445b784207e76a9b9bda1657a1c041542f Author: Monty Taylor Date: Thu Apr 5 19:04:42 2012 -0700 Add Sphinx to test-requires. Change-Id: I8dec9a2e80e0ad8ceb6805a731a26585d9af92b3 commit 66e967685902c5074d67350d8c98b3e9a4df6302 Merge: 9829db1 1458d0e Author: Jenkins Date: Thu Apr 5 06:37:44 2012 +0000 Merge "Add AUTHORS test case" commit cc479717cf675565697d8a7d98a4b74dc41001e9 Author: Monty Taylor Date: Tue Apr 3 21:54:43 2012 -0600 Updated depend processing to norms. Change-Id: I843c30acaf813614def68b936eb5a982e5a9b242 commit 9829db1c073be3674241caa21c38369d1b7cea73 Author: Brian Waldon Date: Tue Apr 3 20:48:20 2012 -0700 Fixing pep8 errors Change-Id: I7c0ffb5626ee030fca7daf8ae10cb17e221e2133 commit 1458d0ea85d0147b3cf8e2fb80ff40024c3f3c30 Author: Brian Waldon Date: Tue Apr 3 20:18:11 2012 -0700 Add AUTHORS test case Change-Id: Ib81aee90fef3b2c101bb6b737e677339638b3cad commit 71036387824eed22a8053c2141239029e3b4c9d4 Author: Monty Taylor Date: Wed Mar 28 09:31:56 2012 -0700 Added gitreview file. commit 538a9e30a26b177c03d61bd7bfb48f157ea4698f Author: Brian Waldon Date: Tue Apr 3 19:26:38 2012 -0700 Adding id for image members commit c72e4dd2b581366c85babbda8b0a5aa23de70363 Author: Brian Waldon Date: Tue Apr 3 17:39:32 2012 -0700 image membership management works commit b87b1b5086b0351c06865ef7b6e9e6057fead0b6 Author: Brian Waldon Date: Tue Apr 3 17:01:48 2012 -0700 Adding support for passing image data through cli commit d1912624139eaf40f522666da3fdb840a98ab020 Author: Brian Waldon Date: Tue Apr 3 16:00:49 2012 -0700 Image update works commit d5bb951e5f3892b6dd3121b21ac8409c57cb5cec Author: Brian Waldon Date: Tue Apr 3 14:43:04 2012 -0700 More complete image creation commit fc02efd1c7b033b67b1ecc6a00b06155f9948cdd Author: Brian Waldon Date: Tue Apr 3 10:01:49 2012 -0700 Correct keystoneclient egg name in pip-requires commit 59e586ed942b94f973ce2a36861b07a0671cfb71 Author: Brian Waldon Date: Tue Apr 3 10:01:08 2012 -0700 Adding image-create action commit d75a029a010bd90e59f1dd26099ab170a18970c7 Author: Brian Waldon Date: Mon Apr 2 16:26:19 2012 -0700 Adding shared-images support commit ca8434f2c3c54e362b1250ca18ad3e741b14b436 Author: Brian Waldon Date: Mon Apr 2 15:44:43 2012 -0700 Image members bones commit 664f37067790c9c27e2bcad0b9b18f5367bdca83 Author: Brian Waldon Date: Mon Apr 2 14:08:03 2012 -0700 Basic testing commit 440ffec57577e1bf7414afaaeb13490536c1f797 Author: Brian Waldon Date: Mon Mar 26 23:44:42 2012 -0700 Update version to 2012.2 commit 883d22d032ed0fc92a3b3b66871f5da8e82b6e96 Author: Brian Waldon Date: Mon Mar 26 23:29:20 2012 -0700 Further cleanup * README is now relevant * Auth now properly skipped with os-image-url and os-auth-token provided commit c530de638916d29c609f66194569f57234a68289 Author: Brian Waldon Date: Mon Mar 26 22:48:48 2012 -0700 Basic get/list operations work * 'glance image-list' and 'glance image-show' work * Set up tests, pep8, venv commit b5847df3e203ec83126a3cc903bca58093399b89 Merge: 972677f 57e352a Author: jaypipes Date: Wed Feb 29 14:17:18 2012 -0800 Merge pull request #1 from emonty/master Updated support files to latest commit 57e352a070665f3dbc2a90d53c34521178669836 Author: Monty Taylor Date: Wed Feb 29 14:14:14 2012 -0800 All the latest OpenStack hotness. commit 972677fc3dc24a5084657fc337fb2b2981a30e0c Author: Jay Pipes Date: Wed Feb 29 16:42:26 2012 -0500 Initial checkin for new CLI and client package Copied mostly from python-keystoneclient with some Glance-specific stuff. README.rst shows what WILL be the way to do things, not what is currently coded :)