Merge "Implement create-delete-check for Manila::Share"
This commit is contained in:
commit
54e530c94f
@ -12,6 +12,7 @@
|
||||
# under the License.
|
||||
|
||||
from heat.engine.clients import client_plugin
|
||||
from heat.engine import constraints
|
||||
from manilaclient import client as manila_client
|
||||
from manilaclient import exceptions
|
||||
|
||||
@ -44,3 +45,84 @@ class ManilaClientPlugin(client_plugin.ClientPlugin):
|
||||
|
||||
def is_conflict(self, ex):
|
||||
return isinstance(ex, exceptions.Conflict)
|
||||
|
||||
@staticmethod
|
||||
def _find_resource_by_id_or_name(id_or_name, resource_list,
|
||||
resource_type_name):
|
||||
"""The method is trying to find id or name in item_list
|
||||
|
||||
The method searches item with id_or_name in list and returns it.
|
||||
If there is more than one value or no values then it raises an
|
||||
exception
|
||||
|
||||
:param id_or_name: resource id or name
|
||||
:param resource_list: list of resources
|
||||
:param resource_type_name: name of resource type that will be used
|
||||
for exceptions
|
||||
:raises NotFound, NoUniqueMatch
|
||||
:return: resource or generate an exception otherwise
|
||||
"""
|
||||
search_result_by_id = [res for res in resource_list
|
||||
if res.id == id_or_name]
|
||||
if search_result_by_id:
|
||||
return search_result_by_id[0]
|
||||
else:
|
||||
# try to find resource by name
|
||||
search_result_by_name = [res for res in resource_list
|
||||
if res.name == id_or_name]
|
||||
match_count = len(search_result_by_name)
|
||||
if match_count > 1:
|
||||
message = ("Ambiguous {0} name '{1}'. Found more than one "
|
||||
"{0} for this name in Manila."
|
||||
).format(resource_type_name, id_or_name)
|
||||
raise exceptions.NoUniqueMatch(message)
|
||||
elif match_count == 1:
|
||||
return search_result_by_name[0]
|
||||
else:
|
||||
message = ("{0} '{1}' was not found in Manila. Please "
|
||||
"use the identity of existing {0} in Heat "
|
||||
"template.").format(resource_type_name, id_or_name)
|
||||
raise exceptions.NotFound(message=message)
|
||||
|
||||
def get_share_type(self, share_type_identity):
|
||||
return self._find_resource_by_id_or_name(
|
||||
share_type_identity,
|
||||
self.client().share_types.list(),
|
||||
"share type"
|
||||
)
|
||||
|
||||
def get_share_network(self, share_network_identity):
|
||||
return self._find_resource_by_id_or_name(
|
||||
share_network_identity,
|
||||
self.client().share_networks.list(),
|
||||
"share network"
|
||||
)
|
||||
|
||||
def get_share_snapshot(self, snapshot_identity):
|
||||
return self._find_resource_by_id_or_name(
|
||||
snapshot_identity,
|
||||
self.client().share_snapshots.list(),
|
||||
"share snapshot"
|
||||
)
|
||||
|
||||
|
||||
class ManilaShareBaseConstraint(constraints.BaseCustomConstraint):
|
||||
# check that exceptions module has been loaded. Without this check
|
||||
# doc tests on gates will fail
|
||||
expected_exceptions = (exceptions.NotFound, exceptions.NoUniqueMatch)
|
||||
|
||||
def validate_with_client(self, client, resource_id):
|
||||
getattr(client.client_plugin("manila"), self.resource_getter_name)(
|
||||
resource_id)
|
||||
|
||||
|
||||
class ManilaShareNetworkConstraint(ManilaShareBaseConstraint):
|
||||
resource_getter_name = 'get_share_network'
|
||||
|
||||
|
||||
class ManilaShareTypeConstraint(ManilaShareBaseConstraint):
|
||||
resource_getter_name = 'get_share_type'
|
||||
|
||||
|
||||
class ManilaShareSnapshotConstraint(ManilaShareBaseConstraint):
|
||||
resource_getter_name = 'get_share_snapshot'
|
||||
|
305
heat/engine/resources/openstack/manila/share.py
Normal file
305
heat/engine/resources/openstack/manila/share.py
Normal file
@ -0,0 +1,305 @@
|
||||
#
|
||||
# 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 oslo_log import log as logging
|
||||
import six
|
||||
|
||||
from heat.common.i18n import _
|
||||
from heat.common.i18n import _LI
|
||||
from heat.engine import attributes
|
||||
from heat.engine import constraints
|
||||
from heat.engine import properties
|
||||
from heat.engine import resource
|
||||
from heat.engine import support
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ManilaShare(resource.Resource):
|
||||
"""A resource that creates shared mountable file system.
|
||||
|
||||
The resource creates a manila share - shared mountable filesystem that
|
||||
can be attached to any client(or clients) that has a network access and
|
||||
permission to mount filesystem. Share is a unit of storage with specific
|
||||
size that supports pre-defined share protocol and advanced security model
|
||||
(access lists, share networks and security services).
|
||||
"""
|
||||
|
||||
support_status = support.SupportStatus(version='5.0.0')
|
||||
|
||||
_ACCESS_RULE_PROPERTIES = (
|
||||
ACCESS_TO, ACCESS_TYPE, ACCESS_LEVEL
|
||||
) = (
|
||||
'access_to', 'access_type', 'access_level')
|
||||
|
||||
_SHARE_STATUSES = (
|
||||
STATUS_CREATING, STATUS_DELETING, STATUS_ERROR, STATUS_ERROR_DELETING,
|
||||
STATUS_AVAILABLE
|
||||
) = (
|
||||
'creating', 'deleting', 'error', 'error_deleting',
|
||||
'available'
|
||||
)
|
||||
|
||||
PROPERTIES = (
|
||||
SHARE_PROTOCOL, SIZE, SHARE_SNAPSHOT, NAME, METADATA,
|
||||
SHARE_NETWORK, DESCRIPTION, SHARE_TYPE, IS_PUBLIC,
|
||||
ACCESS_RULES
|
||||
) = (
|
||||
'share_protocol', 'size', 'snapshot', 'name', 'metadata',
|
||||
'share_network', 'description', 'share_type', 'is_public',
|
||||
'access_rules'
|
||||
)
|
||||
|
||||
ATTRIBUTES = (
|
||||
AVAILABILITY_ZONE_ATTR, HOST_ATTR, EXPORT_LOCATIONS_ATTR,
|
||||
SHARE_SERVER_ID_ATTR, CREATED_AT_ATTR, SHARE_STATUS_ATTR,
|
||||
PROJECT_ID_ATTR
|
||||
) = (
|
||||
'availability_zone', 'host', 'export_locations',
|
||||
'share_server_id', 'created_at', 'status',
|
||||
'project_id'
|
||||
)
|
||||
|
||||
properties_schema = {
|
||||
SHARE_PROTOCOL: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Share protocol supported by shared filesystem.'),
|
||||
required=True,
|
||||
constraints=[constraints.AllowedValues(
|
||||
['NFS', 'CIFS', 'GlusterFS', 'HDFS'])]
|
||||
),
|
||||
SIZE: properties.Schema(
|
||||
properties.Schema.INTEGER,
|
||||
_('Share storage size in GB.'),
|
||||
required=True
|
||||
),
|
||||
SHARE_SNAPSHOT: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Name or ID of shared file system snapshot that will be restored'
|
||||
' and created as a new share.'),
|
||||
constraints=[constraints.CustomConstraint('manila.share_snapshot')]
|
||||
),
|
||||
NAME: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Share name.'),
|
||||
update_allowed=True
|
||||
),
|
||||
METADATA: properties.Schema(
|
||||
properties.Schema.MAP,
|
||||
_('Metadata key-values defined for share.'),
|
||||
update_allowed=True
|
||||
),
|
||||
SHARE_NETWORK: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Name or ID of shared network defined for shared filesystem.'),
|
||||
constraints=[constraints.CustomConstraint('manila.share_network')]
|
||||
),
|
||||
DESCRIPTION: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Share description.'),
|
||||
update_allowed=True
|
||||
),
|
||||
SHARE_TYPE: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Name or ID of shared filesystem type. Types defines some share '
|
||||
'filesystem profiles that will be used for share creation.'),
|
||||
constraints=[constraints.CustomConstraint("manila.share_type")]
|
||||
),
|
||||
IS_PUBLIC: properties.Schema(
|
||||
properties.Schema.BOOLEAN,
|
||||
_('Defines if shared filesystem is public or private.'),
|
||||
default=False,
|
||||
update_allowed=True
|
||||
),
|
||||
ACCESS_RULES: properties.Schema(
|
||||
properties.Schema.LIST,
|
||||
_('A list of access rules that define access from IP to Share.'),
|
||||
schema=properties.Schema(
|
||||
properties.Schema.MAP,
|
||||
schema={
|
||||
ACCESS_TO: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('IP or other address information about guest that '
|
||||
'allowed to access to Share.'),
|
||||
required=True
|
||||
),
|
||||
ACCESS_TYPE: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Type of access that should be provided to guest.'),
|
||||
constraints=[constraints.AllowedValues(
|
||||
['ip', 'domain'])],
|
||||
required=True
|
||||
),
|
||||
ACCESS_LEVEL: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Level of access that need to be provided for '
|
||||
'guest.'),
|
||||
constraints=[constraints.AllowedValues(['ro', 'rw'])]
|
||||
)
|
||||
}
|
||||
),
|
||||
update_allowed=True,
|
||||
default=[]
|
||||
)
|
||||
}
|
||||
|
||||
attributes_schema = {
|
||||
AVAILABILITY_ZONE_ATTR: attributes.Schema(
|
||||
_('The availability zone of shared filesystem.'),
|
||||
type=attributes.Schema.STRING
|
||||
),
|
||||
HOST_ATTR: attributes.Schema(
|
||||
_('Share host.'),
|
||||
type=attributes.Schema.STRING
|
||||
),
|
||||
EXPORT_LOCATIONS_ATTR: attributes.Schema(
|
||||
_('Export locations of share.'),
|
||||
type=attributes.Schema.LIST
|
||||
),
|
||||
SHARE_SERVER_ID_ATTR: attributes.Schema(
|
||||
_('ID of server (VM, etc...) on host that is used for '
|
||||
'exporting network file-system.'),
|
||||
type=attributes.Schema.STRING
|
||||
),
|
||||
CREATED_AT_ATTR: attributes.Schema(
|
||||
_('Datetime when a share was created.'),
|
||||
type=attributes.Schema.STRING
|
||||
),
|
||||
SHARE_STATUS_ATTR: attributes.Schema(
|
||||
_('Current share status.'),
|
||||
type=attributes.Schema.STRING
|
||||
),
|
||||
PROJECT_ID_ATTR: attributes.Schema(
|
||||
_('Share project ID.'),
|
||||
type=attributes.Schema.STRING
|
||||
)
|
||||
}
|
||||
|
||||
default_client_name = 'manila'
|
||||
|
||||
def _request_share(self):
|
||||
return self.client().shares.get(self.resource_id)
|
||||
|
||||
def _resolve_attribute(self, name):
|
||||
share = self._request_share()
|
||||
return six.text_type(getattr(share, name))
|
||||
|
||||
def handle_create(self):
|
||||
# Request IDs of entities from manila
|
||||
# if name of the entity defined in template
|
||||
share_net_identity = self.properties[self.SHARE_NETWORK]
|
||||
if share_net_identity:
|
||||
share_net_identity = self.client_plugin().get_share_network(
|
||||
share_net_identity).id
|
||||
snapshot_identity = self.properties[self.SHARE_SNAPSHOT]
|
||||
if snapshot_identity:
|
||||
snapshot_identity = self.client_plugin().get_share_snapshot(
|
||||
snapshot_identity).id
|
||||
share_type_identity = self.properties[self.SHARE_TYPE]
|
||||
if share_type_identity:
|
||||
share_type_identity = self.client_plugin().get_share_type(
|
||||
share_type_identity).id
|
||||
|
||||
share = self.client().shares.create(
|
||||
share_proto=self.properties[self.SHARE_PROTOCOL],
|
||||
size=self.properties[self.SIZE],
|
||||
snapshot_id=snapshot_identity,
|
||||
name=self.properties[self.NAME],
|
||||
description=self.properties[self.DESCRIPTION],
|
||||
metadata=self.properties[self.METADATA],
|
||||
share_network=share_net_identity,
|
||||
share_type=share_type_identity,
|
||||
is_public=self.properties[self.IS_PUBLIC])
|
||||
|
||||
self.resource_id_set(share.id)
|
||||
|
||||
def check_create_complete(self, *args):
|
||||
share_status = self._request_share().status
|
||||
if share_status == self.STATUS_CREATING:
|
||||
return False
|
||||
elif share_status == self.STATUS_AVAILABLE:
|
||||
LOG.info(_LI('Applying access rules to created Share.'))
|
||||
# apply access rules to created share. please note that it is not
|
||||
# possible to define rules for share with share_status = creating
|
||||
access_rules = self.properties.get(self.ACCESS_RULES)
|
||||
try:
|
||||
if access_rules:
|
||||
for rule in access_rules:
|
||||
self.client().shares.allow(
|
||||
share=self.resource_id,
|
||||
access_type=rule.get(self.ACCESS_TYPE),
|
||||
access=rule.get(self.ACCESS_TO),
|
||||
access_level=rule.get(self.ACCESS_LEVEL))
|
||||
return True
|
||||
except Exception as ex:
|
||||
reason = _(
|
||||
'Error during applying access rules to share "{0}". '
|
||||
'The root cause of the problem is the following: {1}.'
|
||||
).format(self.resource_id, ex.message)
|
||||
raise resource.ResourceInError(status_reason=reason)
|
||||
elif share_status == self.STATUS_ERROR:
|
||||
reason = _('Error during creation of share "{0}"').format(
|
||||
self.resource_id)
|
||||
raise resource.ResourceInError(status_reason=reason,
|
||||
resource_status=share_status)
|
||||
else:
|
||||
reason = _('Unknown share_status during creation of share "{0}"'
|
||||
).format(self.resource_id)
|
||||
raise resource.ResourceUnknownStatus(status_reason=reason,
|
||||
resource_status=share_status)
|
||||
|
||||
def handle_delete(self):
|
||||
if not self.resource_id:
|
||||
return
|
||||
|
||||
try:
|
||||
self.client().shares.delete(self.resource_id)
|
||||
except Exception as ex:
|
||||
self.client_plugin().ignore_not_found(ex)
|
||||
|
||||
def check_delete_complete(self, *args):
|
||||
if not self.resource_id:
|
||||
return True
|
||||
|
||||
try:
|
||||
share = self._request_share()
|
||||
except Exception as ex:
|
||||
self.client_plugin().ignore_not_found(ex)
|
||||
return True
|
||||
else:
|
||||
# when share creation is not finished proceed listening
|
||||
if share.status == self.STATUS_DELETING:
|
||||
return False
|
||||
elif share.status in (self.STATUS_ERROR,
|
||||
self.STATUS_ERROR_DELETING):
|
||||
raise resource.ResourceInError(
|
||||
status_reason=_(
|
||||
'Error during deleting share "{0}".'
|
||||
).format(self.resource_id),
|
||||
resource_status=share.status)
|
||||
else:
|
||||
reason = _('Unknown status during deleting share '
|
||||
'"{0}"').format(self.resource_id)
|
||||
raise resource.ResourceUnknownStatus(
|
||||
status_reason=reason, resource_status=share.status)
|
||||
|
||||
def handle_check(self):
|
||||
share = self._request_share()
|
||||
expected_statuses = [self.STATUS_AVAILABLE]
|
||||
checks = [{'attr': 'status', 'expected': expected_statuses,
|
||||
'current': share.status}]
|
||||
self._verify_check_conditions(checks)
|
||||
|
||||
|
||||
def resource_mapping():
|
||||
return {'OS::Manila::Share': ManilaShare}
|
@ -10,12 +10,43 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import collections
|
||||
|
||||
from manilaclient import exceptions
|
||||
import mock
|
||||
|
||||
from heat.tests import common
|
||||
from heat.tests import utils
|
||||
|
||||
|
||||
class ManilaClientPluginTests(common.HeatTestCase):
|
||||
scenarios = [
|
||||
('share_type',
|
||||
dict(manager_name="share_types",
|
||||
method_name="get_share_type")),
|
||||
('share_network',
|
||||
dict(manager_name="share_networks",
|
||||
method_name="get_share_network")),
|
||||
('share_snapshot',
|
||||
dict(manager_name="share_snapshots",
|
||||
method_name="get_share_snapshot")),
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(ManilaClientPluginTests, self).setUp()
|
||||
# mock client and plugin
|
||||
self.manila_client = mock.MagicMock()
|
||||
con = utils.dummy_context()
|
||||
c = con.clients
|
||||
self.manila_plugin = c.client_plugin('manila')
|
||||
self.manila_plugin._client = self.manila_client
|
||||
# prepare list of items to test search
|
||||
Item = collections.namedtuple('Item', ['id', 'name'])
|
||||
self.item_list = [
|
||||
Item(name="unique_name", id="unique_id"),
|
||||
Item(name="unique_id", id="i_am_checking_that_id_prior"),
|
||||
Item(name="duplicated_name", id="duplicate_test_one"),
|
||||
Item(name="duplicated_name", id="duplicate_test_second")]
|
||||
|
||||
def test_create(self):
|
||||
context = utils.dummy_context()
|
||||
@ -23,3 +54,14 @@ class ManilaClientPluginTests(common.HeatTestCase):
|
||||
client = plugin.client()
|
||||
self.assertIsNotNone(client.security_services)
|
||||
self.assertEqual('http://server.test:5000/v3', client.client.base_url)
|
||||
|
||||
def test_manila_get_method(self):
|
||||
# set item list as client output
|
||||
manager = getattr(self.manila_client, self.manager_name)
|
||||
manager.list.return_value = self.item_list
|
||||
# test that get_<method_name> is searching correctly
|
||||
get_method = getattr(self.manila_plugin, self.method_name)
|
||||
self.assertEqual(get_method("unique_id").name, "unique_name")
|
||||
self.assertRaises(exceptions.NotFound, get_method, "non_exist")
|
||||
self.assertRaises(exceptions.NoUniqueMatch, get_method,
|
||||
"duplicated_name")
|
||||
|
156
heat/tests/test_manila_share.py
Normal file
156
heat/tests/test_manila_share.py
Normal file
@ -0,0 +1,156 @@
|
||||
#
|
||||
# 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 six
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common import template_format
|
||||
from heat.engine.resources.openstack.manila import share as mshare
|
||||
from heat.engine import scheduler
|
||||
from heat.tests import common
|
||||
from heat.tests import utils
|
||||
|
||||
|
||||
manila_template = """
|
||||
heat_template_version: 2015-04-30
|
||||
resources:
|
||||
test_share:
|
||||
type: OS::Manila::Share
|
||||
properties:
|
||||
share_protocol: NFS
|
||||
size: 1
|
||||
access_rules:
|
||||
- access_to: 127.0.0.1
|
||||
access_type: ip
|
||||
access_level: ro
|
||||
name: basic_test_share
|
||||
description: basic test share
|
||||
is_public: True
|
||||
metadata: {"key": "value"}
|
||||
"""
|
||||
|
||||
|
||||
class ManilaShareTest(common.HeatTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ManilaShareTest, self).setUp()
|
||||
utils.setup_dummy_db()
|
||||
self.ctx = utils.dummy_context()
|
||||
|
||||
self.fake_share = mock.MagicMock(id="test_share_id")
|
||||
self.available_share = mock.MagicMock(
|
||||
id="test_share_id",
|
||||
status=mshare.ManilaShare.STATUS_AVAILABLE)
|
||||
self.failed_share = mock.MagicMock(
|
||||
id="test_share_id",
|
||||
status=mshare.ManilaShare.STATUS_ERROR)
|
||||
self.deleting_share = mock.MagicMock(
|
||||
id="test_share_id",
|
||||
status=mshare.ManilaShare.STATUS_DELETING)
|
||||
|
||||
def _init_share(self, stack_name):
|
||||
tmp = template_format.parse(manila_template)
|
||||
self.stack = utils.parse_stack(tmp, stack_name=stack_name)
|
||||
res_def = self.stack.t.resource_definitions(self.stack)["test_share"]
|
||||
share = mshare.ManilaShare("test_share", res_def, self.stack)
|
||||
|
||||
# replace clients and plugins with mocks
|
||||
mock_client = mock.MagicMock()
|
||||
client = mock.MagicMock(return_value=mock_client)
|
||||
share.client = client
|
||||
mock_plugin = mock.MagicMock()
|
||||
client_plugin = mock.MagicMock(return_value=mock_plugin)
|
||||
share.client_plugin = client_plugin
|
||||
|
||||
return share
|
||||
|
||||
def _create_share(self, stack_name):
|
||||
share = self._init_share(stack_name)
|
||||
share.client().shares.create.return_value = self.fake_share
|
||||
share.client().shares.get.return_value = self.available_share
|
||||
scheduler.TaskRunner(share.create)()
|
||||
return share
|
||||
|
||||
def test_share_create(self):
|
||||
share = self._create_share("stack_share_create")
|
||||
|
||||
expected_state = (share.CREATE, share.COMPLETE)
|
||||
self.assertEqual(expected_state, share.state,
|
||||
"Share is not in expected state")
|
||||
self.assertEqual(self.fake_share.id, share.resource_id,
|
||||
"Expected share ID was not propagated to share")
|
||||
|
||||
share.client().shares.allow.assert_called_once_with(
|
||||
access="127.0.0.1", access_level="ro",
|
||||
share=share.resource_id, access_type="ip")
|
||||
args, kwargs = share.client().shares.create.call_args
|
||||
message_end = " parameter was not passed to manila client"
|
||||
self.assertEqual(u"NFS", kwargs["share_proto"],
|
||||
"Share protocol" + message_end)
|
||||
self.assertEqual(1, kwargs["size"], "Share size" + message_end)
|
||||
self.assertEqual("basic_test_share", kwargs["name"],
|
||||
"Share name" + message_end)
|
||||
self.assertEqual("basic test share", kwargs["description"],
|
||||
"Share description" + message_end)
|
||||
self.assertEqual({u"key": u"value"}, kwargs["metadata"],
|
||||
"Metadata" + message_end)
|
||||
self.assertTrue(kwargs["is_public"])
|
||||
share.client().shares.get.assert_called_once_with(self.fake_share.id)
|
||||
|
||||
def test_share_create_fail(self):
|
||||
share = self._init_share("stack_share_create_fail")
|
||||
share.client().shares.create.return_value = self.fake_share
|
||||
share.client().shares.get.return_value = self.failed_share
|
||||
exc = self.assertRaises(exception.ResourceFailure,
|
||||
scheduler.TaskRunner(share.create))
|
||||
self.assertIn("Error during creation", six.text_type(exc))
|
||||
|
||||
def test_share_create_unknown_status(self):
|
||||
share = self._init_share("stack_share_create_unknown")
|
||||
share.client().shares.create.return_value = self.fake_share
|
||||
share.client().shares.get.return_value = self.deleting_share
|
||||
exc = self.assertRaises(exception.ResourceFailure,
|
||||
scheduler.TaskRunner(share.create))
|
||||
self.assertIn("Unknown status", six.text_type(exc))
|
||||
|
||||
def test_share_delete(self):
|
||||
share = self._create_share("stack_share_delete")
|
||||
share.client().shares.get.side_effect = exception.NotFound()
|
||||
share.client_plugin().ignore_not_found.return_value = None
|
||||
scheduler.TaskRunner(share.delete)()
|
||||
share.client().shares.delete.assert_called_once_with(
|
||||
self.fake_share.id)
|
||||
|
||||
def test_share_delete_fail(self):
|
||||
share = self._create_share("stack_share_delete_fail")
|
||||
share.client().shares.delete.return_value = None
|
||||
share.client().shares.get.return_value = self.failed_share
|
||||
exc = self.assertRaises(exception.ResourceFailure,
|
||||
scheduler.TaskRunner(share.delete))
|
||||
self.assertIn("Error during deleting share", six.text_type(exc))
|
||||
|
||||
def test_share_check(self):
|
||||
share = self._create_share("stack_share_check")
|
||||
scheduler.TaskRunner(share.check)()
|
||||
expected_state = (share.CHECK, share.COMPLETE)
|
||||
self.assertEqual(expected_state, share.state,
|
||||
"Share is not in expected state")
|
||||
|
||||
def test_share_check_fail(self):
|
||||
share = self._create_share("stack_share_check_fail")
|
||||
share.client().shares.get.return_value = self.failed_share
|
||||
exc = self.assertRaises(exception.ResourceFailure,
|
||||
scheduler.TaskRunner(share.check))
|
||||
self.assertIn("Error: 'status': expected '['available']'",
|
||||
six.text_type(exc))
|
@ -86,6 +86,9 @@ heat.constraints =
|
||||
keystone.project = heat.engine.clients.os.keystone:KeystoneProjectConstraint
|
||||
keystone.group = heat.engine.clients.os.keystone:KeystoneGroupConstraint
|
||||
keystone.service = heat.engine.clients.os.keystone:KeystoneServiceConstraint
|
||||
manila.share_snapshot = heat.engine.clients.os.manila:ManilaShareSnapshotConstraint
|
||||
manila.share_network = heat.engine.clients.os.manila:ManilaShareNetworkConstraint
|
||||
manila.share_type = heat.engine.clients.os.manila:ManilaShareTypeConstraint
|
||||
|
||||
heat.stack_lifecycle_plugins =
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user