486 lines
16 KiB
Python
486 lines
16 KiB
Python
# 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 testscenarios import load_tests_apply_scenarios as load_tests # noqa
|
|
|
|
import mock
|
|
import munch
|
|
from openstack.tests.unit import base
|
|
|
|
from openstack import exceptions
|
|
from openstack import proxy
|
|
from openstack import resource
|
|
|
|
|
|
class DeleteableResource(resource.Resource):
|
|
allow_delete = True
|
|
|
|
|
|
class UpdateableResource(resource.Resource):
|
|
allow_commit = True
|
|
|
|
|
|
class CreateableResource(resource.Resource):
|
|
allow_create = True
|
|
|
|
|
|
class RetrieveableResource(resource.Resource):
|
|
allow_retrieve = True
|
|
|
|
|
|
class ListableResource(resource.Resource):
|
|
allow_list = True
|
|
|
|
|
|
class HeadableResource(resource.Resource):
|
|
allow_head = True
|
|
|
|
|
|
class TestProxyPrivate(base.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestProxyPrivate, self).setUp()
|
|
|
|
def method(self, expected_type, value):
|
|
return value
|
|
|
|
self.sot = mock.Mock()
|
|
self.sot.method = method
|
|
|
|
self.session = mock.Mock()
|
|
self.session._sdk_connection = self.cloud
|
|
self.fake_proxy = proxy.Proxy(self.session)
|
|
self.fake_proxy._connection = self.cloud
|
|
|
|
def _test_correct(self, value):
|
|
decorated = proxy._check_resource(strict=False)(self.sot.method)
|
|
rv = decorated(self.sot, resource.Resource, value)
|
|
|
|
self.assertEqual(value, rv)
|
|
|
|
def test__check_resource_correct_resource(self):
|
|
res = resource.Resource()
|
|
self._test_correct(res)
|
|
|
|
def test__check_resource_notstrict_id(self):
|
|
self._test_correct("abc123-id")
|
|
|
|
def test__check_resource_strict_id(self):
|
|
decorated = proxy._check_resource(strict=True)(self.sot.method)
|
|
self.assertRaisesRegex(ValueError, "A Resource must be passed",
|
|
decorated, self.sot, resource.Resource,
|
|
"this-is-not-a-resource")
|
|
|
|
def test__check_resource_incorrect_resource(self):
|
|
class OneType(resource.Resource):
|
|
pass
|
|
|
|
class AnotherType(resource.Resource):
|
|
pass
|
|
|
|
value = AnotherType()
|
|
decorated = proxy._check_resource(strict=False)(self.sot.method)
|
|
self.assertRaisesRegex(ValueError,
|
|
"Expected OneType but received AnotherType",
|
|
decorated, self.sot, OneType, value)
|
|
|
|
def test__get_uri_attribute_no_parent(self):
|
|
class Child(resource.Resource):
|
|
something = resource.Body("something")
|
|
|
|
attr = "something"
|
|
value = "nothing"
|
|
child = Child(something=value)
|
|
|
|
result = self.fake_proxy._get_uri_attribute(child, None, attr)
|
|
|
|
self.assertEqual(value, result)
|
|
|
|
def test__get_uri_attribute_with_parent(self):
|
|
class Parent(resource.Resource):
|
|
pass
|
|
|
|
value = "nothing"
|
|
parent = Parent(id=value)
|
|
|
|
result = self.fake_proxy._get_uri_attribute("child", parent, "attr")
|
|
|
|
self.assertEqual(value, result)
|
|
|
|
def test__get_resource_new(self):
|
|
value = "hello"
|
|
fake_type = mock.Mock(spec=resource.Resource)
|
|
fake_type.new = mock.Mock(return_value=value)
|
|
attrs = {"first": "Brian", "last": "Curtin"}
|
|
|
|
result = self.fake_proxy._get_resource(fake_type, None, **attrs)
|
|
|
|
fake_type.new.assert_called_with(connection=self.cloud, **attrs)
|
|
self.assertEqual(value, result)
|
|
|
|
def test__get_resource_from_id(self):
|
|
id = "eye dee"
|
|
value = "hello"
|
|
attrs = {"first": "Brian", "last": "Curtin"}
|
|
|
|
# The isinstance check needs to take a type, not an instance,
|
|
# so the mock.assert_called_with method isn't helpful here since
|
|
# we can't pass in a mocked object. This class is a crude version
|
|
# of that same behavior to let us check that `new` gets
|
|
# called with the expected arguments.
|
|
|
|
class Fake(object):
|
|
call = {}
|
|
|
|
@classmethod
|
|
def new(cls, **kwargs):
|
|
cls.call = kwargs
|
|
return value
|
|
|
|
result = self.fake_proxy._get_resource(Fake, id, **attrs)
|
|
|
|
self.assertDictEqual(
|
|
dict(id=id, connection=mock.ANY, **attrs), Fake.call)
|
|
self.assertEqual(value, result)
|
|
|
|
def test__get_resource_from_resource(self):
|
|
res = mock.Mock(spec=resource.Resource)
|
|
res._update = mock.Mock()
|
|
|
|
attrs = {"first": "Brian", "last": "Curtin"}
|
|
|
|
result = self.fake_proxy._get_resource(resource.Resource,
|
|
res, **attrs)
|
|
|
|
res._update.assert_called_once_with(**attrs)
|
|
self.assertEqual(result, res)
|
|
|
|
def test__get_resource_from_munch(self):
|
|
cls = mock.Mock()
|
|
res = mock.Mock(spec=resource.Resource)
|
|
res._update = mock.Mock()
|
|
cls._from_munch.return_value = res
|
|
|
|
m = munch.Munch(answer=42)
|
|
attrs = {"first": "Brian", "last": "Curtin"}
|
|
|
|
result = self.fake_proxy._get_resource(cls, m, **attrs)
|
|
|
|
cls._from_munch.assert_called_once_with(m, connection=self.cloud)
|
|
res._update.assert_called_once_with(**attrs)
|
|
self.assertEqual(result, res)
|
|
|
|
|
|
class TestProxyDelete(base.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestProxyDelete, self).setUp()
|
|
|
|
self.session = mock.Mock()
|
|
self.session._sdk_connection = self.cloud
|
|
|
|
self.fake_id = 1
|
|
self.res = mock.Mock(spec=DeleteableResource)
|
|
self.res.id = self.fake_id
|
|
self.res.delete = mock.Mock()
|
|
|
|
self.sot = proxy.Proxy(self.session)
|
|
self.sot._connection = self.cloud
|
|
DeleteableResource.new = mock.Mock(return_value=self.res)
|
|
|
|
def test_delete(self):
|
|
self.sot._delete(DeleteableResource, self.res)
|
|
self.res.delete.assert_called_with(self.sot)
|
|
|
|
self.sot._delete(DeleteableResource, self.fake_id)
|
|
DeleteableResource.new.assert_called_with(
|
|
connection=self.cloud, id=self.fake_id)
|
|
self.res.delete.assert_called_with(self.sot)
|
|
|
|
# Delete generally doesn't return anything, so we will normally
|
|
# swallow any return from within a service's proxy, but make sure
|
|
# we can still return for any cases where values are returned.
|
|
self.res.delete.return_value = self.fake_id
|
|
rv = self.sot._delete(DeleteableResource, self.fake_id)
|
|
self.assertEqual(rv, self.fake_id)
|
|
|
|
def test_delete_ignore_missing(self):
|
|
self.res.delete.side_effect = exceptions.ResourceNotFound(
|
|
message="test", http_status=404)
|
|
|
|
rv = self.sot._delete(DeleteableResource, self.fake_id)
|
|
self.assertIsNone(rv)
|
|
|
|
def test_delete_NotFound(self):
|
|
self.res.delete.side_effect = exceptions.ResourceNotFound(
|
|
message="test", http_status=404)
|
|
|
|
self.assertRaisesRegex(
|
|
exceptions.ResourceNotFound,
|
|
# TODO(shade) The mocks here are hiding the thing we want to test.
|
|
"test",
|
|
self.sot._delete, DeleteableResource, self.res,
|
|
ignore_missing=False)
|
|
|
|
def test_delete_HttpException(self):
|
|
self.res.delete.side_effect = exceptions.HttpException(
|
|
message="test", http_status=500)
|
|
|
|
self.assertRaises(exceptions.HttpException, self.sot._delete,
|
|
DeleteableResource, self.res, ignore_missing=False)
|
|
|
|
|
|
class TestProxyUpdate(base.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestProxyUpdate, self).setUp()
|
|
|
|
self.session = mock.Mock()
|
|
|
|
self.fake_id = 1
|
|
self.fake_result = "fake_result"
|
|
|
|
self.res = mock.Mock(spec=UpdateableResource)
|
|
self.res.commit = mock.Mock(return_value=self.fake_result)
|
|
|
|
self.sot = proxy.Proxy(self.session)
|
|
self.sot._connection = self.cloud
|
|
|
|
self.attrs = {"x": 1, "y": 2, "z": 3}
|
|
|
|
UpdateableResource.new = mock.Mock(return_value=self.res)
|
|
|
|
def test_update_resource(self):
|
|
rv = self.sot._update(UpdateableResource, self.res, **self.attrs)
|
|
|
|
self.assertEqual(rv, self.fake_result)
|
|
self.res._update.assert_called_once_with(**self.attrs)
|
|
self.res.commit.assert_called_once_with(self.sot, base_path=None)
|
|
|
|
def test_update_resource_override_base_path(self):
|
|
base_path = 'dummy'
|
|
rv = self.sot._update(UpdateableResource, self.res,
|
|
base_path=base_path, **self.attrs)
|
|
|
|
self.assertEqual(rv, self.fake_result)
|
|
self.res._update.assert_called_once_with(**self.attrs)
|
|
self.res.commit.assert_called_once_with(self.sot, base_path=base_path)
|
|
|
|
def test_update_id(self):
|
|
rv = self.sot._update(UpdateableResource, self.fake_id, **self.attrs)
|
|
|
|
self.assertEqual(rv, self.fake_result)
|
|
self.res.commit.assert_called_once_with(self.sot, base_path=None)
|
|
|
|
|
|
class TestProxyCreate(base.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestProxyCreate, self).setUp()
|
|
|
|
self.session = mock.Mock()
|
|
self.session._sdk_connection = self.cloud
|
|
|
|
self.fake_result = "fake_result"
|
|
self.res = mock.Mock(spec=CreateableResource)
|
|
self.res.create = mock.Mock(return_value=self.fake_result)
|
|
|
|
self.sot = proxy.Proxy(self.session)
|
|
self.sot._connection = self.cloud
|
|
|
|
def test_create_attributes(self):
|
|
CreateableResource.new = mock.Mock(return_value=self.res)
|
|
|
|
attrs = {"x": 1, "y": 2, "z": 3}
|
|
rv = self.sot._create(CreateableResource, **attrs)
|
|
|
|
self.assertEqual(rv, self.fake_result)
|
|
CreateableResource.new.assert_called_once_with(
|
|
connection=self.cloud, **attrs)
|
|
self.res.create.assert_called_once_with(self.sot, base_path=None)
|
|
|
|
def test_create_attributes_override_base_path(self):
|
|
CreateableResource.new = mock.Mock(return_value=self.res)
|
|
|
|
base_path = 'dummy'
|
|
attrs = {"x": 1, "y": 2, "z": 3}
|
|
rv = self.sot._create(CreateableResource, base_path=base_path, **attrs)
|
|
|
|
self.assertEqual(rv, self.fake_result)
|
|
CreateableResource.new.assert_called_once_with(
|
|
connection=self.cloud, **attrs)
|
|
self.res.create.assert_called_once_with(self.sot, base_path=base_path)
|
|
|
|
|
|
class TestProxyGet(base.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestProxyGet, self).setUp()
|
|
|
|
self.session = mock.Mock()
|
|
self.session._sdk_connection = self.cloud
|
|
|
|
self.fake_id = 1
|
|
self.fake_name = "fake_name"
|
|
self.fake_result = "fake_result"
|
|
self.res = mock.Mock(spec=RetrieveableResource)
|
|
self.res.id = self.fake_id
|
|
self.res.fetch = mock.Mock(return_value=self.fake_result)
|
|
|
|
self.sot = proxy.Proxy(self.session)
|
|
self.sot._connection = self.cloud
|
|
RetrieveableResource.new = mock.Mock(return_value=self.res)
|
|
|
|
def test_get_resource(self):
|
|
rv = self.sot._get(RetrieveableResource, self.res)
|
|
|
|
self.res.fetch.assert_called_with(
|
|
self.sot, requires_id=True,
|
|
base_path=None,
|
|
error_message=mock.ANY)
|
|
self.assertEqual(rv, self.fake_result)
|
|
|
|
def test_get_resource_with_args(self):
|
|
args = {"key": "value"}
|
|
rv = self.sot._get(RetrieveableResource, self.res, **args)
|
|
|
|
self.res._update.assert_called_once_with(**args)
|
|
self.res.fetch.assert_called_with(
|
|
self.sot, requires_id=True, base_path=None,
|
|
error_message=mock.ANY)
|
|
self.assertEqual(rv, self.fake_result)
|
|
|
|
def test_get_id(self):
|
|
rv = self.sot._get(RetrieveableResource, self.fake_id)
|
|
|
|
RetrieveableResource.new.assert_called_with(
|
|
connection=self.cloud, id=self.fake_id)
|
|
self.res.fetch.assert_called_with(
|
|
self.sot, requires_id=True, base_path=None,
|
|
error_message=mock.ANY)
|
|
self.assertEqual(rv, self.fake_result)
|
|
|
|
def test_get_base_path(self):
|
|
base_path = 'dummy'
|
|
rv = self.sot._get(RetrieveableResource, self.fake_id,
|
|
base_path=base_path)
|
|
|
|
RetrieveableResource.new.assert_called_with(
|
|
connection=self.cloud, id=self.fake_id)
|
|
self.res.fetch.assert_called_with(
|
|
self.sot, requires_id=True, base_path=base_path,
|
|
error_message=mock.ANY)
|
|
self.assertEqual(rv, self.fake_result)
|
|
|
|
def test_get_not_found(self):
|
|
self.res.fetch.side_effect = exceptions.ResourceNotFound(
|
|
message="test", http_status=404)
|
|
|
|
self.assertRaisesRegex(
|
|
exceptions.ResourceNotFound,
|
|
"test", self.sot._get, RetrieveableResource, self.res)
|
|
|
|
|
|
class TestProxyList(base.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestProxyList, self).setUp()
|
|
|
|
self.session = mock.Mock()
|
|
|
|
self.args = {"a": "A", "b": "B", "c": "C"}
|
|
self.fake_response = [resource.Resource()]
|
|
|
|
self.sot = proxy.Proxy(self.session)
|
|
self.sot._connection = self.cloud
|
|
ListableResource.list = mock.Mock()
|
|
ListableResource.list.return_value = self.fake_response
|
|
|
|
def _test_list(self, paginated, base_path=None):
|
|
rv = self.sot._list(ListableResource, paginated=paginated,
|
|
base_path=base_path, **self.args)
|
|
|
|
self.assertEqual(self.fake_response, rv)
|
|
ListableResource.list.assert_called_once_with(
|
|
self.sot, paginated=paginated, base_path=base_path, **self.args)
|
|
|
|
def test_list_paginated(self):
|
|
self._test_list(True)
|
|
|
|
def test_list_non_paginated(self):
|
|
self._test_list(False)
|
|
|
|
def test_list_override_base_path(self):
|
|
self._test_list(False, base_path='dummy')
|
|
|
|
|
|
class TestProxyHead(base.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestProxyHead, self).setUp()
|
|
|
|
self.session = mock.Mock()
|
|
self.session._sdk_connection = self.cloud
|
|
|
|
self.fake_id = 1
|
|
self.fake_name = "fake_name"
|
|
self.fake_result = "fake_result"
|
|
self.res = mock.Mock(spec=HeadableResource)
|
|
self.res.id = self.fake_id
|
|
self.res.head = mock.Mock(return_value=self.fake_result)
|
|
|
|
self.sot = proxy.Proxy(self.session)
|
|
self.sot._connection = self.cloud
|
|
HeadableResource.new = mock.Mock(return_value=self.res)
|
|
|
|
def test_head_resource(self):
|
|
rv = self.sot._head(HeadableResource, self.res)
|
|
|
|
self.res.head.assert_called_with(self.sot, base_path=None)
|
|
self.assertEqual(rv, self.fake_result)
|
|
|
|
def test_head_resource_base_path(self):
|
|
base_path = 'dummy'
|
|
rv = self.sot._head(HeadableResource, self.res, base_path=base_path)
|
|
|
|
self.res.head.assert_called_with(self.sot, base_path=base_path)
|
|
self.assertEqual(rv, self.fake_result)
|
|
|
|
def test_head_id(self):
|
|
rv = self.sot._head(HeadableResource, self.fake_id)
|
|
|
|
HeadableResource.new.assert_called_with(
|
|
connection=self.cloud, id=self.fake_id)
|
|
self.res.head.assert_called_with(self.sot, base_path=None)
|
|
self.assertEqual(rv, self.fake_result)
|
|
|
|
|
|
class TestExtractName(base.TestCase):
|
|
|
|
scenarios = [
|
|
('slash_servers_bare', dict(url='/servers', parts=['servers'])),
|
|
('slash_servers_arg', dict(url='/servers/1', parts=['servers'])),
|
|
('servers_bare', dict(url='servers', parts=['servers'])),
|
|
('servers_arg', dict(url='servers/1', parts=['servers'])),
|
|
('networks_bare', dict(url='/v2.0/networks', parts=['networks'])),
|
|
('networks_arg', dict(url='/v2.0/networks/1', parts=['networks'])),
|
|
('tokens', dict(url='/v3/tokens', parts=['tokens'])),
|
|
('discovery', dict(url='/', parts=['discovery'])),
|
|
('secgroups', dict(
|
|
url='/servers/1/os-security-groups',
|
|
parts=['servers', 'os-security-groups'])),
|
|
]
|
|
|
|
def test_extract_name(self):
|
|
|
|
results = proxy._extract_name(self.url)
|
|
self.assertEqual(self.parts, results)
|