GCE: Add support for 'number' parameter for manually provisioned Google Compute clusters (#4276)
* Add option for number parameter to generate manually provisioned clusters from a base name * Refactor code to work with starting and stopped when number is specified * Update docs * Fix documentation error breaking Travis * Fixes for async gce operations * Fix documentation * base_name from parameter to alias for name and fixes for renaming variables * Fix breaking change on gce.py * Fix bugs with name parameter * Fix comments for Github build checks * Add logic to set changed appropriately for cluster provisioning
This commit is contained in:
parent
ff6bac126e
commit
352b620665
1 changed files with 110 additions and 66 deletions
|
@ -95,10 +95,17 @@ options:
|
||||||
aliases: []
|
aliases: []
|
||||||
name:
|
name:
|
||||||
description:
|
description:
|
||||||
- identifier when working with a single instance. Will be deprecated in a future release.
|
- either a name of a single instance or when used with 'num_instances',
|
||||||
Please 'instance_names' instead.
|
the base name of a cluster of nodes
|
||||||
required: false
|
required: false
|
||||||
aliases: []
|
aliases: ['base_name']
|
||||||
|
num_instances:
|
||||||
|
description:
|
||||||
|
- can be used with 'name', specifies
|
||||||
|
the number of nodes to provision using 'name'
|
||||||
|
as a base name
|
||||||
|
required: false
|
||||||
|
version_added: "2.3"
|
||||||
network:
|
network:
|
||||||
description:
|
description:
|
||||||
- name of the network, 'default' will be used if not specified
|
- name of the network, 'default' will be used if not specified
|
||||||
|
@ -349,7 +356,7 @@ def get_instance_info(inst):
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
def create_instances(module, gce, instance_names):
|
def create_instances(module, gce, instance_names, number):
|
||||||
"""Creates new instances. Attributes other than instance_names are picked
|
"""Creates new instances. Attributes other than instance_names are picked
|
||||||
up from 'module'
|
up from 'module'
|
||||||
|
|
||||||
|
@ -459,40 +466,62 @@ def create_instances(module, gce, instance_names):
|
||||||
module.fail_json(msg='Missing required create instance variable',
|
module.fail_json(msg='Missing required create instance variable',
|
||||||
changed=False)
|
changed=False)
|
||||||
|
|
||||||
for name in instance_names:
|
gce_args = dict(
|
||||||
pd = None
|
location=lc_zone,
|
||||||
if lc_disks:
|
ex_network=network, ex_tags=tags, ex_metadata=metadata,
|
||||||
pd = lc_disks[0]
|
ex_can_ip_forward=ip_forward,
|
||||||
elif persistent_boot_disk:
|
external_ip=instance_external_ip, ex_disk_auto_delete=disk_auto_delete,
|
||||||
|
ex_service_accounts=ex_sa_perms
|
||||||
|
)
|
||||||
|
if preemptible is not None:
|
||||||
|
gce_args['ex_preemptible'] = preemptible
|
||||||
|
if subnetwork is not None:
|
||||||
|
gce_args['ex_subnetwork'] = subnetwork
|
||||||
|
|
||||||
|
if isinstance(instance_names, str) and not number:
|
||||||
|
instance_names = [instance_names]
|
||||||
|
|
||||||
|
if isinstance(instance_names, str) and number:
|
||||||
|
instance_responses = gce.ex_create_multiple_nodes(instance_names, lc_machine_type,
|
||||||
|
lc_image(), number, **gce_args)
|
||||||
|
for resp in instance_responses:
|
||||||
|
n = resp
|
||||||
|
if isinstance(resp, libcloud.compute.drivers.gce.GCEFailedNode):
|
||||||
|
try:
|
||||||
|
n = gce.ex_get_node(n.name, lc_zone)
|
||||||
|
except ResourceNotFoundError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Assure that at least one node has been created to set changed=True
|
||||||
|
changed = True
|
||||||
|
new_instances.append(n)
|
||||||
|
else:
|
||||||
|
for instance in instance_names:
|
||||||
|
pd = None
|
||||||
|
if lc_disks:
|
||||||
|
pd = lc_disks[0]
|
||||||
|
elif persistent_boot_disk:
|
||||||
|
try:
|
||||||
|
pd = gce.ex_get_volume("%s" % instance, lc_zone)
|
||||||
|
except ResourceNotFoundError:
|
||||||
|
pd = gce.create_volume(None, "%s" % instance, image=lc_image())
|
||||||
|
gce_args['ex_boot_disk'] = pd
|
||||||
|
|
||||||
|
inst = None
|
||||||
try:
|
try:
|
||||||
pd = gce.ex_get_volume("%s" % name, lc_zone)
|
inst = gce.ex_get_node(instance, lc_zone)
|
||||||
except ResourceNotFoundError:
|
except ResourceNotFoundError:
|
||||||
pd = gce.create_volume(None, "%s" % name, image=lc_image())
|
inst = gce.create_node(
|
||||||
|
instance, lc_machine_type, lc_image(), **gce_args
|
||||||
gce_args = dict(
|
)
|
||||||
location=lc_zone,
|
changed = True
|
||||||
ex_network=network, ex_tags=tags, ex_metadata=metadata,
|
except GoogleBaseError as e:
|
||||||
ex_boot_disk=pd, ex_can_ip_forward=ip_forward,
|
module.fail_json(msg='Unexpected error attempting to create ' +
|
||||||
external_ip=instance_external_ip, ex_disk_auto_delete=disk_auto_delete,
|
'instance %s, error: %s' % (instance, e.value))
|
||||||
ex_service_accounts=ex_sa_perms
|
if inst:
|
||||||
)
|
new_instances.append(inst)
|
||||||
if preemptible is not None:
|
|
||||||
gce_args['ex_preemptible'] = preemptible
|
|
||||||
if subnetwork is not None:
|
|
||||||
gce_args['ex_subnetwork'] = subnetwork
|
|
||||||
|
|
||||||
inst = None
|
|
||||||
try:
|
|
||||||
inst = gce.ex_get_node(name, lc_zone)
|
|
||||||
except ResourceNotFoundError:
|
|
||||||
inst = gce.create_node(
|
|
||||||
name, lc_machine_type, lc_image(), **gce_args
|
|
||||||
)
|
|
||||||
changed = True
|
|
||||||
except GoogleBaseError as e:
|
|
||||||
module.fail_json(msg='Unexpected error attempting to create ' +
|
|
||||||
'instance %s, error: %s' % (name, e.value))
|
|
||||||
|
|
||||||
|
for inst in new_instances:
|
||||||
for i, lc_disk in enumerate(lc_disks):
|
for i, lc_disk in enumerate(lc_disks):
|
||||||
# Check whether the disk is already attached
|
# Check whether the disk is already attached
|
||||||
if (len(inst.extra['disks']) > i):
|
if (len(inst.extra['disks']) > i):
|
||||||
|
@ -515,9 +544,6 @@ def create_instances(module, gce, instance_names):
|
||||||
inst.extra['disks'].append(
|
inst.extra['disks'].append(
|
||||||
{'source': lc_disk.extra['selfLink'], 'index': i})
|
{'source': lc_disk.extra['selfLink'], 'index': i})
|
||||||
|
|
||||||
if inst:
|
|
||||||
new_instances.append(inst)
|
|
||||||
|
|
||||||
instance_names = []
|
instance_names = []
|
||||||
instance_json_data = []
|
instance_json_data = []
|
||||||
for inst in new_instances:
|
for inst in new_instances:
|
||||||
|
@ -527,7 +553,7 @@ def create_instances(module, gce, instance_names):
|
||||||
|
|
||||||
return (changed, instance_json_data, instance_names)
|
return (changed, instance_json_data, instance_names)
|
||||||
|
|
||||||
def change_instance_state(module, gce, instance_names, zone_name, state):
|
def change_instance_state(module, gce, instance_names, number, zone_name, state):
|
||||||
"""Changes the state of a list of instances. For example,
|
"""Changes the state of a list of instances. For example,
|
||||||
change from started to stopped, or started to absent.
|
change from started to stopped, or started to absent.
|
||||||
|
|
||||||
|
@ -541,31 +567,46 @@ def change_instance_state(module, gce, instance_names, zone_name, state):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
changed = False
|
changed = False
|
||||||
changed_instance_names = []
|
nodes = []
|
||||||
for name in instance_names:
|
state_instance_names = []
|
||||||
|
|
||||||
|
if isinstance(instance_names, str) and number:
|
||||||
|
node_names = ['%s-%03d' % (instance_names, i) for i in range(number)]
|
||||||
|
elif isinstance(instance_names, str) and not number:
|
||||||
|
node_names = [instance_names]
|
||||||
|
else:
|
||||||
|
node_names = instance_names
|
||||||
|
|
||||||
|
for name in node_names:
|
||||||
inst = None
|
inst = None
|
||||||
try:
|
try:
|
||||||
inst = gce.ex_get_node(name, zone_name)
|
inst = gce.ex_get_node(name, zone_name)
|
||||||
except ResourceNotFoundError:
|
except ResourceNotFoundError:
|
||||||
pass
|
state_instance_names.append(name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
if inst and state in ['absent', 'deleted']:
|
else:
|
||||||
gce.destroy_node(inst)
|
nodes.append(inst)
|
||||||
changed_instance_names.append(inst.name)
|
state_instance_names.append(name)
|
||||||
changed = True
|
|
||||||
elif inst and state == 'started' and \
|
|
||||||
inst.state == libcloud.compute.types.NodeState.STOPPED:
|
|
||||||
gce.ex_start_node(inst)
|
|
||||||
changed_instance_names.append(inst.name)
|
|
||||||
changed = True
|
|
||||||
elif inst and state in ['stopped', 'terminated'] and \
|
|
||||||
inst.state == libcloud.compute.types.NodeState.RUNNING:
|
|
||||||
gce.ex_stop_node(inst)
|
|
||||||
changed_instance_names.append(inst.name)
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
return (changed, changed_instance_names)
|
if state in ['absent', 'deleted'] and number:
|
||||||
|
changed_nodes = gce.ex_destroy_multiple_nodes(nodes) or [False]
|
||||||
|
changed = reduce(lambda x, y: x or y, changed_nodes)
|
||||||
|
else:
|
||||||
|
for node in nodes:
|
||||||
|
if state in ['absent', 'deleted']:
|
||||||
|
gce.destroy_node(node)
|
||||||
|
changed = True
|
||||||
|
elif state == 'started' and \
|
||||||
|
node.state == libcloud.compute.types.NodeState.STOPPED:
|
||||||
|
gce.ex_start_node(node)
|
||||||
|
changed = True
|
||||||
|
elif state in ['stopped', 'terminated'] and \
|
||||||
|
node.state == libcloud.compute.types.NodeState.RUNNING:
|
||||||
|
gce.ex_stop_node(node)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
return (changed, state_instance_names)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
|
@ -574,7 +615,8 @@ def main():
|
||||||
instance_names = dict(),
|
instance_names = dict(),
|
||||||
machine_type = dict(default='n1-standard-1'),
|
machine_type = dict(default='n1-standard-1'),
|
||||||
metadata = dict(),
|
metadata = dict(),
|
||||||
name = dict(),
|
name = dict(aliases=['base_name']),
|
||||||
|
num_instances = dict(type='int'),
|
||||||
network = dict(default='default'),
|
network = dict(default='default'),
|
||||||
subnetwork = dict(),
|
subnetwork = dict(),
|
||||||
persistent_boot_disk = dict(type='bool', default=False),
|
persistent_boot_disk = dict(type='bool', default=False),
|
||||||
|
@ -593,7 +635,8 @@ def main():
|
||||||
external_ip=dict(default='ephemeral'),
|
external_ip=dict(default='ephemeral'),
|
||||||
disk_auto_delete = dict(type='bool', default=True),
|
disk_auto_delete = dict(type='bool', default=True),
|
||||||
preemptible = dict(type='bool', default=None),
|
preemptible = dict(type='bool', default=None),
|
||||||
)
|
),
|
||||||
|
mutually_exclusive=[('instance_names', 'name')]
|
||||||
)
|
)
|
||||||
|
|
||||||
if not HAS_PYTHON26:
|
if not HAS_PYTHON26:
|
||||||
|
@ -608,6 +651,7 @@ def main():
|
||||||
machine_type = module.params.get('machine_type')
|
machine_type = module.params.get('machine_type')
|
||||||
metadata = module.params.get('metadata')
|
metadata = module.params.get('metadata')
|
||||||
name = module.params.get('name')
|
name = module.params.get('name')
|
||||||
|
number = module.params.get('num_instances')
|
||||||
network = module.params.get('network')
|
network = module.params.get('network')
|
||||||
subnetwork = module.params.get('subnetwork')
|
subnetwork = module.params.get('subnetwork')
|
||||||
persistent_boot_disk = module.params.get('persistent_boot_disk')
|
persistent_boot_disk = module.params.get('persistent_boot_disk')
|
||||||
|
@ -618,13 +662,13 @@ def main():
|
||||||
preemptible = module.params.get('preemptible')
|
preemptible = module.params.get('preemptible')
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
inames = []
|
inames = None
|
||||||
if isinstance(instance_names, list):
|
if isinstance(instance_names, list):
|
||||||
inames = instance_names
|
inames = instance_names
|
||||||
elif isinstance(instance_names, str):
|
elif isinstance(instance_names, str):
|
||||||
inames = instance_names.split(',')
|
inames = instance_names.split(',')
|
||||||
if name:
|
if name:
|
||||||
inames.append(name)
|
inames = name
|
||||||
if not inames:
|
if not inames:
|
||||||
module.fail_json(msg='Must specify a "name" or "instance_names"',
|
module.fail_json(msg='Must specify a "name" or "instance_names"',
|
||||||
changed=False)
|
changed=False)
|
||||||
|
@ -642,20 +686,20 @@ def main():
|
||||||
json_output = {'zone': zone}
|
json_output = {'zone': zone}
|
||||||
if state in ['absent', 'deleted', 'started', 'stopped', 'terminated']:
|
if state in ['absent', 'deleted', 'started', 'stopped', 'terminated']:
|
||||||
json_output['state'] = state
|
json_output['state'] = state
|
||||||
(changed, changed_instance_names) = change_instance_state(
|
(changed, state_instance_names) = change_instance_state(
|
||||||
module, gce, inames, zone, state)
|
module, gce, inames, number, zone, state)
|
||||||
|
|
||||||
# based on what user specified, return the same variable, although
|
# based on what user specified, return the same variable, although
|
||||||
# value could be different if an instance could not be destroyed
|
# value could be different if an instance could not be destroyed
|
||||||
if instance_names:
|
if instance_names or name and number:
|
||||||
json_output['instance_names'] = changed_instance_names
|
json_output['instance_names'] = state_instance_names
|
||||||
elif name:
|
elif name:
|
||||||
json_output['name'] = name
|
json_output['name'] = name
|
||||||
|
|
||||||
elif state in ['active', 'present']:
|
elif state in ['active', 'present']:
|
||||||
json_output['state'] = 'present'
|
json_output['state'] = 'present'
|
||||||
(changed, instance_data, instance_name_list) = create_instances(
|
(changed, instance_data, instance_name_list) = create_instances(
|
||||||
module, gce, inames)
|
module, gce, inames, number)
|
||||||
json_output['instance_data'] = instance_data
|
json_output['instance_data'] = instance_data
|
||||||
if instance_names:
|
if instance_names:
|
||||||
json_output['instance_names'] = instance_name_list
|
json_output['instance_names'] = instance_name_list
|
||||||
|
|
Loading…
Reference in a new issue