Skip to content

Commit

Permalink
Extract group functions into a utility module.
Browse files Browse the repository at this point in the history
This patch extracts group checking functions into a dedicated module so
that resource implementations are simplified.  The module could later be
used by the autoscaling engine as well.

Change-Id: I99cdd8c9e8fe377e6923ab047a9c2ef08d1defad
partial-blueprint: reorg-asg-code
partial-blueprint: as-lib
  • Loading branch information
tengqm committed Dec 4, 2014
1 parent 5596e63 commit 11dc22a
Show file tree
Hide file tree
Showing 11 changed files with 242 additions and 100 deletions.
62 changes: 62 additions & 0 deletions heat/common/grouputils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import six


def get_size(group):
"""Get number of member resources managed by the specified group.
The list of members are not sorted or returned.
"""
if group.nested():
resources = [r for r in six.itervalues(group.nested())
if r.status != r.FAILED]
return len(resources)
else:
return 0


def get_members(group):
"""Get a list of member resources managed by the specified group.
Sort the list of instances first by created_time then by name.
"""
resources = []
if group.nested():
resources = [r for r in six.itervalues(group.nested())
if r.status != r.FAILED]

return sorted(resources, key=lambda r: (r.created_time, r.name))


def get_member_refids(group, exclude=None):
"""Get a list of member resources managed by the specified group.
The list of resources is sorted first by created_time then by name.
"""
members = get_members(group)
if len(members) == 0:
return []

if exclude is None:
exclude = []
return [r.FnGetRefId() for r in members
if r.FnGetRefId() not in exclude]


def get_member_names(group):
"""Get a list of resource names of the resources in the specified group.
Failed resources will be ignored.
"""
return [r.name for r in get_members(group)]
7 changes: 4 additions & 3 deletions heat/engine/resources/aws/autoscaling_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import six

from heat.common import exception
from heat.common import grouputils
from heat.common.i18n import _
from heat.common.i18n import _LI
from heat.engine import constraints
Expand Down Expand Up @@ -213,7 +214,7 @@ def check_create_complete(self, task):
done = super(AutoScalingGroup, self).check_create_complete(task)
if done:
self._cooldown_timestamp(
"%s : %s" % (EXACT_CAPACITY, len(self.get_instances())))
"%s : %s" % (EXACT_CAPACITY, grouputils.get_size(self)))
return done

def handle_update(self, json_snippet, tmpl_diff, prop_diff):
Expand Down Expand Up @@ -241,7 +242,7 @@ def handle_update(self, json_snippet, tmpl_diff, prop_diff):
self.adjust(self.properties[self.DESIRED_CAPACITY],
adjustment_type=EXACT_CAPACITY)
else:
current_capacity = len(self.get_instances())
current_capacity = grouputils.get_size(self)
self.adjust(current_capacity, adjustment_type=EXACT_CAPACITY)

def adjust(self, adjustment, adjustment_type=CHANGE_IN_CAPACITY):
Expand All @@ -255,7 +256,7 @@ def adjust(self, adjustment, adjustment_type=CHANGE_IN_CAPACITY):
'cooldown': self.properties[self.COOLDOWN]})
return

capacity = len(self.get_instances())
capacity = grouputils.get_size(self)
lower = self.properties[self.MIN_SIZE]
upper = self.properties[self.MAX_SIZE]

Expand Down
32 changes: 7 additions & 25 deletions heat/engine/resources/instance_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from heat.common import environment_format
from heat.common import exception
from heat.common import grouputils
from heat.common.i18n import _
from heat.common import timeutils as iso8601utils
from heat.engine import attributes
Expand Down Expand Up @@ -165,24 +166,6 @@ def validate_launchconfig(self):
lc=self.LAUNCH_CONFIGURATION_NAME,
ref=conf_refid))

def get_instance_names(self):
"""Get a list of resource names of the instances in this InstanceGroup.
Failed resources will be ignored.
"""
return [r.name for r in self.get_instances()]

