Merge "Add Bay to Magnum resources"
This commit is contained in:
commit
fcb771feca
155
heat/engine/resources/openstack/magnum/bay.py
Normal file
155
heat/engine/resources/openstack/magnum/bay.py
Normal file
@ -0,0 +1,155 @@
|
||||
#
|
||||
# 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
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.engine import constraints
|
||||
from heat.engine import properties
|
||||
from heat.engine import resource
|
||||
from heat.engine import support
|
||||
|
||||
|
||||
class Bay(resource.Resource):
|
||||
"""A resource that creates a Magnum Bay.
|
||||
|
||||
This resource creates a Magnum bay, which is a
|
||||
collection of node objects where work is scheduled.
|
||||
"""
|
||||
|
||||
support_status = support.SupportStatus(version='6.0.0')
|
||||
|
||||
PROPERTIES = (
|
||||
NAME, BAYMODEL, NODE_COUNT, MASTER_COUNT, DISCOVERY_URL,
|
||||
BAY_CREATE_TIMEOUT
|
||||
) = (
|
||||
'name', 'baymodel', 'node_count', 'master_count',
|
||||
'discovery_url', 'bay_create_timeout'
|
||||
)
|
||||
|
||||
properties_schema = {
|
||||
NAME: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('The bay name.')
|
||||
),
|
||||
BAYMODEL: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('The ID of the bay model.'),
|
||||
required=True
|
||||
),
|
||||
NODE_COUNT: properties.Schema(
|
||||
properties.Schema.INTEGER,
|
||||
_('The node count for this bay.'),
|
||||
constraints=[constraints.Range(min=1)],
|
||||
update_allowed=True
|
||||
),
|
||||
MASTER_COUNT: properties.Schema(
|
||||
properties.Schema.INTEGER,
|
||||
_('The number of master nodes for this bay.'),
|
||||
constraints=[constraints.Range(min=1)],
|
||||
update_allowed=True
|
||||
),
|
||||
DISCOVERY_URL: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Url used for bay node discovery.')
|
||||
),
|
||||
BAY_CREATE_TIMEOUT: properties.Schema(
|
||||
properties.Schema.INTEGER,
|
||||
_('Timeout for creating the bay in minutes. '
|
||||
'Set to 0 for no timeout.'),
|
||||
constraints=[constraints.Range(min=0)],
|
||||
default=0
|
||||
)
|
||||
}
|
||||
|
||||
default_client_name = 'magnum'
|
||||
|
||||
entity = 'bays'
|
||||
|
||||
def handle_create(self):
|
||||
args = {
|
||||
'name': self.properties[self.NAME],
|
||||
'baymodel_id': self.properties[self.BAYMODEL],
|
||||
'node_count': self.properties[self.NODE_COUNT],
|
||||
'master_count': self.properties[self.NODE_COUNT],
|
||||
'discovery_url': self.properties[self.DISCOVERY_URL],
|
||||
'bay_create_timeout': self.properties[self.BAY_CREATE_TIMEOUT]
|
||||
}
|
||||
bay = self.client().bays.create(**args)
|
||||
self.resource_id_set(bay.uuid)
|
||||
return bay.uuid
|
||||
|
||||
def check_create_complete(self, id):
|
||||
bay = self.client().bays.get(id)
|
||||
if bay.status == 'CREATE_IN_PROGRESS':
|
||||
return False
|
||||
elif bay.status is None:
|
||||
return False
|
||||
elif bay.status == 'CREATE_COMPLETE':
|
||||
return True
|
||||
elif bay.status == 'CREATE_FAILED':
|
||||
msg = (_("Failed to create Bay '%(name)s' - %(reason)s")
|
||||
% {'name': self.name, 'reason': bay.status_reason})
|
||||
raise exception.ResourceInError(status_reason=msg,
|
||||
resource_status=bay.status)
|
||||
else:
|
||||
msg = (_("Unknown status creating Bay '%(name)s' - %(reason)s")
|
||||
% {'name': self.name, 'reason': bay.status_reason})
|
||||
raise exception.ResourceUnknownStatus(status_reason=msg,
|
||||
resource_status=bay.status)
|
||||
|
||||
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||
if prop_diff:
|
||||
patch = [{'op': 'replace', 'path': '/' + k, 'value': v}
|
||||
for k, v in six.iteritems(prop_diff)]
|
||||
self.client().bays.update(self.resource_id, patch)
|
||||
return self.resource_id
|
||||
|
||||
def check_update_complete(self, id):
|
||||
bay = self.client().bays.get(id)
|
||||
if bay.status == 'UPDATE_IN_PROGRESS':
|
||||
return False
|
||||
# check for None due to Magnum bug
|
||||
# https://bugs.launchpad.net/magnum/+bug/1507598
|
||||
elif bay.status is None:
|
||||
return False
|
||||
elif bay.status == 'UPDATE_COMPLETE':
|
||||
return True
|
||||
elif bay.status == 'UPDATE_FAILED':
|
||||
msg = (_("Failed to update Bay '%(name)s' - %(reason)s")
|
||||
% {'name': self.name, 'reason': bay.status_reason})
|
||||
raise exception.ResourceInError(status_reason=msg,
|
||||
resource_status=bay.status)
|
||||
|
||||
else:
|
||||
msg = (_("Unknown status updating Bay '%(name)s' - %(reason)s")
|
||||
% {'name': self.name, 'reason': bay.status_reason})
|
||||
raise exception.ResourceUnknownStatus(status_reason=msg,
|
||||
resource_status=bay.status)
|
||||
|
||||
def check_delete_complete(self, id):
|
||||
if not id:
|
||||
return True
|
||||
try:
|
||||
self.client().bays.get(id)
|
||||
except Exception as exc:
|
||||
self.client_plugin().ignore_not_found(exc)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def resource_mapping():
|
||||
return {
|
||||
'OS::Magnum::Bay': Bay
|
||||
}
|
146
heat/tests/magnum/test_magnum_bay.py
Normal file
146
heat/tests/magnum/test_magnum_bay.py
Normal file
@ -0,0 +1,146 @@
|
||||
#
|
||||
# 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 mock
|
||||
from oslo_config import cfg
|
||||
import six
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common import template_format
|
||||
from heat.engine import resource
|
||||
from heat.engine.resources.openstack.magnum import bay
|
||||
from heat.engine import scheduler
|
||||
from heat.engine import template
|
||||
from heat.tests import common
|
||||
from heat.tests import utils
|
||||
|
||||
|
||||
magnum_template = '''
|
||||
heat_template_version: 2015-04-30
|
||||
resources:
|
||||
test_bay:
|
||||
type: OS::Magnum::Bay
|
||||
properties:
|
||||
name: test_bay
|
||||
baymodel: 123456
|
||||
node_count: 5
|
||||
master_count: 1
|
||||
discovery_url: https://discovery.etcd.io
|
||||
bay_create_timeout: 15
|
||||
'''
|
||||
|
||||
RESOURCE_TYPE = 'OS::Magnum::Bay'
|
||||
|
||||
|
||||
class TestMagnumBay(common.HeatTestCase):
|
||||
def setUp(self):
|
||||
super(TestMagnumBay, self).setUp()
|
||||
self.ctx = utils.dummy_context()
|
||||
resource._register_class(RESOURCE_TYPE, bay.Bay)
|
||||
t = template_format.parse(magnum_template)
|
||||
self.stack = utils.parse_stack(t)
|
||||
resource_defns = self.stack.t.resource_definitions(self.stack)
|
||||
self.rsrc_defn = resource_defns['test_bay']
|
||||
self.client = mock.Mock()
|
||||
self.patchobject(bay.Bay, 'client', return_value=self.client)
|
||||
|
||||
def _create_resource(self, name, snippet, stack, stat='CREATE_COMPLETE'):
|
||||
self.resource_id = '12345'
|
||||
value = mock.MagicMock(uuid=self.resource_id)
|
||||
self.client.bays.create.return_value = value
|
||||
get_rv = mock.MagicMock(status=stat)
|
||||
self.client.bays.get.return_value = get_rv
|
||||
b = bay.Bay(name, snippet, stack)
|
||||
return b
|
||||
|
||||
def test_bay_create(self):
|
||||
b = self._create_resource('bay', self.rsrc_defn, self.stack)
|
||||
scheduler.TaskRunner(b.create)()
|
||||
self.assertEqual(self.resource_id, b.resource_id)
|
||||
self.assertEqual((b.CREATE, b.COMPLETE), b.state)
|
||||
|
||||
def test_bay_create_failed(self):
|
||||
cfg.CONF.set_override('action_retry_limit', 0)
|
||||
b = self._create_resource('bay', self.rsrc_defn, self.stack,
|
||||
stat='CREATE_FAILED')
|
||||
exc = self.assertRaises(
|
||||
exception.ResourceFailure,
|
||||
scheduler.TaskRunner(b.create))
|
||||
self.assertIn("Failed to create Bay", six.text_type(exc))
|
||||
|
||||
def test_bay_create_unknown_status(self):
|
||||
b = self._create_resource('bay', self.rsrc_defn, self.stack,
|
||||
stat='CREATE_FOO')
|
||||
exc = self.assertRaises(
|
||||
exception.ResourceFailure,
|
||||
scheduler.TaskRunner(b.create))
|
||||
self.assertIn("Unknown status creating Bay", six.text_type(exc))
|
||||
|
||||
def test_bay_update(self):
|
||||
b = self._create_resource('bay', self.rsrc_defn, self.stack)
|
||||
scheduler.TaskRunner(b.create)()
|
||||
status = mock.MagicMock(status='UPDATE_COMPLETE')
|
||||
self.client.bays.get.return_value = status
|
||||
t = template_format.parse(magnum_template)
|
||||
new_t = copy.deepcopy(t)
|
||||
new_t['resources']['test_bay']['properties']['node_count'] = 10
|
||||
rsrc_defns = template.Template(new_t).resource_definitions(self.stack)
|
||||
new_bm = rsrc_defns['test_bay']
|
||||
scheduler.TaskRunner(b.update, new_bm)()
|
||||
self.assertEqual((b.UPDATE, b.COMPLETE), b.state)
|
||||
|
||||
def test_bay_update_failed(self):
|
||||
b = self._create_resource('bay', self.rsrc_defn, self.stack)
|
||||
scheduler.TaskRunner(b.create)()
|
||||
status = mock.MagicMock(status='UPDATE_FAILED')
|
||||
self.client.bays.get.return_value = status
|
||||
t = template_format.parse(magnum_template)
|
||||
new_t = copy.deepcopy(t)
|
||||
new_t['resources']['test_bay']['properties']['node_count'] = 10
|
||||
rsrc_defns = template.Template(new_t).resource_definitions(self.stack)
|
||||
new_bm = rsrc_defns['test_bay']
|
||||
exc = self.assertRaises(
|
||||
exception.ResourceFailure,
|
||||
scheduler.TaskRunner(b.update, new_bm))
|
||||
self.assertIn("Failed to update Bay", six.text_type(exc))
|
||||
|
||||
def test_bay_update_unknown_status(self):
|
||||
b = self._create_resource('bay', self.rsrc_defn, self.stack)
|
||||
scheduler.TaskRunner(b.create)()
|
||||
status = mock.MagicMock(status='UPDATE_BAR')
|
||||
self.client.bays.get.return_value = status
|
||||
t = template_format.parse(magnum_template)
|
||||
new_t = copy.deepcopy(t)
|
||||
new_t['resources']['test_bay']['properties']['node_count'] = 10
|
||||
rsrc_defns = template.Template(new_t).resource_definitions(self.stack)
|
||||
new_bm = rsrc_defns['test_bay']
|
||||
exc = self.assertRaises(
|
||||
exception.ResourceFailure,
|
||||
scheduler.TaskRunner(b.update, new_bm))
|
||||
self.assertIn("Unknown status updating Bay", six.text_type(exc))
|
||||
|
||||
def test_bay_delete(self):
|
||||
b = self._create_resource('bay', self.rsrc_defn, self.stack)
|
||||
scheduler.TaskRunner(b.create)()
|
||||
b.client_plugin = mock.MagicMock()
|
||||
self.client.bays.get.side_effect = Exception('Not Found')
|
||||
self.client.get.reset_mock()
|
||||
scheduler.TaskRunner(b.delete)()
|
||||
self.assertEqual((b.DELETE, b.COMPLETE), b.state)
|
||||
self.assertEqual(2, self.client.bays.get.call_count)
|
||||
|
||||
def test_resource_mapping(self):
|
||||
mapping = bay.resource_mapping()
|
||||
self.assertEqual(1, len(mapping))
|
||||
self.assertEqual(mapping[RESOURCE_TYPE], bay.Bay)
|
Loading…
x
Reference in New Issue
Block a user