Remove Senlin support

The senlin project was marked inactive during this cycle[1]. Its
project health has not been improved even at m-2 thus 2024.1 release
will not be created for this project.

The project is retired because of no interest/volunteer to maintain
it[2].

[1] https://review.opendev.org/c/openstack/governance/+/902626
[2] https://review.opendev.org/c/openstack/governance/+/919347

Change-Id: I615de4d659f1d7879ad311e9f1c97a6937934262
This commit is contained in:
Takashi Kajinami 2024-01-03 18:26:40 +09:00
parent 1d155e48a2
commit 0357a47900
18 changed files with 32 additions and 2384 deletions

View File

@ -74,6 +74,5 @@ We have integration with
* https://opendev.org/openstack/python-zunclient (container management service)
* https://opendev.org/openstack/python-blazarclient (reservation service)
* https://opendev.org/openstack/python-octaviaclient.git (Load-balancer service)
* https://opendev.org/openstack/python-senlinclient (Clustering service)
* https://opendev.org/openstack/python-vitrageclient.git (RCA service)
* https://opendev.org/openstack/python-ironicclient (baremetal provisioning service)

View File

@ -460,7 +460,7 @@ def list_opts():
for client in ('aodh', 'barbican', 'cinder', 'designate',
'glance', 'heat', 'keystone', 'magnum', 'manila', 'mistral',
'monasca', 'neutron', 'nova', 'octavia', 'sahara', 'senlin',
'monasca', 'neutron', 'nova', 'octavia', 'sahara',
'swift', 'trove', 'vitrage', 'zaqar'
):
client_specific_group = 'clients_' + client

View File

@ -1,160 +0,0 @@
#
# 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 openstack import exceptions
from heat.common import exception
from heat.common.i18n import _
from heat.engine.clients.os import openstacksdk as sdk_plugin
from heat.engine import constraints
CLIENT_NAME = 'senlin'
class SenlinClientPlugin(sdk_plugin.OpenStackSDKPlugin):
exceptions_module = exceptions
def _create(self, version=None):
client = super(SenlinClientPlugin, self)._create(version=version)
return client.clustering
def _get_additional_create_args(self, version):
return {
'clustering_api_version': version or '1'
}
def generate_spec(self, spec_type, spec_props):
spec = {'properties': spec_props}
spec['type'], spec['version'] = spec_type.split('-')
return spec
def check_action_status(self, action_id):
action = self.client().get_action(action_id)
if action.status == 'SUCCEEDED':
return True
elif action.status == 'FAILED':
raise exception.ResourceInError(
status_reason=action.status_reason,
resource_status=action.status,
)
return False
def cluster_is_active(self, cluster_id):
cluster = self.client().get_cluster(cluster_id)
if cluster.status == 'ACTIVE':
return True
elif cluster.status == 'ERROR':
raise exception.ResourceInError(
status_reason=cluster.status_reason,
resource_status=cluster.status,
)
return False
def get_profile_id(self, profile_name):
profile = self.client().get_profile(profile_name)
return profile.id
def get_cluster_id(self, cluster_name):
cluster = self.client().get_cluster(cluster_name)
return cluster.id
def get_policy_id(self, policy_name):
policy = self.client().get_policy(policy_name)
return policy.id
def is_bad_request(self, ex):
return (isinstance(ex, exceptions.HttpException) and
ex.status_code == 400)
def execute_actions(self, actions):
all_executed = True
for action in actions:
if action['done']:
continue
all_executed = False
if 'action_id' in action:
if action['action_id'] is None:
func = getattr(self.client(), action['func'])
ret = func(**action['params'])
if isinstance(ret, dict):
action['action_id'] = ret['action']
else:
action['action_id'] = ret.location.split('/')[-1]
else:
ret = self.check_action_status(action['action_id'])
action['done'] = ret
else:
ret = self.cluster_is_active(action['cluster_id'])
action['done'] = ret
# Execute these actions one by one.
break
return all_executed
class ProfileConstraint(constraints.BaseCustomConstraint):
# If name is not unique, will raise exceptions.HttpException
expected_exceptions = (exceptions.HttpException,)
def validate_with_client(self, client, profile):
client.client(CLIENT_NAME).get_profile(profile)
class ClusterConstraint(constraints.BaseCustomConstraint):
# If name is not unique, will raise exceptions.HttpException
expected_exceptions = (exceptions.HttpException,)
def validate_with_client(self, client, value):
client.client(CLIENT_NAME).get_cluster(value)
class PolicyConstraint(constraints.BaseCustomConstraint):
# If name is not unique, will raise exceptions.HttpException
expected_exceptions = (exceptions.HttpException,)
def validate_with_client(self, client, value):
client.client(CLIENT_NAME).get_policy(value)
class ProfileTypeConstraint(constraints.BaseCustomConstraint):
expected_exceptions = (exception.StackValidationFailed,)
def validate_with_client(self, client, value):
conn = client.client(CLIENT_NAME)
type_list = conn.profile_types()
names = [pt.name for pt in type_list]
if value not in names:
not_found_message = (
_("Unable to find senlin profile type '%(pt)s', "
"available profile types are %(pts)s.") %
{'pt': value, 'pts': names}
)
raise exception.StackValidationFailed(message=not_found_message)
class PolicyTypeConstraint(constraints.BaseCustomConstraint):
expected_exceptions = (exception.StackValidationFailed,)
def validate_with_client(self, client, value):
conn = client.client(CLIENT_NAME)
type_list = conn.policy_types()
names = [pt.name for pt in type_list]
if value not in names:
not_found_message = (
_("Unable to find senlin policy type '%(pt)s', "
"available policy types are %(pts)s.") %
{'pt': value, 'pts': names}
)
raise exception.StackValidationFailed(message=not_found_message)

View File