def get_instances(self):
"""Get a list of all the instance resources managed by this group.
Sort the list of instances first by created_time then by name.
"""
resources = []
if self.nested():
resources = [resource for resource in self.nested().itervalues()
if resource.status != resource.FAILED]
return sorted(resources, key=lambda r: (r.created_time, r.name))

def _environment(self):
"""Return the environment for the nested stack."""
return {
Expand Down Expand Up @@ -232,8 +215,8 @@ def handle_update(self, json_snippet, tmpl_diff, prop_diff):
# Get the current capacity, we may need to adjust if
# Size has changed
if self.SIZE in prop_diff:
inst_list = self.get_instances()
if len(inst_list) != self.properties[self.SIZE]:
curr_size = grouputils.get_size(self)
if curr_size != self.properties[self.SIZE]:
self.resize(self.properties[self.SIZE])

def _tags(self):
Expand Down Expand Up @@ -270,7 +253,7 @@ def _get_instance_definition(self):
def _get_instance_templates(self):
"""Get templates for resource instances."""
return [(instance.name, instance.t)
for instance in self.get_instances()]
for instance in grouputils.get_members(self)]

def _create_template(self, num_instances, num_replace=0,
template_version=('HeatTemplateFormatVersion',
Expand Down Expand Up @@ -301,7 +284,7 @@ def _replace(self, min_in_service, batch_size, pause_sec):
Replace the instances in the group using updated launch configuration
"""
def changing_instances(tmpl):
instances = self.get_instances()
instances = grouputils.get_members(self)
current = set((i.name, i.t) for i in instances)
updated = set(tmpl.resource_definitions(self.nested()).items())
# includes instances to be updated and deleted
Expand Down Expand Up @@ -374,8 +357,7 @@ def _lb_reload(self, exclude=None):
'''
exclude = exclude or []
if self.properties[self.LOAD_BALANCER_NAMES]:
id_list = [inst.FnGetRefId() for inst in self.get_instances()
if inst.FnGetRefId() not in exclude]
id_list = grouputils.get_member_refids(self, exclude=exclude)
for lb in self.properties[self.LOAD_BALANCER_NAMES]:
lb_resource = self.stack[lb]

Expand Down Expand Up @@ -408,7 +390,7 @@ def _resolve_attribute(self, name):
'''
if name == self.INSTANCE_LIST:
return u','.join(inst.FnGetAtt('PublicIp')
for inst in self.get_instances()) or None
for inst in grouputils.get_members(self)) or None

def child_template(self):
num_instances = int(self.properties[self.SIZE])
Expand Down
7 changes: 4 additions & 3 deletions heat/engine/resources/openstack/autoscaling_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# under the License.

from heat.common import exception
from heat.common import grouputils
from heat.common.i18n import _
from heat.engine import attributes
from heat.engine import constraints
Expand Down Expand Up @@ -148,10 +149,10 @@ def _create_template(self, num_instances, num_replace=0,

def FnGetAtt(self, key, *path):
if key == self.CURRENT_SIZE:
return len(self.get_instances())
return grouputils.get_size(self)
if path:
attrs = ((rsrc.name,
rsrc.FnGetAtt(*path)) for rsrc in self.get_instances())
members = grouputils.get_members(self)
attrs = ((rsrc.name, rsrc.FnGetAtt(*path)) for rsrc in members)
if key == self.OUTPUTS:
return dict(attrs)
if key == self.OUTPUTS_LIST:
Expand Down
7 changes: 4 additions & 3 deletions heat/tests/autoscaling/test_scaling_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import six

from heat.common import exception
from heat.common import grouputils
from heat.common import template_format
from heat.engine import scheduler
from heat.tests.autoscaling import inline_templates
Expand Down Expand Up @@ -178,14 +179,14 @@ def setUp(self):
def test_scaling_policy_cooldown_toosoon(self):
"""If _cooldown_inprogress() returns True don't progress."""

dont_call = self.patchobject(self.group, 'get_instances')
dont_call = self.patchobject(grouputils, 'get_members')
with mock.patch.object(self.group, '_cooldown_inprogress',
return_value=True):
self.group.adjust(1)
self.assertEqual([], dont_call.call_args_list)

def test_scaling_policy_cooldown_ok(self):
self.patchobject(self.group, 'get_instances', return_value=[])
self.patchobject(grouputils, 'get_members', return_value=[])
resize = self.patchobject(self.group, 'resize')
cd_stamp = self.patchobject(self.group, '_cooldown_timestamp')
notify = self.patch('heat.engine.notification.autoscaling.send')
Expand Down Expand Up @@ -213,7 +214,7 @@ def test_scaling_policy_cooldown_ok(self):
cd_stamp.assert_called_once_with('ChangeInCapacity : 1')

def test_scaling_policy_resize_fail(self):
self.patchobject(self.group, 'get_instances', return_value=[])
self.patchobject(grouputils, 'get_members', return_value=[])
self.patchobject(self.group, 'resize',
side_effect=ValueError('test error'))
notify = self.patch('heat.engine.notification.autoscaling.send')
Expand Down
31 changes: 16 additions & 15 deletions heat/tests/test_autoscaling.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import six

from heat.common import exception
from heat.common import grouputils
from heat.common import short_id
from heat.common import template_format
from heat.engine.notification import autoscaling as notification
Expand Down Expand Up @@ -260,7 +261,7 @@ def test_scaling_group_update_replace(self):
rsrc = self.create_scaling_group(t, stack, 'WebServerGroup')
self.assertEqual(utils.PhysName(stack.name, rsrc.name),
rsrc.FnGetRefId())
self.assertEqual(1, len(rsrc.get_instance_names()))
self.assertEqual(1, len(grouputils.get_member_names(rsrc)))
props = copy.copy(rsrc.properties.data)
props['AvailabilityZones'] = ['foo']
update_snippet = rsrc_defn.ResourceDefinition(rsrc.name,
Expand All @@ -284,7 +285,7 @@ def test_scaling_group_suspend(self):
rsrc = self.create_scaling_group(t, stack, 'WebServerGroup')
self.assertEqual(utils.PhysName(stack.name, rsrc.name),
rsrc.FnGetRefId())
self.assertEqual(1, len(rsrc.get_instance_names()))
self.assertEqual(1, len(grouputils.get_member_names(rsrc)))
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)

self.m.VerifyAll()
Expand All @@ -311,7 +312,7 @@ def test_scaling_group_resume(self):
rsrc = self.create_scaling_group(t, stack, 'WebServerGroup')
self.assertEqual(utils.PhysName(stack.name, rsrc.name),
rsrc.FnGetRefId())
self.assertEqual(1, len(rsrc.get_instance_names()))
self.assertEqual(1, len(grouputils.get_member_names(rsrc)))
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)

self.m.VerifyAll()
Expand Down Expand Up @@ -344,7 +345,7 @@ def test_scaling_group_suspend_multiple(self):
rsrc = self.create_scaling_group(t, stack, 'WebServerGroup')
self.assertEqual(utils.PhysName(stack.name, rsrc.name),
rsrc.FnGetRefId())
self.assertEqual(2, len(rsrc.get_instance_names()))
self.assertEqual(2, len(grouputils.get_member_names(rsrc)))
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)

