Merge "Add handle_update for senlin cluster"

This commit is contained in:
Jenkins 2016-01-21 03:42:16 +00:00 committed by Gerrit Code Review
commit 68ca825d20
2 changed files with 138 additions and 1 deletions

View File

@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import six
from heat.common import exception from heat.common import exception
from heat.common.i18n import _ from heat.common.i18n import _
from heat.engine import attributes from heat.engine import attributes
@ -57,11 +59,18 @@ class Cluster(resource.Resource):
'CREATING', 'DELETING', 'UPDATING' 'CREATING', 'DELETING', 'UPDATING'
) )
_ACTION_STATUS = (
ACTION_SUCCEEDED, ACTION_FAILED,
) = (
'SUCCEEDED', 'FAILED',
)
properties_schema = { properties_schema = {
PROFILE: properties.Schema( PROFILE: properties.Schema(
properties.Schema.STRING, properties.Schema.STRING,
_('The name or id of the Senlin profile.'), _('The name or id of the Senlin profile.'),
required=True, required=True,
update_allowed=True,
constraints=[ constraints=[
constraints.CustomConstraint('senlin.profile') constraints.CustomConstraint('senlin.profile')
] ]
@ -70,16 +79,19 @@ class Cluster(resource.Resource):
properties.Schema.STRING, properties.Schema.STRING,
_('Name of the cluster. By default, physical resource name ' _('Name of the cluster. By default, physical resource name '
'is used.'), 'is used.'),
update_allowed=True,
), ),
DESIRED_CAPACITY: properties.Schema( DESIRED_CAPACITY: properties.Schema(
properties.Schema.INTEGER, properties.Schema.INTEGER,
_('Desired initial number of resources in cluster.'), _('Desired initial number of resources in cluster.'),
default=0 default=0,
update_allowed=True,
), ),
MIN_SIZE: properties.Schema( MIN_SIZE: properties.Schema(
properties.Schema.INTEGER, properties.Schema.INTEGER,
_('Minimum number of resources in the cluster.'), _('Minimum number of resources in the cluster.'),
default=0, default=0,
update_allowed=True,
constraints=[ constraints=[
constraints.Range(min=0) constraints.Range(min=0)
] ]
@ -89,6 +101,7 @@ class Cluster(resource.Resource):
_('Maximum number of resources in the cluster. ' _('Maximum number of resources in the cluster. '
'-1 means unlimited.'), '-1 means unlimited.'),
default=-1, default=-1,
update_allowed=True,
constraints=[ constraints=[
constraints.Range(min=-1) constraints.Range(min=-1)
] ]
@ -96,10 +109,12 @@ class Cluster(resource.Resource):
METADATA: properties.Schema( METADATA: properties.Schema(
properties.Schema.MAP, properties.Schema.MAP,
_('Metadata key-values defined for cluster.'), _('Metadata key-values defined for cluster.'),
update_allowed=True,
), ),
TIMEOUT: properties.Schema( TIMEOUT: properties.Schema(
properties.Schema.INTEGER, properties.Schema.INTEGER,
_('The number of seconds to wait for the cluster actions.'), _('The number of seconds to wait for the cluster actions.'),
update_allowed=True,
constraints=[ constraints=[
constraints.Range(min=0) constraints.Range(min=0)
] ]
@ -177,6 +192,63 @@ class Cluster(resource.Resource):
return True return True
return False return False
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
UPDATE_PROPS = (self.NAME, self.METADATA, self.TIMEOUT, self.PROFILE)
RESIZE_PROPS = (self.MIN_SIZE, self.MAX_SIZE, self.DESIRED_CAPACITY)
updaters = {}
if prop_diff:
if any(p in prop_diff for p in UPDATE_PROPS):
params = dict((k, v) for k, v in six.iteritems(prop_diff)
if k in UPDATE_PROPS)
if self.PROFILE in prop_diff:
params.pop(self.PROFILE)
params['profile_id'] = prop_diff[self.PROFILE]
updaters['cluster_update'] = {
'params': params,
'start': False,
}
if any(p in prop_diff for p in RESIZE_PROPS):
params = dict((k, v) for k, v in six.iteritems(prop_diff)
if k in RESIZE_PROPS)
if self.DESIRED_CAPACITY in prop_diff:
params.pop(self.DESIRED_CAPACITY)
params['adjustment_type'] = 'EXACT_CAPACITY'
params['number'] = prop_diff.pop(self.DESIRED_CAPACITY)
updaters['cluster_resize'] = {
'params': params,
'start': False,
}
return updaters
def check_update_complete(self, updaters):
def start_action(action, params):
if action == 'cluster_resize':
resp = self.client().cluster_resize(self.resource_id,
**params)
return resp['action']
elif action == 'cluster_update':
resp = self.client().update_cluster(self.resource_id,
**params)
return resp.location.split('/')[-1]
if not updaters:
return True
for k in six.iterkeys(updaters):
if not updaters[k]['start']:
action_id = start_action(k, updaters[k]['params'])
updaters[k]['action'] = action_id
updaters[k]['start'] = True
else:
action = self.client().get_action(updaters[k]['action'])
if action.status == self.ACTION_SUCCEEDED:
del updaters[k]
elif action.status == self.ACTION_FAILED:
raise exception.ResourceInError(
status_reason=action.status_reason,
resource_status=self.FAILED,
)
return False
def validate(self): def validate(self):
min_size = self.properties[self.MIN_SIZE] min_size = self.properties[self.MIN_SIZE]
max_size = self.properties[self.MAX_SIZE] max_size = self.properties[self.MAX_SIZE]

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import copy
import mock import mock
from oslo_config import cfg from oslo_config import cfg
import six import six
@ -22,6 +23,7 @@ from heat.common import template_format
from heat.engine.clients.os import senlin from heat.engine.clients.os import senlin
from heat.engine.resources.openstack.senlin import cluster as sc from heat.engine.resources.openstack.senlin import cluster as sc
from heat.engine import scheduler from heat.engine import scheduler
from heat.engine import template
from heat.tests import common from heat.tests import common
from heat.tests import utils from heat.tests import utils
from senlinclient.common import exc from senlinclient.common import exc
@ -149,6 +151,69 @@ class SenlinClusterTest(common.HeatTestCase):
expected = 'Error: resources.senlin-cluster: oops' expected = 'Error: resources.senlin-cluster: oops'
self.assertEqual(expected, six.text_type(ex)) self.assertEqual(expected, six.text_type(ex))
def test_cluster_update_profile(self):
cluster = self._create_cluster(self.t)
new_t = copy.deepcopy(self.t)
props = new_t['resources']['senlin-cluster']['properties']
props['profile'] = 'new_profile'
props['name'] = 'new_name'
rsrc_defns = template.Template(new_t).resource_definitions(self.stack)
new_cluster = rsrc_defns['senlin-cluster']
self.senlin_mock.update_cluster.return_value = mock.Mock(
location='/actions/fake-action')
self.senlin_mock.get_action.return_value = mock.Mock(
status='SUCCEEDED')
scheduler.TaskRunner(cluster.update, new_cluster)()
self.assertEqual((cluster.UPDATE, cluster.COMPLETE), cluster.state)
cluster_update_kwargs = {
'profile_id': 'new_profile',
'name': 'new_name'
}
self.senlin_mock.update_cluster.assert_called_once_with(
cluster.resource_id, **cluster_update_kwargs)
self.senlin_mock.get_action.assert_called_once_with(
'fake-action')
def test_cluster_update_desire_capacity(self):
cluster = self._create_cluster(self.t)
new_t = copy.deepcopy(self.t)
props = new_t['resources']['senlin-cluster']['properties']
props['desired_capacity'] = 10
rsrc_defns = template.Template(new_t).resource_definitions(self.stack)
new_cluster = rsrc_defns['senlin-cluster']
self.senlin_mock.cluster_resize.return_value = {
'action': 'fake-action'}
self.senlin_mock.get_action.return_value = mock.Mock(
status='SUCCEEDED')
scheduler.TaskRunner(cluster.update, new_cluster)()
self.assertEqual((cluster.UPDATE, cluster.COMPLETE), cluster.state)
cluster_resize_kwargs = {
'adjustment_type': 'EXACT_CAPACITY',
'number': 10
}
self.senlin_mock.cluster_resize.assert_called_once_with(
cluster.resource_id, **cluster_resize_kwargs)
self.senlin_mock.get_action.assert_called_once_with(
'fake-action')
def test_cluster_update_failed(self):
cluster = self._create_cluster(self.t)
new_t = copy.deepcopy(self.t)
props = new_t['resources']['senlin-cluster']['properties']
props['desired_capacity'] = 3
rsrc_defns = template.Template(new_t).resource_definitions(self.stack)
update_snippet = rsrc_defns['senlin-cluster']
self.senlin_mock.cluster_resize.return_value = {
'action': 'fake-action'}
self.senlin_mock.get_action.return_value = mock.Mock(
status='FAILED', status_reason='Unknown')
exc = self.assertRaises(
exception.ResourceFailure,
scheduler.TaskRunner(cluster.update, update_snippet))
self.assertEqual('ResourceInError: resources.senlin-cluster: '
'Went to status FAILED due to "Unknown"',
six.text_type(exc))
def test_cluster_resolve_attribute(self): def test_cluster_resolve_attribute(self):
excepted_show = { excepted_show = {
'id': 'some_id', 'id': 'some_id',