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:
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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__())
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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")
|
||||
@@ -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
@@ -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)
|
||||
@@ -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
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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-----
|
||||
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
|
||||
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-----
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user