self.m.VerifyAll()
Expand Down Expand Up @@ -374,7 +375,7 @@ def test_scaling_group_resume_multiple(self):
rsrc = self.create_scaling_group(t, stack, 'WebServerGroup')
self.assertEqual(utils.PhysName(stack.name, rsrc.name),
rsrc.FnGetRefId())
self.assertEqual(2, len(rsrc.get_instance_names()))
self.assertEqual(2, len(grouputils.get_member_names(rsrc)))
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)

self.m.VerifyAll()
Expand Down Expand Up @@ -406,7 +407,7 @@ def test_scaling_group_suspend_fail(self):
rsrc = self.create_scaling_group(t, stack, 'WebServerGroup')
self.assertEqual(utils.PhysName(stack.name, rsrc.name),
rsrc.FnGetRefId())
self.assertEqual(1, len(rsrc.get_instance_names()))
self.assertEqual(1, len(grouputils.get_member_names(rsrc)))
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)

self.m.VerifyAll()
Expand Down Expand Up @@ -436,7 +437,7 @@ def test_scaling_group_resume_fail(self):
rsrc = self.create_scaling_group(t, stack, 'WebServerGroup')
self.assertEqual(utils.PhysName(stack.name, rsrc.name),
rsrc.FnGetRefId())
self.assertEqual(1, len(rsrc.get_instance_names()))
self.assertEqual(1, len(grouputils.get_member_names(rsrc)))
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)

