208 lines
7.0 KiB
Python
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
|
|
}
|