@ -12,14 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from heat.common import exception
from heat.common.i18n import _
from heat.engine import attributes
from heat.engine import constraints
from heat.engine import properties
from heat.engine.resources.openstack.senlin import res_base
from heat.engine import support
from heat.engine import translation
class Cluster(res_base.BaseSenlinResource):
@ -29,388 +22,7 @@ class Cluster(res_base.BaseSenlinResource):
the same nature, e.g. Nova servers, Heat stacks, Cinder volumes, etc.
The collection of these objects is referred to as a cluster.
"""
entity = 'cluster'
PROPERTIES = (
NAME, PROFILE, DESIRED_CAPACITY, MIN_SIZE, MAX_SIZE,
METADATA, TIMEOUT, POLICIES,
) = (
'name', 'profile', 'desired_capacity', 'min_size', 'max_size',
'metadata', 'timeout', 'policies',
)
ATTRIBUTES = (
ATTR_NAME, ATTR_METADATA, ATTR_NODES, ATTR_DESIRED_CAPACITY,
ATTR_MIN_SIZE, ATTR_MAX_SIZE, ATTR_POLICIES, ATTR_COLLECT,
) = (
"name", 'metadata', 'nodes', 'desired_capacity',
'min_size', 'max_size', 'policies', 'collect',
)
_POLICIES = (
P_POLICY, P_ENABLED,
) = (
"policy", "enabled",
)
_CLUSTER_STATUS = (
CLUSTER_INIT, CLUSTER_ACTIVE, CLUSTER_ERROR, CLUSTER_WARNING,
CLUSTER_CREATING, CLUSTER_DELETING, CLUSTER_UPDATING
) = (
'INIT', 'ACTIVE', 'ERROR', 'WARNING',
'CREATING', 'DELETING', 'UPDATING'
)
properties_schema = {
PROFILE: properties.Schema(
properties.Schema.STRING,
_('The name or id of the Senlin profile.'),
required=True,
update_allowed=True,
constraints=[
constraints.CustomConstraint('senlin.profile')
]
),
NAME: properties.Schema(
properties.Schema.STRING,
_('Name of the cluster. By default, physical resource name '
'is used.'),
update_allowed=True,
),
DESIRED_CAPACITY: properties.Schema(
properties.Schema.INTEGER,
_('Desired initial number of resources in cluster.'),
default=0,
update_allowed=True,
),
MIN_SIZE: properties.Schema(
properties.Schema.INTEGER,
_('Minimum number of resources in the cluster.'),
default=0,
update_allowed=True,
constraints=[
constraints.Range(min=0)
]
),
MAX_SIZE: properties.Schema(
properties.Schema.INTEGER,
_('Maximum number of resources in the cluster. '
'-1 means unlimited.'),
default=-1,
update_allowed=True,
constraints=[
constraints.Range(min=-1)
]
),
METADATA: properties.Schema(
properties.Schema.MAP,
_('Metadata key-values defined for cluster.'),
update_allowed=True,
default={},
),
TIMEOUT: properties.Schema(
properties.Schema.INTEGER,
_('The number of seconds to wait for the cluster actions.'),
update_allowed=True,
constraints=[
constraints.Range(min=0)
]
),
POLICIES: properties.Schema(
properties.Schema.LIST,
_('A list of policies to attach to this cluster.'),
update_allowed=True,
default=[],
support_status=support.SupportStatus(version='8.0.0'),
schema=properties.Schema(
properties.Schema.MAP,
schema={
P_POLICY: properties.Schema(
properties.Schema.STRING,
_("The name or ID of the policy."),
required=True,
constraints=[
constraints.CustomConstraint('senlin.policy')
]
),
P_ENABLED: properties.Schema(
properties.Schema.BOOLEAN,
_("Whether enable this policy on this cluster."),
default=True,
),
}
)
),
}
attributes_schema = {
ATTR_NAME: attributes.Schema(
_("Cluster name."),
type=attributes.Schema.STRING
),
ATTR_METADATA: attributes.Schema(
_("Cluster metadata."),
type=attributes.Schema.MAP
),
ATTR_DESIRED_CAPACITY: attributes.Schema(
_("Desired capacity of the cluster."),
type=attributes.Schema.INTEGER
),
ATTR_NODES: attributes.Schema(
_("Nodes list in the cluster."),
type=attributes.Schema.LIST,
cache_mode=attributes.Schema.CACHE_NONE
),
ATTR_MIN_SIZE: attributes.Schema(
_("Min size of the cluster."),
type=attributes.Schema.INTEGER
),
ATTR_MAX_SIZE: attributes.Schema(
_("Max size of the cluster."),
type=attributes.Schema.INTEGER
),
ATTR_POLICIES: attributes.Schema(
_("Policies attached to the cluster."),
type=attributes.Schema.LIST,
support_status=support.SupportStatus(version='8.0.0'),
),
ATTR_COLLECT: attributes.Schema(
_("Attributes collected from cluster. According to the jsonpath "
"following this attribute, it will return a list of attributes "
"collected from the nodes of this cluster."),
type=attributes.Schema.LIST,
support_status=support.SupportStatus(version='8.0.0'),
cache_mode=attributes.Schema.CACHE_NONE
)
}
def translation_rules(self, props):
rules = [
translation.TranslationRule(
props,
translation.TranslationRule.RESOLVE,
translation_path=[self.PROFILE],
client_plugin=self.client_plugin(),
finder='get_profile_id'),
translation.TranslationRule(
props,
translation.TranslationRule.RESOLVE,
translation_path=[self.POLICIES, self.P_POLICY],
client_plugin=self.client_plugin(),
finder='get_policy_id'),
]
return rules
def handle_create(self):
actions = []
params = {
'name': (self.properties[self.NAME] or
self.physical_resource_name()),
'profile_id': self.properties[self.PROFILE],
'desired_capacity': self.properties[self.DESIRED_CAPACITY],
'min_size': self.properties[self.MIN_SIZE],
'max_size': self.properties[self.MAX_SIZE],
'metadata': self.properties[self.METADATA],
'timeout': self.properties[self.TIMEOUT]
}
cluster = self.client().create_cluster(**params)
self.resource_id_set(cluster.id)
# for cluster creation, we just to check the action status
# the action is executed above
action = {
'cluster_id': cluster.id,
'done': False,
}
actions.append(action)
if self.properties[self.POLICIES]:
for p in self.properties[self.POLICIES]:
params = {
'cluster': cluster.id,
'policy': p[self.P_POLICY],
'enabled': p[self.P_ENABLED],
}
action = {
'func': 'attach_policy_to_cluster',
'params': params,
'action_id': None,
'done': False,
}
actions.append(action)
return actions
def check_create_complete(self, actions):
return self.client_plugin().execute_actions(actions)
def handle_delete(self):
if self.resource_id is not None:
with self.client_plugin().ignore_not_found:
self.client().delete_cluster(self.resource_id)
return self.resource_id
def check_delete_complete(self, resource_id):
if resource_id:
with self.client_plugin().ignore_not_found:
self.client().get_cluster(self.resource_id)
return False
return True
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)
actions = []
if not prop_diff:
return actions
cluster_obj = self.client().get_cluster(self.resource_id)
# Update Policies
if self.POLICIES in prop_diff:
old_policies = self.properties[self.POLICIES]
new_policies = prop_diff[self.POLICIES]
old_policy_ids = [p[self.P_POLICY] for p in old_policies]
update_policies = [p for p in new_policies
if p[self.P_POLICY] in old_policy_ids]
update_policy_ids = [p[self.P_POLICY] for p in update_policies]
add_policies = [p for p in new_policies
if p[self.P_POLICY] not in old_policy_ids]
remove_policies = [p for p in old_policies
if p[self.P_POLICY] not in update_policy_ids]
for p in update_policies:
params = {
'policy': p[self.P_POLICY],
'cluster': self.resource_id,
'enabled': p[self.P_ENABLED]
}
action = {
'func': 'update_cluster_policy',
'params': params,
'action_id': None,
'done': False,
}
actions.append(action)
for p in remove_policies:
params = {
'policy': p[self.P_POLICY],
'cluster': self.resource_id,
'enabled': p[self.P_ENABLED]
}
action = {
'func': 'detach_policy_from_cluster',
'params': params,
'action_id': None,
'done': False,
}
actions.append(action)
for p in add_policies:
params = {
'policy': p[self.P_POLICY],
'cluster': self.resource_id,
'enabled': p[self.P_ENABLED]
}
action = {
'func': 'attach_policy_to_cluster',
'params': params,
'action_id': None,
'done': False,
}
actions.append(action)
# Update cluster
if any(p in prop_diff for p in UPDATE_PROPS):
params = dict((k, v) for k, v in prop_diff.items()
if k in UPDATE_PROPS)
params['cluster'] = cluster_obj
if self.PROFILE in params:
params['profile_id'] = params.pop(self.PROFILE)
self.client().update_cluster(**params)
action = {
'cluster_id': self.resource_id,
'done': False
}
actions.append(action)
# Resize Cluster
if any(p in prop_diff for p in RESIZE_PROPS):
params = dict((k, v) for k, v in prop_diff.items()
if k in RESIZE_PROPS)
if self.DESIRED_CAPACITY in params:
params['adjustment_type'] = 'EXACT_CAPACITY'
params['number'] = params.pop(self.DESIRED_CAPACITY)
params['cluster'] = self.resource_id
action = {
'func': 'resize_cluster',
'params': params,
'action_id': None,
'done': False,
}
actions.append(action)
return actions
def check_update_complete(self, actions):
return self.client_plugin().execute_actions(actions)
def validate(self):
min_size = self.properties[self.MIN_SIZE]
max_size = self.properties[self.MAX_SIZE]
desired_capacity = self.properties[self.DESIRED_CAPACITY]
if max_size != -1 and max_size < min_size:
msg = _("%(min_size)s can not be greater than %(max_size)s") % {
'min_size': self.MIN_SIZE,
'max_size': self.MAX_SIZE,
}
raise exception.StackValidationFailed(message=msg)
if (desired_capacity < min_size or
(max_size != -1 and desired_capacity > max_size)):
msg = _("%(desired_capacity)s must be between %(min_size)s "
"and %(max_size)s") % {
'desired_capacity': self.DESIRED_CAPACITY,
'min_size': self.MIN_SIZE,
'max_size': self.MAX_SIZE,
}
raise exception.StackValidationFailed(message=msg)
def get_attribute(self, key, *path):
if self.resource_id is None:
return None
if key == self.ATTR_COLLECT:
if not path:
raise exception.InvalidTemplateAttribute(
resource=self.name, key=key)
attrs = self.client().collect_cluster_attrs(
self.resource_id, path[0])
attr = [attr.attr_value for attr in attrs]
return attributes.select_from_attribute(attr, path[1:])
else:
return super(Cluster, self).get_attribute(key, *path)
def _show_resource(self):
cluster_dict = super(Cluster, self)._show_resource()
cluster_dict[self.ATTR_POLICIES] = self.client().cluster_policies(
self.resource_id)
return cluster_dict
def parse_live_resource_data(self, resource_properties, resource_data):
reality = {}
for key in self._update_allowed_properties:
if key == self.PROFILE:
value = resource_data.get('profile_id')
elif key == self.POLICIES:
value = []
for p in resource_data.get(self.POLICIES):
v = {
'policy': p.get('policy_id'),
'enabled': p.get('enabled'),
}
value.append(v)
else:
value = resource_data.get(key)
reality.update({key: value})
return reality
pass
def resource_mapping():

View File

@ -11,15 +11,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
from heat.common.i18n import _
from heat.engine import attributes
from heat.engine import constraints
from heat.engine import properties
from heat.engine.resources.openstack.senlin import res_base
from heat.engine import support
from heat.engine import translation
class Node(res_base.BaseSenlinResource):
@ -28,188 +20,7 @@ class Node(res_base.BaseSenlinResource):
Node is an object that belongs to at most one Cluster, it can be created
based on a profile.
"""
entity = 'node'
PROPERTIES = (
NAME, METADATA, PROFILE, CLUSTER
) = (
'name', 'metadata', 'profile', 'cluster'
)
_NODE_STATUS = (
INIT, ACTIVE, CREATING,
) = (
'INIT', 'ACTIVE', 'CREATING',
)
ATTRIBUTES = (
ATTR_DETAILS, ATTR_CLUSTER,
) = (
'details', 'cluster_id'
)
properties_schema = {
NAME: properties.Schema(
properties.Schema.STRING,
_('Name of the senlin node. By default, physical resource name '
'is used.'),
update_allowed=True,
),
METADATA: properties.Schema(
properties.Schema.MAP,
_('Metadata key-values defined for node.'),
update_allowed=True,
),
PROFILE: properties.Schema(
properties.Schema.STRING,
_('Name or ID of senlin profile to create this node.'),
required=True,
update_allowed=True,
constraints=[
constraints.CustomConstraint('senlin.profile')
]
),
CLUSTER: properties.Schema(
properties.Schema.STRING,
_('The name of senlin cluster to attach to.'),
update_allowed=True,
constraints=[
constraints.CustomConstraint('senlin.cluster')
],
support_status=support.SupportStatus(version='8.0.0'),
),
}
attributes_schema = {
ATTR_DETAILS: attributes.Schema(
_("The details of physical object."),
type=attributes.Schema.MAP
),
ATTR_CLUSTER: attributes.Schema(
_("The cluster ID this node belongs to."),
type=attributes.Schema.STRING
),
}
def translation_rules(self, props):
rules = [
translation.TranslationRule(
props,
translation.TranslationRule.RESOLVE,
translation_path=[self.PROFILE],
client_plugin=self.client_plugin(),
finder='get_profile_id'),
translation.TranslationRule(
props,
translation.TranslationRule.RESOLVE,
translation_path=[self.CLUSTER],
client_plugin=self.client_plugin(),
finder='get_cluster_id'),
]
return rules
def handle_create(self):
params = {
'name': (self.properties[self.NAME] or
self.physical_resource_name()),
'metadata': self.properties[self.METADATA],
'profile_id': self.properties[self.PROFILE],
'cluster_id': self.properties[self.CLUSTER],
}
node = self.client().create_node(**params)
action_id = node.location.split('/')[-1]
self.resource_id_set(node.id)
return action_id
def check_create_complete(self, action_id):
return self.client_plugin().check_action_status(action_id)
def handle_delete(self):
if self.resource_id is not None:
with self.client_plugin().ignore_not_found:
self.client().delete_node(self.resource_id)
return self.resource_id
def check_delete_complete(self, res_id):
if res_id:
with self.client_plugin().ignore_not_found:
self.client().get_node(self.resource_id)
return False
return True
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
actions = []
if prop_diff:
old_cluster = None
new_cluster = None
if self.PROFILE in prop_diff:
prop_diff['profile_id'] = prop_diff.pop(self.PROFILE)
if self.CLUSTER in prop_diff:
old_cluster = self.properties[self.CLUSTER]
new_cluster = prop_diff.pop(self.CLUSTER)
if old_cluster:
params = {
'cluster': old_cluster,
'nodes': [self.resource_id],
}
action = {
'func': 'remove_nodes_from_cluster',
'action_id': None,
'params': params,
'done': False,
}
actions.append(action)
if prop_diff:
node = self.client().get_node(self.resource_id)
params = copy.deepcopy(prop_diff)
params['node'] = node
action = {
'func': 'update_node',
'action_id': None,
'params': params,
'done': False,
}
actions.append(action)
if new_cluster:
params = {
'cluster': new_cluster,
'nodes': [self.resource_id],
}
action = {
'func': 'add_nodes_to_cluster',
'action_id': None,
'params': params,
'done': False,
}
actions.append(action)
return actions
def check_update_complete(self, actions):
return self.client_plugin().execute_actions(actions)
def _resolve_attribute(self, name):
if self.resource_id is None:
return
node = self.client().get_node(self.resource_id, details=True)
return getattr(node, name, None)
def parse_live_resource_data(self, resource_properties, resource_data):
reality = {}
for key in self._update_allowed_properties:
if key == self.PROFILE:
value = resource_data.get('profile_id')
elif key == self.CLUSTER:
value = resource_data.get('cluster_id')
else:
value = resource_data.get(key)
reality.update({key: value})
return reality
pass
def resource_mapping():

