heat/heat/engine/resources/openstack/senlin/policy.py

208 lines
7.0 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.
#
import copy
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 Policy(resource.Resource):
"""A resource that creates a Senlin Policy.
A policy is a set of rules that can be checked and/or enforced when
an action is performed on a Cluster.
"""
support_status = support.SupportStatus(version='6.0.0')
default_client_name = 'senlin'
PROPERTIES = (
NAME, TYPE, POLICY_PROPS, BINDINGS,
) = (
'name', 'type', 'properties', 'bindings'
)
_BINDINGS = (
BD_CLUSTER, BD_ENABLED,
) = (
'cluster', 'enabled'
)
_ACTION_STATUS = (
ACTION_SUCCEEDED, ACTION_FAILED,
) = (
'SUCCEEDED', 'FAILED',
)
properties_schema = {
NAME: properties.Schema(
properties.Schema.STRING,
_('Name of the senlin policy. By default, physical resource name '
'is used.'),
update_allowed=True,
),
TYPE: properties.Schema(
properties.Schema.STRING,
_('The type of senlin policy.'),
required=True,
constraints=[
constraints.CustomConstraint('senlin.policy_type')
]
),
POLICY_PROPS: properties.Schema(
properties.Schema.MAP,
_('Properties of this policy.'),
),
BINDINGS: properties.Schema(
properties.Schema.LIST,
_('A list of clusters to which this policy is attached.'),
update_allowed=True,
schema=properties.Schema(
properties.Schema.MAP,
schema={
BD_CLUSTER: properties.Schema(
properties.Schema.STRING,
_("The name or ID of target cluster."),
required=True,
constraints=[
constraints.CustomConstraint('senlin.cluster')
]
),
BD_ENABLED: properties.Schema(
properties.Schema.BOOLEAN,
_("Whether enable this policy on that cluster."),
default=True,
),
}
)
)
}
def remove_bindings(self, bindings):
for bd in bindings:
try:
bd['action'] = self.client().cluster_detach_policy(
bd[self.BD_CLUSTER], self.resource_id)['action']
bd['finished'] = False
except Exception as ex:
# policy didn't attach to cluster, skip.
if (self.client_plugin().is_bad_request(ex) or
self.client_plugin().is_not_found(ex)):
bd['finished'] = True
else:
raise
def add_bindings(self, bindings):
for bd in bindings:
bd['action'] = self.client().cluster_attach_policy(
bd[self.BD_CLUSTER], self.resource_id,
enabled=bd[self.BD_ENABLED])['action']
bd['finished'] = False
def check_action_done(self, bindings):
ret = True
if not bindings:
return ret
for bd in bindings:
if bd.get('finished', False):
continue
action = self.client().get_action(bd['action'])
if action.status == self.ACTION_SUCCEEDED:
bd['finished'] = True
elif action.status == self.ACTION_FAILED:
err_msg = _('Failed to execute %(action)s for '
'%(cluster)s: %(reason)s') % {
'action': action.action,
'cluster': bd[self.BD_CLUSTER],
'reason': action.status_reason}
raise exception.ResourceInError(
status_reason=err_msg,
resource_status=self.FAILED)
else:
ret = False
return ret
def handle_create(self):
params = {
'name': (self.properties[self.NAME] or
self.physical_resource_name()),
'spec': self.client_plugin().generate_spec(
self.properties[self.TYPE],
self.properties[self.POLICY_PROPS]
)
}
policy = self.client().create_policy(**params)
self.resource_id_set(policy.id)
bindings = copy.deepcopy(self.properties[self.BINDINGS])
if bindings:
self.add_bindings(bindings)
return bindings
def check_create_complete(self, bindings):
return self.check_action_done(bindings)
def handle_delete(self):
return copy.deepcopy(self.properties[self.BINDINGS])
def check_delete_complete(self, bindings):
if not self.resource_id:
return True
self.remove_bindings(bindings)
if self.check_action_done(bindings):
with self.client_plugin().ignore_not_found:
self.client().delete_policy(self.resource_id)
return True
return False
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
if self.NAME in prop_diff:
param = {'name': prop_diff[self.NAME]}
self.client().update_policy(self.resource_id, **param)
actions = dict()
if self.BINDINGS in prop_diff:
old = self.properties[self.BINDINGS] or []
new = prop_diff[self.BINDINGS] or []
actions['remove'] = [bd for bd in old if bd not in new]
actions['add'] = [bd for bd in new if bd not in old]
self.remove_bindings(actions['remove'])
return actions
def check_update_complete(self, actions):
ret = True
remove_done = self.check_action_done(actions.get('remove', []))
# wait until detach finished, then start attach
if remove_done and 'add' in actions:
if not actions.get('add_started', False):
self.add_bindings(actions['add'])
actions['add_started'] = True
ret = self.check_action_done(actions['add'])
return ret
def _show_resource(self):
policy = self.client().get_policy(self.resource_id)
return policy.to_dict()
def resource_mapping():
return {
'OS::Senlin::Policy': Policy
}