Don't count DELETED servers when adding new servers

reviewable/pr18780/r1
Matt Martz 10 years ago
parent 1c2995d71f
commit 5dcc0ff0d9

@ -64,7 +64,10 @@ options:
exact_count: exact_count:
description: description:
- Explicitly ensure an exact count of instances, used with - Explicitly ensure an exact count of instances, used with
state=active/present state=active/present. If specified as C(yes) and I(count) is less than
the servers matched, servers will be deleted to match the count. If
the number of matched servers is fewer than specified in I(count)
additional servers will be added.
default: no default: no
choices: choices:
- "yes" - "yes"
@ -150,6 +153,12 @@ options:
- how long before wait gives up, in seconds - how long before wait gives up, in seconds
default: 300 default: 300
author: Jesse Keating, Matt Martz author: Jesse Keating, Matt Martz
notes:
- I(exact_count) can be "destructive" if the number of running servers in
the I(group) is larger than that specified in I(count). In such a case, the
I(state) is effectively set to C(absent) and the extra servers are deleted.
In the case of deletion, the returned data structure will have C(action)
set to C(delete), and the oldest servers in the group will be deleted.
extends_documentation_fragment: rackspace.openstack extends_documentation_fragment: rackspace.openstack
''' '''
@ -441,79 +450,102 @@ def cloudservers(module, state=None, name=None, flavor=None, image=None,
if group is None: if group is None:
module.fail_json(msg='"group" must be provided when using ' module.fail_json(msg='"group" must be provided when using '
'"exact_count"') '"exact_count"')
else:
if auto_increment:
numbers = set()
try: if auto_increment:
name % 0 numbers = set()
except TypeError, e:
if e.message.startswith('not all'):
name = '%s%%d' % name
else:
module.fail_json(msg=e.message)
pattern = re.sub(r'%\d*[sd]', r'(\d+)', name) # See if the name is a printf like string, if not append
for server in cs.servers.list(): # %d to the end
if server.metadata.get('group') == group: try:
servers.append(server) name % 0
match = re.search(pattern, server.name) except TypeError, e:
if match: if e.message.startswith('not all'):
number = int(match.group(1)) name = '%s%%d' % name
numbers.add(number)
number_range = xrange(count_offset, count_offset + count)
available_numbers = list(set(number_range)
.difference(numbers))
else:
for server in cs.servers.list():
if server.metadata.get('group') == group:
servers.append(server)
# If state was absent but the count was changed,
# assume we only wanted to remove that number of instances
if was_absent:
diff = len(servers) - count
if diff < 0:
count = 0
else: else:
count = diff module.fail_json(msg=e.message)
if len(servers) > count: # regex pattern to match printf formatting
state = 'absent' pattern = re.sub(r'%\d*[sd]', r'(\d+)', name)
kept = servers[:count] for server in cs.servers.list():
del servers[:count] # Ignore DELETED servers
instance_ids = [] if server.status == 'DELETED':
for server in servers: continue
instance_ids.append(server.id) if server.metadata.get('group') == group:
delete(module, instance_ids=instance_ids, wait=wait, servers.append(server)
wait_timeout=wait_timeout, kept=kept) match = re.search(pattern, server.name)
elif len(servers) < count: if match:
if auto_increment: number = int(match.group(1))
names = [] numbers.add(number)
name_slice = count - len(servers)
numbers_to_use = available_numbers[:name_slice] number_range = xrange(count_offset, count_offset + count)
for number in numbers_to_use: available_numbers = list(set(number_range)
names.append(name % number) .difference(numbers))
else: else: # Not auto incrementing
names = [name] * (count - len(servers)) for server in cs.servers.list():
# Ignore DELETED servers
if server.status == 'DELETED':
continue
if server.metadata.get('group') == group:
servers.append(server)
# available_numbers not needed here, we inspect auto_increment
# again later
# If state was absent but the count was changed,
# assume we only wanted to remove that number of instances
if was_absent:
diff = len(servers) - count
if diff < 0:
count = 0
else: else:
instances = [] count = diff
instance_ids = []
for server in servers: if len(servers) > count:
instances.append(rax_to_dict(server, 'server')) # We have more servers than we need, set state='absent'
instance_ids.append(server.id) # and delete the extras, this should delete the oldest
module.exit_json(changed=False, action=None, state = 'absent'
instances=instances, kept = servers[:count]
success=[], error=[], timeout=[], del servers[:count]
instance_ids={'instances': instance_ids, instance_ids = []
'success': [], 'error': [], for server in servers:
'timeout': []}) instance_ids.append(server.id)
else: delete(module, instance_ids=instance_ids, wait=wait,
wait_timeout=wait_timeout, kept=kept)
elif len(servers) < count:
# we have fewer servers than we need
if auto_increment:
# auto incrementing server numbers
names = []
name_slice = count - len(servers)
numbers_to_use = available_numbers[:name_slice]
for number in numbers_to_use:
names.append(name % number)
else:
# We are not auto incrementing server numbers,
# create a list of 'name' that matches how many we need
names = [name] * (count - len(servers))
else:
# we have the right number of servers, just return info
# about all of the matched servers
instances = []
instance_ids = []
for server in servers:
instances.append(rax_to_dict(server, 'server'))
instance_ids.append(server.id)
module.exit_json(changed=False, action=None,
instances=instances,
success=[], error=[], timeout=[],
instance_ids={'instances': instance_ids,
'success': [], 'error': [],
'timeout': []})
else: # not called with exact_count=True
if group is not None: if group is not None:
if auto_increment: if auto_increment:
# we are auto incrementing server numbers, but not with
# exact_count
numbers = set() numbers = set()
# See if the name is a printf like string, if not append
# %d to the end
try: try:
name % 0 name % 0
except TypeError, e: except TypeError, e:
@ -522,8 +554,12 @@ def cloudservers(module, state=None, name=None, flavor=None, image=None,
else: else:
module.fail_json(msg=e.message) module.fail_json(msg=e.message)
# regex pattern to match printf formatting
pattern = re.sub(r'%\d*[sd]', r'(\d+)', name) pattern = re.sub(r'%\d*[sd]', r'(\d+)', name)
for server in cs.servers.list(): for server in cs.servers.list():
# Ignore DELETED servers
if server.status == 'DELETED':
continue
if server.metadata.get('group') == group: if server.metadata.get('group') == group:
servers.append(server) servers.append(server)
match = re.search(pattern, server.name) match = re.search(pattern, server.name)
@ -540,8 +576,11 @@ def cloudservers(module, state=None, name=None, flavor=None, image=None,
for number in numbers_to_use: for number in numbers_to_use:
names.append(name % number) names.append(name % number)
else: else:
# Not auto incrementing
names = [name] * count names = [name] * count
else: else:
# No group was specified, and not using exact_count
# Perform more simplistic matching
search_opts = { search_opts = {
'name': '^%s$' % name, 'name': '^%s$' % name,
'image': image, 'image': image,
@ -549,11 +588,18 @@ def cloudservers(module, state=None, name=None, flavor=None, image=None,
} }
servers = [] servers = []
for server in cs.servers.list(search_opts=search_opts): for server in cs.servers.list(search_opts=search_opts):
# Ignore DELETED servers
if server.status == 'DELETED':
continue
# Ignore servers with non matching metadata
if server.metadata != meta: if server.metadata != meta:
continue continue
servers.append(server) servers.append(server)
if len(servers) >= count: if len(servers) >= count:
# We have more servers than were requested, don't do
# anything. Not running with exact_count=True, so we assume
# more is OK
instances = [] instances = []
for server in servers: for server in servers:
instances.append(rax_to_dict(server, 'server')) instances.append(rax_to_dict(server, 'server'))
@ -566,6 +612,8 @@ def cloudservers(module, state=None, name=None, flavor=None, image=None,
'success': [], 'error': [], 'success': [], 'error': [],
'timeout': []}) 'timeout': []})
# We need more servers to reach out target, create names for
# them, we aren't performing auto_increment here
names = [name] * (count - len(servers)) names = [name] * (count - len(servers))
create(module, names=names, flavor=flavor, image=image, create(module, names=names, flavor=flavor, image=image,
@ -577,6 +625,8 @@ def cloudservers(module, state=None, name=None, flavor=None, image=None,
elif state == 'absent': elif state == 'absent':
if instance_ids is None: if instance_ids is None:
# We weren't given an explicit list of server IDs to delete
# Let's match instead
for arg, value in dict(name=name, flavor=flavor, for arg, value in dict(name=name, flavor=flavor,
image=image).iteritems(): image=image).iteritems():
if not value: if not value:
@ -588,10 +638,15 @@ def cloudservers(module, state=None, name=None, flavor=None, image=None,
'flavor': flavor 'flavor': flavor
} }
for server in cs.servers.list(search_opts=search_opts): for server in cs.servers.list(search_opts=search_opts):
# Ignore DELETED servers
if server.status == 'DELETED':
continue
# Ignore servers with non matching metadata
if meta != server.metadata: if meta != server.metadata:
continue continue
servers.append(server) servers.append(server)
# Build a list of server IDs to delete
instance_ids = [] instance_ids = []
for server in servers: for server in servers:
if len(instance_ids) < count: if len(instance_ids) < count:
@ -600,6 +655,8 @@ def cloudservers(module, state=None, name=None, flavor=None, image=None,
break break
if not instance_ids: if not instance_ids:
# No server IDs were matched for deletion, or no IDs were
# explicitly provided, just exit and don't do anything
module.exit_json(changed=False, action=None, instances=[], module.exit_json(changed=False, action=None, instances=[],
success=[], error=[], timeout=[], success=[], error=[], timeout=[],
instance_ids={'instances': [], instance_ids={'instances': [],

Loading…
Cancel
Save