View File

@ -12,14 +12,7 @@
# 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.resources.openstack.senlin import res_base
from heat.engine import translation
class Policy(res_base.BaseSenlinResource):
@ -28,186 +21,7 @@ class Policy(res_base.BaseSenlinResource):
A policy is a set of rules that can be checked and/or enforced when
an action is performed on a Cluster.
"""
entity = 'policy'
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 translation_rules(self, props):
rules = [
translation.TranslationRule(
props,
translation.TranslationRule.RESOLVE,
translation_path=[self.BINDINGS, self.BD_CLUSTER],
client_plugin=self.client_plugin(),
finder='get_cluster_id'),
]
return rules
def remove_bindings(self, bindings):
for bd in bindings:
try:
bd['action'] = self.client().detach_policy_from_cluster(
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().attach_policy_to_cluster(
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):
if not self.resource_id:
return
return copy.deepcopy(self.properties[self.BINDINGS])
def check_delete_complete(self, bindings):
if not bindings:
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]}
policy_obj = self.client().get_policy(self.resource_id)
self.client().update_policy(policy_obj, **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
pass
def resource_mapping():

View File

@ -13,9 +13,6 @@
#
# Copyright 2015 IBM Corp.
from heat.common.i18n import _
from heat.engine import constraints
from heat.engine import properties
from heat.engine.resources.openstack.senlin import res_base
@ -25,63 +22,7 @@ class Profile(res_base.BaseSenlinResource):
Profile resource in senlin is a template describing how to create nodes in
cluster.
"""
entity = 'profile'
PROPERTIES = (
NAME, TYPE, METADATA, PROFILE_PROPERTIES,
) = (
'name', 'type', 'metadata', 'properties',
)
properties_schema = {
NAME: properties.Schema(
properties.Schema.STRING,
_('Name of the senlin profile. By default, physical resource name '
'is used.'),
update_allowed=True,
),
TYPE: properties.Schema(
properties.Schema.STRING,
_('The type of profile.'),
required=True,
constraints=[
constraints.CustomConstraint('senlin.profile_type')
]
),
METADATA: properties.Schema(
properties.Schema.MAP,
_('Metadata key-values defined for profile.'),
update_allowed=True,
),
PROFILE_PROPERTIES: properties.Schema(
properties.Schema.MAP,
_('Properties for profile.'),
)
}
def handle_create(self):
params = {
'name': (self.properties[self.NAME] or
self.physical_resource_name()),
'spec': self.client_plugin().generate_spec(
spec_type=self.properties[self.TYPE],
spec_props=self.properties[self.PROFILE_PROPERTIES]),
'metadata': self.properties[self.METADATA],
}
profile = self.client().create_profile(**params)
self.resource_id_set(profile.id)
def handle_delete(self):
if self.resource_id is not None:
with self.client_plugin().ignore_not_found:
self.client().delete_profile(self.resource_id)
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
if prop_diff:
profile_obj = self.client().get_profile(self.resource_id)
self.client().update_profile(profile_obj, **prop_diff)
pass
def resource_mapping():

View File