self.m.VerifyAll()
Expand Down Expand Up @@ -480,7 +481,7 @@ def test_scaling_group_create_error(self):
scheduler.TaskRunner(rsrc.create))
self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state)

self.assertEqual([], rsrc.get_instance_names())
self.assertEqual([], grouputils.get_members(rsrc))

self.m.VerifyAll()

Expand Down Expand Up @@ -520,7 +521,7 @@ def test_lb_reload_static_resolve(self):
rsrc = self.create_scaling_group(t, stack, 'WebServerGroup')
self.assertEqual(utils.PhysName(stack.name, rsrc.name),
rsrc.FnGetRefId())
self.assertEqual(1, len(rsrc.get_instance_names()))
self.assertEqual(1, len(grouputils.get_member_names(rsrc)))
props = copy.copy(rsrc.properties.data)
props['Cooldown'] = '61'
update_snippet = rsrc_defn.ResourceDefinition(rsrc.name,
Expand Down Expand Up @@ -627,7 +628,7 @@ def test_scaling_up_meta_update(self):
self.m.ReplayAll()
rsrc = self.create_scaling_group(t, stack, 'WebServerGroup')
stack.resources['WebServerGroup'] = rsrc
self.assertEqual(1, len(rsrc.get_instance_names()))
self.assertEqual(1, len(grouputils.get_member_names(rsrc)))

# Scale up one
self._stub_lb_reload(2)
Expand All @@ -641,7 +642,7 @@ def test_scaling_up_meta_update(self):
alarm_url = up_policy.FnGetAtt('AlarmUrl')
self.assertIsNotNone(alarm_url)
up_policy.signal()
self.assertEqual(2, len(rsrc.get_instance_names()))
self.assertEqual(2, len(grouputils.get_member_names(rsrc)))

# Check CustomLB metadata was updated
self.m.StubOutWithMock(instance.Instance, '_ipaddress')
Expand All @@ -668,7 +669,7 @@ def test_scaling_policy_update(self):
self.m.ReplayAll()
rsrc = self.create_scaling_group(t, stack, 'WebServerGroup')
stack.resources['WebServerGroup'] = rsrc
self.assertEqual(1, len(rsrc.get_instance_names()))
self.assertEqual(1, len(grouputils.get_member_names(rsrc)))

# Create initial scaling policy
up_policy = self.create_scaling_policy(t, stack,
Expand All @@ -683,7 +684,7 @@ def test_scaling_policy_update(self):

# Trigger alarm
up_policy.signal()
self.assertEqual(2, len(rsrc.get_instance_names()))
self.assertEqual(2, len(grouputils.get_member_names(rsrc)))

# Update scaling policy
props = copy.copy(up_policy.properties.data)
Expand Down Expand Up @@ -717,7 +718,7 @@ def test_scaling_policy_update(self):

# Trigger alarm
up_policy.signal()
self.assertEqual(4, len(rsrc.get_instance_names()))
self.assertEqual(4, len(grouputils.get_member_names(rsrc)))

rsrc.delete()
self.m.VerifyAll()
Expand All @@ -736,7 +737,7 @@ def test_vpc_zone_identifier(self):
self.m.ReplayAll()

rsrc = self.create_scaling_group(t, stack, 'WebServerGroup')
instances = rsrc.get_instances()
instances = grouputils.get_members(rsrc)
self.assertEqual(1, len(instances))
self.assertEqual('xxxx', instances[0].properties['SubnetId'])

Expand Down
Loading

0 comments on commit 11dc22a

Please sign in to comment.