Move unit tests to standard directory

This patch moves the glanceclient unit tests to the standard directory
(xxxclient/tests/unit) in preparation for adding functional gate tests
'check-glanceclient-dsvm-functional' in the same vein as existing client
tests for other projects, eg:

* check-novaclient-dsvm-functional
* check-keystoneclient-dsvm-functional
* check-neutronclient-dsvm-functional

Change-Id: I29d4b9e3a428c851575ee9afde40d6df583456c4
This commit is contained in:
Stuart McLaren
2015-04-17 14:02:33 +00:00
parent 825c4a5df2
commit f2a8a520e7
33 changed files with 16 additions and 16 deletions
View File
View File
+57
View File
@@ -0,0 +1,57 @@
# 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.
import testtools
from glanceclient.openstack.common.apiclient import base
class TestBase(testtools.TestCase):
def test_resource_repr(self):
r = base.Resource(None, dict(foo="bar", baz="spam"))
self.assertEqual("<Resource baz=spam, foo=bar>", repr(r))
def test_getid(self):
self.assertEqual(4, base.getid(4))
class TmpObject(object):
id = 4
self.assertEqual(4, base.getid(TmpObject))
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)
+46
View File
@@ -0,0 +1,46 @@
# Copyright 2014 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.
import testtools
from glanceclient import client
from glanceclient.v1 import client as v1
from glanceclient.v2 import client as v2
class ClientTest(testtools.TestCase):
def test_no_endpoint_error(self):
self.assertRaises(ValueError, client.Client, None)
def test_endpoint(self):
gc = client.Client(1, "http://example.com")
self.assertEqual("http://example.com", gc.http_client.endpoint)
self.assertIsInstance(gc, v1.Client)
def test_versioned_endpoint(self):
gc = client.Client(1, "http://example.com/v2")
self.assertEqual("http://example.com", gc.http_client.endpoint)
self.assertIsInstance(gc, v1.Client)
def test_versioned_endpoint_no_version(self):
gc = client.Client(endpoint="http://example.com/v2")
self.assertEqual("http://example.com", gc.http_client.endpoint)
self.assertIsInstance(gc, v2.Client)
def test_versioned_endpoint_with_minor_revision(self):
gc = client.Client(2.2, "http://example.com/v2.1")
self.assertEqual("http://example.com", gc.http_client.endpoint)
self.assertIsInstance(gc, v2.Client)
+70
View File
@@ -0,0 +1,70 @@
# 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 mock
import testtools
from glanceclient import exc
HTML_MSG = """<html>
<head>
<title>404 Entity Not Found</title>
</head>
<body>
<h1>404 Entity Not Found</h1>
Entity could not be found
<br /><br />
</body>
</html>"""
class TestHTTPExceptions(testtools.TestCase):
def test_from_response(self):
"""exc.from_response should return instance of an HTTP exception."""
mock_resp = mock.Mock()
mock_resp.status_code = 400
out = exc.from_response(mock_resp)
self.assertIsInstance(out, exc.HTTPBadRequest)
def test_handles_json(self):
"""exc.from_response should not print JSON."""
mock_resp = mock.Mock()
mock_resp.status_code = 413
mock_resp.json.return_value = {
"overLimit": {
"code": 413,
"message": "OverLimit Retry...",
"details": "Error Details...",
"retryAt": "2014-12-03T13:33:06Z"
}
}
mock_resp.headers = {
"content-type": "application/json"
}
err = exc.from_response(mock_resp, "Non-empty body")
self.assertIsInstance(err, exc.HTTPOverLimit)
self.assertEqual("OverLimit Retry...", err.details)
def test_handles_html(self):
"""exc.from_response should not print HTML."""
mock_resp = mock.Mock()
mock_resp.status_code = 404
mock_resp.text = HTML_MSG
mock_resp.headers = {
"content-type": "text/html"
}
err = exc.from_response(mock_resp, HTML_MSG)
self.assertIsInstance(err, exc.HTTPNotFound)
self.assertEqual("404 Entity Not Found: Entity could not be found",
err.details)
+326
View File
@@ -0,0 +1,326 @@
# 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 json
import mock
import requests
from requests_mock.contrib import fixture
import six
from six.moves.urllib import parse
import testtools
from testtools import matchers
import types
import glanceclient
from glanceclient.common import http
from glanceclient.common import https
from glanceclient import exc
from glanceclient.tests import utils
class TestClient(testtools.TestCase):
def setUp(self):
super(TestClient, self).setUp()
self.mock = self.useFixture(fixture.Fixture())
self.endpoint = 'http://example.com:9292'
self.ssl_endpoint = 'https://example.com:9292'
self.client = http.HTTPClient(self.endpoint, token=u'abc123')
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('auth_token', http_client_object.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(u'fake-token', http_client_object.auth_token)
self.assertTrue(http_client_object.identity_headers.
get('X-Auth-Token') is None)
def test_identity_headers_and_no_token_in_session_header(self):
# Tests that if token or X-Auth-Token are not provided in the kwargs
# when creating the http client, the session headers don't contain
# the X-Auth-Token key.
identity_headers = {
'X-User-Id': 'user',
'X-Tenant-Id': 'tenant',
'X-Roles': 'roles',
'X-Identity-Status': 'Confirmed',
'X-Service-Catalog': 'service_catalog',
}
kwargs = {'identity_headers': identity_headers}
http_client_object = http.HTTPClient(self.endpoint, **kwargs)
self.assertIsNone(http_client_object.auth_token)
self.assertNotIn('X-Auth-Token', http_client_object.session.headers)
def test_identity_headers_are_passed(self):
# Tests that if token or X-Auth-Token are not provided in the kwargs
# when creating the http client, the session headers don't contain
# the X-Auth-Token key.
identity_headers = {
'X-User-Id': b'user',
'X-Tenant-Id': b'tenant',
'X-Roles': b'roles',
'X-Identity-Status': b'Confirmed',
'X-Service-Catalog': b'service_catalog',
}
kwargs = {'identity_headers': identity_headers}
http_client = http.HTTPClient(self.endpoint, **kwargs)
path = '/v1/images/my-image'
self.mock.get(self.endpoint + path)
http_client.get(path)
headers = self.mock.last_request.headers
for k, v in six.iteritems(identity_headers):
self.assertEqual(v, headers[k])
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
"""
def cb(request, context):
raise requests.exceptions.ConnectionError()
path = '/v1/images/detail?limit=20'
self.mock.get(self.endpoint + path, text=cb)
comm_err = self.assertRaises(glanceclient.exc.CommunicationError,
self.client.get,
'/v1/images/detail?limit=20')
self.assertIn(self.endpoint, comm_err.message)
def test_http_encoding(self):
path = '/v1/images/detail'
text = 'Ok'
self.mock.get(self.endpoint + path, text=text,
headers={"Content-Type": "text/plain"})
headers = {"test": u'ni\xf1o'}
resp, body = self.client.get(path, headers=headers)
self.assertEqual(text, resp.text)
def test_headers_encoding(self):
value = u'ni\xf1o'
headers = {"test": value, "none-val": None}
encoded = self.client.encode_headers(headers)
self.assertEqual(b"ni\xc3\xb1o", encoded[b"test"])
self.assertNotIn("none-val", encoded)
def test_raw_request(self):
" Verify the path being used for HTTP requests reflects accurately. "
headers = {"Content-Type": "text/plain"}
text = 'Ok'
path = '/v1/images/detail'
self.mock.get(self.endpoint + path, text=text, headers=headers)
resp, body = self.client.get('/v1/images/detail', headers=headers)
self.assertEqual(headers, resp.headers)
self.assertEqual(text, resp.text)
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 = parse.SplitResult(scheme='http',
netloc='example.com:9292', path='',
query='', fragment='')
self.assertEqual(expected, actual)
def test_get_connections_kwargs_http(self):
endpoint = 'http://example.com:9292'
test_client = http.HTTPClient(endpoint, token=u'adc123')
self.assertEqual(test_client.timeout, 600.0)
def test_http_chunked_request(self):
text = "Ok"
data = six.StringIO(text)
path = '/v1/images/'
self.mock.post(self.endpoint + path, text=text)
headers = {"test": u'chunked_request'}
resp, body = self.client.post(path, headers=headers, data=data)
self.assertIsInstance(self.mock.last_request.body, types.GeneratorType)
self.assertEqual(text, resp.text)
def test_http_json(self):
data = {"test": "json_request"}
path = '/v1/images'
text = 'OK'
self.mock.post(self.endpoint + path, text=text)
headers = {"test": u'chunked_request'}
resp, body = self.client.post(path, headers=headers, data=data)
self.assertEqual(text, resp.text)
self.assertIsInstance(self.mock.last_request.body, six.string_types)
self.assertEqual(data, json.loads(self.mock.last_request.body))
def test_http_chunked_response(self):
data = "TEST"
path = '/v1/images/'
self.mock.get(self.endpoint + path, body=six.StringIO(data),
headers={"Content-Type": "application/octet-stream"})
resp, body = self.client.get(path)
self.assertTrue(isinstance(body, types.GeneratorType))
self.assertEqual([data], list(body))
def test_log_http_response_with_non_ascii_char(self):
try:
response = 'Ok'
headers = {"Content-Type": "text/plain",
"test": "value1\xa5\xa6"}
fake = utils.FakeResponse(headers, six.StringIO(response))
self.client.log_http_response(fake)
except UnicodeDecodeError as e:
self.fail("Unexpected UnicodeDecodeError exception '%s'" % e)
def test_log_curl_request_with_non_ascii_char(self):
try:
headers = {'header1': 'value1\xa5\xa6'}
body = 'examplebody\xa5\xa6'
self.client.log_curl_request('GET', '/api/v1/\xa5', headers, body,
None)
except UnicodeDecodeError as e:
self.fail("Unexpected UnicodeDecodeError exception '%s'" % e)
@mock.patch('glanceclient.common.http.LOG.debug')
def test_log_curl_request_with_body_and_header(self, mock_log):
hd_name = 'header1'
hd_val = 'value1'
headers = {hd_name: hd_val}
body = 'examplebody'
self.client.log_curl_request('GET', '/api/v1/', headers, body, None)
self.assertTrue(mock_log.called, 'LOG.debug never called')
self.assertTrue(mock_log.call_args[0],
'LOG.debug called with no arguments')
hd_regex = ".*\s-H\s+'\s*%s\s*:\s*%s\s*'.*" % (hd_name, hd_val)
self.assertThat(mock_log.call_args[0][0],
matchers.MatchesRegex(hd_regex),
'header not found in curl command')
body_regex = ".*\s-d\s+'%s'\s.*" % body
self.assertThat(mock_log.call_args[0][0],
matchers.MatchesRegex(body_regex),
'body not found in curl command')
def _test_log_curl_request_with_certs(self, mock_log, key, cert, cacert):
headers = {'header1': 'value1'}
http_client_object = http.HTTPClient(self.ssl_endpoint, key_file=key,
cert_file=cert, cacert=cacert,
token='fake-token')
http_client_object.log_curl_request('GET', '/api/v1/', headers, None,
None)
self.assertTrue(mock_log.called, 'LOG.debug never called')
self.assertTrue(mock_log.call_args[0],
'LOG.debug called with no arguments')
needles = {'key': key, 'cert': cert, 'cacert': cacert}
for option, value in six.iteritems(needles):
if value:
regex = ".*\s--%s\s+('%s'|%s).*" % (option, value, value)
self.assertThat(mock_log.call_args[0][0],
matchers.MatchesRegex(regex),
'no --%s option in curl command' % option)
else:
regex = ".*\s--%s\s+.*" % option
self.assertThat(mock_log.call_args[0][0],
matchers.Not(matchers.MatchesRegex(regex)),
'unexpected --%s option in curl command' %
option)
@mock.patch('glanceclient.common.http.LOG.debug')
def test_log_curl_request_with_all_certs(self, mock_log):
self._test_log_curl_request_with_certs(mock_log, 'key1', 'cert1',
'cacert2')
@mock.patch('glanceclient.common.http.LOG.debug')
def test_log_curl_request_with_some_certs(self, mock_log):
self._test_log_curl_request_with_certs(mock_log, 'key1', 'cert1', None)
@mock.patch('glanceclient.common.http.LOG.debug')
def test_log_curl_request_with_insecure_param(self, mock_log):
headers = {'header1': 'value1'}
http_client_object = http.HTTPClient(self.ssl_endpoint, insecure=True,
token='fake-token')
http_client_object.log_curl_request('GET', '/api/v1/', headers, None,
None)
self.assertTrue(mock_log.called, 'LOG.debug never called')
self.assertTrue(mock_log.call_args[0],
'LOG.debug called with no arguments')
self.assertThat(mock_log.call_args[0][0],
matchers.MatchesRegex('.*\s-k\s.*'),
'no -k option in curl command')
@mock.patch('glanceclient.common.http.LOG.debug')
def test_log_curl_request_with_token_header(self, mock_log):
fake_token = 'fake-token'
headers = {'X-Auth-Token': fake_token}
http_client_object = http.HTTPClient(self.endpoint,
identity_headers=headers)
http_client_object.log_curl_request('GET', '/api/v1/', headers, None,
None)
self.assertTrue(mock_log.called, 'LOG.debug never called')
self.assertTrue(mock_log.call_args[0],
'LOG.debug called with no arguments')
token_regex = '.*%s.*' % fake_token
self.assertThat(mock_log.call_args[0][0],
matchers.Not(matchers.MatchesRegex(token_regex)),
'token found in LOG.debug parameter')
class TestVerifiedHTTPSConnection(testtools.TestCase):
"""Test fixture for glanceclient.common.http.VerifiedHTTPSConnection."""
def test_setcontext_unable_to_load_cacert(self):
"""Add this UT case with Bug#1265730."""
self.assertRaises(exc.SSLConfigurationError,
https.VerifiedHTTPSConnection,
"127.0.0.1",
None,
None,
None,
"gx_cacert",
None,
False,
True)
@@ -0,0 +1,75 @@
# 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 six
import testtools
from glanceclient.common import progressbar
from glanceclient.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(['X'] * 100, data)
self.assertEqual(
'[%s>] 100%%\n' % ('=' * 29),
output.getvalue()
)
finally:
sys.stdout = saved_stdout
def test_iter_file_display_progress_bar(self):
size = 98304
file_obj = six.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(
'[%s>] 100%%\n' % ('=' * 29),
output.getvalue()
)
finally:
sys.stdout = saved_stdout
def test_iter_file_no_tty(self):
size = 98304
file_obj = six.StringIO('X' * size)
saved_stdout = sys.stdout
try:
sys.stdout = output = test_utils.FakeNoTTYStdout()
file_obj = progressbar.VerboseFileWrapper(file_obj, size)
chunksize = 1024
chunk = file_obj.read(chunksize)
while chunk:
chunk = file_obj.read(chunksize)
# If stdout is not a tty progress bar should do nothing.
self.assertEqual('', output.getvalue())
finally:
sys.stdout = saved_stdout
+512
View File
@@ -0,0 +1,512 @@
# 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.
import argparse
import os
import sys
import uuid
import fixtures
from keystoneclient import exceptions as ks_exc
from keystoneclient import fixture as ks_fixture
import mock
import requests
from requests_mock.contrib import fixture as rm_fixture
import six
from glanceclient import exc
from glanceclient import shell as openstack_shell
from glanceclient.tests import utils
#NOTE (esheffield) Used for the schema caching tests
from glanceclient.v2 import schemas as schemas
import json
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_PROJECT_ID = '0123456789'
DEFAULT_USER_DOMAIN_NAME = 'user_domain_name'
DEFAULT_UNVERSIONED_AUTH_URL = 'http://127.0.0.1:5000/'
DEFAULT_V2_AUTH_URL = '%sv2.0' % DEFAULT_UNVERSIONED_AUTH_URL
DEFAULT_V3_AUTH_URL = '%sv3' % DEFAULT_UNVERSIONED_AUTH_URL
DEFAULT_AUTH_TOKEN = ' 3bcc3d3a03f44e3d8377f9247b0ad155'
TEST_SERVICE_URL = 'http://127.0.0.1:5000/'
FAKE_V2_ENV = {'OS_USERNAME': DEFAULT_USERNAME,
'OS_PASSWORD': DEFAULT_PASSWORD,
'OS_TENANT_NAME': DEFAULT_TENANT_NAME,
'OS_AUTH_URL': DEFAULT_V2_AUTH_URL,
'OS_IMAGE_URL': DEFAULT_IMAGE_URL}
FAKE_V3_ENV = {'OS_USERNAME': DEFAULT_USERNAME,
'OS_PASSWORD': DEFAULT_PASSWORD,
'OS_PROJECT_ID': DEFAULT_PROJECT_ID,
'OS_USER_DOMAIN_NAME': DEFAULT_USER_DOMAIN_NAME,
'OS_AUTH_URL': DEFAULT_V3_AUTH_URL,
'OS_IMAGE_URL': DEFAULT_IMAGE_URL}
TOKEN_ID = uuid.uuid4().hex
V2_TOKEN = ks_fixture.V2Token(token_id=TOKEN_ID)
V2_TOKEN.set_scope()
_s = V2_TOKEN.add_service('image', name='glance')
_s.add_endpoint(DEFAULT_IMAGE_URL)
V3_TOKEN = ks_fixture.V3Token()
V3_TOKEN.set_project_scope()
_s = V3_TOKEN.add_service('image', name='glance')
_s.add_standard_endpoints(public=DEFAULT_IMAGE_URL)
class ShellTest(utils.TestCase):
# auth environment to use
auth_env = FAKE_V2_ENV.copy()
# expected auth plugin to invoke
token_url = DEFAULT_V2_AUTH_URL + '/tokens'
# Patch os.environ to avoid required auth info
def make_env(self, exclude=None):
env = dict((k, v) for k, v in self.auth_env.items() if k != exclude)
self.useFixture(fixtures.MonkeyPatch('os.environ', env))
def setUp(self):
super(ShellTest, self).setUp()
global _old_env
_old_env, os.environ = os.environ, self.auth_env
self.requests = self.useFixture(rm_fixture.Fixture())
json_list = ks_fixture.DiscoveryList(DEFAULT_UNVERSIONED_AUTH_URL)
self.requests.get(DEFAULT_IMAGE_URL, json=json_list, status_code=300)
json_v2 = {'version': ks_fixture.V2Discovery(DEFAULT_V2_AUTH_URL)}
self.requests.get(DEFAULT_V2_AUTH_URL, json=json_v2)
json_v3 = {'version': ks_fixture.V3Discovery(DEFAULT_V3_AUTH_URL)}
self.requests.get(DEFAULT_V3_AUTH_URL, json=json_v3)
self.v2_auth = self.requests.post(DEFAULT_V2_AUTH_URL + '/tokens',
json=V2_TOKEN)
headers = {'X-Subject-Token': TOKEN_ID}
self.v3_auth = self.requests.post(DEFAULT_V3_AUTH_URL + '/auth/tokens',
headers=headers,
json=V3_TOKEN)
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 shell(self, argstr, exitcodes=(0,)):
orig = sys.stdout
orig_stderr = sys.stderr
try:
sys.stdout = six.StringIO()
sys.stderr = six.StringIO()
_shell = openstack_shell.OpenStackImagesShell()
_shell.main(argstr.split())
except SystemExit:
exc_type, exc_value, exc_traceback = sys.exc_info()
self.assertIn(exc_value.code, exitcodes)
finally:
stdout = sys.stdout.getvalue()
sys.stdout.close()
sys.stdout = orig
stderr = sys.stderr.getvalue()
sys.stderr.close()
sys.stderr = orig_stderr
return (stdout, stderr)
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,
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))
@mock.patch.object(openstack_shell.OpenStackImagesShell,
'_get_versioned_client')
def test_cert_and_key_args_interchangeable(self,
mock_versioned_client):
# make sure --os-cert and --os-key are passed correctly
args = '--os-cert mycert --os-key mykey image-list'
shell(args)
assert mock_versioned_client.called
((api_version, args), kwargs) = mock_versioned_client.call_args
self.assertEqual('mycert', args.os_cert)
self.assertEqual('mykey', args.os_key)
# make sure we get the same thing with --cert-file and --key-file
args = '--cert-file mycertfile --key-file mykeyfile image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
assert mock_versioned_client.called
((api_version, args), kwargs) = mock_versioned_client.call_args
self.assertEqual('mycertfile', args.os_cert)
self.assertEqual('mykeyfile', args.os_key)
@mock.patch('glanceclient.v1.client.Client')
def test_no_auth_with_token_and_image_url_with_v1(self, v1_client):
# test no authentication is required if both token and endpoint url
# are specified
args = ('--os-auth-token mytoken --os-image-url https://image:1234/v1 '
'image-list')
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
assert v1_client.called
(args, kwargs) = v1_client.call_args
self.assertEqual('mytoken', kwargs['token'])
self.assertEqual('https://image:1234', args[0])
@mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas')
def test_no_auth_with_token_and_image_url_with_v2(self,
cache_schemas):
with mock.patch('glanceclient.v2.client.Client') as v2_client:
# test no authentication is required if both token and endpoint url
# are specified
args = ('--os-auth-token mytoken '
'--os-image-url https://image:1234/v2 '
'--os-image-api-version 2 image-list')
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
((args), kwargs) = v2_client.call_args
self.assertEqual('https://image:1234', args[0])
self.assertEqual('mytoken', kwargs['token'])
def _assert_auth_plugin_args(self):
# make sure our auth plugin is invoked with the correct args
self.assertEqual(1, self.v2_auth.call_count)
self.assertFalse(self.v3_auth.called)
body = json.loads(self.v2_auth.last_request.body)
self.assertEqual(self.auth_env['OS_TENANT_NAME'],
body['auth']['tenantName'])
self.assertEqual(self.auth_env['OS_USERNAME'],
body['auth']['passwordCredentials']['username'])
self.assertEqual(self.auth_env['OS_PASSWORD'],
body['auth']['passwordCredentials']['password'])
@mock.patch('glanceclient.v1.client.Client')
def test_auth_plugin_invocation_with_v1(self, v1_client):
args = 'image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
self._assert_auth_plugin_args()
@mock.patch('glanceclient.v2.client.Client')
@mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas')
def test_auth_plugin_invocation_with_v2(self,
v2_client,
cache_schemas):
args = '--os-image-api-version 2 image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
self._assert_auth_plugin_args()
@mock.patch('glanceclient.v1.client.Client')
def test_auth_plugin_invocation_with_unversioned_auth_url_with_v1(
self, v1_client):
args = '--os-auth-url %s image-list' % DEFAULT_UNVERSIONED_AUTH_URL
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
self._assert_auth_plugin_args()
@mock.patch('glanceclient.v2.client.Client')
@mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas')
def test_auth_plugin_invocation_with_unversioned_auth_url_with_v2(
self, v2_client, cache_schemas):
args = ('--os-auth-url %s --os-image-api-version 2 '
'image-list') % DEFAULT_UNVERSIONED_AUTH_URL
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
self._assert_auth_plugin_args()
@mock.patch('sys.stdin', side_effect=mock.MagicMock)
@mock.patch('getpass.getpass', return_value='password')
def test_password_prompted_with_v2(self, mock_getpass, mock_stdin):
self.requests.post(self.token_url, exc=requests.ConnectionError)
glance_shell = openstack_shell.OpenStackImagesShell()
self.make_env(exclude='OS_PASSWORD')
self.assertRaises(ks_exc.ConnectionRefused,
glance_shell.main, ['image-list'])
# Make sure we are actually prompted.
mock_getpass.assert_called_with('OS Password: ')
@mock.patch('sys.stdin', side_effect=mock.MagicMock)
@mock.patch('getpass.getpass', side_effect=EOFError)
def test_password_prompted_ctrlD_with_v2(self, mock_getpass, mock_stdin):
glance_shell = openstack_shell.OpenStackImagesShell()
self.make_env(exclude='OS_PASSWORD')
# We should get Command Error because we mock Ctl-D.
self.assertRaises(exc.CommandError, glance_shell.main, ['image-list'])
# Make sure we are actually prompted.
mock_getpass.assert_called_with('OS Password: ')
@mock.patch(
'glanceclient.shell.OpenStackImagesShell._get_keystone_session')
@mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas')
def test_no_auth_with_proj_name(self, cache_schemas, session):
with mock.patch('glanceclient.v2.client.Client'):
args = ('--os-project-name myname '
'--os-project-domain-name mydomain '
'--os-project-domain-id myid '
'--os-image-api-version 2 image-list')
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
((args), kwargs) = session.call_args
self.assertEqual('myname', kwargs['project_name'])
self.assertEqual('mydomain', kwargs['project_domain_name'])
self.assertEqual('myid', kwargs['project_domain_id'])
@mock.patch.object(openstack_shell.OpenStackImagesShell, 'main')
def test_shell_keyboard_interrupt(self, mock_glance_shell):
# Ensure that exit code is 130 for KeyboardInterrupt
try:
mock_glance_shell.side_effect = KeyboardInterrupt()
openstack_shell.main()
except SystemExit as ex:
self.assertEqual(130, ex.code)
@mock.patch('glanceclient.v1.client.Client')
def test_auth_plugin_invocation_without_username_with_v1(self, v1_client):
self.make_env(exclude='OS_USERNAME')
args = 'image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
self.assertRaises(exc.CommandError, glance_shell.main, args.split())
@mock.patch('glanceclient.v2.client.Client')
def test_auth_plugin_invocation_without_username_with_v2(self, v2_client):
self.make_env(exclude='OS_USERNAME')
args = '--os-image-api-version 2 image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
self.assertRaises(exc.CommandError, glance_shell.main, args.split())
@mock.patch('glanceclient.v1.client.Client')
def test_auth_plugin_invocation_without_auth_url_with_v1(self, v1_client):
self.make_env(exclude='OS_AUTH_URL')
args = 'image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
self.assertRaises(exc.CommandError, glance_shell.main, args.split())
@mock.patch('glanceclient.v2.client.Client')
def test_auth_plugin_invocation_without_auth_url_with_v2(self, v2_client):
self.make_env(exclude='OS_AUTH_URL')
args = '--os-image-api-version 2 image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
self.assertRaises(exc.CommandError, glance_shell.main, args.split())
@mock.patch('glanceclient.v1.client.Client')
def test_auth_plugin_invocation_without_tenant_with_v1(self, v1_client):
if 'OS_TENANT_NAME' in os.environ:
self.make_env(exclude='OS_TENANT_NAME')
if 'OS_PROJECT_ID' in os.environ:
self.make_env(exclude='OS_PROJECT_ID')
args = 'image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
self.assertRaises(exc.CommandError, glance_shell.main, args.split())
@mock.patch('glanceclient.v2.client.Client')
def test_auth_plugin_invocation_without_tenant_with_v2(self, v2_client):
if 'OS_TENANT_NAME' in os.environ:
self.make_env(exclude='OS_TENANT_NAME')
if 'OS_PROJECT_ID' in os.environ:
self.make_env(exclude='OS_PROJECT_ID')
args = '--os-image-api-version 2 image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
self.assertRaises(exc.CommandError, glance_shell.main, args.split())
class ShellTestWithKeystoneV3Auth(ShellTest):
# auth environment to use
auth_env = FAKE_V3_ENV.copy()
token_url = DEFAULT_V3_AUTH_URL + '/auth/tokens'
def _assert_auth_plugin_args(self):
self.assertFalse(self.v2_auth.called)
self.assertEqual(1, self.v3_auth.call_count)
body = json.loads(self.v3_auth.last_request.body)
user = body['auth']['identity']['password']['user']
self.assertEqual(self.auth_env['OS_USERNAME'], user['name'])
self.assertEqual(self.auth_env['OS_PASSWORD'], user['password'])
self.assertEqual(self.auth_env['OS_USER_DOMAIN_NAME'],
user['domain']['name'])
self.assertEqual(self.auth_env['OS_PROJECT_ID'],
body['auth']['scope']['project']['id'])
@mock.patch('glanceclient.v1.client.Client')
def test_auth_plugin_invocation_with_v1(self, v1_client):
args = 'image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
self._assert_auth_plugin_args()
@mock.patch('glanceclient.v2.client.Client')
@mock.patch.object(openstack_shell.OpenStackImagesShell, '_cache_schemas')
def test_auth_plugin_invocation_with_v2(self, v2_client, cache_schemas):
args = '--os-image-api-version 2 image-list'
glance_shell = openstack_shell.OpenStackImagesShell()
glance_shell.main(args.split())
self._assert_auth_plugin_args()
@mock.patch('keystoneclient.discover.Discover',
side_effect=ks_exc.ClientException())
def test_api_discovery_failed_with_unversioned_auth_url(self,
discover):
args = '--os-auth-url %s image-list' % DEFAULT_UNVERSIONED_AUTH_URL
glance_shell = openstack_shell.OpenStackImagesShell()
self.assertRaises(exc.CommandError, glance_shell.main, args.split())
def test_bash_completion(self):
stdout, stderr = self.shell('bash_completion')
# just check we have some output
required = [
'--status',
'image-create',
'help',
'--size']
for r in required:
self.assertIn(r, stdout.split())
avoided = [
'bash_completion',
'bash-completion']
for r in avoided:
self.assertNotIn(r, stdout.split())
class ShellCacheSchemaTest(utils.TestCase):
def setUp(self):
super(ShellCacheSchemaTest, self).setUp()
self._mock_client_setup()
self._mock_shell_setup()
self.cache_dir = '/dir_for_cached_schema'
self.cache_files = [self.cache_dir + '/image_schema.json',
self.cache_dir + '/namespace_schema.json',
self.cache_dir + '/resource_type_schema.json']
def tearDown(self):
super(ShellCacheSchemaTest, self).tearDown()
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 _mock_shell_setup(self):
mocked_get_client = mock.MagicMock(return_value=self.client)
self.shell = openstack_shell.OpenStackImagesShell()
self.shell._get_versioned_client = mocked_get_client
def _make_args(self, args):
class Args():
def __init__(self, entries):
self.__dict__.update(entries)
return Args(args)
@mock.patch('six.moves.builtins.open', new=mock.mock_open(), create=True)
@mock.patch('os.path.exists', return_value=True)
def test_cache_schemas_gets_when_forced(self, exists_mock):
options = {
'get_schema': True
}
self.shell._cache_schemas(self._make_args(options),
home_dir=self.cache_dir)
self.assertEqual(12, open.mock_calls.__len__())
self.assertEqual(mock.call(self.cache_files[0], 'w'),
open.mock_calls[0])
self.assertEqual(mock.call(self.cache_files[1], 'w'),
open.mock_calls[4])
self.assertEqual(mock.call().write(json.dumps(self.schema_dict)),
open.mock_calls[2])
self.assertEqual(mock.call().write(json.dumps(self.schema_dict)),
open.mock_calls[6])
@mock.patch('six.moves.builtins.open', new=mock.mock_open(), create=True)
@mock.patch('os.path.exists', side_effect=[True, False, False, False])
def test_cache_schemas_gets_when_not_exists(self, exists_mock):
options = {
'get_schema': False
}
self.shell._cache_schemas(self._make_args(options),
home_dir=self.cache_dir)
self.assertEqual(12, open.mock_calls.__len__())
self.assertEqual(mock.call(self.cache_files[0], 'w'),
open.mock_calls[0])
self.assertEqual(mock.call(self.cache_files[1], 'w'),
open.mock_calls[4])
self.assertEqual(mock.call().write(json.dumps(self.schema_dict)),
open.mock_calls[2])
self.assertEqual(mock.call().write(json.dumps(self.schema_dict)),
open.mock_calls[6])
@mock.patch('six.moves.builtins.open', new=mock.mock_open(), create=True)
@mock.patch('os.path.exists', return_value=True)
def test_cache_schemas_leaves_when_present_not_forced(self, exists_mock):
options = {
'get_schema': False
}
self.shell._cache_schemas(self._make_args(options),
home_dir=self.cache_dir)
os.path.exists.assert_any_call(self.cache_dir)
os.path.exists.assert_any_call(self.cache_files[0])
os.path.exists.assert_any_call(self.cache_files[1])
self.assertEqual(4, exists_mock.call_count)
self.assertEqual(0, open.mock_calls.__len__())
+452
View File
@@ -0,0 +1,452 @@
# 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
from OpenSSL import SSL
try:
from requests.packages.urllib3 import poolmanager
except ImportError:
from urllib3 import poolmanager
import six
import ssl
import testtools
import threading
from glanceclient.common import http
from glanceclient.common import https
from glanceclient import Client
from glanceclient import exc
if six.PY3 is True:
import socketserver
else:
import SocketServer as socketserver
TEST_VAR_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
'var'))
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
self.request.recv(1024)
response = b'somebytes'
self.request.sendall(response)
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
def get_request(self):
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')
(_sock, addr) = socketserver.TCPServer.get_request(self)
sock = ssl.wrap_socket(_sock,
certfile=cert_file,
keyfile=key_file,
ca_certs=cacert,
server_side=True,
cert_reqs=ssl.CERT_REQUIRED)
return sock, addr
class TestHTTPSVerifyCert(testtools.TestCase):
"""Check 'requests' based ssl verification occurs
The requests library performs SSL certificate validation,
however there is still a need to check that the glance
client is properly integrated with requests so that
cert validation actually happens.
"""
def setUp(self):
# Rather than spinning up a new process, we create
# a thread to perform client/server interaction.
# This should run more quickly.
super(TestHTTPSVerifyCert, self).setUp()
server = ThreadedTCPServer(('127.0.0.1', 0),
ThreadedTCPRequestHandler)
__, self.port = server.server_address
server_thread = threading.Thread(target=server.serve_forever)
server_thread.daemon = True
server_thread.start()
def test_v1_requests_cert_verification(self):
"""v1 regression test for bug 115260."""
port = self.port
url = 'https://0.0.0.0:%d' % port
try:
client = Client('1', url,
insecure=False,
ssl_compression=True)
client.images.get('image123')
self.fail('No SSL exception raised')
except exc.CommunicationError as e:
if 'certificate verify failed' not in e.message:
self.fail('No certificate failure message received')
except Exception as e:
self.fail('Unexpected exception raised')
def test_v1_requests_cert_verification_no_compression(self):
"""v1 regression test for bug 115260."""
port = self.port
url = 'https://0.0.0.0:%d' % port
try:
client = Client('1', url,
insecure=False,
ssl_compression=False)
client.images.get('image123')
self.fail('No SSL exception raised')
except SSL.Error as e:
if 'certificate verify failed' not in str(e):
self.fail('No certificate failure message received')
except Exception as e:
self.fail('Unexpected exception raised')
def test_v2_requests_cert_verification(self):
"""v2 regression test for bug 115260."""
port = self.port
url = 'https://0.0.0.0:%d' % port
try:
gc = Client('2', url,
insecure=False,
ssl_compression=True)
gc.images.get('image123')
self.fail('No SSL exception raised')
except exc.CommunicationError as e:
if 'certificate verify failed' not in e.message:
self.fail('No certificate failure message received')
except Exception as e:
self.fail('Unexpected exception raised')
def test_v2_requests_cert_verification_no_compression(self):
"""v2 regression test for bug 115260."""
port = self.port
url = 'https://0.0.0.0:%d' % port
try:
gc = Client('2', url,
insecure=False,
ssl_compression=False)
gc.images.get('image123')
self.fail('No SSL exception raised')
except SSL.Error as e:
if 'certificate verify failed' not in str(e):
self.fail('No certificate failure message received')
except Exception as e:
self.fail('Unexpected exception raised')
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:
https.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: absence of SSL key file.
"""
cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt')
cacert = os.path.join(TEST_VAR_DIR, 'ca.crt')
try:
https.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: absence of SSL cert file.
"""
key_file = os.path.join(TEST_VAR_DIR, 'privatekey.key')
cacert = os.path.join(TEST_VAR_DIR, 'ca.crt')
try:
https.VerifiedHTTPSConnection('127.0.0.1', 0,
key_file=key_file,
cacert=cacert)
except exc.SSLConfigurationError:
pass
except Exception:
self.fail('Failed to init VerifiedHTTPSConnection.')
def test_ssl_init_bad_key(self):
"""
Test VerifiedHTTPSConnection: bad key.
"""
cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt')
cacert = os.path.join(TEST_VAR_DIR, 'ca.crt')
key_file = os.path.join(TEST_VAR_DIR, 'badkey.key')
try:
https.VerifiedHTTPSConnection('127.0.0.1', 0,
key_file=key_file,
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.
"""
cert_file = os.path.join(TEST_VAR_DIR, 'badcert.crt')
cacert = os.path.join(TEST_VAR_DIR, 'ca.crt')
try:
https.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.
"""
cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt')
cacert = os.path.join(TEST_VAR_DIR, 'badca.crt')
try:
https.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,
open(cert_file).read())
# The expected cert should have CN=0.0.0.0
self.assertEqual('0.0.0.0', cert.get_subject().commonName)
try:
conn = https.VerifiedHTTPSConnection('0.0.0.0', 0)
https.do_verify_callback(None, cert, 0, 0, 1, host=conn.host)
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,
open(cert_file).read())
# The expected cert should have CN=*.pong.example.com
self.assertEqual('*.pong.example.com', cert.get_subject().commonName)
try:
conn = https.VerifiedHTTPSConnection('ping.pong.example.com', 0)
https.do_verify_callback(None, cert, 0, 0, 1, host=conn.host)
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,
open(cert_file).read())
# The expected cert should have CN=0.0.0.0
self.assertEqual('0.0.0.0', cert.get_subject().commonName)
try:
conn = https.VerifiedHTTPSConnection('alt1.example.com', 0)
https.do_verify_callback(None, cert, 0, 0, 1, host=conn.host)
except Exception:
self.fail('Unexpected exception.')
try:
conn = https.VerifiedHTTPSConnection('alt2.example.com', 0)
https.do_verify_callback(None, cert, 0, 0, 1, host=conn.host)
except Exception:
self.fail('Unexpected exception.')
def test_ssl_cert_subject_alt_name_wildcard(self):
"""
Test certificate: wildcard SAN match
"""
cert_file = os.path.join(TEST_VAR_DIR, 'wildcard-san-certificate.crt')
cert = crypto.load_certificate(crypto.FILETYPE_PEM,
open(cert_file).read())
# The expected cert should have CN=0.0.0.0
self.assertEqual('0.0.0.0', cert.get_subject().commonName)
try:
conn = https.VerifiedHTTPSConnection('alt1.example.com', 0)
https.do_verify_callback(None, cert, 0, 0, 1, host=conn.host)
except Exception:
self.fail('Unexpected exception.')
try:
conn = https.VerifiedHTTPSConnection('alt2.example.com', 0)
https.do_verify_callback(None, cert, 0, 0, 1, host=conn.host)
except Exception:
self.fail('Unexpected exception.')
try:
conn = https.VerifiedHTTPSConnection('alt3.example.net', 0)
https.do_verify_callback(None, cert, 0, 0, 1, host=conn.host)
self.fail('Failed to raise assertion.')
except exc.SSLCertificateError:
pass
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,
open(cert_file).read())
# The expected cert should have CN=0.0.0.0
self.assertEqual('0.0.0.0', cert.get_subject().commonName)
try:
conn = https.VerifiedHTTPSConnection('mismatch.example.com', 0)
except Exception:
self.fail('Failed to init VerifiedHTTPSConnection.')
self.assertRaises(exc.SSLCertificateError,
https.do_verify_callback, None, cert, 0, 0, 1,
host=conn.host)
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,
open(cert_file).read())
# The expected expired cert has CN=openstack.example.com
self.assertEqual('openstack.example.com',
cert.get_subject().commonName)
try:
conn = https.VerifiedHTTPSConnection('openstack.example.com', 0)
except Exception:
raise
self.fail('Failed to init VerifiedHTTPSConnection.')
self.assertRaises(exc.SSLCertificateError,
https.do_verify_callback, None, cert, 0, 0, 1,
host=conn.host)
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,
https.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:
https.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:
https.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.')
def test_ssl_init_non_byte_string(self):
"""
Test VerifiedHTTPSConnection class non byte string
Reproduces bug #1301849
"""
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')
# Note: we reproduce on python 2.6/2.7, on 3.3 the bug doesn't occur.
key_file = key_file.encode('ascii', 'strict').decode('utf-8')
cert_file = cert_file.encode('ascii', 'strict').decode('utf-8')
cacert = cacert.encode('ascii', 'strict').decode('utf-8')
try:
https.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.')
class TestRequestsIntegration(testtools.TestCase):
def test_pool_patch(self):
client = http.HTTPClient("https://localhost",
ssl_compression=True)
self.assertNotEqual(https.HTTPSConnectionPool,
poolmanager.pool_classes_by_scheme["https"])
adapter = client.session.adapters.get("https://")
self.assertFalse(isinstance(adapter, https.HTTPSAdapter))
adapter = client.session.adapters.get("glance+https://")
self.assertFalse(isinstance(adapter, https.HTTPSAdapter))
def test_custom_https_adapter(self):
client = http.HTTPClient("https://localhost",
ssl_compression=False)
self.assertNotEqual(https.HTTPSConnectionPool,
poolmanager.pool_classes_by_scheme["https"])
adapter = client.session.adapters.get("https://")
self.assertFalse(isinstance(adapter, https.HTTPSAdapter))
adapter = client.session.adapters.get("glance+https://")
self.assertTrue(isinstance(adapter, https.HTTPSAdapter))
+165
View File
@@ -0,0 +1,165 @@
# 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 six
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range
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 = six.StringIO('X' * size)
try:
self.assertEqual(size, utils.get_file_size(file_obj))
# Check that get_file_size didn't change original file position.
self.assertEqual(0, file_obj.tell())
finally:
file_obj.close()
def test_get_consumed_file_size(self):
size, consumed = 98304, 304
file_obj = six.StringIO('X' * size)
file_obj.seek(consumed)
try:
self.assertEqual(size, utils.get_file_size(file_obj))
# Check that get_file_size didn't change original file position.
self.assertEqual(consumed, file_obj.tell())
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 = six.StringIO()
utils.print_list(images, columns)
sys.stdout = output_dict = six.StringIO()
utils.print_dict({'K': 'k', 'Key': 'veeeeeeeeeeeeeeeeeeeeeeee'
'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
'eeeeeeeeeeeery long value'},
max_column_width=60)
finally:
sys.stdout = saved_stdout
self.assertEqual('''\
+-------+--------------+
| ID | Name |
+-------+--------------+
| | Name1 |
| 1 | another |
| 65536 | veeeery long |
+-------+--------------+
''',
output_list.getvalue())
self.assertEqual('''\
+----------+--------------------------------------------------------------+
| Property | Value |
+----------+--------------------------------------------------------------+
| K | k |
| Key | veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee |
| | eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee |
| | ery long value |
+----------+--------------------------------------------------------------+
''',
output_dict.getvalue())
def test_exception_to_str(self):
class FakeException(Exception):
def __str__(self):
raise UnicodeError()
ret = utils.exception_to_str(Exception('error message'))
self.assertEqual('error message', ret)
ret = utils.exception_to_str(Exception('\xa5 error message'))
if six.PY2:
self.assertEqual(' error message', ret)
else:
self.assertEqual('\xa5 error message', ret)
ret = utils.exception_to_str(FakeException('\xa5 error message'))
self.assertEqual("Caught '%(exception)s' exception." %
{'exception': 'FakeException'}, ret)
def test_schema_args_with_list_types(self):
# NOTE(flaper87): Regression for bug
# https://bugs.launchpad.net/python-glanceclient/+bug/1401032
def schema_getter(_type='string', enum=False):
prop = {
'type': ['null', _type],
'description': 'Test schema (READ-ONLY)',
}
if enum:
prop['enum'] = [None, 'opt-1', 'opt-2']
def actual_getter():
return {
'additionalProperties': False,
'required': ['name'],
'name': 'test_schema',
'properties': {
'test': prop,
}
}
return actual_getter
def dummy_func():
pass
decorated = utils.schema_args(schema_getter())(dummy_func)
arg, opts = decorated.__dict__['arguments'][0]
self.assertIn('--test', arg)
self.assertEqual(str, opts['type'])
decorated = utils.schema_args(schema_getter('integer'))(dummy_func)
arg, opts = decorated.__dict__['arguments'][0]
self.assertIn('--test', arg)
self.assertEqual(int, opts['type'])
decorated = utils.schema_args(schema_getter(enum=True))(dummy_func)
arg, opts = decorated.__dict__['arguments'][0]
self.assertIn('--test', arg)
self.assertEqual(str, opts['type'])
self.assertIn('None, opt-1, opt-2', opts['help'])
@@ -0,0 +1,125 @@
# 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
from glanceclient.tests import utils
import glanceclient.v1.image_members
import glanceclient.v1.images
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(expect, self.api.calls)
self.assertEqual(1, len(members))
self.assertEqual('1', members[0].member_id)
self.assertEqual('1', members[0].image_id)
self.assertEqual(False, members[0].can_share)
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(expect, self.api.calls)
def test_get(self):
member = self.mgr.get(self.image, '1')
expect = [('GET', '/v1/images/1/members/1', {}, None)]
self.assertEqual(expect, self.api.calls)
self.assertEqual('1', member.member_id)
self.assertEqual('1', member.image_id)
self.assertEqual(False, member.can_share)
def test_delete(self):
self.mgr.delete('1', '1')
expect = [('DELETE', '/v1/images/1/members/1', {}, None)]
self.assertEqual(expect, self.api.calls)
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', {},
sorted(expect_body.items()))]
self.assertEqual(expect, self.api.calls)
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', {},
sorted({'memberships': body}.items()))]
self.assertEqual(expect, self.api.calls)
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', {},
sorted(expect_body.items()))]
self.assertEqual(expect, self.api.calls)
+963
View File
@@ -0,0 +1,963 @@
# 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 testtools
import six
from six.moves.urllib import parse
from glanceclient.tests import utils
from glanceclient.v1 import client
from glanceclient.v1 import images
from glanceclient.v1 import shell
fixtures = {
'/v1/images': {
'POST': (
{
'location': '/v1/images/1',
'x-openstack-request-id': 'req-1234',
},
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': (
{'x-openstack-request-id': 'req-1234'},
{'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?limit=20&marker=a': {
'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?limit=1&marker=a': {
'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?limit=2&marker=b': {
'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?limit=20&property-ping=pong':
{
'GET': (
{},
{'images': [
{
'id': '1',
'name': 'image-1',
'properties': {'arch': 'x86_64'},
},
]},
),
},
'/v1/images/detail?limit=20&sort_dir=desc': {
'GET': (
{},
{'images': [
{
'id': 'a',
'name': 'image-1',
'properties': {'arch': 'x86_64'},
},
{
'id': 'b',
'name': 'image-2',
'properties': {'arch': 'x86_64'},
},
]},
),
},
'/v1/images/detail?limit=20&sort_key=name': {
'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': u"ni\xf1o"
},
None,
),
'GET': (
{
'x-image-meta-checksum': '0745064918b49693cca64d6b6a13d28a'
},
'ZZZ',
),
},
'/v1/images/4': {
'HEAD': (
{
'x-image-meta-id': '4',
'x-image-meta-name': 'image-4',
'x-image-meta-property-arch': 'x86_64',
'x-image-meta-is_public': 'false',
'x-image-meta-protected': 'false',
'x-image-meta-deleted': 'false',
'x-openstack-request-id': 'req-1234',
},
None),
'GET': (
{
'x-openstack-request-id': 'req-1234',
},
'XXX',
),
'PUT': (
{
'x-openstack-request-id': 'req-1234',
},
json.dumps(
{'image': {
'id': '4',
'name': 'image-4',
'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': (
{
'x-openstack-request-id': 'req-1234',
},
None),
},
'/v1/images/v2_created_img': {
'PUT': (
{},
json.dumps({
"image": {
"status": "queued",
"deleted": False,
"container_format": "bare",
"min_ram": 0,
"updated_at": "2013-12-20T01:51:45",
"owner": "foo",
"min_disk": 0,
"is_public": False,
"deleted_at": None,
"id": "v2_created_img",
"size": None,
"name": "bar",
"checksum": None,
"created_at": "2013-12-20T01:50:38",
"disk_format": "qcow2",
"properties": {},
"protected": False
}
})
),
},
}
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?limit=2&marker=b', {}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(3, len(images))
self.assertEqual('a', images[0].id)
self.assertEqual('b', images[1].id)
self.assertEqual('c', images[2].id)
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(expect, self.api.calls)
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?limit=1&marker=a', {}, None),
]
self.assertEqual(2, len(images))
self.assertEqual('a', images[0].id)
self.assertEqual('b', images[1].id)
self.assertEqual(expect, self.api.calls)
def test_list_with_marker(self):
list(self.mgr.list(marker='a'))
url = '/v1/images/detail?limit=20&marker=a'
expect = [('GET', url, {}, None)]
self.assertEqual(expect, self.api.calls)
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(expect, self.api.calls)
def test_list_with_property_filters(self):
list(self.mgr.list(filters={'properties': {'ping': 'pong'}}))
url = '/v1/images/detail?limit=20&property-ping=pong'
expect = [('GET', url, {}, None)]
self.assertEqual(expect, self.api.calls)
def test_list_with_sort_dir(self):
list(self.mgr.list(sort_dir='desc'))
url = '/v1/images/detail?limit=20&sort_dir=desc'
expect = [('GET', url, {}, None)]
self.assertEqual(expect, self.api.calls)
def test_list_with_sort_key(self):
list(self.mgr.list(sort_key='name'))
url = '/v1/images/detail?limit=20&sort_key=name'
expect = [('GET', url, {}, None)]
self.assertEqual(expect, self.api.calls)
def test_get(self):
image = self.mgr.get('1')
expect = [('HEAD', '/v1/images/1', {}, None)]
self.assertEqual(expect, self.api.calls)
self.assertEqual('1', image.id)
self.assertEqual('image-1', image.name)
self.assertEqual(False, image.is_public)
self.assertEqual(False, image.protected)
self.assertEqual(False, image.deleted)
self.assertEqual({u'arch': u'x86_64'}, image.properties)
def test_get_int(self):
image = self.mgr.get(1)
expect = [('HEAD', '/v1/images/1', {}, None)]
self.assertEqual(expect, self.api.calls)
self.assertEqual('1', image.id)
self.assertEqual('image-1', image.name)
self.assertEqual(False, image.is_public)
self.assertEqual(False, image.protected)
self.assertEqual(False, image.deleted)
self.assertEqual({u'arch': u'x86_64'}, image.properties)
def test_get_encoding(self):
image = self.mgr.get('3')
self.assertEqual(u"ni\xf1o", image.name)
def test_get_req_id(self):
params = {'return_req_id': []}
self.mgr.get('4', **params)
expect_req_id = ['req-1234']
self.assertEqual(expect_req_id, params['return_req_id'])
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(expect, self.api.calls)
self.assertEqual('XXX', data)
expect += [('GET', '/v1/images/1', {}, None)]
data = ''.join([b for b in self.mgr.data('1')])
self.assertEqual(expect, self.api.calls)
self.assertEqual('XXX', data)
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(expect, self.api.calls)
self.assertEqual('YYY', data)
expect += [('GET', '/v1/images/2', {}, None)]
data = self.mgr.data('2')
self.assertEqual(expect, self.api.calls)
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_req_id(self):
params = {
'do_checksum': False,
'return_req_id': [],
}
''.join([b for b in self.mgr.data('4', **params)])
expect_req_id = ['req-1234']
self.assertEqual(expect_req_id, params['return_req_id'])
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(expect, self.api.calls)
self.assertEqual('ZZZ', data)
expect += [('GET', '/v1/images/3', {}, None)]
data = ''.join([b for b in self.mgr.data('3')])
self.assertEqual(expect, self.api.calls)
self.assertEqual('ZZZ', data)
def test_delete(self):
self.mgr.delete('1')
expect = [('DELETE', '/v1/images/1', {}, None)]
self.assertEqual(expect, self.api.calls)
def test_delete_req_id(self):
params = {
'return_req_id': []
}
self.mgr.delete('4', **params)
expect = [('DELETE', '/v1/images/4', {}, None)]
self.assertEqual(self.api.calls, expect)
expect_req_id = ['req-1234']
self.assertEqual(expect_req_id, params['return_req_id'])
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(expect, self.api.calls)
self.assertEqual('1', image.id)
self.assertEqual('image-1', image.name)
self.assertEqual('ovf', image.container_format)
self.assertEqual('vhd', image.disk_format)
self.assertEqual('asdf', image.owner)
self.assertEqual(1024, image.size)
self.assertEqual(512, image.min_ram)
self.assertEqual(10, image.min_disk)
self.assertEqual(False, image.is_public)
self.assertEqual(False, image.protected)
self.assertEqual(False, image.deleted)
self.assertEqual({'a': 'b', 'c': 'd'}, image.properties)
def test_create_with_data(self):
image_data = six.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(expect, self.api.calls)
def test_create_req_id(self):
params = {
'id': '4',
'name': 'image-4',
'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'},
'return_req_id': [],
}
image = self.mgr.create(**params)
expect_headers = {
'x-image-meta-id': '4',
'x-image-meta-name': 'image-4',
'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')
expect_req_id = ['req-1234']
self.assertEqual(expect_req_id, params['return_req_id'])
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',
'x-glance-registry-purge-props': 'false',
}
expect = [('PUT', '/v1/images/1', expect_hdrs, None)]
self.assertEqual(expect, self.api.calls)
self.assertEqual('1', image.id)
self.assertEqual('image-2', image.name)
self.assertEqual(1024, image.size)
self.assertEqual(512, image.min_ram)
self.assertEqual(10, image.min_disk)
def test_update_with_data(self):
image_data = six.StringIO('XXX')
self.mgr.update('1', data=image_data)
expect_headers = {'x-image-meta-size': '3',
'x-glance-registry-purge-props': 'false'}
expect = [('PUT', '/v1/images/1', expect_headers, image_data)]
self.assertEqual(expect, self.api.calls)
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(expect, self.api.calls)
def test_update_with_purge_props_false(self):
self.mgr.update('1', purge_props=False)
expect_headers = {'x-glance-registry-purge-props': 'false'}
expect = [('PUT', '/v1/images/1', expect_headers, None)]
self.assertEqual(expect, self.api.calls)
def test_update_req_id(self):
fields = {
'purge_props': True,
'return_req_id': [],
}
self.mgr.update('4', **fields)
expect_headers = {'x-glance-registry-purge-props': 'true'}
expect = [('PUT', '/v1/images/4', expect_headers, None)]
self.assertEqual(self.api.calls, expect)
expect_req_id = ['req-1234']
self.assertEqual(expect_req_id, fields['return_req_id'])
def test_image_meta_from_headers_encoding(self):
value = u"ni\xf1o"
if six.PY2:
fields = {"x-image-meta-name": "ni\xc3\xb1o"}
else:
fields = {"x-image-meta-name": value}
headers = self.mgr._image_meta_from_headers(fields)
self.assertEqual(value, headers["name"])
def test_image_list_with_owner(self):
images = self.mgr.list(owner='A', page_size=20)
image_list = list(images)
self.assertEqual('A', image_list[0].owner)
self.assertEqual('a', image_list[0].id)
self.assertEqual(1, len(image_list))
def test_image_list_with_owner_req_id(self):
fields = {
'owner': 'A',
'return_req_id': [],
}
images = self.mgr.list(**fields)
next(images)
self.assertEqual(fields['return_req_id'], ['req-1234'])
def test_image_list_with_notfound_owner(self):
images = self.mgr.list(owner='X', page_size=20)
self.assertEqual(0, len(list(images)))
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('c', image_list[0].id)
self.assertEqual(1, len(image_list))
def test_image_list_with_unspecified_owner(self):
images = self.mgr.list(owner=None, page_size=5)
image_list = list(images)
self.assertEqual('A', image_list[0].owner)
self.assertEqual('a', image_list[0].id)
self.assertEqual('A', image_list[1].owner)
self.assertEqual('b', image_list[1].id)
self.assertEqual('B', image_list[2].owner)
self.assertEqual('b2', image_list[2].id)
self.assertRaises(AttributeError, lambda: image_list[3].owner)
self.assertEqual('c', image_list[3].id)
self.assertEqual(4, len(image_list))
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('B', image_list[0].owner)
self.assertEqual('b', image_list[0].id)
self.assertEqual(1, len(image_list))
def test_image_list_all_tenants(self):
images = self.mgr.list(is_public=None, page_size=5)
image_list = list(images)
self.assertEqual('A', image_list[0].owner)
self.assertEqual('a', image_list[0].id)
self.assertEqual('B', image_list[1].owner)
self.assertEqual('b', image_list[1].id)
self.assertEqual('B', image_list[2].owner)
self.assertEqual('b2', image_list[2].id)
self.assertRaises(AttributeError, lambda: image_list[3].owner)
self.assertEqual('c', image_list[3].id)
self.assertEqual(4, len(image_list))
def test_update_v2_created_image_using_v1(self):
fields_to_update = {
'name': 'bar',
'container_format': 'bare',
'disk_format': 'qcow2',
}
image = self.mgr.update('v2_created_img', **fields_to_update)
expect_hdrs = {
'x-image-meta-name': 'bar',
'x-image-meta-container_format': 'bare',
'x-image-meta-disk_format': 'qcow2',
'x-glance-registry-purge-props': 'false',
}
expect = [('PUT', '/v1/images/v2_created_img', expect_hdrs, None)]
self.assertEqual(expect, self.api.calls)
self.assertEqual('v2_created_img', image.id)
self.assertEqual('bar', image.name)
self.assertEqual(0, image.size)
self.assertEqual('bare', image.container_format)
self.assertEqual('qcow2', image.disk_format)
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),
('HEAD', '/v1/images/1', {}, None),
('DELETE', '/v1/images/1', {}, None),
]
self.assertEqual(expect, self.api.calls)
def test_update(self):
image = self.mgr.get('1')
image.update(name='image-5')
expect = [
('HEAD', '/v1/images/1', {}, None),
('HEAD', '/v1/images/1', {}, None),
('PUT', '/v1/images/1',
{'x-image-meta-name': 'image-5',
'x-glance-registry-purge-props': 'false'}, None),
]
self.assertEqual(expect, self.api.calls)
def test_data(self):
image = self.mgr.get('1')
data = ''.join([b for b in image.data()])
expect = [
('HEAD', '/v1/images/1', {}, None),
('HEAD', '/v1/images/1', {}, None),
('GET', '/v1/images/1', {}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual('XXX', data)
data = ''.join([b for b in image.data(do_checksum=False)])
expect += [('GET', '/v1/images/1', {}, None)]
self.assertEqual(expect, self.api.calls)
self.assertEqual('XXX', data)
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),
('HEAD', '/v1/images/2', {}, None),
('GET', '/v1/images/2', {}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual('YYY', data)
data = image.data()
expect += [('GET', '/v1/images/2', {}, None)]
self.assertEqual(expect, self.api.calls)
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),
('HEAD', '/v1/images/3', {}, None),
('GET', '/v1/images/3', {}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual('ZZZ', data)
data = ''.join([b for b in image.data()])
expect += [('GET', '/v1/images/3', {}, None)]
self.assertEqual(expect, self.api.calls)
self.assertEqual('ZZZ', data)
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 get(self, 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 = parse.urlparse(self.api.url)
qs_dict = parse.parse_qs(parts.query)
self.assertTrue('is_public' in qs_dict)
self.assertTrue(qs_dict['is_public'][0].lower() == "true")
+503
View File
@@ -0,0 +1,503 @@
# 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.
import argparse
import json
import os
import six
import subprocess
import tempfile
import testtools
import mock
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 glanceclient.tests import utils
if six.PY3:
import io
file_type = io.IOBase
else:
file_type = file
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
)
},
'/v1/images/detail?limit=20&name=70aa106f-3750-4d7c-a5ce-0a535ac08d0a': {
'GET': (
{},
{'images': [
{
'id': '70aa106f-3750-4d7c-a5ce-0a535ac08d0a',
'name': 'imagedeleted',
'deleted': True,
'status': 'deleted',
},
]},
),
},
'/v1/images/70aa106f-3750-4d7c-a5ce-0a535ac08d0a': {
'HEAD': (
{
'x-image-meta-id': '70aa106f-3750-4d7c-a5ce-0a535ac08d0a',
'x-image-meta-status': 'deleted'
},
None
)
}
}
class ShellInvalidEndpointandParameterTest(utils.TestCase):
# Patch os.environ to avoid required auth info.
def setUp(self):
"""Run before each test."""
super(ShellInvalidEndpointandParameterTest, 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(ShellInvalidEndpointandParameterTest, 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.CommunicationError, self.run_command, 'image-list')
def test_image_create_invalid_endpoint(self):
self.assertRaises(
exc.CommunicationError,
self.run_command, 'image-create')
def test_image_delete_invalid_endpoint(self):
self.assertRaises(
exc.CommunicationError,
self.run_command, 'image-delete <fake>')
def test_image_download_invalid_endpoint(self):
self.assertRaises(
exc.CommunicationError,
self.run_command, 'image-download <fake>')
def test_members_list_invalid_endpoint(self):
self.assertRaises(
exc.CommunicationError,
self.run_command, 'member-list --image-id fake')
def test_image_show_invalid_endpoint(self):
self.assertRaises(
exc.CommunicationError,
self.run_command, 'image-show --human-readable <IMAGE_ID>')
def test_member_create_invalid_endpoint(self):
self.assertRaises(
exc.CommunicationError,
self.run_command,
'member-create --can-share <IMAGE_ID> <TENANT_ID>')
def test_member_delete_invalid_endpoint(self):
self.assertRaises(
exc.CommunicationError,
self.run_command,
'member-delete <IMAGE_ID> <TENANT_ID>')
@mock.patch('sys.stderr')
def test_image_create_invalid_size_parameter(self, __):
self.assertRaises(
SystemExit,
self.run_command, 'image-create --size 10gb')
@mock.patch('sys.stderr')
def test_image_create_invalid_ram_parameter(self, __):
self.assertRaises(
SystemExit,
self.run_command, 'image-create --min-ram 10gb')
@mock.patch('sys.stderr')
def test_image_create_invalid_min_disk_parameter(self, __):
self.assertRaises(
SystemExit,
self.run_command, 'image-create --min-disk 10gb')
@mock.patch('sys.stderr')
def test_image_update_invalid_size_parameter(self, __):
self.assertRaises(
SystemExit,
self.run_command, 'image-update --size 10gb')
@mock.patch('sys.stderr')
def test_image_update_invalid_min_disk_parameter(self, __):
self.assertRaises(
SystemExit,
self.run_command, 'image-update --min-disk 10gb')
@mock.patch('sys.stderr')
def test_image_update_invalid_ram_parameter(self, __):
self.assertRaises(
SystemExit,
self.run_command, 'image-update --min-ram 10gb')
@mock.patch('sys.stderr')
def test_image_list_invalid_min_size_parameter(self, __):
self.assertRaises(
SystemExit,
self.run_command, 'image-list --size-min 10gb')
@mock.patch('sys.stderr')
def test_image_list_invalid_max_size_parameter(self, __):
self.assertRaises(
SystemExit,
self.run_command, 'image-list --size-max 10gb')
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_delete_deleted(self):
self.assertRaises(
exc.CommandError,
v1shell.do_image_delete,
self.gc,
argparse.Namespace(
images=['70aa106f-3750-4d7c-a5ce-0a535ac08d0a']
)
)
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_opened_stdin(self):
"""Supply glanceclient with a stdin, and perform an image
update to an active image. Glanceclient should not allow it.
"""
self.assertRaises(
SystemExit,
v1shell.do_image_update,
self.gc,
argparse.Namespace(
image='96d2c7e1-de4e-4612-8aa2-ba26610c804e',
property={},
)
)
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_type)
self.assertEqual('Some Data',
self.collected_args[1]['data'].read())
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_type)
self.assertEqual('Some Data\n',
self.collected_args[1]['data'].read())
finally:
try:
process.stdout.close()
except OSError:
pass
File diff suppressed because it is too large Load Diff
+120
View File
@@ -0,0 +1,120 @@
# 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
from glanceclient.tests import utils
from glanceclient.v2 import image_members
IMAGE = '3a4560a1-e585-443e-9b39-553b46ec92d1'
MEMBER = '11223344-5566-7788-9911-223344556677'
data_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'
}
),
}
}
schema_fixtures = {
'member': {
'GET': (
{},
{
'name': 'member',
'properties': {
'image_id': {},
'member_id': {}
}
},
)
}
}
class TestController(testtools.TestCase):
def setUp(self):
super(TestController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = image_members.Controller(self.api, self.schema_api)
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, image_members[0].image_id)
self.assertEqual(MEMBER, image_members[0].member_id)
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(expect, self.api.calls)
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, image_member.image_id)
self.assertEqual(MEMBER, image_member.member_id)
self.assertEqual(status, image_member.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, image_member.image_id)
self.assertEqual(MEMBER, image_member.member_id)
self.assertEqual(status, image_member.status)
@@ -0,0 +1,674 @@
# 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
from glanceclient.tests import utils
from glanceclient.v2 import metadefs
NAMESPACE1 = 'Namespace1'
NAMESPACE2 = 'Namespace2'
NAMESPACE3 = 'Namespace3'
NAMESPACE4 = 'Namespace4'
NAMESPACE5 = 'Namespace5'
NAMESPACE6 = 'Namespace6'
NAMESPACE7 = 'Namespace7'
NAMESPACE8 = 'Namespace8'
NAMESPACENEW = 'NamespaceNew'
RESOURCE_TYPE1 = 'ResourceType1'
RESOURCE_TYPE2 = 'ResourceType2'
OBJECT1 = 'Object1'
PROPERTY1 = 'Property1'
PROPERTY2 = 'Property2'
def _get_namespace_fixture(ns_name, rt_name=RESOURCE_TYPE1, **kwargs):
ns = {
"display_name": "Flavor Quota",
"description": "DESCRIPTION1",
"self": "/v2/metadefs/namespaces/%s" % ns_name,
"namespace": ns_name,
"visibility": "public",
"protected": True,
"owner": "admin",
"resource_types": [
{
"name": rt_name
}
],
"schema": "/v2/schemas/metadefs/namespace",
"created_at": "2014-08-14T09:07:06Z",
"updated_at": "2014-08-14T09:07:06Z",
}
ns.update(kwargs)
return ns
data_fixtures = {
"/v2/metadefs/namespaces?limit=20": {
"GET": (
{},
{
"first": "/v2/metadefs/namespaces?limit=20",
"namespaces": [
_get_namespace_fixture(NAMESPACE1),
_get_namespace_fixture(NAMESPACE2),
],
"schema": "/v2/schemas/metadefs/namespaces"
}
)
},
"/v2/metadefs/namespaces?limit=1": {
"GET": (
{},
{
"first": "/v2/metadefs/namespaces?limit=1",
"namespaces": [
_get_namespace_fixture(NAMESPACE7),
],
"schema": "/v2/schemas/metadefs/namespaces",
"next": "/v2/metadefs/namespaces?marker=%s&limit=1"
% NAMESPACE7,
}
)
},
"/v2/metadefs/namespaces?limit=1&marker=%s" % NAMESPACE7: {
"GET": (
{},
{
"first": "/v2/metadefs/namespaces?limit=2",
"namespaces": [
_get_namespace_fixture(NAMESPACE8),
],
"schema": "/v2/schemas/metadefs/namespaces"
}
)
},
"/v2/metadefs/namespaces?limit=2&marker=%s" % NAMESPACE6: {
"GET": (
{},
{
"first": "/v2/metadefs/namespaces?limit=2",
"namespaces": [
_get_namespace_fixture(NAMESPACE7),
_get_namespace_fixture(NAMESPACE8),
],
"schema": "/v2/schemas/metadefs/namespaces"
}
)
},
"/v2/metadefs/namespaces?limit=20&sort_dir=asc": {
"GET": (
{},
{
"first": "/v2/metadefs/namespaces?limit=1",
"namespaces": [
_get_namespace_fixture(NAMESPACE1),
],
"schema": "/v2/schemas/metadefs/namespaces"
}
)
},
"/v2/metadefs/namespaces?limit=20&sort_key=created_at": {
"GET": (
{},
{
"first": "/v2/metadefs/namespaces?limit=1",
"namespaces": [
_get_namespace_fixture(NAMESPACE1),
],
"schema": "/v2/schemas/metadefs/namespaces"
}
)
},
"/v2/metadefs/namespaces?limit=20&resource_types=%s" % RESOURCE_TYPE1: {
"GET": (
{},
{
"first": "/v2/metadefs/namespaces?limit=20",
"namespaces": [
_get_namespace_fixture(NAMESPACE3),
],
"schema": "/v2/schemas/metadefs/namespaces"
}
)
},
"/v2/metadefs/namespaces?limit=20&resource_types="
"%s%%2C%s" % (RESOURCE_TYPE1, RESOURCE_TYPE2): {
"GET": (
{},
{
"first": "/v2/metadefs/namespaces?limit=20",
"namespaces": [
_get_namespace_fixture(NAMESPACE4),
],
"schema": "/v2/schemas/metadefs/namespaces"
}
)
},
"/v2/metadefs/namespaces?limit=20&visibility=private": {
"GET": (
{},
{
"first": "/v2/metadefs/namespaces?limit=20",
"namespaces": [
_get_namespace_fixture(NAMESPACE5),
],
"schema": "/v2/schemas/metadefs/namespaces"
}
)
},
"/v2/metadefs/namespaces": {
"POST": (
{},
{
"display_name": "Flavor Quota",
"description": "DESCRIPTION1",
"self": "/v2/metadefs/namespaces/%s" % 'NamespaceNew',
"namespace": 'NamespaceNew',
"visibility": "public",
"protected": True,
"owner": "admin",
"schema": "/v2/schemas/metadefs/namespace",
"created_at": "2014-08-14T09:07:06Z",
"updated_at": "2014-08-14T09:07:06Z",
}
)
},
"/v2/metadefs/namespaces/%s" % NAMESPACE1: {
"GET": (
{},
{
"display_name": "Flavor Quota",
"description": "DESCRIPTION1",
"objects": [
{
"description": "DESCRIPTION2",
"name": "OBJECT1",
"self": "/v2/metadefs/namespaces/%s/objects/" %
OBJECT1,
"required": [],
"properties": {
PROPERTY1: {
"type": "integer",
"description": "DESCRIPTION3",
"title": "Quota: CPU Shares"
},
PROPERTY2: {
"minimum": 1000,
"type": "integer",
"description": "DESCRIPTION4",
"maximum": 1000000,
"title": "Quota: CPU Period"
},
},
"schema": "/v2/schemas/metadefs/object"
}
],
"self": "/v2/metadefs/namespaces/%s" % NAMESPACE1,
"namespace": NAMESPACE1,
"visibility": "public",
"protected": True,
"owner": "admin",
"resource_types": [
{
"name": RESOURCE_TYPE1
}
],
"schema": "/v2/schemas/metadefs/namespace",
"created_at": "2014-08-14T09:07:06Z",
"updated_at": "2014-08-14T09:07:06Z",
}
),
"PUT": (
{},
{
"display_name": "Flavor Quota",
"description": "DESCRIPTION1",
"objects": [
{
"description": "DESCRIPTION2",
"name": "OBJECT1",
"self": "/v2/metadefs/namespaces/%s/objects/" %
OBJECT1,
"required": [],
"properties": {
PROPERTY1: {
"type": "integer",
"description": "DESCRIPTION3",
"title": "Quota: CPU Shares"
},
PROPERTY2: {
"minimum": 1000,
"type": "integer",
"description": "DESCRIPTION4",
"maximum": 1000000,
"title": "Quota: CPU Period"
},
},
"schema": "/v2/schemas/metadefs/object"
}
],
"self": "/v2/metadefs/namespaces/%s" % NAMESPACENEW,
"namespace": NAMESPACENEW,
"visibility": "public",
"protected": True,
"owner": "admin",
"resource_types": [
{
"name": RESOURCE_TYPE1
}
],
"schema": "/v2/schemas/metadefs/namespace",
"created_at": "2014-08-14T09:07:06Z",
"updated_at": "2014-08-14T09:07:06Z",
}
),
"DELETE": (
{},
{}
)
},
"/v2/metadefs/namespaces/%s?resource_type=%s" % (NAMESPACE6,
RESOURCE_TYPE1):
{
"GET": (
{},
{
"display_name": "Flavor Quota",
"description": "DESCRIPTION1",
"objects": [],
"self": "/v2/metadefs/namespaces/%s" % NAMESPACE1,
"namespace": NAMESPACE6,
"visibility": "public",
"protected": True,
"owner": "admin",
"resource_types": [
{
"name": RESOURCE_TYPE1
}
],
"schema": "/v2/schemas/metadefs/namespace",
"created_at": "2014-08-14T09:07:06Z",
"updated_at": "2014-08-14T09:07:06Z",
}
),
},
}
schema_fixtures = {
"metadefs/namespace":
{
"GET": (
{},
{
"additionalProperties": False,
"definitions": {
"property": {
"additionalProperties": {
"required": [
"title",
"type"
],
"type": "object",
"properties": {
"additionalItems": {
"type": "boolean"
},
"enum": {
"type": "array"
},
"description": {
"type": "string"
},
"title": {
"type": "string"
},
"default": {},
"minLength": {
"$ref": "#/definitions/"
"positiveIntegerDefault0"
},
"required": {
"$ref": "#/definitions/stringArray"
},
"maximum": {
"type": "number"
},
"minItems": {
"$ref": "#/definitions/"
"positiveIntegerDefault0"
},
"readonly": {
"type": "boolean"
},
"minimum": {
"type": "number"
},
"maxItems": {
"$ref": "#/definitions/"
"positiveInteger"
},
"maxLength": {
"$ref": "#/definitions/positiveInteger"
},
"uniqueItems": {
"default": False,
"type": "boolean"
},
"pattern": {
"type": "string",
"format": "regex"
},
"items": {
"type": "object",
"properties": {
"enum": {
"type": "array"
},
"type": {
"enum": [
"array",
"boolean",
"integer",
"number",
"object",
"string",
"null"
],
"type": "string"
}
}
},
"type": {
"enum": [
"array",
"boolean",
"integer",
"number",
"object",
"string",
"null"
],
"type": "string"
}
}
},
"type": "object"
},
"positiveIntegerDefault0": {
"allOf": [
{
"$ref": "#/definitions/positiveInteger"
},
{
"default": 0
}
]
},
"stringArray": {
"uniqueItems": True,
"items": {
"type": "string"
},
"type": "array"
},
"positiveInteger": {
"minimum": 0,
"type": "integer"
}
},
"required": [
"namespace"
],
"name": "namespace",
"properties": {
"description": {
"type": "string",
"description": "Provides a user friendly description "
"of the namespace.",
"maxLength": 500
},
"updated_at": {
"type": "string",
"description": "Date and time of the last namespace "
"modification (READ-ONLY)",
"format": "date-time"
},
"visibility": {
"enum": [
"public",
"private"
],
"type": "string",
"description": "Scope of namespace accessibility."
},
"self": {
"type": "string"
},
"objects": {
"items": {
"type": "object",
"properties": {
"properties": {
"$ref": "#/definitions/property"
},
"required": {
"$ref": "#/definitions/stringArray"
},
"name": {
"type": "string"
},
"description": {
"type": "string"
}
}
},
"type": "array"
},
"owner": {
"type": "string",
"description": "Owner of the namespace.",
"maxLength": 255
},
"resource_types": {
"items": {
"type": "object",
"properties": {
"prefix": {
"type": "string"
},
"name": {
"type": "string"
},
"metadata_type": {
"type": "string"
}
}
},
"type": "array"
},
"properties": {
"$ref": "#/definitions/property"
},
"display_name": {
"type": "string",
"description": "The user friendly name for the "
"namespace. Used by UI if available.",
"maxLength": 80
},
"created_at": {
"type": "string",
"description": "Date and time of namespace creation "
"(READ-ONLY)",
"format": "date-time"
},
"namespace": {
"type": "string",
"description": "The unique namespace text.",
"maxLength": 80
},
"protected": {
"type": "boolean",
"description": "If true, namespace will not be "
"deletable."
},
"schema": {
"type": "string"
}
}
}
),
}
}
class TestNamespaceController(testtools.TestCase):
def setUp(self):
super(TestNamespaceController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = metadefs.NamespaceController(self.api,
self.schema_api)
def test_list_namespaces(self):
namespaces = list(self.controller.list())
self.assertEqual(2, len(namespaces))
self.assertEqual(NAMESPACE1, namespaces[0]['namespace'])
self.assertEqual(NAMESPACE2, namespaces[1]['namespace'])
def test_list_namespaces_paginate(self):
namespaces = list(self.controller.list(page_size=1))
self.assertEqual(2, len(namespaces))
self.assertEqual(NAMESPACE7, namespaces[0]['namespace'])
self.assertEqual(NAMESPACE8, namespaces[1]['namespace'])
def test_list_with_limit_greater_than_page_size(self):
namespaces = list(self.controller.list(page_size=1, limit=2))
self.assertEqual(2, len(namespaces))
self.assertEqual(NAMESPACE7, namespaces[0]['namespace'])
self.assertEqual(NAMESPACE8, namespaces[1]['namespace'])
def test_list_with_marker(self):
namespaces = list(self.controller.list(marker=NAMESPACE6, page_size=2))
self.assertEqual(2, len(namespaces))
self.assertEqual(NAMESPACE7, namespaces[0]['namespace'])
self.assertEqual(NAMESPACE8, namespaces[1]['namespace'])
def test_list_with_sort_dir(self):
namespaces = list(self.controller.list(sort_dir='asc', limit=1))
self.assertEqual(1, len(namespaces))
self.assertEqual(NAMESPACE1, namespaces[0]['namespace'])
def test_list_with_sort_dir_invalid(self):
# NOTE(TravT): The clients work by returning an iterator.
# Invoking the iterator is what actually executes the logic.
ns_iterator = self.controller.list(sort_dir='foo')
self.assertRaises(ValueError, next, ns_iterator)
def test_list_with_sort_key(self):
namespaces = list(self.controller.list(sort_key='created_at', limit=1))
self.assertEqual(1, len(namespaces))
self.assertEqual(NAMESPACE1, namespaces[0]['namespace'])
def test_list_with_sort_key_invalid(self):
# NOTE(TravT): The clients work by returning an iterator.
# Invoking the iterator is what actually executes the logic.
ns_iterator = self.controller.list(sort_key='foo')
self.assertRaises(ValueError, next, ns_iterator)
def test_list_namespaces_with_one_resource_type_filter(self):
namespaces = list(self.controller.list(
filters={
'resource_types': [RESOURCE_TYPE1]
}
))
self.assertEqual(1, len(namespaces))
self.assertEqual(NAMESPACE3, namespaces[0]['namespace'])
def test_list_namespaces_with_multiple_resource_types_filter(self):
namespaces = list(self.controller.list(
filters={
'resource_types': [RESOURCE_TYPE1, RESOURCE_TYPE2]
}
))
self.assertEqual(1, len(namespaces))
self.assertEqual(NAMESPACE4, namespaces[0]['namespace'])
def test_list_namespaces_with_visibility_filter(self):
namespaces = list(self.controller.list(
filters={
'visibility': 'private'
}
))
self.assertEqual(1, len(namespaces))
self.assertEqual(NAMESPACE5, namespaces[0]['namespace'])
def test_get_namespace(self):
namespace = self.controller.get(NAMESPACE1)
self.assertEqual(NAMESPACE1, namespace.namespace)
self.assertTrue(namespace.protected)
def test_get_namespace_with_resource_type(self):
namespace = self.controller.get(NAMESPACE6,
resource_type=RESOURCE_TYPE1)
self.assertEqual(NAMESPACE6, namespace.namespace)
self.assertTrue(namespace.protected)
def test_create_namespace(self):
properties = {
'namespace': NAMESPACENEW
}
namespace = self.controller.create(**properties)
self.assertEqual(NAMESPACENEW, namespace.namespace)
self.assertTrue(namespace.protected)
def test_create_namespace_invalid_data(self):
properties = {}
self.assertRaises(TypeError, self.controller.create, **properties)
def test_create_namespace_invalid_property(self):
properties = {'namespace': 'NewNamespace', 'protected': '123'}
self.assertRaises(TypeError, self.controller.create, **properties)
def test_update_namespace(self):
properties = {'display_name': 'My Updated Name'}
namespace = self.controller.update(NAMESPACE1, **properties)
self.assertEqual(NAMESPACE1, namespace.namespace)
def test_update_namespace_invalid_property(self):
properties = {'protected': '123'}
self.assertRaises(TypeError, self.controller.update, NAMESPACE1,
**properties)
def test_delete_namespace(self):
self.controller.delete(NAMESPACE1)
expect = [
('DELETE',
'/v2/metadefs/namespaces/%s' % NAMESPACE1,
{},
None)]
self.assertEqual(expect, self.api.calls)
@@ -0,0 +1,323 @@
# 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 six
import testtools
from glanceclient.tests import utils
from glanceclient.v2 import metadefs
NAMESPACE1 = 'Namespace1'
OBJECT1 = 'Object1'
OBJECT2 = 'Object2'
OBJECTNEW = 'ObjectNew'
PROPERTY1 = 'Property1'
PROPERTY2 = 'Property2'
PROPERTY3 = 'Property3'
PROPERTY4 = 'Property4'
def _get_object_fixture(ns_name, obj_name, **kwargs):
obj = {
"description": "DESCRIPTION",
"name": obj_name,
"self": "/v2/metadefs/namespaces/%s/objects/%s" %
(ns_name, obj_name),
"required": [],
"properties": {
PROPERTY1: {
"type": "integer",
"description": "DESCRIPTION",
"title": "Quota: CPU Shares"
},
PROPERTY2: {
"minimum": 1000,
"type": "integer",
"description": "DESCRIPTION",
"maximum": 1000000,
"title": "Quota: CPU Period"
}},
"schema": "/v2/schemas/metadefs/object",
"created_at": "2014-08-14T09:07:06Z",
"updated_at": "2014-08-14T09:07:06Z",
}
obj.update(kwargs)
return obj
data_fixtures = {
"/v2/metadefs/namespaces/%s/objects" % NAMESPACE1: {
"GET": (
{},
{
"objects": [
_get_object_fixture(NAMESPACE1, OBJECT1),
_get_object_fixture(NAMESPACE1, OBJECT2)
],
"schema": "v2/schemas/metadefs/objects"
}
),
"POST": (
{},
_get_object_fixture(NAMESPACE1, OBJECTNEW)
),
"DELETE": (
{},
{}
)
},
"/v2/metadefs/namespaces/%s/objects/%s" % (NAMESPACE1, OBJECT1): {
"GET": (
{},
_get_object_fixture(NAMESPACE1, OBJECT1)
),
"PUT": (
{},
_get_object_fixture(NAMESPACE1, OBJECT1)
),
"DELETE": (
{},
{}
)
}
}
schema_fixtures = {
"metadefs/object": {
"GET": (
{},
{
"additionalProperties": False,
"definitions": {
"property": {
"additionalProperties": {
"required": [
"title",
"type"
],
"type": "object",
"properties": {
"additionalItems": {
"type": "boolean"
},
"enum": {
"type": "array"
},
"description": {
"type": "string"
},
"title": {
"type": "string"
},
"default": {},
"minLength": {
"$ref": "#/definitions/positiveInteger"
"Default0"
},
"required": {
"$ref": "#/definitions/stringArray"
},
"maximum": {
"type": "number"
},
"minItems": {
"$ref": "#/definitions/positiveInteger"
"Default0"
},
"readonly": {
"type": "boolean"
},
"minimum": {
"type": "number"
},
"maxItems": {
"$ref": "#/definitions/positiveInteger"
},
"maxLength": {
"$ref": "#/definitions/positiveInteger"
},
"uniqueItems": {
"default": False,
"type": "boolean"
},
"pattern": {
"type": "string",
"format": "regex"
},
"items": {
"type": "object",
"properties": {
"enum": {
"type": "array"
},
"type": {
"enum": [
"array",
"boolean",
"integer",
"number",
"object",
"string",
"null"
],
"type": "string"
}
}
},
"type": {
"enum": [
"array",
"boolean",
"integer",
"number",
"object",
"string",
"null"
],
"type": "string"
}
}
},
"type": "object"
},
"positiveIntegerDefault0": {
"allOf": [
{
"$ref": "#/definitions/positiveInteger"
},
{
"default": 0
}
]
},
"stringArray": {
"uniqueItems": True,
"items": {
"type": "string"
},
"type": "array"
},
"positiveInteger": {
"minimum": 0,
"type": "integer"
}
},
"required": [
"name"
],
"name": "object",
"properties": {
"created_at": {
"type": "string",
"description": "Date and time of object creation "
"(READ-ONLY)",
"format": "date-time"
},
"description": {
"type": "string"
},
"name": {
"type": "string"
},
"self": {
"type": "string"
},
"required": {
"$ref": "#/definitions/stringArray"
},
"properties": {
"$ref": "#/definitions/property"
},
"schema": {
"type": "string"
},
"updated_at": {
"type": "string",
"description": "Date and time of the last object "
"modification (READ-ONLY)",
"format": "date-time"
},
}
}
)
}
}
class TestObjectController(testtools.TestCase):
def setUp(self):
super(TestObjectController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = metadefs.ObjectController(self.api, self.schema_api)
def test_list_object(self):
objects = list(self.controller.list(NAMESPACE1))
actual = [obj.name for obj in objects]
self.assertEqual([OBJECT1, OBJECT2], actual)
def test_get_object(self):
obj = self.controller.get(NAMESPACE1, OBJECT1)
self.assertEqual(OBJECT1, obj.name)
self.assertEqual(sorted([PROPERTY1, PROPERTY2]),
sorted(list(six.iterkeys(obj.properties))))
def test_create_object(self):
properties = {
'name': OBJECTNEW,
'description': 'DESCRIPTION'
}
obj = self.controller.create(NAMESPACE1, **properties)
self.assertEqual(OBJECTNEW, obj.name)
def test_create_object_invalid_property(self):
properties = {
'namespace': NAMESPACE1
}
self.assertRaises(TypeError, self.controller.create, **properties)
def test_update_object(self):
properties = {
'description': 'UPDATED_DESCRIPTION'
}
obj = self.controller.update(NAMESPACE1, OBJECT1, **properties)
self.assertEqual(OBJECT1, obj.name)
def test_update_object_invalid_property(self):
properties = {
'required': 'INVALID'
}
self.assertRaises(TypeError, self.controller.update, NAMESPACE1,
OBJECT1, **properties)
def test_delete_object(self):
self.controller.delete(NAMESPACE1, OBJECT1)
expect = [
('DELETE',
'/v2/metadefs/namespaces/%s/objects/%s' % (NAMESPACE1, OBJECT1),
{},
None)]
self.assertEqual(expect, self.api.calls)
def test_delete_all_objects(self):
self.controller.delete_all(NAMESPACE1)
expect = [
('DELETE',
'/v2/metadefs/namespaces/%s/objects' % NAMESPACE1,
{},
None)]
self.assertEqual(expect, self.api.calls)
@@ -0,0 +1,300 @@
# 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
from glanceclient.tests import utils
from glanceclient.v2 import metadefs
NAMESPACE1 = 'Namespace1'
PROPERTY1 = 'Property1'
PROPERTY2 = 'Property2'
PROPERTYNEW = 'PropertyNew'
data_fixtures = {
"/v2/metadefs/namespaces/%s/properties" % NAMESPACE1: {
"GET": (
{},
{
"properties": {
PROPERTY1: {
"default": "1",
"type": "integer",
"description": "Number of cores.",
"title": "cores"
},
PROPERTY2: {
"items": {
"enum": [
"Intel",
"AMD"
],
"type": "string"
},
"type": "array",
"description": "Specifies the CPU manufacturer.",
"title": "Vendor"
},
}
}
),
"POST": (
{},
{
"items": {
"enum": [
"Intel",
"AMD"
],
"type": "string"
},
"type": "array",
"description": "UPDATED_DESCRIPTION",
"title": "Vendor",
"name": PROPERTYNEW
}
),
"DELETE": (
{},
{}
)
},
"/v2/metadefs/namespaces/%s/properties/%s" % (NAMESPACE1, PROPERTY1): {
"GET": (
{},
{
"items": {
"enum": [
"Intel",
"AMD"
],
"type": "string"
},
"type": "array",
"description": "Specifies the CPU manufacturer.",
"title": "Vendor"
}
),
"PUT": (
{},
{
"items": {
"enum": [
"Intel",
"AMD"
],
"type": "string"
},
"type": "array",
"description": "UPDATED_DESCRIPTION",
"title": "Vendor"
}
),
"DELETE": (
{},
{}
)
}
}
schema_fixtures = {
"metadefs/property": {
"GET": (
{},
{
"additionalProperties": False,
"definitions": {
"positiveIntegerDefault0": {
"allOf": [
{
"$ref": "#/definitions/positiveInteger"
},
{
"default": 0
}
]
},
"stringArray": {
"minItems": 1,
"items": {
"type": "string"
},
"uniqueItems": True,
"type": "array"
},
"positiveInteger": {
"minimum": 0,
"type": "integer"
}
},
"required": [
"name",
"title",
"type"
],
"name": "property",
"properties": {
"description": {
"type": "string"
},
"minLength": {
"$ref": "#/definitions/positiveIntegerDefault0"
},
"enum": {
"type": "array"
},
"minimum": {
"type": "number"
},
"maxItems": {
"$ref": "#/definitions/positiveInteger"
},
"maxLength": {
"$ref": "#/definitions/positiveInteger"
},
"uniqueItems": {
"default": False,
"type": "boolean"
},
"additionalItems": {
"type": "boolean"
},
"name": {
"type": "string"
},
"title": {
"type": "string"
},
"default": {},
"pattern": {
"type": "string",
"format": "regex"
},
"required": {
"$ref": "#/definitions/stringArray"
},
"maximum": {
"type": "number"
},
"minItems": {
"$ref": "#/definitions/positiveIntegerDefault0"
},
"readonly": {
"type": "boolean"
},
"items": {
"type": "object",
"properties": {
"enum": {
"type": "array"
},
"type": {
"enum": [
"array",
"boolean",
"integer",
"number",
"object",
"string",
"null"
],
"type": "string"
}
}
},
"type": {
"enum": [
"array",
"boolean",
"integer",
"number",
"object",
"string",
"null"
],
"type": "string"
}
}
}
)
}
}
class TestPropertyController(testtools.TestCase):
def setUp(self):
super(TestPropertyController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = metadefs.PropertyController(self.api,
self.schema_api)
def test_list_property(self):
properties = list(self.controller.list(NAMESPACE1))
actual = [prop.name for prop in properties]
self.assertEqual(sorted([PROPERTY1, PROPERTY2]), sorted(actual))
def test_get_property(self):
prop = self.controller.get(NAMESPACE1, PROPERTY1)
self.assertEqual(PROPERTY1, prop.name)
def test_create_property(self):
properties = {
'name': PROPERTYNEW,
'title': 'TITLE',
'type': 'string'
}
obj = self.controller.create(NAMESPACE1, **properties)
self.assertEqual(PROPERTYNEW, obj.name)
def test_create_property_invalid_property(self):
properties = {
'namespace': NAMESPACE1
}
self.assertRaises(TypeError, self.controller.create, **properties)
def test_update_property(self):
properties = {
'description': 'UPDATED_DESCRIPTION'
}
prop = self.controller.update(NAMESPACE1, PROPERTY1, **properties)
self.assertEqual(PROPERTY1, prop.name)
def test_update_property_invalid_property(self):
properties = {
'type': 'INVALID'
}
self.assertRaises(TypeError, self.controller.update, NAMESPACE1,
PROPERTY1, **properties)
def test_delete_property(self):
self.controller.delete(NAMESPACE1, PROPERTY1)
expect = [
('DELETE',
'/v2/metadefs/namespaces/%s/properties/%s' % (NAMESPACE1,
PROPERTY1),
{},
None)]
self.assertEqual(expect, self.api.calls)
def test_delete_all_properties(self):
self.controller.delete_all(NAMESPACE1)
expect = [
('DELETE',
'/v2/metadefs/namespaces/%s/properties' % NAMESPACE1,
{},
None)]
self.assertEqual(expect, self.api.calls)
@@ -0,0 +1,186 @@
# 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
from glanceclient.tests import utils
from glanceclient.v2 import metadefs
NAMESPACE1 = 'Namespace1'
RESOURCE_TYPE1 = 'ResourceType1'
RESOURCE_TYPE2 = 'ResourceType2'
RESOURCE_TYPE3 = 'ResourceType3'
RESOURCE_TYPE4 = 'ResourceType4'
RESOURCE_TYPENEW = 'ResourceTypeNew'
data_fixtures = {
"/v2/metadefs/namespaces/%s/resource_types" % NAMESPACE1: {
"GET": (
{},
{
"resource_type_associations": [
{
"name": RESOURCE_TYPE3,
"created_at": "2014-08-14T09:07:06Z",
"updated_at": "2014-08-14T09:07:06Z",
},
{
"name": RESOURCE_TYPE4,
"prefix": "PREFIX:",
"created_at": "2014-08-14T09:07:06Z",
"updated_at": "2014-08-14T09:07:06Z",
}
]
}
),
"POST": (
{},
{
"name": RESOURCE_TYPENEW,
"prefix": "PREFIX:",
"created_at": "2014-08-14T09:07:06Z",
"updated_at": "2014-08-14T09:07:06Z",
}
),
},
"/v2/metadefs/namespaces/%s/resource_types/%s" % (NAMESPACE1,
RESOURCE_TYPE1):
{
"DELETE": (
{},
{}
),
},
"/v2/metadefs/resource_types": {
"GET": (
{},
{
"resource_types": [
{
"name": RESOURCE_TYPE1,
"created_at": "2014-08-14T09:07:06Z",
"updated_at": "2014-08-14T09:07:06Z",
},
{
"name": RESOURCE_TYPE2,
"created_at": "2014-08-14T09:07:06Z",
"updated_at": "2014-08-14T09:07:06Z",
}
]
}
)
}
}
schema_fixtures = {
"metadefs/resource_type": {
"GET": (
{},
{
"name": "resource_type",
"properties": {
"prefix": {
"type": "string",
"description": "Specifies the prefix to use for the "
"given resource type. Any properties "
"in the namespace should be prefixed "
"with this prefix when being applied "
"to the specified resource type. Must "
"include prefix separator (e.g. a "
"colon :).",
"maxLength": 80
},
"properties_target": {
"type": "string",
"description": "Some resource types allow more than "
"one key / value pair per instance. "
"For example, Cinder allows user and "
"image metadata on volumes. Only the "
"image properties metadata is "
"evaluated by Nova (scheduling or "
"drivers). This property allows a "
"namespace target to remove the "
"ambiguity.",
"maxLength": 80
},
"name": {
"type": "string",
"description": "Resource type names should be "
"aligned with Heat resource types "
"whenever possible: http://docs."
"openstack.org/developer/heat/"
"template_guide/openstack.html",
"maxLength": 80
},
"created_at": {
"type": "string",
"description": "Date and time of resource type "
"association (READ-ONLY)",
"format": "date-time"
},
"updated_at": {
"type": "string",
"description": "Date and time of the last resource "
"type association modification "
"(READ-ONLY)",
"format": "date-time"
},
}
}
)
}
}
class TestResoureTypeController(testtools.TestCase):
def setUp(self):
super(TestResoureTypeController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = metadefs.ResourceTypeController(self.api,
self.schema_api)
def test_list_resource_types(self):
resource_types = list(self.controller.list())
names = [rt.name for rt in resource_types]
self.assertEqual([RESOURCE_TYPE1, RESOURCE_TYPE2], names)
def test_get_resource_types(self):
resource_types = list(self.controller.get(NAMESPACE1))
names = [rt.name for rt in resource_types]
self.assertEqual([RESOURCE_TYPE3, RESOURCE_TYPE4], names)
def test_associate_resource_types(self):
resource_types = self.controller.associate(NAMESPACE1,
name=RESOURCE_TYPENEW)
self.assertEqual(RESOURCE_TYPENEW, resource_types['name'])
def test_associate_resource_types_invalid_property(self):
longer = '1234' * 50
properties = {'name': RESOURCE_TYPENEW, 'prefix': longer}
self.assertRaises(TypeError, self.controller.associate, NAMESPACE1,
**properties)
def test_deassociate_resource_types(self):
self.controller.deassociate(NAMESPACE1, RESOURCE_TYPE1)
expect = [
('DELETE',
'/v2/metadefs/namespaces/%s/resource_types/%s' % (NAMESPACE1,
RESOURCE_TYPE1),
{},
None)]
self.assertEqual(expect, self.api.calls)
+231
View File
@@ -0,0 +1,231 @@
# 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 jsonpatch import JsonPatch
import testtools
import warlock
from glanceclient.tests import utils
from glanceclient.v2 import schemas
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'},
'tags': {'type': 'array'}
},
},
),
},
}
_SCHEMA = schemas.Schema({
'name': 'image',
'properties': {
'name': {'type': 'string'},
'color': {'type': 'string'},
'shape': {'type': 'string', 'is_base': False},
'tags': {'type': 'array'}
},
})
def compare_json_patches(a, b):
"""Return 0 if a and b describe the same JSON patch."""
return JsonPatch.from_string(a) == JsonPatch.from_string(b)
class TestSchemaProperty(testtools.TestCase):
def test_property_minimum(self):
prop = schemas.SchemaProperty('size')
self.assertEqual('size', prop.name)
def test_property_description(self):
prop = schemas.SchemaProperty('size', description='some quantity')
self.assertEqual('size', prop.name)
self.assertEqual('some quantity', prop.description)
def test_property_is_base(self):
prop1 = schemas.SchemaProperty('name')
prop2 = schemas.SchemaProperty('foo', is_base=False)
prop3 = schemas.SchemaProperty('foo', is_base=True)
self.assertTrue(prop1.is_base)
self.assertFalse(prop2.is_base)
self.assertTrue(prop3.is_base)
class TestSchema(testtools.TestCase):
def test_schema_minimum(self):
raw_schema = {'name': 'Country', 'properties': {}}
schema = schemas.Schema(raw_schema)
self.assertEqual('Country', schema.name)
self.assertEqual([], schema.properties)
def test_schema_with_property(self):
raw_schema = {'name': 'Country', 'properties': {'size': {}}}
schema = schemas.Schema(raw_schema)
self.assertEqual('Country', schema.name)
self.assertEqual(['size'], [p.name for p in schema.properties])
def test_raw(self):
raw_schema = {'name': 'Country', 'properties': {}}
schema = schemas.Schema(raw_schema)
self.assertEqual(raw_schema, schema.raw())
def test_property_is_base(self):
raw_schema = {'name': 'Country',
'properties': {
'size': {},
'population': {'is_base': False}}}
schema = schemas.Schema(raw_schema)
self.assertTrue(schema.is_base_property('size'))
self.assertFalse(schema.is_base_property('population'))
self.assertFalse(schema.is_base_property('foo'))
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('image', schema.name)
self.assertEqual(['name', 'tags'],
[p.name for p in schema.properties])
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.assertTrue(compare_json_patches(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.assertTrue(compare_json_patches(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.assertTrue(compare_json_patches(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.assertTrue(compare_json_patches(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.assertTrue(compare_json_patches(patch, expected))
def test_patch_should_add_missing_custom_properties(self):
obj = {
'name': 'fred'
}
original = self.model(obj)
original['shape'] = 'circle'
patch = original.patch
expected = '[{"path": "/shape", "value": "circle", "op": "add"}]'
self.assertTrue(compare_json_patches(patch, expected))
def test_patch_should_replace_custom_properties(self):
obj = {
'name': 'fred',
'shape': 'circle'
}
original = self.model(obj)
original['shape'] = 'square'
patch = original.patch
expected = '[{"path": "/shape", "value": "square", "op": "replace"}]'
self.assertTrue(compare_json_patches(patch, expected))
def test_patch_should_replace_tags(self):
obj = {'name': 'fred', }
original = self.model(obj)
original['tags'] = ['tag1', 'tag2']
patch = original.patch
expected = '[{"path": "/tags", "value": ["tag1", "tag2"], ' \
'"op": "replace"}]'
self.assertTrue(compare_json_patches(patch, expected))
File diff suppressed because it is too large Load Diff
+81
View File
@@ -0,0 +1,81 @@
# 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
from glanceclient.tests import utils
from glanceclient.v2 import image_tags
IMAGE = '3a4560a1-e585-443e-9b39-553b46ec92d1'
TAG = 'tag01'
data_fixtures = {
'/v2/images/{image}/tags/{tag_value}'.format(image=IMAGE, tag_value=TAG): {
'DELETE': (
{},
None,
),
'PUT': (
{},
{
'image_id': IMAGE,
'tag_value': TAG
}
),
}
}
schema_fixtures = {
'tag': {
'GET': (
{},
{'name': 'image', 'properties': {'image_id': {}, 'tags': {}}}
)
}
}
class TestController(testtools.TestCase):
def setUp(self):
super(TestController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = image_tags.Controller(self.api, self.schema_api)
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(expect, self.api.calls)
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(expect, self.api.calls)
+285
View File
@@ -0,0 +1,285 @@
# Copyright 2013 OpenStack Foundation.
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import testtools
from glanceclient.tests import utils
from glanceclient.v2 import tasks
_OWNED_TASK_ID = 'a4963502-acc7-42ba-ad60-5aa0962b7faf'
_OWNER_ID = '6bd473f0-79ae-40ad-a927-e07ec37b642f'
_FAKE_OWNER_ID = '63e7f218-29de-4477-abdc-8db7c9533188'
fixtures = {
'/v2/tasks?limit=%d' % tasks.DEFAULT_PAGE_SIZE: {
'GET': (
{},
{'tasks': [
{
'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
'type': 'import',
'status': 'pending',
},
{
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
'type': 'import',
'status': 'processing',
},
]},
),
},
'/v2/tasks?limit=1': {
'GET': (
{},
{
'tasks': [
{
'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
'type': 'import',
'status': 'pending',
},
],
'next': ('/v2/tasks?limit=1&'
'marker=3a4560a1-e585-443e-9b39-553b46ec92d1'),
},
),
},
('/v2/tasks?limit=1&marker=3a4560a1-e585-443e-9b39-553b46ec92d1'): {
'GET': (
{},
{'tasks': [
{
'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810',
'type': 'import',
'status': 'pending',
},
]},
),
},
'/v2/tasks/3a4560a1-e585-443e-9b39-553b46ec92d1': {
'GET': (
{},
{
'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
'type': 'import',
'status': 'pending',
},
),
'PATCH': (
{},
'',
),
},
'/v2/tasks/e7e59ff6-fa2e-4075-87d3-1a1398a07dc3': {
'GET': (
{},
{
'id': 'e7e59ff6-fa2e-4075-87d3-1a1398a07dc3',
'type': 'import',
'status': 'pending',
},
),
'PATCH': (
{},
'',
),
},
'/v2/tasks': {
'POST': (
{},
{
'id': '3a4560a1-e585-443e-9b39-553b46ec92d1',
'type': 'import',
'status': 'pending',
'input': '{"import_from": "file:///", '
'"import_from_format": "qcow2"}'
},
),
},
'/v2/tasks?limit=%d&owner=%s' % (tasks.DEFAULT_PAGE_SIZE, _OWNER_ID): {
'GET': (
{},
{'tasks': [
{
'id': _OWNED_TASK_ID,
},
]},
),
},
'/v2/tasks?limit=%d&status=processing' % (tasks.DEFAULT_PAGE_SIZE): {
'GET': (
{},
{'tasks': [
{
'id': _OWNED_TASK_ID,
},
]},
),
},
'/v2/tasks?limit=%d&type=import' % (tasks.DEFAULT_PAGE_SIZE): {
'GET': (
{},
{'tasks': [
{
'id': _OWNED_TASK_ID,
},
]},
),
},
'/v2/tasks?limit=%d&type=fake' % (tasks.DEFAULT_PAGE_SIZE): {
'GET': (
{},
{'tasks': [
]},
),
},
'/v2/tasks?limit=%d&status=fake' % (tasks.DEFAULT_PAGE_SIZE): {
'GET': (
{},
{'tasks': [
]},
),
},
'/v2/tasks?limit=%d&type=import' % (tasks.DEFAULT_PAGE_SIZE): {
'GET': (
{},
{'tasks': [
{
'id': _OWNED_TASK_ID,
},
]},
),
},
'/v2/tasks?limit=%d&owner=%s' % (tasks.DEFAULT_PAGE_SIZE, _FAKE_OWNER_ID):
{
'GET': ({},
{'tasks': []},
),
}
}
schema_fixtures = {
'task': {
'GET': (
{},
{
'name': 'task',
'properties': {
'id': {},
'type': {},
'status': {},
'input': {},
'result': {},
'message': {},
},
'additionalProperties': False,
}
)
}
}
class TestController(testtools.TestCase):
def setUp(self):
super(TestController, self).setUp()
self.api = utils.FakeAPI(fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = tasks.Controller(self.api, self.schema_api)
def test_list_tasks(self):
#NOTE(flwang): cast to list since the controller returns a generator
tasks = list(self.controller.list())
self.assertEqual(tasks[0].id, '3a4560a1-e585-443e-9b39-553b46ec92d1')
self.assertEqual(tasks[0].type, 'import')
self.assertEqual(tasks[0].status, 'pending')
self.assertEqual(tasks[1].id, '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810')
self.assertEqual(tasks[1].type, 'import')
self.assertEqual(tasks[1].status, 'processing')
def test_list_tasks_paginated(self):
#NOTE(flwang): cast to list since the controller returns a generator
tasks = list(self.controller.list(page_size=1))
self.assertEqual(tasks[0].id, '3a4560a1-e585-443e-9b39-553b46ec92d1')
self.assertEqual(tasks[0].type, 'import')
self.assertEqual(tasks[1].id, '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810')
self.assertEqual(tasks[1].type, 'import')
def test_list_tasks_with_status(self):
filters = {'filters': {'status': 'processing'}}
tasks = list(self.controller.list(**filters))
self.assertEqual(tasks[0].id, _OWNED_TASK_ID)
def test_list_tasks_with_wrong_status(self):
filters = {'filters': {'status': 'fake'}}
tasks = list(self.controller.list(**filters))
self.assertEqual(len(tasks), 0)
def test_list_tasks_with_type(self):
filters = {'filters': {'type': 'import'}}
tasks = list(self.controller.list(**filters))
self.assertEqual(tasks[0].id, _OWNED_TASK_ID)
def test_list_tasks_with_wrong_type(self):
filters = {'filters': {'type': 'fake'}}
tasks = list(self.controller.list(**filters))
self.assertEqual(len(tasks), 0)
def test_list_tasks_for_owner(self):
filters = {'filters': {'owner': _OWNER_ID}}
tasks = list(self.controller.list(**filters))
self.assertEqual(tasks[0].id, _OWNED_TASK_ID)
def test_list_tasks_for_fake_owner(self):
filters = {'filters': {'owner': _FAKE_OWNER_ID}}
tasks = list(self.controller.list(**filters))
self.assertEqual(tasks, [])
def test_list_tasks_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/tasks?owner=ni%C3%B1o&limit=20
# We just want to make sure filters are correctly encoded.
pass
self.assertEqual(b"ni\xc3\xb1o", filters["owner"])
def test_get_task(self):
task = self.controller.get('3a4560a1-e585-443e-9b39-553b46ec92d1')
self.assertEqual(task.id, '3a4560a1-e585-443e-9b39-553b46ec92d1')
self.assertEqual(task.type, 'import')
def test_create_task(self):
properties = {
'type': 'import',
'input': {'import_from_format': 'ovf', 'import_from':
'swift://cloud.foo/myaccount/mycontainer/path'},
}
task = self.controller.create(**properties)
self.assertEqual(task.id, '3a4560a1-e585-443e-9b39-553b46ec92d1')
self.assertEqual(task.type, 'import')
def test_create_task_invalid_property(self):
properties = {
'type': 'import',
'bad_prop': 'value',
}
self.assertRaises(TypeError, self.controller.create, **properties)
+34
View File
@@ -0,0 +1,34 @@
-----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-----
@@ -0,0 +1,66 @@
# 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-----
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
tdc5rw==
-----END CERTIFICATE-----
@@ -0,0 +1,35 @@
-----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-----
@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
@@ -0,0 +1,61 @@
#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-----
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
-----END CERTIFICATE-----
@@ -0,0 +1,54 @@
#Certificate:
# Data:
# Version: 3 (0x2)
# Serial Number: 11990626514780340979 (0xa66743493fdcc2f3)
# Signature Algorithm: sha1WithRSAEncryption
# Issuer: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=0.0.0.0
# Validity
# Not Before: Dec 10 15:31:22 2013 GMT
# Not After : Nov 16 15:31:22 2113 GMT
# Subject: C=US, ST=CA, L=State1, O=Openstack Test Org, OU=Openstack Test Unit, CN=0.0.0.0
# Subject Public Key Info:
# Public Key Algorithm: rsaEncryption
# Public-Key: (2048 bit)
# Modulus:
# 00:ca:6b:07:73:53:24:45:74:05:a5:2a:27:bd:3e:
# .
# .
# .
# Exponent: 65537 (0x10001)
# X509v3 extensions:
# X509v3 Key Usage:
# Key Encipherment, Data Encipherment
# X509v3 Extended Key Usage:
# TLS Web Server Authentication
# X509v3 Subject Alternative Name:
# DNS:foo.example.net, DNS:*.example.com
# Signature Algorithm: sha1WithRSAEncryption
# 7e:41:69:da:f4:3c:06:d6:83:c6:f2:db:df:37:f1:ac:fa:f5:
# .
# .
# .
-----BEGIN CERTIFICATE-----
MIIDxDCCAqygAwIBAgIJAKZnQ0k/3MLzMA0GCSqGSIb3DQEBBQUAMHgxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDQTEPMA0GA1UEBxMGU3RhdGUxMRswGQYDVQQKExJP
cGVuc3RhY2sgVGVzdCBPcmcxHDAaBgNVBAsTE09wZW5zdGFjayBUZXN0IFVuaXQx
EDAOBgNVBAMTBzAuMC4wLjAwIBcNMTMxMjEwMTUzMTIyWhgPMjExMzExMTYxNTMx
MjJaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEPMA0GA1UEBxMGU3RhdGUx
MRswGQYDVQQKExJPcGVuc3RhY2sgVGVzdCBPcmcxHDAaBgNVBAsTE09wZW5zdGFj
ayBUZXN0IFVuaXQxEDAOBgNVBAMTBzAuMC4wLjAwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDKawdzUyRFdAWlKie9Pn10j7frffN+z1gEMluK2CtDEwv9
kbD4uS/Kz4dujfTx03mdyNfiMVlOM+YJm/qeLLSdJyFyvZ9Y3WmJ+vT2RGlMMhLd
/wEnMRrTYLL39pwI6z+gyw+4D78Pyv/OXy02IA6WtVEefYSx1vmVngb3pL+iBzhO
8CZXNI6lqrFhh+Hr4iMkYMtY1vTnwezAL6p64E/ZAFNPYCEJlacESTLQ4VZYniHc
QTgnE1czlI1vxlIk1KDXAzUGeeopZecRih9qlTxtOpklqEciQEE+sHtPcvyvdRE9
Bdyx5rNSALLIcXs0ViJE1RPlw3fjdBoDIOygqvX1AgMBAAGjTzBNMAsGA1UdDwQE
AwIEMDATBgNVHSUEDDAKBggrBgEFBQcDATApBgNVHREEIjAggg9mb28uZXhhbXBs
ZS5uZXSCDSouZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEFBQADggEBAH5Badr0PAbW
g8by29838az69Raul5IkpZQ5V3O1NaNNWxvmF1q8zFFqqGK5ktXJAwGiwnYEBb30
Zfrr+eFIEERzBthSJkWlP8NG+2ooMyg50femp+asAvW+KYYefJW8KaXTsznMsAFy
z1agcWVYVZ4H9PwunEYn/rM1krLEe4Cagsw5nmf8VqZg+hHtw930q8cRzgDsZdfA
jVK6dWdmzmLCUTL1GKCeNriDw1jIeFvNufC+Q3orH7xBx4VL+NV5ORWdNY/B8q1b
mFHdzbuZX6v39+2ww6aZqG2orfxUocc/5Ox6fXqenKPI3moeHS6Ktesq7sEQSJ6H
QZFsTuT/124=
-----END CERTIFICATE-----
+215
View File
@@ -0,0 +1,215 @@
# 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 six
import six.moves.urllib.parse as urlparse
import testtools
from glanceclient.v2.schemas import Schema
class FakeAPI(object):
def __init__(self, fixtures):
self.fixtures = fixtures
self.calls = []
def _request(self, method, url, headers=None, data=None,
content_length=None):
call = build_call_record(method, sort_url_by_query_keys(url),
headers or {}, data)
if content_length is not None:
call = tuple(list(call) + [content_length])
self.calls.append(call)
fixture = self.fixtures[sort_url_by_query_keys(url)][method]
data = fixture[1]
if isinstance(fixture[1], six.string_types):
try:
data = json.loads(fixture[1])
except ValueError:
data = six.StringIO(fixture[1])
return FakeResponse(fixture[0], fixture[1]), data
def get(self, *args, **kwargs):
return self._request('GET', *args, **kwargs)
def post(self, *args, **kwargs):
return self._request('POST', *args, **kwargs)
def put(self, *args, **kwargs):
return self._request('PUT', *args, **kwargs)
def patch(self, *args, **kwargs):
return self._request('PATCH', *args, **kwargs)
def delete(self, *args, **kwargs):
return self._request('DELETE', *args, **kwargs)
def head(self, *args, **kwargs):
return self._request('HEAD', *args, **kwargs)
class FakeSchemaAPI(FakeAPI):
def get(self, *args, **kwargs):
_, raw_schema = self._request('GET', *args, **kwargs)
return Schema(raw_schema)
class RawRequest(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 FakeResponse(object):
def __init__(self, headers=None, body=None,
version=1.0, status_code=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.reason = reason
self.version = version
self.headers = headers
self.status_code = status_code
self.raw = RawRequest(headers, body=body, reason=reason,
version=version, status=status_code)
@property
def ok(self):
return (self.status_code < 400 or
self.status_code >= 600)
def read(self, amt):
return self.body.read(amt)
def close(self):
pass
@property
def content(self):
if hasattr(self.body, "read"):
return self.body.read()
return self.body
@property
def text(self):
if isinstance(self.content, six.binary_type):
return self.content.decode('utf-8')
return self.content
def json(self, **kwargs):
return self.body and json.loads(self.text) or ""
def iter_content(self, chunk_size=1, decode_unicode=False):
while True:
chunk = self.raw.read(chunk_size)
if not chunk:
break
yield chunk
class TestCase(testtools.TestCase):
TEST_REQUEST_BASE = {
'config': {'danger_mode': False},
'verify': True}
class FakeTTYStdout(six.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 six.StringIO.write(self, data)
class FakeNoTTYStdout(FakeTTYStdout):
"""A Fake stdout that is not a TTY device."""
def isatty(self):
return False
def sort_url_by_query_keys(url):
"""A helper function which sorts the keys of the query string of a url.
For example, an input of '/v2/tasks?sort_key=id&sort_dir=asc&limit=10'
returns '/v2/tasks?limit=10&sort_dir=asc&sort_key=id'. This is to
prevent non-deterministic ordering of the query string causing
problems with unit tests.
:param url: url which will be ordered by query keys
:returns url: url with ordered query keys
"""
parsed = urlparse.urlparse(url)
queries = urlparse.parse_qsl(parsed.query, True)
sorted_query = sorted(queries, key=lambda x: x[0])
encoded_sorted_query = urlparse.urlencode(sorted_query, True)
url_parts = (parsed.scheme, parsed.netloc, parsed.path,
parsed.params, encoded_sorted_query,
parsed.fragment)
return urlparse.urlunparse(url_parts)
def build_call_record(method, url, headers, data):
"""Key the request body be ordered if it's a dict type.
"""
if isinstance(data, dict):
data = sorted(data.items())
if isinstance(data, six.string_types):
# NOTE(flwang): For image update, the data will be a 'list' which
# contains operation dict, such as: [{"op": "remove", "path": "/a"}]
try:
data = json.loads(data)
except ValueError:
return (method, url, headers or {}, data)
data = [sorted(d.items()) for d in data]
return (method, url, headers or {}, data)