@ -11,10 +11,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from heat.common.i18n import _
from heat.engine import attributes
from heat.engine import constraints
from heat.engine import properties
from heat.engine.resources.openstack.senlin import res_base
@ -24,94 +20,7 @@ class Receiver(res_base.BaseSenlinResource):
Receiver is an abstract resource created at the senlin engine
that can be used to hook the engine to some external event/alarm sources.
"""
entity = 'receiver'
PROPERTIES = (
CLUSTER, ACTION, NAME, TYPE, PARAMS,
) = (
'cluster', 'action', 'name', 'type', 'params',
)
ATTRIBUTES = (
ATTR_CHANNEL,
) = (
'channel',
)
_ACTIONS = (
CLUSTER_SCALE_OUT, CLUSTER_SCALE_IN,
) = (
'CLUSTER_SCALE_OUT', 'CLUSTER_SCALE_IN',
)
_TYPES = (
WEBHOOK,
) = (
'webhook',
)
properties_schema = {
CLUSTER: properties.Schema(
properties.Schema.STRING,
_('Name or ID of target cluster.'),
required=True,
constraints=[
constraints.CustomConstraint('senlin.cluster')
]
),
ACTION: properties.Schema(
properties.Schema.STRING,
_('The action to be executed when the receiver is signaled.'),
required=True,
constraints=[
constraints.AllowedValues(_ACTIONS)
]
),
NAME: properties.Schema(
properties.Schema.STRING,
_('Name of the senlin receiver. By default, '
'physical resource name is used.'),
),
TYPE: properties.Schema(
properties.Schema.STRING,
_('Type of receiver.'),
default=WEBHOOK,
constraints=[
constraints.AllowedValues(_TYPES)
]
),
PARAMS: properties.Schema(
properties.Schema.MAP,
_('The parameters passed to action when the receiver '
'is signaled.'),
),
}
attributes_schema = {
ATTR_CHANNEL: attributes.Schema(
_("The channel for receiving signals."),
type=attributes.Schema.MAP
),
}
def handle_create(self):
params = {
'name': (self.properties[self.NAME] or
self.physical_resource_name()),
'cluster_id': self.properties[self.CLUSTER],
'type': self.properties[self.TYPE],
'action': self.properties[self.ACTION],
'params': self.properties[self.PARAMS],
}
recv = self.client().create_receiver(**params)
self.resource_id_set(recv.id)
def handle_delete(self):
if self.resource_id is not None:
with self.client_plugin().ignore_not_found:
self.client().delete_receiver(self.resource_id)
pass
def resource_mapping():

View File

@ -12,39 +12,22 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
from heat.common.i18n import _
from heat.engine import resource
from heat.engine.resources.openstack.heat import none_resource
from heat.engine import support
LOG = logging.getLogger(__name__)
class BaseSenlinResource(resource.Resource):
class BaseSenlinResource(none_resource.NoneResource):
"""A base class for Senlin resources."""
support_status = support.SupportStatus(
version='22.0.0',
status=support.DEPRECATED,
message=_('Senlin project was marked inactive'),
version='23.0.0',
status=support.HIDDEN,
message=_('Senlin project was retired'),
previous_status=support.SupportStatus(
version='6.0.0',
))
default_client_name = 'senlin'
def _show_resource(self):
method_name = 'get_' + self.entity
try:
client_method = getattr(self.client(), method_name)
res_info = client_method(self.resource_id)
return res_info.to_dict()
except AttributeError as ex:
LOG.warning("No method to get the resource: %s", ex)
def _resolve_attribute(self, name):
if self.resource_id is None:
return
res_info = self._show_resource()
return res_info.get(name)
version='22.0.0',
status=support.DEPRECATED,
message=_('Senlin project was marked inactive'),
previous_status=support.SupportStatus(
version='6.0.0',
)))

View File

@ -1,198 +0,0 @@
#
# 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 unittest import mock
from openstack import exceptions
from heat.engine.clients.os import senlin as senlin_plugin
from heat.tests import common
from heat.tests import utils
class SenlinClientPluginTest(common.HeatTestCase):
@mock.patch('openstack.connection.Connection')
def setUp(self, mock_connection):
super(SenlinClientPluginTest, self).setUp()
context = utils.dummy_context()
self.plugin = context.clients.client_plugin('senlin')
self.client = self.plugin.client()
def test_cluster_get(self):
self.assertIsNotNone(self.client.clusters)
def test_is_bad_request(self):
self.assertTrue(self.plugin.is_bad_request(
exceptions.HttpException(http_status=400)))
self.assertFalse(self.plugin.is_bad_request(Exception))
self.assertFalse(self.plugin.is_bad_request(
exceptions.HttpException(http_status=404)))
def test_check_action_success(self):
mock_action = mock.MagicMock()
mock_action.status = 'SUCCEEDED'
mock_get = self.patchobject(self.client, 'get_action')
mock_get.return_value = mock_action
self.assertTrue(self.plugin.check_action_status('fake_id'))
mock_get.assert_called_once_with('fake_id')
def test_get_profile_id(self):
mock_profile = mock.Mock(id='fake_profile_id')
mock_get = self.patchobject(self.client, 'get_profile',
return_value=mock_profile)
ret = self.plugin.get_profile_id('fake_profile')
self.assertEqual('fake_profile_id', ret)
mock_get.assert_called_once_with('fake_profile')
def test_get_cluster_id(self):
mock_cluster = mock.Mock(id='fake_cluster_id')
mock_get = self.patchobject(self.client, 'get_cluster',
return_value=mock_cluster)
ret = self.plugin.get_cluster_id('fake_cluster')
self.assertEqual('fake_cluster_id', ret)
mock_get.assert_called_once_with('fake_cluster')
def test_get_policy_id(self):
mock_policy = mock.Mock(id='fake_policy_id')
mock_get = self.patchobject(self.client, 'get_policy',
return_value=mock_policy)
ret = self.plugin.get_policy_id('fake_policy')
self.assertEqual('fake_policy_id', ret)
mock_get.assert_called_once_with('fake_policy')
class ProfileConstraintTest(common.HeatTestCase):
@mock.patch('openstack.connection.Connection')
def setUp(self, mock_connection):
super(ProfileConstraintTest, self).setUp()
self.senlin_client = mock.MagicMock()
self.ctx = utils.dummy_context()
self.mock_get_profile = mock.Mock()
self.ctx.clients.client(
'senlin').get_profile = self.mock_get_profile
self.constraint = senlin_plugin.ProfileConstraint()
def test_validate_true(self):
self.mock_get_profile.return_value = None
self.assertTrue(self.constraint.validate("PROFILE_ID", self.ctx))
def test_validate_false(self):
self.mock_get_profile.side_effect = exceptions.ResourceNotFound(
'PROFILE_ID')
self.assertFalse(self.constraint.validate("PROFILE_ID", self.ctx))
self.mock_get_profile.side_effect = exceptions.HttpException(
'PROFILE_ID')
self.assertFalse(self.constraint.validate("PROFILE_ID", self.ctx))
class ClusterConstraintTest(common.HeatTestCase):
@mock.patch('openstack.connection.Connection')
def setUp(self, mock_connection):
super(ClusterConstraintTest, self).setUp()
self.senlin_client = mock.MagicMock()
self.ctx = utils.dummy_context()
self.mock_get_cluster = mock.Mock()
self.ctx.clients.client(
'senlin').get_cluster = self.mock_get_cluster
self.constraint = senlin_plugin.ClusterConstraint()
def test_validate_true(self):
self.mock_get_cluster.return_value = None
self.assertTrue(self.constraint.validate("CLUSTER_ID", self.ctx))
def test_validate_false(self):
self.mock_get_cluster.side_effect = exceptions.ResourceNotFound(
'CLUSTER_ID')
self.assertFalse(self.constraint.validate("CLUSTER_ID", self.ctx))
self.mock_get_cluster.side_effect = exceptions.HttpException(
'CLUSTER_ID')
self.assertFalse(self.constraint.validate("CLUSTER_ID", self.ctx))
class PolicyConstraintTest(common.HeatTestCase):
@mock.patch('openstack.connection.Connection')
def setUp(self, mock_connection):
super(PolicyConstraintTest, self).setUp()
self.senlin_client = mock.MagicMock()
self.ctx = utils.dummy_context()
self.mock_get_policy = mock.Mock()
self.ctx.clients.client(
'senlin').get_policy = self.mock_get_policy
self.constraint = senlin_plugin.PolicyConstraint()
def test_validate_true(self):
self.mock_get_policy.return_value = None
self.assertTrue(self.constraint.validate("POLICY_ID", self.ctx))
def test_validate_false(self):
self.mock_get_policy.side_effect = exceptions.ResourceNotFound(
'POLICY_ID')
self.assertFalse(self.constraint.validate("POLICY_ID", self.ctx))
self.mock_get_policy.side_effect = exceptions.HttpException(
'POLICY_ID')
self.assertFalse(self.constraint.validate("POLICY_ID", self.ctx))
class ProfileTypeConstraintTest(common.HeatTestCase):
@mock.patch('openstack.connection.Connection')
def setUp(self, mock_connection):
super(ProfileTypeConstraintTest, self).setUp()
self.senlin_client = mock.MagicMock()
self.ctx = utils.dummy_context()
heat_profile_type = mock.MagicMock()
heat_profile_type.name = 'os.heat.stack-1.0'
nova_profile_type = mock.MagicMock()
nova_profile_type.name = 'os.nova.server-1.0'
self.mock_profile_types = mock.Mock(
return_value=[heat_profile_type, nova_profile_type])
self.ctx.clients.client(
'senlin').profile_types = self.mock_profile_types
self.constraint = senlin_plugin.ProfileTypeConstraint()
def test_validate_true(self):
self.assertTrue(self.constraint.validate("os.heat.stack-1.0",
self.ctx))
def test_validate_false(self):
self.assertFalse(self.constraint.validate("Invalid_type",
self.ctx))
class PolicyTypeConstraintTest(common.HeatTestCase):
@mock.patch('openstack.connection.Connection')
def setUp(self, mock_connection):
super(PolicyTypeConstraintTest, self).setUp()
self.senlin_client = mock.MagicMock()
self.ctx = utils.dummy_context()
deletion_policy_type = mock.MagicMock()
deletion_policy_type.name = 'senlin.policy.deletion-1.0'
lb_policy_type = mock.MagicMock()
lb_policy_type.name = 'senlin.policy.loadbalance-1.0'
self.mock_policy_types = mock.Mock(
return_value=[deletion_policy_type, lb_policy_type])
self.ctx.clients.client(
'senlin').policy_types = self.mock_policy_types
self.constraint = senlin_plugin.PolicyTypeConstraint()
def test_validate_true(self):
self.assertTrue(self.constraint.validate(
"senlin.policy.deletion-1.0", self.ctx))
def test_validate_false(self):
self.assertFalse(self.constraint.validate("Invalid_type",
self.ctx))

View File

@ -1,390 +0,0 @@
# Copyright 2015 IBM Corp.
#
# 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 unittest import mock
from oslo_config import cfg
from heat.common import exception
from heat.common import template_format
from heat.engine.clients.os import senlin
from heat.engine.resources.openstack.senlin import cluster as sc
from heat.engine import scheduler
from heat.engine import template
from heat.tests import common
from heat.tests import utils
from openstack import exceptions
cluster_stack_template = """
heat_template_version: 2016-04-08
description: Senlin Cluster Template
resources:
senlin-cluster:
type: OS::Senlin::Cluster
properties:
name: SenlinCluster
profile: fake_profile
policies:
- policy: fake_policy
enabled: true
min_size: 0
max_size: -1
desired_capacity: 1
timeout: 3600
metadata:
foo: bar
"""
class FakeCluster(object):
def __init__(self, id='some_id', status='ACTIVE'):
self.status = status
self.status_reason = 'Unknown'
self.id = id
self.name = "SenlinCluster"
self.metadata = {}
self.nodes = ['node1']
self.desired_capacity = 1
self.metadata = {'foo': 'bar'}
self.timeout = 3600
self.max_size = -1
self.min_size = 0
self.location = 'actions/fake-action'
self.profile_name = 'fake_profile'
self.profile_id = 'fake_profile_id'
def to_dict(self):
return {
'id': self.id,
'status': self.status,
'status_reason': self.status_reason,
'name': self.name,
'metadata': self.metadata,
'timeout': self.timeout,
'desired_capacity': self.desired_capacity,
'max_size': self.max_size,
'min_size': self.min_size,
'nodes': self.nodes,
'profile_name': self.profile_name,
'profile_id': self.profile_id
}
class SenlinClusterTest(common.HeatTestCase):
def setUp(self):
super(SenlinClusterTest, self).setUp()
self.senlin_mock = mock.MagicMock()
self.senlin_mock.get_profile.return_value = mock.Mock(
id='fake_profile_id'
)
self.patchobject(sc.Cluster, 'client', return_value=self.senlin_mock)
self.patchobject(senlin.SenlinClientPlugin, 'client',
return_value=self.senlin_mock)
self.patchobject(senlin.ProfileConstraint, 'validate',
return_value=True)
self.patchobject(senlin.PolicyConstraint, 'validate',
return_value=True)
self.fake_cl = FakeCluster()
self.t = template_format.parse(cluster_stack_template)
def _init_cluster(self, template):
self.stack = utils.parse_stack(template)
cluster = self.stack['senlin-cluster']
return cluster
def _create_cluster(self, template):
cluster = self._init_cluster(template)
self.senlin_mock.create_cluster.return_value = self.fake_cl
self.senlin_mock.get_cluster.return_value = self.fake_cl
self.senlin_mock.get_action.return_value = mock.Mock(
status='SUCCEEDED')
self.senlin_mock.get_policy.return_value = mock.Mock(
id='fake_policy_id'
)
self.senlin_mock.cluster_policies.return_value = [
{'policy_id': 'fake_policy_id', 'enabled': True}
]
scheduler.TaskRunner(cluster.create)()
self.assertEqual((cluster.CREATE, cluster.COMPLETE),
cluster.state)
self.assertEqual(self.fake_cl.id, cluster.resource_id)
self.assertEqual(1, self.senlin_mock.get_action.call_count)
self.assertEqual(1, self.senlin_mock.get_cluster.call_count)
return cluster
def test_cluster_create_success(self):
self._create_cluster(self.t)
create_cluster_kwargs = {
'name': 'SenlinCluster',
'profile_id': 'fake_profile_id',
'desired_capacity': 1,
'min_size': 0,
'max_size': -1,
'metadata': {'foo': 'bar'},
'timeout': 3600,
}
attach_policy_kwargs = {
'cluster': self.fake_cl.id,
'policy': 'fake_policy_id',
'enabled': True
}
self.senlin_mock.create_cluster.assert_called_once_with(
**create_cluster_kwargs)
self.senlin_mock.attach_policy_to_cluster.assert_called_once_with(
**attach_policy_kwargs)
def test_cluster_create_error(self):
cfg.CONF.set_override('action_retry_limit', 0)
cluster = self._init_cluster(self.t)
self.senlin_mock.create_cluster.return_value = self.fake_cl
mock_cluster = mock.MagicMock()
mock_cluster.status = 'ERROR'
mock_cluster.status_reason = 'oops'
self.senlin_mock.get_policy.return_value = mock.Mock(
id='fake_policy_id'
)
self.senlin_mock.get_cluster.return_value = mock_cluster
create_task = scheduler.TaskRunner(cluster.create)
ex = self.assertRaises(exception.ResourceFailure, create_task)
expected = ('ResourceInError: resources.senlin-cluster: '
'Went to status ERROR due to "oops"')
self.assertEqual(expected, str(ex))
def test_cluster_delete_success(self):
cluster = self._create_cluster(self.t)
self.senlin_mock.get_cluster.side_effect = [
exceptions.ResourceNotFound('SenlinCluster'),
]
scheduler.TaskRunner(cluster.delete)()
self.senlin_mock.delete_cluster.assert_called_once_with(
cluster.resource_id)
def test_cluster_delete_error(self):
cluster = self._create_cluster(self.t)
self.senlin_mock.get_cluster.side_effect = exception.Error('oops')
delete_task = scheduler.TaskRunner(cluster.delete)
ex = self.assertRaises(exception.ResourceFailure, delete_task)
expected = 'Error: resources.senlin-cluster: oops'
self.assertEqual(expected, str(ex))
def test_cluster_update_profile(self):
cluster = self._create_cluster(self.t)
# Mock translate rules
self.senlin_mock.get_profile.side_effect = [
mock.Mock(id='new_profile_id'),
mock.Mock(id='fake_profile_id'),
mock.Mock(id='new_profile_id'),
]
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(
cluster=new_cluster)
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_id',
'name': 'new_name'
}
self.senlin_mock.update_cluster.assert_called_once_with(
cluster=self.fake_cl, **cluster_update_kwargs)
self.assertEqual(1, self.senlin_mock.get_action.call_count)
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.resize_cluster.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.resize_cluster.assert_called_once_with(
cluster=cluster.resource_id, **cluster_resize_kwargs)
self.assertEqual(2, self.senlin_mock.get_action.call_count)
def test_cluster_update_policy_add_remove(self):
cluster = self._create_cluster(self.t)
# Mock translate rules
self.senlin_mock.get_policy.side_effect = [
mock.Mock(id='new_policy_id'),
mock.Mock(id='fake_policy_id'),
mock.Mock(id='new_policy_id'),
]
new_t = copy.deepcopy(self.t)
props = new_t['resources']['senlin-cluster']['properties']
props['policies'] = [{'policy': 'new_policy'}]
rsrc_defns = template.Template(new_t).resource_definitions(self.stack)
new_cluster = rsrc_defns['senlin-cluster']
self.senlin_mock.detach_policy_from_cluster.return_value = {
'action': 'fake-action'}
self.senlin_mock.attach_policy_to_cluster.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)
detach_policy_kwargs = {
'policy': 'fake_policy_id',
'cluster': cluster.resource_id,
'enabled': True,
}
self.assertEqual(2,
self.senlin_mock.attach_policy_to_cluster.call_count)
self.senlin_mock.detach_policy_from_cluster.assert_called_once_with(
**detach_policy_kwargs)
self.assertEqual(0, self.senlin_mock.update_cluster_policy.call_count)
self.assertEqual(3, self.senlin_mock.get_action.call_count)
def test_cluster_update_policy_exists(self):
cluster = self._create_cluster(self.t)
new_t = copy.deepcopy(self.t)
props = new_t['resources']['senlin-cluster']['properties']
props['policies'] = [{'policy': 'fake_policy', 'enabled': False}]
rsrc_defns = template.Template(new_t).resource_definitions(self.stack)
new_cluster = rsrc_defns['senlin-cluster']
self.senlin_mock.update_cluster_policy.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)
update_policy_kwargs = {
'policy': 'fake_policy_id',
'cluster': cluster.resource_id,
'enabled': False,
}
self.senlin_mock.update_cluster_policy.assert_called_once_with(
**update_policy_kwargs)
self.assertEqual(1, self.senlin_mock.
attach_policy_to_cluster.call_count)
self.assertEqual(0, self.senlin_mock.
detach_policy_from_cluster.call_count)
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.resize_cluster.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"',
str(exc))
def test_cluster_get_attr_collect(self):
cluster = self._create_cluster(self.t)
self.senlin_mock.collect_cluster_attrs.return_value = [
mock.Mock(attr_value='ip1')]
attr_path1 = ['details.addresses.private[0].addr']
self.assertEqual(
['ip1'], cluster.get_attribute(cluster.ATTR_COLLECT, *attr_path1))
attr_path2 = ['details.addresses.private[0].addr', 0]
self.assertEqual(
'ip1', cluster.get_attribute(cluster.ATTR_COLLECT, *attr_path2))
self.senlin_mock.collect_cluster_attrs.assert_called_with(
cluster.resource_id, attr_path2[0])
def test_cluster_resolve_attribute(self):
excepted_show = {
'id': 'some_id',
'status': 'ACTIVE',
'status_reason': 'Unknown',
'name': 'SenlinCluster',
'metadata': {'foo': 'bar'},
'timeout': 3600,
'desired_capacity': 1,
'max_size': -1,
'min_size': 0,
'nodes': ['node1'],
'profile_name': 'fake_profile',
'profile_id': 'fake_profile_id',
'policies': [{'policy_id': 'fake_policy_id', 'enabled': True}]
}
cluster = self._create_cluster(self.t)
self.assertEqual(self.fake_cl.desired_capacity,
cluster._resolve_attribute('desired_capacity'))
self.assertEqual(['node1'],
cluster._resolve_attribute('nodes'))
self.assertEqual(excepted_show,
cluster._show_resource())
def test_cluster_get_live_state(self):
expected_reality = {
'name': 'SenlinCluster',
'metadata': {'foo': 'bar'},
'timeout': 3600,
'desired_capacity': 1,
'max_size': -1,
'min_size': 0,
'profile': 'fake_profile_id',
'policies': [{'policy': 'fake_policy_id', 'enabled': True}]
}
cluster = self._create_cluster(self.t)
self.senlin_mock.get_cluster.return_value = self.fake_cl
reality = cluster.get_live_state(cluster.properties)
self.assertEqual(expected_reality, reality)
class TestSenlinClusterValidation(common.HeatTestCase):
def setUp(self):
super(TestSenlinClusterValidation, self).setUp()
self.t = template_format.parse(cluster_stack_template)
def test_invalid_min_max_size(self):
self.t['resources']['senlin-cluster']['properties']['min_size'] = 2
self.t['resources']['senlin-cluster']['properties']['max_size'] = 1
stack = utils.parse_stack(self.t)
ex = self.assertRaises(exception.StackValidationFailed,
stack['senlin-cluster'].validate)
self.assertEqual('min_size can not be greater than max_size',
str(ex))
def test_invalid_desired_capacity(self):
self.t['resources']['senlin-cluster']['properties']['min_size'] = 1
self.t['resources']['senlin-cluster']['properties']['max_size'] = 2
self.t['resources']['senlin-cluster']['properties'][
'desired_capacity'] = 3
stack = utils.parse_stack(self.t)
ex = self.assertRaises(exception.StackValidationFailed,
stack['senlin-cluster'].validate)
self.assertEqual(
'desired_capacity must be between min_size and max_size',
str(ex)
)

View File

@ -1,242 +0,0 @@
#
# 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 unittest import mock
from oslo_config import cfg
from heat.common import exception
from heat.common import template_format
from heat.engine.clients.os import senlin
from heat.engine.resources.openstack.senlin import node as sn
from heat.engine import scheduler
from heat.engine import template
from heat.tests import common
from heat.tests import utils
from openstack import exceptions
node_stack_template = """
heat_template_version: 2016-04-08
description: Senlin Node Template
resources:
senlin-node:
type: OS::Senlin::Node
properties:
name: SenlinNode
profile: fake_profile
cluster: fake_cluster
metadata:
foo: bar
"""
class FakeNode(object):
def __init__(self, id='some_id', status='ACTIVE'):
self.status = status
self.status_reason = 'Unknown'
self.id = id
self.name = "SenlinNode"
self.metadata = {'foo': 'bar'}
self.profile_id = "fake_profile_id"
self.cluster_id = "fake_cluster_id"
self.details = {'id': 'physical_object_id'}
self.location = "actions/fake_action"
def to_dict(self):
return {
'id': self.id,
'status': self.status,
'status_reason': self.status_reason,
'name': self.name,
'metadata': self.metadata,
'profile_id': self.profile_id,
'cluster_id': self.cluster_id,
}
class SenlinNodeTest(common.HeatTestCase):
def setUp(self):
super(SenlinNodeTest, self).setUp()
self.senlin_mock = mock.MagicMock()
self.senlin_mock.get_profile.return_value = mock.Mock(
id='fake_profile_id'
)
self.senlin_mock.get_cluster.return_value = mock.Mock(
id='fake_cluster_id'
)
self.patchobject(sn.Node, 'client', return_value=self.senlin_mock)
self.patchobject(senlin.SenlinClientPlugin, 'client',
return_value=self.senlin_mock)
self.patchobject(senlin.ProfileConstraint, 'validate',
return_value=True)
self.patchobject(senlin.ClusterConstraint, 'validate',
return_value=True)
self.fake_node = FakeNode()
self.t = template_format.parse(node_stack_template)
self.stack = utils.parse_stack(self.t)
self.node = self.stack['senlin-node']
def _create_node(self):
self.senlin_mock.create_node.return_value = self.fake_node
self.senlin_mock.get_node.return_value = self.fake_node
self.senlin_mock.get_action.return_value = mock.Mock(
status='SUCCEEDED')
scheduler.TaskRunner(self.node.create)()
self.assertEqual((self.node.CREATE, self.node.COMPLETE),
self.node.state)
self.assertEqual(self.fake_node.id, self.node.resource_id)
return self.node
def test_node_create_success(self):
self._create_node()
expect_kwargs = {
'name': 'SenlinNode',
'profile_id': 'fake_profile_id',
'metadata': {'foo': 'bar'},
'cluster_id': 'fake_cluster_id',
}
self.senlin_mock.create_node.assert_called_once_with(
**expect_kwargs)
def test_node_create_error(self):
cfg.CONF.set_override('action_retry_limit', 0)
self.senlin_mock.create_node.return_value = self.fake_node
mock_action = mock.MagicMock()
mock_action.status = 'FAILED'
mock_action.status_reason = 'oops'
self.senlin_mock.get_action.return_value = mock_action
create_task = scheduler.TaskRunner(self.node.create)
ex = self.assertRaises(exception.ResourceFailure, create_task)
expected = ('ResourceInError: resources.senlin-node: '
'Went to status FAILED due to "oops"')
self.assertEqual(expected, str(ex))
def test_node_delete_success(self):
node = self._create_node()
self.senlin_mock.get_node.side_effect = [
exceptions.ResourceNotFound('SenlinNode'),
]
scheduler.TaskRunner(node.delete)()
self.senlin_mock.delete_node.assert_called_once_with(
node.resource_id)
def test_cluster_delete_error(self):
node = self._create_node()
self.senlin_mock.get_node.side_effect = exception.Error('oops')
delete_task = scheduler.TaskRunner(node.delete)
ex = self.assertRaises(exception.ResourceFailure, delete_task)
expected = 'Error: resources.senlin-node: oops'
self.assertEqual(expected, str(ex))
def test_node_update_profile(self):
node = self._create_node()
# Mock translate rules
self.senlin_mock.get_profile.side_effect = [
mock.Mock(id='new_profile_id'),
mock.Mock(id='fake_profile_id'),
mock.Mock(id='new_profile_id'),
]
new_t = copy.deepcopy(self.t)
props = new_t['resources']['senlin-node']['properties']
props['profile'] = 'new_profile'
props['name'] = 'new_name'
rsrc_defns = template.Template(new_t).resource_definitions(self.stack)
new_node = rsrc_defns['senlin-node']
self.senlin_mock.update_node.return_value = mock.Mock(
location='/actions/fake-action')
scheduler.TaskRunner(node.update, new_node)()
self.assertEqual((node.UPDATE, node.COMPLETE), node.state)
node_update_kwargs = {
'profile_id': 'new_profile_id',
'name': 'new_name'
}
self.senlin_mock.update_node.assert_called_once_with(
node=self.fake_node, **node_update_kwargs)
self.assertEqual(2, self.senlin_mock.get_action.call_count)
def test_node_update_cluster(self):
node = self._create_node()
# Mock translate rules
self.senlin_mock.get_cluster.side_effect = [
mock.Mock(id='new_cluster_id'),
mock.Mock(id='fake_cluster_id'),
mock.Mock(id='new_cluster_id'),
]
new_t = copy.deepcopy(self.t)
props = new_t['resources']['senlin-node']['properties']
props['cluster'] = 'new_cluster'
rsrc_defns = template.Template(new_t).resource_definitions(self.stack)
new_node = rsrc_defns['senlin-node']
self.senlin_mock.remove_nodes_from_cluster.return_value = {
'action': 'remove_node_from_cluster'
}
self.senlin_mock.add_nodes_to_cluster.return_value = {
'action': 'add_node_to_cluster'
}
scheduler.TaskRunner(node.update, new_node)()
self.assertEqual((node.UPDATE, node.COMPLETE), node.state)
self.senlin_mock.remove_nodes_from_cluster.assert_called_once_with(
cluster='fake_cluster_id', nodes=[node.resource_id])
self.senlin_mock.add_nodes_to_cluster.assert_called_once_with(
cluster='new_cluster_id', nodes=[node.resource_id])
def test_node_update_failed(self):
node = self._create_node()
new_t = copy.deepcopy(self.t)
props = new_t['resources']['senlin-node']['properties']
props['name'] = 'new_name'
rsrc_defns = template.Template(new_t).resource_definitions(self.stack)
new_node = rsrc_defns['senlin-node']
self.senlin_mock.update_node.return_value = mock.Mock(
location='/actions/fake-action')
self.senlin_mock.get_action.return_value = mock.Mock(
status='FAILED', status_reason='oops')
update_task = scheduler.TaskRunner(node.update, new_node)
ex = self.assertRaises(exception.ResourceFailure, update_task)
expected = ('ResourceInError: resources.senlin-node: Went to '
'status FAILED due to "oops"')
self.assertEqual(expected, str(ex))
self.assertEqual((node.UPDATE, node.FAILED), node.state)
self.assertEqual(2, self.senlin_mock.get_action.call_count)
def test_cluster_resolve_attribute(self):
excepted_show = {
'id': 'some_id',
'status': 'ACTIVE',
'status_reason': 'Unknown',
'name': 'SenlinNode',
'metadata': {'foo': 'bar'},
'profile_id': 'fake_profile_id',
'cluster_id': 'fake_cluster_id'
}
node = self._create_node()
self.assertEqual(excepted_show,
node._show_resource())
self.assertEqual(self.fake_node.details,
node._resolve_attribute('details'))
self.senlin_mock.get_node.assert_called_with(
node.resource_id, details=True)
def test_node_get_live_state(self):
expected_reality = {
'name': 'SenlinNode',
'metadata': {'foo': 'bar'},
'profile': 'fake_profile_id',
'cluster': 'fake_cluster_id'
}
node = self._create_node()
reality = node.get_live_state(node.properties)
self.assertEqual(expected_reality, reality)

View File

@ -1,195 +0,0 @@
#
# 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 unittest import mock
from openstack.clustering.v1._proxy import Proxy
from openstack import exceptions
from oslo_config import cfg
from heat.common import exception
from heat.common import template_format
from heat.engine.clients.os import senlin
from heat.engine.resources.openstack.senlin import policy
from heat.engine import scheduler
from heat.engine import template
from heat.tests import common
from heat.tests import utils
policy_stack_template = """
heat_template_version: 2016-04-08
description: Senlin Policy Template
resources:
senlin-policy:
type: OS::Senlin::Policy
properties:
name: SenlinPolicy
type: senlin.policy.deletion-1.0
properties:
criteria: OLDEST_FIRST
bindings:
- cluster: c1
"""
policy_spec = {
'type': 'senlin.policy.deletion',
'version': '1.0',
'properties': {
'criteria': 'OLDEST_FIRST'
}
}
class FakePolicy(object):
def __init__(self, id='some_id', spec=None):
self.id = id
self.name = "SenlinPolicy"
def to_dict(self):
return {
'id': self.id,
'name': self.name,
}
class SenlinPolicyTest(common.HeatTestCase):
def setUp(self):
super(SenlinPolicyTest, self).setUp()
self.patchobject(senlin.ClusterConstraint, 'validate',
return_value=True)
self.patchobject(senlin.PolicyTypeConstraint, 'validate',
return_value=True)
self.senlin_mock = mock.MagicMock(spec=Proxy)
self.senlin_mock.get_cluster.return_value = mock.Mock(
id='c1_id')
self.patchobject(policy.Policy, 'client',
return_value=self.senlin_mock)
self.patchobject(senlin.SenlinClientPlugin, 'client',
return_value=self.senlin_mock)
self.fake_p = FakePolicy()
self.t = template_format.parse(policy_stack_template)
def _init_policy(self, template):
self.stack = utils.parse_stack(template)
policy = self.stack['senlin-policy']
return policy
def _create_policy(self, template):
policy = self._init_policy(template)
self.senlin_mock.create_policy.return_value = self.fake_p
self.senlin_mock.attach_policy_to_cluster.return_value = {
'action': 'fake_action'}
self.senlin_mock.get_action.return_value = mock.Mock(
status='SUCCEEDED')
scheduler.TaskRunner(policy.create)()
self.assertEqual((policy.CREATE, policy.COMPLETE),
policy.state)
self.assertEqual(self.fake_p.id, policy.resource_id)
self.senlin_mock.attach_policy_to_cluster.assert_called_once_with(
'c1_id', policy.resource_id, enabled=True)
self.senlin_mock.get_action.assert_called_once_with('fake_action')
return policy
def test_policy_create(self):
self._create_policy(self.t)
expect_kwargs = {
'name': 'SenlinPolicy',
'spec': policy_spec
}
self.senlin_mock.create_policy.assert_called_once_with(
**expect_kwargs)
def test_policy_create_fail(self):
cfg.CONF.set_override('action_retry_limit', 0)
policy = self._init_policy(self.t)
self.senlin_mock.create_policy.return_value = self.fake_p
self.senlin_mock.attach_policy_to_cluster.return_value = {
'action': 'fake_action'}
self.senlin_mock.get_action.return_value = mock.Mock(
status='FAILED', status_reason='oops',
action='CLUSTER_ATTACH_POLICY')
create_task = scheduler.TaskRunner(policy.create)
self.assertRaises(exception.ResourceFailure, create_task)
self.assertEqual((policy.CREATE, policy.FAILED),
policy.state)
err_msg = ('ResourceInError: resources.senlin-policy: Went to status '
'FAILED due to "Failed to execute CLUSTER_ATTACH_POLICY '
'for c1_id: oops"')
self.assertEqual(err_msg, policy.status_reason)
def test_policy_delete_not_found(self):
self.senlin_mock.detach_policy_from_cluster.return_value = {
'action': 'fake_action'}
policy = self._create_policy(self.t)
self.senlin_mock.get_policy.side_effect = [
exceptions.ResourceNotFound('SenlinPolicy'),
]
scheduler.TaskRunner(policy.delete)()
self.senlin_mock.detach_policy_from_cluster.assert_called_once_with(
'c1_id', policy.resource_id)
self.senlin_mock.delete_policy.assert_called_once_with(
policy.resource_id)
def test_policy_delete_not_attached(self):
policy = self._create_policy(self.t)
self.senlin_mock.get_policy.side_effect = [
exceptions.ResourceNotFound('SenlinPolicy'),
]
self.senlin_mock.detach_policy_from_cluster.side_effect = [
exceptions.HttpException(http_status=400),
]
scheduler.TaskRunner(policy.delete)()
self.senlin_mock.detach_policy_from_cluster.assert_called_once_with(
'c1_id', policy.resource_id)
self.senlin_mock.delete_policy.assert_called_once_with(
policy.resource_id)
def test_policy_update(self):
policy = self._create_policy(self.t)
# Mock translate rules
self.senlin_mock.get_cluster.side_effect = [
mock.Mock(id='c2_id'),
mock.Mock(id='c1_id'),
mock.Mock(id='c2_id'),
]
new_t = copy.deepcopy(self.t)
props = new_t['resources']['senlin-policy']['properties']
props['bindings'] = [{'cluster': 'c2'}]
props['name'] = 'new_name'
rsrc_defns = template.Template(new_t).resource_definitions(self.stack)
new_cluster = rsrc_defns['senlin-policy']
self.senlin_mock.attach_policy_to_cluster.return_value = {
'action': 'fake_action1'}
self.senlin_mock.detach_policy_from_cluster.return_value = {
'action': 'fake_action2'}
self.senlin_mock.get_policy.return_value = self.fake_p
scheduler.TaskRunner(policy.update, new_cluster)()
self.assertEqual((policy.UPDATE, policy.COMPLETE), policy.state)
self.senlin_mock.update_policy.assert_called_once_with(
self.fake_p, name='new_name')
self.senlin_mock.detach_policy_from_cluster.assert_called_once_with(
'c1_id', policy.resource_id)
self.senlin_mock.attach_policy_to_cluster.assert_called_with(
'c2_id', policy.resource_id, enabled=True)
def test_policy_resolve_attribute(self):
excepted_show = {
'id': 'some_id',
'name': 'SenlinPolicy',
}
policy = self._create_policy(self.t)
self.senlin_mock.get_policy.return_value = FakePolicy()
self.assertEqual(excepted_show, policy._show_resource())

View File

@ -1,116 +0,0 @@
# Copyright 2015 IBM Corp.
#
# 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 unittest import mock
from heat.common import template_format
from heat.engine.clients.os import senlin
from heat.engine.resources.openstack.senlin import profile as sp
from heat.engine import scheduler
from heat.tests import common
from heat.tests import utils
profile_stack_template = """
heat_template_version: 2016-04-08
description: Senlin Profile Template
resources:
senlin-profile:
type: OS::Senlin::Profile
properties:
name: SenlinProfile
type: os.heat.stack-1.0
properties:
template:
heat_template_version: 2014-10-16
resources:
random:
type: OS::Heat::RandomString
"""
profile_spec = {
'type': 'os.heat.stack',
'version': '1.0',
'properties': {
'template': {
'heat_template_version': '2014-10-16',
'resources': {
'random': {
'type': 'OS::Heat::RandomString'
}
}
}
}
}
class FakeProfile(object):
def __init__(self, id='some_id', spec=None):
self.id = id
self.name = "SenlinProfile"
self.metadata = {}
self.spec = spec or profile_spec
class SenlinProfileTest(common.HeatTestCase):
def setUp(self):
super(SenlinProfileTest, self).setUp()
self.senlin_mock = mock.MagicMock()
self.patchobject(sp.Profile, 'client', return_value=self.senlin_mock)
self.patchobject(senlin.ProfileTypeConstraint, 'validate',
return_value=True)
self.fake_p = FakeProfile()
self.t = template_format.parse(profile_stack_template)
def _init_profile(self, template):
self.stack = utils.parse_stack(template)
profile = self.stack['senlin-profile']
return profile
def _create_profile(self, template):
profile = self._init_profile(template)
self.senlin_mock.create_profile.return_value = self.fake_p
scheduler.TaskRunner(profile.create)()
self.assertEqual((profile.CREATE, profile.COMPLETE),
profile.state)
self.assertEqual(self.fake_p.id, profile.resource_id)
return profile
def test_profile_create(self):
self._create_profile(self.t)
expect_kwargs = {
'name': 'SenlinProfile',
'metadata': None,
'spec': profile_spec
}
self.senlin_mock.create_profile.assert_called_once_with(
**expect_kwargs)
def test_profile_delete(self):
self.senlin_mock.delete_profile.return_value = None
profile = self._create_profile(self.t)
scheduler.TaskRunner(profile.delete)()
self.senlin_mock.delete_profile.assert_called_once_with(
profile.resource_id)
def test_profile_update(self):
profile = self._create_profile(self.t)
prop_diff = {'metadata': {'foo': 'bar'}}
self.senlin_mock.get_profile.return_value = self.fake_p
profile.handle_update(json_snippet=None,
tmpl_diff=None,
prop_diff=prop_diff)
self.senlin_mock.update_profile.assert_called_once_with(
self.fake_p, **prop_diff)

View File

@ -1,129 +0,0 @@
#
# 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 unittest import mock
from openstack import exceptions
from heat.common import template_format
from heat.engine.clients.os import senlin
from heat.engine.resources.openstack.senlin import receiver as sr
from heat.engine import scheduler
from heat.tests import common
from heat.tests import utils
receiver_stack_template = """
heat_template_version: 2016-04-08
description: Senlin Receiver Template
resources:
senlin-receiver:
type: OS::Senlin::Receiver
properties:
name: SenlinReceiver
cluster: fake_cluster
action: CLUSTER_SCALE_OUT
type: webhook
params:
foo: bar
"""
class FakeReceiver(object):
def __init__(self, id='some_id'):
self.id = id
self.name = "SenlinReceiver"
self.cluster_id = "fake_cluster"
self.action = "CLUSTER_SCALE_OUT"
self.channel = {'alarm_url': "http://foo.bar/webhooks/fake_url"}
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'cluster_id': self.cluster_id,
'action': self.action,
'channel': self.channel,
'actor': {'trust_id': ['fake_trust_id']}
}
class SenlinReceiverTest(common.HeatTestCase):
def setUp(self):
super(SenlinReceiverTest, self).setUp()
self.senlin_mock = mock.MagicMock()
self.patchobject(sr.Receiver, 'client',
return_value=self.senlin_mock)
self.patchobject(senlin.ClusterConstraint, 'validate',
return_value=True)
self.fake_r = FakeReceiver()
self.t = template_format.parse(receiver_stack_template)
def _init_recv(self, template):
self.stack = utils.parse_stack(template)
recv = self.stack['senlin-receiver']
return recv
def _create_recv(self, template):
recv = self._init_recv(template)
self.senlin_mock.create_receiver.return_value = self.fake_r
self.senlin_mock.get_receiver.return_value = self.fake_r
scheduler.TaskRunner(recv.create)()
self.assertEqual((recv.CREATE, recv.COMPLETE),
recv.state)
self.assertEqual(self.fake_r.id, recv.resource_id)
return recv
def test_recv_create_success(self):
self._create_recv(self.t)
expect_kwargs = {
'name': 'SenlinReceiver',
'cluster_id': 'fake_cluster',
'action': 'CLUSTER_SCALE_OUT',
'type': 'webhook',
'params': {'foo': 'bar'},
}
self.senlin_mock.create_receiver.assert_called_once_with(
**expect_kwargs)
def test_recv_delete_success(self):
self.senlin_mock.delete_receiver.return_value = None
recv = self._create_recv(self.t)
scheduler.TaskRunner(recv.delete)()
self.senlin_mock.delete_receiver.assert_called_once_with(
recv.resource_id)
def test_recv_delete_not_found(self):
self.senlin_mock.delete_receiver.side_effect = [
exceptions.ResourceNotFound(http_status=404)
]
recv = self._create_recv(self.t)
scheduler.TaskRunner(recv.delete)()
self.senlin_mock.delete_receiver.assert_called_once_with(
recv.resource_id)
def test_cluster_resolve_attribute(self):
excepted_show = {
'id': 'some_id',
'name': 'SenlinReceiver',
'cluster_id': 'fake_cluster',
'action': 'CLUSTER_SCALE_OUT',
'channel': {'alarm_url': "http://foo.bar/webhooks/fake_url"},
'actor': {'trust_id': ['fake_trust_id']}
}
recv = self._create_recv(self.t)
self.assertEqual(self.fake_r.channel,
recv._resolve_attribute('channel'))
self.assertEqual(excepted_show,
recv._show_resource())

View File

@ -0,0 +1,15 @@
---
upgrade:
- |
Integration with senlin has been removed because the senlin project has
been retired. Because of the removal, the following resource types are no
longer supported and now hidden.
- ``OS::Senlin::Cluster``
- ``OS::Senlin::Node``
- ``OS::Senlin::Policy``
- ``OS::Senlin::Profile``
- ``OS::Senlin::Receiver``
Also, the options in ``[clients_senlin]`` section have been removed.

View File

@ -90,7 +90,6 @@ heat.clients =
octavia = heat.engine.clients.os.octavia:OctaviaClientPlugin
openstack = heat.engine.clients.os.openstacksdk:OpenStackSDKPlugin
sahara = heat.engine.clients.os.sahara:SaharaClientPlugin
senlin = heat.engine.clients.os.senlin:SenlinClientPlugin
swift = heat.engine.clients.os.swift:SwiftClientPlugin
trove = heat.engine.clients.os.trove:TroveClientPlugin
vitrage = heat.engine.clients.os.vitrage:VitrageClientPlugin
@ -171,11 +170,6 @@ heat.constraints =
sahara.job_binary = heat.engine.clients.os.sahara:JobBinaryConstraint
sahara.job_type = heat.engine.clients.os.sahara:JobTypeConstraint
sahara.plugin = heat.engine.clients.os.sahara:PluginConstraint
senlin.cluster = heat.engine.clients.os.senlin:ClusterConstraint
senlin.policy = heat.engine.clients.os.senlin:PolicyConstraint
senlin.policy_type = heat.engine.clients.os.senlin:PolicyTypeConstraint
senlin.profile = heat.engine.clients.os.senlin:ProfileConstraint
senlin.profile_type = heat.engine.clients.os.senlin:ProfileTypeConstraint
trove.flavor = heat.engine.clients.os.trove:FlavorConstraint
zaqar.queue = heat.engine.clients.os.zaqar:QueueConstraint
#ironic