2015-07-27 20:59:16 +02:00
|
|
|
#!/usr/bin/python
|
|
|
|
|
|
|
|
#
|
|
|
|
# Copyright (c) 2015 CenturyLink
|
|
|
|
#
|
|
|
|
# This file is part of Ansible.
|
|
|
|
#
|
|
|
|
# Ansible is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# Ansible is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>
|
|
|
|
#
|
|
|
|
|
|
|
|
DOCUMENTATION = '''
|
|
|
|
module: clc_server
|
|
|
|
short_description: Create, Delete, Start and Stop servers in CenturyLink Cloud.
|
|
|
|
description:
|
|
|
|
- An Ansible module to Create, Delete, Start and Stop servers in CenturyLink Cloud.
|
|
|
|
version_added: "2.0"
|
|
|
|
options:
|
|
|
|
additional_disks:
|
|
|
|
description:
|
|
|
|
- The list of additional disks for the server
|
|
|
|
required: False
|
|
|
|
default: []
|
|
|
|
add_public_ip:
|
|
|
|
description:
|
|
|
|
- Whether to add a public ip to the server
|
|
|
|
required: False
|
|
|
|
default: False
|
|
|
|
choices: [False, True]
|
|
|
|
alias:
|
|
|
|
description:
|
|
|
|
- The account alias to provision the servers under.
|
|
|
|
required: False
|
|
|
|
default: None
|
|
|
|
anti_affinity_policy_id:
|
|
|
|
description:
|
|
|
|
- The anti-affinity policy to assign to the server. This is mutually exclusive with 'anti_affinity_policy_name'.
|
|
|
|
required: False
|
|
|
|
default: None
|
|
|
|
anti_affinity_policy_name:
|
|
|
|
description:
|
|
|
|
- The anti-affinity policy to assign to the server. This is mutually exclusive with 'anti_affinity_policy_id'.
|
|
|
|
required: False
|
|
|
|
default: None
|
|
|
|
alert_policy_id:
|
|
|
|
description:
|
|
|
|
- The alert policy to assign to the server. This is mutually exclusive with 'alert_policy_name'.
|
|
|
|
required: False
|
|
|
|
default: None
|
|
|
|
alert_policy_name:
|
|
|
|
description:
|
|
|
|
- The alert policy to assign to the server. This is mutually exclusive with 'alert_policy_id'.
|
|
|
|
required: False
|
|
|
|
default: None
|
|
|
|
count:
|
|
|
|
description:
|
|
|
|
- The number of servers to build (mutually exclusive with exact_count)
|
|
|
|
required: False
|
|
|
|
default: 1
|
|
|
|
count_group:
|
|
|
|
description:
|
|
|
|
- Required when exact_count is specified. The Server Group use to determine how many severs to deploy.
|
|
|
|
required: False
|
|
|
|
default: None
|
|
|
|
cpu:
|
|
|
|
description:
|
|
|
|
- How many CPUs to provision on the server
|
|
|
|
default: 1
|
|
|
|
required: False
|
|
|
|
cpu_autoscale_policy_id:
|
|
|
|
description:
|
|
|
|
- The autoscale policy to assign to the server.
|
|
|
|
default: None
|
|
|
|
required: False
|
|
|
|
custom_fields:
|
|
|
|
description:
|
|
|
|
- The list of custom fields to set on the server.
|
|
|
|
default: []
|
|
|
|
required: False
|
|
|
|
description:
|
|
|
|
description:
|
|
|
|
- The description to set for the server.
|
|
|
|
default: None
|
|
|
|
required: False
|
|
|
|
exact_count:
|
|
|
|
description:
|
|
|
|
- Run in idempotent mode. Will insure that this exact number of servers are running in the provided group,
|
|
|
|
creating and deleting them to reach that count. Requires count_group to be set.
|
|
|
|
default: None
|
|
|
|
required: False
|
|
|
|
group:
|
|
|
|
description:
|
|
|
|
- The Server Group to create servers under.
|
|
|
|
default: 'Default Group'
|
|
|
|
required: False
|
|
|
|
ip_address:
|
|
|
|
description:
|
|
|
|
- The IP Address for the server. One is assigned if not provided.
|
|
|
|
default: None
|
|
|
|
required: False
|
|
|
|
location:
|
|
|
|
description:
|
|
|
|
- The Datacenter to create servers in.
|
|
|
|
default: None
|
|
|
|
required: False
|
|
|
|
managed_os:
|
|
|
|
description:
|
|
|
|
- Whether to create the server as 'Managed' or not.
|
|
|
|
default: False
|
|
|
|
required: False
|
|
|
|
choices: [True, False]
|
|
|
|
memory:
|
|
|
|
description:
|
|
|
|
- Memory in GB.
|
|
|
|
default: 1
|
|
|
|
required: False
|
|
|
|
name:
|
|
|
|
description:
|
|
|
|
- A 1 to 6 character identifier to use for the server. This is required when state is 'present'
|
|
|
|
default: None
|
|
|
|
required: False
|
|
|
|
network_id:
|
|
|
|
description:
|
|
|
|
- The network UUID on which to create servers.
|
|
|
|
default: None
|
|
|
|
required: False
|
|
|
|
packages:
|
|
|
|
description:
|
|
|
|
- The list of blue print packages to run on the server after its created.
|
|
|
|
default: []
|
|
|
|
required: False
|
|
|
|
password:
|
|
|
|
description:
|
|
|
|
- Password for the administrator / root user
|
|
|
|
default: None
|
|
|
|
required: False
|
|
|
|
primary_dns:
|
|
|
|
description:
|
|
|
|
- Primary DNS used by the server.
|
|
|
|
default: None
|
|
|
|
required: False
|
|
|
|
public_ip_protocol:
|
|
|
|
description:
|
|
|
|
- The protocol to use for the public ip if add_public_ip is set to True.
|
|
|
|
default: 'TCP'
|
|
|
|
choices: ['TCP', 'UDP', 'ICMP']
|
|
|
|
required: False
|
|
|
|
public_ip_ports:
|
|
|
|
description:
|
|
|
|
- A list of ports to allow on the firewall to the servers public ip, if add_public_ip is set to True.
|
|
|
|
default: []
|
|
|
|
required: False
|
|
|
|
secondary_dns:
|
|
|
|
description:
|
|
|
|
- Secondary DNS used by the server.
|
|
|
|
default: None
|
|
|
|
required: False
|
|
|
|
server_ids:
|
|
|
|
description:
|
|
|
|
- Required for started, stopped, and absent states.
|
|
|
|
A list of server Ids to insure are started, stopped, or absent.
|
|
|
|
default: []
|
|
|
|
required: False
|
|
|
|
source_server_password:
|
|
|
|
description:
|
|
|
|
- The password for the source server if a clone is specified.
|
|
|
|
default: None
|
|
|
|
required: False
|
|
|
|
state:
|
|
|
|
description:
|
|
|
|
- The state to insure that the provided resources are in.
|
|
|
|
default: 'present'
|
|
|
|
required: False
|
|
|
|
choices: ['present', 'absent', 'started', 'stopped']
|
|
|
|
storage_type:
|
|
|
|
description:
|
|
|
|
- The type of storage to attach to the server.
|
|
|
|
default: 'standard'
|
|
|
|
required: False
|
|
|
|
choices: ['standard', 'hyperscale']
|
|
|
|
template:
|
|
|
|
description:
|
|
|
|
- The template to use for server creation. Will search for a template if a partial string is provided.
|
|
|
|
This is required when state is 'present'
|
|
|
|
default: None
|
2015-10-21 19:36:51 +02:00
|
|
|
required: False
|
2015-07-27 20:59:16 +02:00
|
|
|
ttl:
|
|
|
|
description:
|
|
|
|
- The time to live for the server in seconds. The server will be deleted when this time expires.
|
|
|
|
default: None
|
|
|
|
required: False
|
|
|
|
type:
|
|
|
|
description:
|
|
|
|
- The type of server to create.
|
|
|
|
default: 'standard'
|
|
|
|
required: False
|
2015-10-21 19:36:51 +02:00
|
|
|
choices: ['standard', 'hyperscale', 'bareMetal']
|
|
|
|
configuration_id:
|
|
|
|
description:
|
|
|
|
- Only required for bare metal servers.
|
|
|
|
Specifies the identifier for the specific configuration type of bare metal server to deploy.
|
|
|
|
default: None
|
|
|
|
required: False
|
|
|
|
os_type:
|
|
|
|
description:
|
|
|
|
- Only required for bare metal servers.
|
|
|
|
Specifies the OS to provision with the bare metal server.
|
|
|
|
default: None
|
|
|
|
required: False
|
|
|
|
choices: ['redHat6_64Bit', 'centOS6_64Bit', 'windows2012R2Standard_64Bit', 'ubuntu14_64Bit']
|
2015-07-27 20:59:16 +02:00
|
|
|
wait:
|
|
|
|
description:
|
|
|
|
- Whether to wait for the provisioning tasks to finish before returning.
|
|
|
|
default: True
|
|
|
|
required: False
|
|
|
|
choices: [True, False]
|
|
|
|
requirements:
|
|
|
|
- python = 2.7
|
|
|
|
- requests >= 2.5.0
|
|
|
|
- clc-sdk
|
2015-10-23 16:34:58 +02:00
|
|
|
author: "CLC Runner (@clc-runner)"
|
2015-07-27 20:59:16 +02:00
|
|
|
notes:
|
|
|
|
- To use this module, it is required to set the below environment variables which enables access to the
|
|
|
|
Centurylink Cloud
|
|
|
|
- CLC_V2_API_USERNAME, the account login id for the centurylink cloud
|
|
|
|
- CLC_V2_API_PASSWORD, the account password for the centurylink cloud
|
|
|
|
- Alternatively, the module accepts the API token and account alias. The API token can be generated using the
|
|
|
|
CLC account login and password via the HTTP api call @ https://api.ctl.io/v2/authentication/login
|
|
|
|
- CLC_V2_API_TOKEN, the API token generated from https://api.ctl.io/v2/authentication/login
|
|
|
|
- CLC_ACCT_ALIAS, the account alias associated with the centurylink cloud
|
|
|
|
- Users can set CLC_V2_API_URL to specify an endpoint for pointing to a different CLC environment.
|
|
|
|
'''
|
|
|
|
|
|
|
|
EXAMPLES = '''
|
|
|
|
# Note - You must set the CLC_V2_API_USERNAME And CLC_V2_API_PASSWD Environment variables before running these examples
|
|
|
|
|
|
|
|
- name: Provision a single Ubuntu Server
|
|
|
|
clc_server:
|
|
|
|
name: test
|
|
|
|
template: ubuntu-14-64
|
|
|
|
count: 1
|
|
|
|
group: 'Default Group'
|
|
|
|
state: present
|
|
|
|
|
|
|
|
- name: Ensure 'Default Group' has exactly 5 servers
|
|
|
|
clc_server:
|
|
|
|
name: test
|
|
|
|
template: ubuntu-14-64
|
|
|
|
exact_count: 5
|
|
|
|
count_group: 'Default Group'
|
|
|
|
group: 'Default Group'
|
|
|
|
|
|
|
|
- name: Stop a Server
|
|
|
|
clc_server:
|
2015-10-21 19:36:51 +02:00
|
|
|
server_ids: ['UC1ACCT-TEST01']
|
2015-07-27 20:59:16 +02:00
|
|
|
state: stopped
|
|
|
|
|
|
|
|
- name: Start a Server
|
|
|
|
clc_server:
|
2015-10-21 19:36:51 +02:00
|
|
|
server_ids: ['UC1ACCT-TEST01']
|
2015-07-27 20:59:16 +02:00
|
|
|
state: started
|
|
|
|
|
|
|
|
- name: Delete a Server
|
|
|
|
clc_server:
|
2015-10-21 19:36:51 +02:00
|
|
|
server_ids: ['UC1ACCT-TEST01']
|
2015-07-27 20:59:16 +02:00
|
|
|
state: absent
|
|
|
|
'''
|
|
|
|
|
2015-10-21 19:36:51 +02:00
|
|
|
RETURN = '''
|
|
|
|
server_ids:
|
|
|
|
description: The list of server ids that are created
|
|
|
|
returned: success
|
|
|
|
type: list
|
|
|
|
sample:
|
|
|
|
[
|
|
|
|
"UC1TEST-SVR01",
|
|
|
|
"UC1TEST-SVR02"
|
|
|
|
]
|
|
|
|
partially_created_server_ids:
|
|
|
|
description: The list of server ids that are partially created
|
|
|
|
returned: success
|
|
|
|
type: list
|
|
|
|
sample:
|
|
|
|
[
|
|
|
|
"UC1TEST-SVR01",
|
|
|
|
"UC1TEST-SVR02"
|
|
|
|
]
|
|
|
|
servers:
|
|
|
|
description: The list of server objects returned from CLC
|
|
|
|
returned: success
|
|
|
|
type: list
|
|
|
|
sample:
|
|
|
|
[
|
|
|
|
{
|
|
|
|
"changeInfo":{
|
|
|
|
"createdBy":"service.wfad",
|
|
|
|
"createdDate":1438196820,
|
|
|
|
"modifiedBy":"service.wfad",
|
|
|
|
"modifiedDate":1438196820
|
|
|
|
},
|
|
|
|
"description":"test-server",
|
|
|
|
"details":{
|
|
|
|
"alertPolicies":[
|
|
|
|
|
|
|
|
],
|
|
|
|
"cpu":1,
|
|
|
|
"customFields":[
|
|
|
|
|
|
|
|
],
|
|
|
|
"diskCount":3,
|
|
|
|
"disks":[
|
|
|
|
{
|
|
|
|
"id":"0:0",
|
|
|
|
"partitionPaths":[
|
|
|
|
|
|
|
|
],
|
|
|
|
"sizeGB":1
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"id":"0:1",
|
|
|
|
"partitionPaths":[
|
|
|
|
|
|
|
|
],
|
|
|
|
"sizeGB":2
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"id":"0:2",
|
|
|
|
"partitionPaths":[
|
|
|
|
|
|
|
|
],
|
|
|
|
"sizeGB":14
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"hostName":"",
|
|
|
|
"inMaintenanceMode":false,
|
|
|
|
"ipAddresses":[
|
|
|
|
{
|
|
|
|
"internal":"10.1.1.1"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"memoryGB":1,
|
|
|
|
"memoryMB":1024,
|
|
|
|
"partitions":[
|
|
|
|
|
|
|
|
],
|
|
|
|
"powerState":"started",
|
|
|
|
"snapshots":[
|
|
|
|
|
|
|
|
],
|
|
|
|
"storageGB":17
|
|
|
|
},
|
|
|
|
"groupId":"086ac1dfe0b6411989e8d1b77c4065f0",
|
|
|
|
"id":"test-server",
|
|
|
|
"ipaddress":"10.120.45.23",
|
|
|
|
"isTemplate":false,
|
|
|
|
"links":[
|
|
|
|
{
|
|
|
|
"href":"/v2/servers/wfad/test-server",
|
|
|
|
"id":"test-server",
|
|
|
|
"rel":"self",
|
|
|
|
"verbs":[
|
|
|
|
"GET",
|
|
|
|
"PATCH",
|
|
|
|
"DELETE"
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"href":"/v2/groups/wfad/086ac1dfe0b6411989e8d1b77c4065f0",
|
|
|
|
"id":"086ac1dfe0b6411989e8d1b77c4065f0",
|
|
|
|
"rel":"group"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"href":"/v2/accounts/wfad",
|
|
|
|
"id":"wfad",
|
|
|
|
"rel":"account"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"href":"/v2/billing/wfad/serverPricing/test-server",
|
|
|
|
"rel":"billing"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"href":"/v2/servers/wfad/test-server/publicIPAddresses",
|
|
|
|
"rel":"publicIPAddresses",
|
|
|
|
"verbs":[
|
|
|
|
"POST"
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"href":"/v2/servers/wfad/test-server/credentials",
|
|
|
|
"rel":"credentials"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"href":"/v2/servers/wfad/test-server/statistics",
|
|
|
|
"rel":"statistics"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"href":"/v2/servers/wfad/510ec21ae82d4dc89d28479753bf736a/upcomingScheduledActivities",
|
|
|
|
"rel":"upcomingScheduledActivities"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"href":"/v2/servers/wfad/510ec21ae82d4dc89d28479753bf736a/scheduledActivities",
|
|
|
|
"rel":"scheduledActivities",
|
|
|
|
"verbs":[
|
|
|
|
"GET",
|
|
|
|
"POST"
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"href":"/v2/servers/wfad/test-server/capabilities",
|
|
|
|
"rel":"capabilities"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"href":"/v2/servers/wfad/test-server/alertPolicies",
|
|
|
|
"rel":"alertPolicyMappings",
|
|
|
|
"verbs":[
|
|
|
|
"POST"
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"href":"/v2/servers/wfad/test-server/antiAffinityPolicy",
|
|
|
|
"rel":"antiAffinityPolicyMapping",
|
|
|
|
"verbs":[
|
|
|
|
"PUT",
|
|
|
|
"DELETE"
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"href":"/v2/servers/wfad/test-server/cpuAutoscalePolicy",
|
|
|
|
"rel":"cpuAutoscalePolicyMapping",
|
|
|
|
"verbs":[
|
|
|
|
"PUT",
|
|
|
|
"DELETE"
|
|
|
|
]
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"locationId":"UC1",
|
|
|
|
"name":"test-server",
|
|
|
|
"os":"ubuntu14_64Bit",
|
|
|
|
"osType":"Ubuntu 14 64-bit",
|
|
|
|
"status":"active",
|
|
|
|
"storageType":"standard",
|
|
|
|
"type":"standard"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
'''
|
|
|
|
|
2015-07-27 20:59:16 +02:00
|
|
|
__version__ = '${version}'
|
|
|
|
|
|
|
|
from time import sleep
|
|
|
|
from distutils.version import LooseVersion
|
|
|
|
|
|
|
|
try:
|
|
|
|
import requests
|
|
|
|
except ImportError:
|
|
|
|
REQUESTS_FOUND = False
|
|
|
|
else:
|
|
|
|
REQUESTS_FOUND = True
|
|
|
|
|
|
|
|
#
|
|
|
|
# Requires the clc-python-sdk.
|
|
|
|
# sudo pip install clc-sdk
|
|
|
|
#
|
|
|
|
try:
|
|
|
|
import clc as clc_sdk
|
|
|
|
from clc import CLCException
|
|
|
|
from clc import APIFailedResponse
|
|
|
|
except ImportError:
|
|
|
|
CLC_FOUND = False
|
|
|
|
clc_sdk = None
|
|
|
|
else:
|
|
|
|
CLC_FOUND = True
|
|
|
|
|
|
|
|
|
|
|
|
class ClcServer:
|
|
|
|
clc = clc_sdk
|
|
|
|
|
|
|
|
def __init__(self, module):
|
|
|
|
"""
|
|
|
|
Construct module
|
|
|
|
"""
|
|
|
|
self.clc = clc_sdk
|
|
|
|
self.module = module
|
|
|
|
self.group_dict = {}
|
|
|
|
|
|
|
|
if not CLC_FOUND:
|
|
|
|
self.module.fail_json(
|
|
|
|
msg='clc-python-sdk required for this module')
|
|
|
|
if not REQUESTS_FOUND:
|
|
|
|
self.module.fail_json(
|
|
|
|
msg='requests library is required for this module')
|
|
|
|
if requests.__version__ and LooseVersion(
|
|
|
|
requests.__version__) < LooseVersion('2.5.0'):
|
|
|
|
self.module.fail_json(
|
|
|
|
msg='requests library version should be >= 2.5.0')
|
|
|
|
|
|
|
|
self._set_user_agent(self.clc)
|
|
|
|
|
|
|
|
def process_request(self):
|
|
|
|
"""
|
|
|
|
Process the request - Main Code Path
|
|
|
|
:return: Returns with either an exit_json or fail_json
|
|
|
|
"""
|
|
|
|
changed = False
|
|
|
|
new_server_ids = []
|
|
|
|
server_dict_array = []
|
|
|
|
|
|
|
|
self._set_clc_credentials_from_env()
|
|
|
|
self.module.params = self._validate_module_params(
|
|
|
|
self.clc,
|
|
|
|
self.module)
|
|
|
|
p = self.module.params
|
|
|
|
state = p.get('state')
|
|
|
|
|
|
|
|
#
|
|
|
|
# Handle each state
|
|
|
|
#
|
|
|
|
partial_servers_ids = []
|
|
|
|
if state == 'absent':
|
|
|
|
server_ids = p['server_ids']
|
|
|
|
if not isinstance(server_ids, list):
|
|
|
|
return self.module.fail_json(
|
|
|
|
msg='server_ids needs to be a list of instances to delete: %s' %
|
|
|
|
server_ids)
|
|
|
|
|
|
|
|
(changed,
|
|
|
|
server_dict_array,
|
|
|
|
new_server_ids) = self._delete_servers(module=self.module,
|
|
|
|
clc=self.clc,
|
|
|
|
server_ids=server_ids)
|
|
|
|
|
|
|
|
elif state in ('started', 'stopped'):
|
|
|
|
server_ids = p.get('server_ids')
|
|
|
|
if not isinstance(server_ids, list):
|
|
|
|
return self.module.fail_json(
|
|
|
|
msg='server_ids needs to be a list of servers to run: %s' %
|
|
|
|
server_ids)
|
|
|
|
|
|
|
|
(changed,
|
|
|
|
server_dict_array,
|
|
|
|
new_server_ids) = self._start_stop_servers(self.module,
|
|
|
|
self.clc,
|
|
|
|
server_ids)
|
|
|
|
|
|
|
|
elif state == 'present':
|
|
|
|
# Changed is always set to true when provisioning new instances
|
2015-10-21 19:36:51 +02:00
|
|
|
if not p.get('template') and p.get('type') != 'bareMetal':
|
2015-07-27 20:59:16 +02:00
|
|
|
return self.module.fail_json(
|
|
|
|
msg='template parameter is required for new instance')
|
|
|
|
|
|
|
|
if p.get('exact_count') is None:
|
|
|
|
(server_dict_array,
|
|
|
|
new_server_ids,
|
|
|
|
partial_servers_ids,
|
|
|
|
changed) = self._create_servers(self.module,
|
|
|
|
self.clc)
|
|
|
|
else:
|
|
|
|
(server_dict_array,
|
|
|
|
new_server_ids,
|
|
|
|
partial_servers_ids,
|
|
|
|
changed) = self._enforce_count(self.module,
|
|
|
|
self.clc)
|
|
|
|
|
|
|
|
self.module.exit_json(
|
|
|
|
changed=changed,
|
|
|
|
server_ids=new_server_ids,
|
|
|
|
partially_created_server_ids=partial_servers_ids,
|
|
|
|
servers=server_dict_array)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _define_module_argument_spec():
|
|
|
|
"""
|
|
|
|
Define the argument spec for the ansible module
|
|
|
|
:return: argument spec dictionary
|
|
|
|
"""
|
|
|
|
argument_spec = dict(
|
|
|
|
name=dict(),
|
|
|
|
template=dict(),
|
|
|
|
group=dict(default='Default Group'),
|
|
|
|
network_id=dict(),
|
|
|
|
location=dict(default=None),
|
|
|
|
cpu=dict(default=1),
|
|
|
|
memory=dict(default=1),
|
|
|
|
alias=dict(default=None),
|
2015-07-28 15:57:57 +02:00
|
|
|
password=dict(default=None, no_log=True),
|
2015-07-27 20:59:16 +02:00
|
|
|
ip_address=dict(default=None),
|
|
|
|
storage_type=dict(
|
|
|
|
default='standard',
|
|
|
|
choices=[
|
|
|
|
'standard',
|
|
|
|
'hyperscale']),
|
2015-10-21 19:36:51 +02:00
|
|
|
type=dict(default='standard', choices=['standard', 'hyperscale', 'bareMetal']),
|
2015-07-27 20:59:16 +02:00
|
|
|
primary_dns=dict(default=None),
|
|
|
|
secondary_dns=dict(default=None),
|
|
|
|
additional_disks=dict(type='list', default=[]),
|
|
|
|
custom_fields=dict(type='list', default=[]),
|
|
|
|
ttl=dict(default=None),
|
|
|
|
managed_os=dict(type='bool', default=False),
|
|
|
|
description=dict(default=None),
|
|
|
|
source_server_password=dict(default=None),
|
|
|
|
cpu_autoscale_policy_id=dict(default=None),
|
|
|
|
anti_affinity_policy_id=dict(default=None),
|
|
|
|
anti_affinity_policy_name=dict(default=None),
|
|
|
|
alert_policy_id=dict(default=None),
|
|
|
|
alert_policy_name=dict(default=None),
|
|
|
|
packages=dict(type='list', default=[]),
|
|
|
|
state=dict(
|
|
|
|
default='present',
|
|
|
|
choices=[
|
|
|
|
'present',
|
|
|
|
'absent',
|
|
|
|
'started',
|
|
|
|
'stopped']),
|
|
|
|
count=dict(type='int', default=1),
|
|
|
|
exact_count=dict(type='int', default=None),
|
|
|
|
count_group=dict(),
|
|
|
|
server_ids=dict(type='list', default=[]),
|
|
|
|
add_public_ip=dict(type='bool', default=False),
|
|
|
|
public_ip_protocol=dict(
|
|
|
|
default='TCP',
|
|
|
|
choices=[
|
|
|
|
'TCP',
|
|
|
|
'UDP',
|
|
|
|
'ICMP']),
|
|
|
|
public_ip_ports=dict(type='list', default=[]),
|
2015-10-21 19:36:51 +02:00
|
|
|
configuration_id=dict(default=None),
|
|
|
|
os_type=dict(default=None,
|
|
|
|
choices=[
|
|
|
|
'redHat6_64Bit',
|
|
|
|
'centOS6_64Bit',
|
|
|
|
'windows2012R2Standard_64Bit',
|
|
|
|
'ubuntu14_64Bit'
|
|
|
|
]),
|
2015-07-27 20:59:16 +02:00
|
|
|
wait=dict(type='bool', default=True))
|
|
|
|
|
|
|
|
mutually_exclusive = [
|
|
|
|
['exact_count', 'count'],
|
|
|
|
['exact_count', 'state'],
|
|
|
|
['anti_affinity_policy_id', 'anti_affinity_policy_name'],
|
|
|
|
['alert_policy_id', 'alert_policy_name'],
|
|
|
|
]
|
|
|
|
return {"argument_spec": argument_spec,
|
|
|
|
"mutually_exclusive": mutually_exclusive}
|
|
|
|
|
|
|
|
def _set_clc_credentials_from_env(self):
|
|
|
|
"""
|
|
|
|
Set the CLC Credentials on the sdk by reading environment variables
|
|
|
|
:return: none
|
|
|
|
"""
|
|
|
|
env = os.environ
|
|
|
|
v2_api_token = env.get('CLC_V2_API_TOKEN', False)
|
|
|
|
v2_api_username = env.get('CLC_V2_API_USERNAME', False)
|
|
|
|
v2_api_passwd = env.get('CLC_V2_API_PASSWD', False)
|
|
|
|
clc_alias = env.get('CLC_ACCT_ALIAS', False)
|
|
|
|
api_url = env.get('CLC_V2_API_URL', False)
|
|
|
|
if api_url:
|
|
|
|
self.clc.defaults.ENDPOINT_URL_V2 = api_url
|
|
|
|
|
|
|
|
if v2_api_token and clc_alias:
|
|
|
|
self.clc._LOGIN_TOKEN_V2 = v2_api_token
|
|
|
|
self.clc._V2_ENABLED = True
|
|
|
|
self.clc.ALIAS = clc_alias
|
|
|
|
elif v2_api_username and v2_api_passwd:
|
|
|
|
self.clc.v2.SetCredentials(
|
|
|
|
api_username=v2_api_username,
|
|
|
|
api_passwd=v2_api_passwd)
|
|
|
|
else:
|
|
|
|
return self.module.fail_json(
|
|
|
|
msg="You must set the CLC_V2_API_USERNAME and CLC_V2_API_PASSWD "
|
|
|
|
"environment variables")
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _validate_module_params(clc, module):
|
|
|
|
"""
|
|
|
|
Validate the module params, and lookup default values.
|
|
|
|
:param clc: clc-sdk instance to use
|
|
|
|
:param module: module to validate
|
|
|
|
:return: dictionary of validated params
|
|
|
|
"""
|
|
|
|
params = module.params
|
|
|
|
datacenter = ClcServer._find_datacenter(clc, module)
|
|
|
|
|
|
|
|
ClcServer._validate_types(module)
|
|
|
|
ClcServer._validate_name(module)
|
|
|
|
|
|
|
|
params['alias'] = ClcServer._find_alias(clc, module)
|
|
|
|
params['cpu'] = ClcServer._find_cpu(clc, module)
|
|
|
|
params['memory'] = ClcServer._find_memory(clc, module)
|
|
|
|
params['description'] = ClcServer._find_description(module)
|
|
|
|
params['ttl'] = ClcServer._find_ttl(clc, module)
|
|
|
|
params['template'] = ClcServer._find_template_id(module, datacenter)
|
|
|
|
params['group'] = ClcServer._find_group(module, datacenter).id
|
|
|
|
params['network_id'] = ClcServer._find_network_id(module, datacenter)
|
|
|
|
params['anti_affinity_policy_id'] = ClcServer._find_aa_policy_id(
|
|
|
|
clc,
|
|
|
|
module)
|
|
|
|
params['alert_policy_id'] = ClcServer._find_alert_policy_id(
|
|
|
|
clc,
|
|
|
|
module)
|
|
|
|
|
|
|
|
return params
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _find_datacenter(clc, module):
|
|
|
|
"""
|
|
|
|
Find the datacenter by calling the CLC API.
|
|
|
|
:param clc: clc-sdk instance to use
|
|
|
|
:param module: module to validate
|
|
|
|
:return: clc-sdk.Datacenter instance
|
|
|
|
"""
|
|
|
|
location = module.params.get('location')
|
|
|
|
try:
|
2015-10-21 19:36:51 +02:00
|
|
|
if not location:
|
|
|
|
account = clc.v2.Account()
|
|
|
|
location = account.data.get('primaryDataCenter')
|
|
|
|
data_center = clc.v2.Datacenter(location)
|
|
|
|
return data_center
|
|
|
|
except CLCException as ex:
|
2015-07-27 20:59:16 +02:00
|
|
|
module.fail_json(
|
|
|
|
msg=str(
|
|
|
|
"Unable to find location: {0}".format(location)))
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _find_alias(clc, module):
|
|
|
|
"""
|
|
|
|
Find or Validate the Account Alias by calling the CLC API
|
|
|
|
:param clc: clc-sdk instance to use
|
|
|
|
:param module: module to validate
|
|
|
|
:return: clc-sdk.Account instance
|
|
|
|
"""
|
|
|
|
alias = module.params.get('alias')
|
|
|
|
if not alias:
|
|
|
|
try:
|
|
|
|
alias = clc.v2.Account.GetAlias()
|
|
|
|
except CLCException as ex:
|
|
|
|
module.fail_json(msg='Unable to find account alias. {0}'.format(
|
|
|
|
ex.message
|
|
|
|
))
|
|
|
|
return alias
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _find_cpu(clc, module):
|
|
|
|
"""
|
|
|
|
Find or validate the CPU value by calling the CLC API
|
|
|
|
:param clc: clc-sdk instance to use
|
|
|
|
:param module: module to validate
|
|
|
|
:return: Int value for CPU
|
|
|
|
"""
|
|
|
|
cpu = module.params.get('cpu')
|
|
|
|
group_id = module.params.get('group_id')
|
|
|
|
alias = module.params.get('alias')
|
|
|
|
state = module.params.get('state')
|
|
|
|
|
|
|
|
if not cpu and state == 'present':
|
|
|
|
group = clc.v2.Group(id=group_id,
|
|
|
|
alias=alias)
|
|
|
|
if group.Defaults("cpu"):
|
|
|
|
cpu = group.Defaults("cpu")
|
|
|
|
else:
|
|
|
|
module.fail_json(
|
|
|
|
msg=str("Can\'t determine a default cpu value. Please provide a value for cpu."))
|
|
|
|
return cpu
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _find_memory(clc, module):
|
|
|
|
"""
|
|
|
|
Find or validate the Memory value by calling the CLC API
|
|
|
|
:param clc: clc-sdk instance to use
|
|
|
|
:param module: module to validate
|
|
|
|
:return: Int value for Memory
|
|
|
|
"""
|
|
|
|
memory = module.params.get('memory')
|
|
|
|
group_id = module.params.get('group_id')
|
|
|
|
alias = module.params.get('alias')
|
|
|
|
state = module.params.get('state')
|
|
|
|
|
|
|
|
if not memory and state == 'present':
|
|
|
|
group = clc.v2.Group(id=group_id,
|
|
|
|
alias=alias)
|
|
|
|
if group.Defaults("memory"):
|
|
|
|
memory = group.Defaults("memory")
|
|
|
|
else:
|
|
|
|
module.fail_json(msg=str(
|
|
|
|
"Can\'t determine a default memory value. Please provide a value for memory."))
|
|
|
|
return memory
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _find_description(module):
|
|
|
|
"""
|
|
|
|
Set the description module param to name if description is blank
|
|
|
|
:param module: the module to validate
|
|
|
|
:return: string description
|
|
|
|
"""
|
|
|
|
description = module.params.get('description')
|
|
|
|
if not description:
|
|
|
|
description = module.params.get('name')
|
|
|
|
return description
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _validate_types(module):
|
|
|
|
"""
|
|
|
|
Validate that type and storage_type are set appropriately, and fail if not
|
|
|
|
:param module: the module to validate
|
|
|
|
:return: none
|
|
|
|
"""
|
|
|
|
state = module.params.get('state')
|
|
|
|
server_type = module.params.get(
|
|
|
|
'type').lower() if module.params.get('type') else None
|
|
|
|
storage_type = module.params.get(
|
|
|
|
'storage_type').lower() if module.params.get('storage_type') else None
|
|
|
|
|
|
|
|
if state == "present":
|
|
|
|
if server_type == "standard" and storage_type not in (
|
|
|
|
"standard", "premium"):
|
|
|
|
module.fail_json(
|
|
|
|
msg=str("Standard VMs must have storage_type = 'standard' or 'premium'"))
|
|
|
|
|
|
|
|
if server_type == "hyperscale" and storage_type != "hyperscale":
|
|
|
|
module.fail_json(
|
|
|
|
msg=str("Hyperscale VMs must have storage_type = 'hyperscale'"))
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _validate_name(module):
|
|
|
|
"""
|
|
|
|
Validate that name is the correct length if provided, fail if it's not
|
|
|
|
:param module: the module to validate
|
|
|
|
:return: none
|
|
|
|
"""
|
|
|
|
server_name = module.params.get('name')
|
|
|
|
state = module.params.get('state')
|
|
|
|
|
|
|
|
if state == 'present' and (
|
|
|
|
len(server_name) < 1 or len(server_name) > 6):
|
|
|
|
module.fail_json(msg=str(
|
|
|
|
"When state = 'present', name must be a string with a minimum length of 1 and a maximum length of 6"))
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _find_ttl(clc, module):
|
|
|
|
"""
|
|
|
|
Validate that TTL is > 3600 if set, and fail if not
|
|
|
|
:param clc: clc-sdk instance to use
|
|
|
|
:param module: module to validate
|
|
|
|
:return: validated ttl
|
|
|
|
"""
|
|
|
|
ttl = module.params.get('ttl')
|
|
|
|
|
|
|
|
if ttl:
|
|
|
|
if ttl <= 3600:
|
|
|
|
return module.fail_json(msg=str("Ttl cannot be <= 3600"))
|
|
|
|
else:
|
|
|
|
ttl = clc.v2.time_utils.SecondsToZuluTS(int(time.time()) + ttl)
|
|
|
|
return ttl
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _find_template_id(module, datacenter):
|
|
|
|
"""
|
|
|
|
Find the template id by calling the CLC API.
|
|
|
|
:param module: the module to validate
|
|
|
|
:param datacenter: the datacenter to search for the template
|
|
|
|
:return: a valid clc template id
|
|
|
|
"""
|
|
|
|
lookup_template = module.params.get('template')
|
|
|
|
state = module.params.get('state')
|
2015-10-21 19:36:51 +02:00
|
|
|
type = module.params.get('type')
|
2015-07-27 20:59:16 +02:00
|
|
|
result = None
|
|
|
|
|
2015-10-21 19:36:51 +02:00
|
|
|
if state == 'present' and type != 'bareMetal':
|
2015-07-27 20:59:16 +02:00
|
|
|
try:
|
|
|
|
result = datacenter.Templates().Search(lookup_template)[0].id
|
|
|
|
except CLCException:
|
|
|
|
module.fail_json(
|
|
|
|
msg=str(
|
|
|
|
"Unable to find a template: " +
|
|
|
|
lookup_template +
|
|
|
|
" in location: " +
|
|
|
|
datacenter.id))
|
|
|
|
return result
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _find_network_id(module, datacenter):
|
|
|
|
"""
|
|
|
|
Validate the provided network id or return a default.
|
|
|
|
:param module: the module to validate
|
|
|
|
:param datacenter: the datacenter to search for a network id
|
|
|
|
:return: a valid network id
|
|
|
|
"""
|
|
|
|
network_id = module.params.get('network_id')
|
|
|
|
|
|
|
|
if not network_id:
|
|
|
|
try:
|
|
|
|
network_id = datacenter.Networks().networks[0].id
|
|
|
|
# -- added for clc-sdk 2.23 compatibility
|
|
|
|
# datacenter_networks = clc_sdk.v2.Networks(
|
|
|
|
# networks_lst=datacenter._DeploymentCapabilities()['deployableNetworks'])
|
|
|
|
# network_id = datacenter_networks.networks[0].id
|
|
|
|
# -- end
|
|
|
|
except CLCException:
|
|
|
|
module.fail_json(
|
|
|
|
msg=str(
|
|
|
|
"Unable to find a network in location: " +
|
|
|
|
datacenter.id))
|
|
|
|
|
|
|
|
return network_id
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _find_aa_policy_id(clc, module):
|
|
|
|
"""
|
|
|
|
Validate if the anti affinity policy exist for the given name and throw error if not
|
|
|
|
:param clc: the clc-sdk instance
|
|
|
|
:param module: the module to validate
|
|
|
|
:return: aa_policy_id: the anti affinity policy id of the given name.
|
|
|
|
"""
|
|
|
|
aa_policy_id = module.params.get('anti_affinity_policy_id')
|
|
|
|
aa_policy_name = module.params.get('anti_affinity_policy_name')
|
|
|
|
if not aa_policy_id and aa_policy_name:
|
|
|
|
alias = module.params.get('alias')
|
|
|
|
aa_policy_id = ClcServer._get_anti_affinity_policy_id(
|
|
|
|
clc,
|
|
|
|
module,
|
|
|
|
alias,
|
|
|
|
aa_policy_name)
|
|
|
|
if not aa_policy_id:
|
|
|
|
module.fail_json(
|
|
|
|
msg='No anti affinity policy was found with policy name : %s' % aa_policy_name)
|
|
|
|
return aa_policy_id
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _find_alert_policy_id(clc, module):
|
|
|
|
"""
|
|
|
|
Validate if the alert policy exist for the given name and throw error if not
|
|
|
|
:param clc: the clc-sdk instance
|
|
|
|
:param module: the module to validate
|
|
|
|
:return: alert_policy_id: the alert policy id of the given name.
|
|
|
|
"""
|
|
|
|
alert_policy_id = module.params.get('alert_policy_id')
|
|
|
|
alert_policy_name = module.params.get('alert_policy_name')
|
|
|
|
if not alert_policy_id and alert_policy_name:
|
|
|
|
alias = module.params.get('alias')
|
|
|
|
alert_policy_id = ClcServer._get_alert_policy_id_by_name(
|
|
|
|
clc=clc,
|
|
|
|
module=module,
|
|
|
|
alias=alias,
|
|
|
|
alert_policy_name=alert_policy_name
|
|
|
|
)
|
|
|
|
if not alert_policy_id:
|
|
|
|
module.fail_json(
|
|
|
|
msg='No alert policy exist with name : %s' % alert_policy_name)
|
|
|
|
return alert_policy_id
|
|
|
|
|
|
|
|
def _create_servers(self, module, clc, override_count=None):
|
|
|
|
"""
|
|
|
|
Create New Servers in CLC cloud
|
|
|
|
:param module: the AnsibleModule object
|
|
|
|
:param clc: the clc-sdk instance to use
|
|
|
|
:return: a list of dictionaries with server information about the servers that were created
|
|
|
|
"""
|
|
|
|
p = module.params
|
|
|
|
request_list = []
|
|
|
|
servers = []
|
|
|
|
server_dict_array = []
|
|
|
|
created_server_ids = []
|
|
|
|
partial_created_servers_ids = []
|
|
|
|
|
|
|
|
add_public_ip = p.get('add_public_ip')
|
|
|
|
public_ip_protocol = p.get('public_ip_protocol')
|
|
|
|
public_ip_ports = p.get('public_ip_ports')
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'name': p.get('name'),
|
|
|
|
'template': p.get('template'),
|
|
|
|
'group_id': p.get('group'),
|
|
|
|
'network_id': p.get('network_id'),
|
|
|
|
'cpu': p.get('cpu'),
|
|
|
|
'memory': p.get('memory'),
|
|
|
|
'alias': p.get('alias'),
|
|
|
|
'password': p.get('password'),
|
|
|
|
'ip_address': p.get('ip_address'),
|
|
|
|
'storage_type': p.get('storage_type'),
|
|
|
|
'type': p.get('type'),
|
|
|
|
'primary_dns': p.get('primary_dns'),
|
|
|
|
'secondary_dns': p.get('secondary_dns'),
|
|
|
|
'additional_disks': p.get('additional_disks'),
|
|
|
|
'custom_fields': p.get('custom_fields'),
|
|
|
|
'ttl': p.get('ttl'),
|
|
|
|
'managed_os': p.get('managed_os'),
|
|
|
|
'description': p.get('description'),
|
|
|
|
'source_server_password': p.get('source_server_password'),
|
|
|
|
'cpu_autoscale_policy_id': p.get('cpu_autoscale_policy_id'),
|
|
|
|
'anti_affinity_policy_id': p.get('anti_affinity_policy_id'),
|
2015-10-21 19:36:51 +02:00
|
|
|
'packages': p.get('packages'),
|
|
|
|
'configuration_id': p.get('configuration_id'),
|
|
|
|
'os_type': p.get('os_type')
|
2015-07-27 20:59:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
count = override_count if override_count else p.get('count')
|
|
|
|
|
|
|
|
changed = False if count == 0 else True
|
|
|
|
|
|
|
|
if not changed:
|
|
|
|
return server_dict_array, created_server_ids, partial_created_servers_ids, changed
|
|
|
|
for i in range(0, count):
|
|
|
|
if not module.check_mode:
|
|
|
|
req = self._create_clc_server(clc=clc,
|
|
|
|
module=module,
|
|
|
|
server_params=params)
|
|
|
|
server = req.requests[0].Server()
|
|
|
|
request_list.append(req)
|
|
|
|
servers.append(server)
|
|
|
|
|
|
|
|
self._wait_for_requests(module, request_list)
|
|
|
|
self._refresh_servers(module, servers)
|
|
|
|
|
|
|
|
ip_failed_servers = self._add_public_ip_to_servers(
|
|
|
|
module=module,
|
|
|
|
should_add_public_ip=add_public_ip,
|
|
|
|
servers=servers,
|
|
|
|
public_ip_protocol=public_ip_protocol,
|
|
|
|
public_ip_ports=public_ip_ports)
|
|
|
|
ap_failed_servers = self._add_alert_policy_to_servers(clc=clc,
|
|
|
|
module=module,
|
|
|
|
servers=servers)
|
|
|
|
|
|
|
|
for server in servers:
|
|
|
|
if server in ip_failed_servers or server in ap_failed_servers:
|
|
|
|
partial_created_servers_ids.append(server.id)
|
|
|
|
else:
|
|
|
|
# reload server details
|
|
|
|
server = clc.v2.Server(server.id)
|
|
|
|
server.data['ipaddress'] = server.details[
|
|
|
|
'ipAddresses'][0]['internal']
|
|
|
|
|
|
|
|
if add_public_ip and len(server.PublicIPs().public_ips) > 0:
|
|
|
|
server.data['publicip'] = str(
|
|
|
|
server.PublicIPs().public_ips[0])
|
|
|
|
created_server_ids.append(server.id)
|
|
|
|
server_dict_array.append(server.data)
|
|
|
|
|
|
|
|
return server_dict_array, created_server_ids, partial_created_servers_ids, changed
|
|
|
|
|
|
|
|
def _enforce_count(self, module, clc):
|
|
|
|
"""
|
|
|
|
Enforce that there is the right number of servers in the provided group.
|
|
|
|
Starts or stops servers as necessary.
|
|
|
|
:param module: the AnsibleModule object
|
|
|
|
:param clc: the clc-sdk instance to use
|
|
|
|
:return: a list of dictionaries with server information about the servers that were created or deleted
|
|
|
|
"""
|
|
|
|
p = module.params
|
|
|
|
changed = False
|
|
|
|
count_group = p.get('count_group')
|
|
|
|
datacenter = ClcServer._find_datacenter(clc, module)
|
|
|
|
exact_count = p.get('exact_count')
|
|
|
|
server_dict_array = []
|
|
|
|
partial_servers_ids = []
|
|
|
|
changed_server_ids = []
|
|
|
|
|
|
|
|
# fail here if the exact count was specified without filtering
|
|
|
|
# on a group, as this may lead to a undesired removal of instances
|
|
|
|
if exact_count and count_group is None:
|
|
|
|
return module.fail_json(
|
|
|
|
msg="you must use the 'count_group' option with exact_count")
|
|
|
|
|
|
|
|
servers, running_servers = ClcServer._find_running_servers_by_group(
|
|
|
|
module, datacenter, count_group)
|
|
|
|
|
|
|
|
if len(running_servers) == exact_count:
|
|
|
|
changed = False
|
|
|
|
|
|
|
|
elif len(running_servers) < exact_count:
|
|
|
|
to_create = exact_count - len(running_servers)
|
|
|
|
server_dict_array, changed_server_ids, partial_servers_ids, changed \
|
|
|
|
= self._create_servers(module, clc, override_count=to_create)
|
|
|
|
|
|
|
|
for server in server_dict_array:
|
|
|
|
running_servers.append(server)
|
|
|
|
|
|
|
|
elif len(running_servers) > exact_count:
|
|
|
|
to_remove = len(running_servers) - exact_count
|
|
|
|
all_server_ids = sorted([x.id for x in running_servers])
|
|
|
|
remove_ids = all_server_ids[0:to_remove]
|
|
|
|
|
|
|
|
(changed, server_dict_array, changed_server_ids) \
|
|
|
|
= ClcServer._delete_servers(module, clc, remove_ids)
|
|
|
|
|
|
|
|
return server_dict_array, changed_server_ids, partial_servers_ids, changed
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _wait_for_requests(module, request_list):
|
|
|
|
"""
|
|
|
|
Block until server provisioning requests are completed.
|
|
|
|
:param module: the AnsibleModule object
|
|
|
|
:param request_list: a list of clc-sdk.Request instances
|
|
|
|
:return: none
|
|
|
|
"""
|
|
|
|
wait = module.params.get('wait')
|
|
|
|
if wait:
|
|
|
|
# Requests.WaitUntilComplete() returns the count of failed requests
|
|
|
|
failed_requests_count = sum(
|
|
|
|
[request.WaitUntilComplete() for request in request_list])
|
|
|
|
|
|
|
|
if failed_requests_count > 0:
|
|
|
|
module.fail_json(
|
|
|
|
msg='Unable to process server request')
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _refresh_servers(module, servers):
|
|
|
|
"""
|
|
|
|
Loop through a list of servers and refresh them.
|
|
|
|
:param module: the AnsibleModule object
|
|
|
|
:param servers: list of clc-sdk.Server instances to refresh
|
|
|
|
:return: none
|
|
|
|
"""
|
|
|
|
for server in servers:
|
|
|
|
try:
|
|
|
|
server.Refresh()
|
|
|
|
except CLCException as ex:
|
|
|
|
module.fail_json(msg='Unable to refresh the server {0}. {1}'.format(
|
|
|
|
server.id, ex.message
|
|
|
|
))
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _add_public_ip_to_servers(
|
|
|
|
module,
|
|
|
|
should_add_public_ip,
|
|
|
|
servers,
|
|
|
|
public_ip_protocol,
|
|
|
|
public_ip_ports):
|
|
|
|
"""
|
|
|
|
Create a public IP for servers
|
|
|
|
:param module: the AnsibleModule object
|
|
|
|
:param should_add_public_ip: boolean - whether or not to provision a public ip for servers. Skipped if False
|
|
|
|
:param servers: List of servers to add public ips to
|
|
|
|
:param public_ip_protocol: a protocol to allow for the public ips
|
|
|
|
:param public_ip_ports: list of ports to allow for the public ips
|
|
|
|
:return: none
|
|
|
|
"""
|
|
|
|
failed_servers = []
|
|
|
|
if not should_add_public_ip:
|
|
|
|
return failed_servers
|
|
|
|
|
|
|
|
ports_lst = []
|
|
|
|
request_list = []
|
|
|
|
server = None
|
|
|
|
|
|
|
|
for port in public_ip_ports:
|
|
|
|
ports_lst.append(
|
|
|
|
{'protocol': public_ip_protocol, 'port': port})
|
|
|
|
try:
|
|
|
|
if not module.check_mode:
|
|
|
|
for server in servers:
|
|
|
|
request = server.PublicIPs().Add(ports_lst)
|
|
|
|
request_list.append(request)
|
|
|
|
except APIFailedResponse:
|
|
|
|
failed_servers.append(server)
|
|
|
|
ClcServer._wait_for_requests(module, request_list)
|
|
|
|
return failed_servers
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _add_alert_policy_to_servers(clc, module, servers):
|
|
|
|
"""
|
|
|
|
Associate the alert policy to servers
|
|
|
|
:param clc: the clc-sdk instance to use
|
|
|
|
:param module: the AnsibleModule object
|
|
|
|
:param servers: List of servers to add alert policy to
|
|
|
|
:return: failed_servers: the list of servers which failed while associating alert policy
|
|
|
|
"""
|
|
|
|
failed_servers = []
|
|
|
|
p = module.params
|
|
|
|
alert_policy_id = p.get('alert_policy_id')
|
|
|
|
alias = p.get('alias')
|
|
|
|
|
|
|
|
if alert_policy_id and not module.check_mode:
|
|
|
|
for server in servers:
|
|
|
|
try:
|
|
|
|
ClcServer._add_alert_policy_to_server(
|
|
|
|
clc=clc,
|
|
|
|
alias=alias,
|
|
|
|
server_id=server.id,
|
|
|
|
alert_policy_id=alert_policy_id)
|
|
|
|
except CLCException:
|
|
|
|
failed_servers.append(server)
|
|
|
|
return failed_servers
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _add_alert_policy_to_server(
|
|
|
|
clc, alias, server_id, alert_policy_id):
|
|
|
|
"""
|
|
|
|
Associate an alert policy to a clc server
|
|
|
|
:param clc: the clc-sdk instance to use
|
|
|
|
:param alias: the clc account alias
|
|
|
|
:param server_id: The clc server id
|
|
|
|
:param alert_policy_id: the alert policy id to be associated to the server
|
|
|
|
:return: none
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
clc.v2.API.Call(
|
|
|
|
method='POST',
|
|
|
|
url='servers/%s/%s/alertPolicies' % (alias, server_id),
|
|
|
|
payload=json.dumps(
|
|
|
|
{
|
|
|
|
'id': alert_policy_id
|
|
|
|
}))
|
|
|
|
except APIFailedResponse as e:
|
|
|
|
raise CLCException(
|
|
|
|
'Failed to associate alert policy to the server : {0} with Error {1}'.format(
|
|
|
|
server_id, str(e.response_text)))
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _get_alert_policy_id_by_name(clc, module, alias, alert_policy_name):
|
|
|
|
"""
|
|
|
|
Returns the alert policy id for the given alert policy name
|
|
|
|
:param clc: the clc-sdk instance to use
|
|
|
|
:param module: the AnsibleModule object
|
|
|
|
:param alias: the clc account alias
|
|
|
|
:param alert_policy_name: the name of the alert policy
|
|
|
|
:return: alert_policy_id: the alert policy id
|
|
|
|
"""
|
|
|
|
alert_policy_id = None
|
|
|
|
policies = clc.v2.API.Call('GET', '/v2/alertPolicies/%s' % alias)
|
|
|
|
if not policies:
|
|
|
|
return alert_policy_id
|
|
|
|
for policy in policies.get('items'):
|
|
|
|
if policy.get('name') == alert_policy_name:
|
|
|
|
if not alert_policy_id:
|
|
|
|
alert_policy_id = policy.get('id')
|
|
|
|
else:
|
|
|
|
return module.fail_json(
|
|
|
|
msg='multiple alert policies were found with policy name : %s' % alert_policy_name)
|
|
|
|
return alert_policy_id
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _delete_servers(module, clc, server_ids):
|
|
|
|
"""
|
|
|
|
Delete the servers on the provided list
|
|
|
|
:param module: the AnsibleModule object
|
|
|
|
:param clc: the clc-sdk instance to use
|
|
|
|
:param server_ids: list of servers to delete
|
|
|
|
:return: a list of dictionaries with server information about the servers that were deleted
|
|
|
|
"""
|
|
|
|
terminated_server_ids = []
|
|
|
|
server_dict_array = []
|
|
|
|
request_list = []
|
|
|
|
|
|
|
|
if not isinstance(server_ids, list) or len(server_ids) < 1:
|
|
|
|
return module.fail_json(
|
|
|
|
msg='server_ids should be a list of servers, aborting')
|
|
|
|
|
|
|
|
servers = clc.v2.Servers(server_ids).Servers()
|
|
|
|
for server in servers:
|
|
|
|
if not module.check_mode:
|
|
|
|
request_list.append(server.Delete())
|
|
|
|
ClcServer._wait_for_requests(module, request_list)
|
|
|
|
|
|
|
|
for server in servers:
|
|
|
|
terminated_server_ids.append(server.id)
|
|
|
|
|
|
|
|
return True, server_dict_array, terminated_server_ids
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _start_stop_servers(module, clc, server_ids):
|
|
|
|
"""
|
|
|
|
Start or Stop the servers on the provided list
|
|
|
|
:param module: the AnsibleModule object
|
|
|
|
:param clc: the clc-sdk instance to use
|
|
|
|
:param server_ids: list of servers to start or stop
|
|
|
|
:return: a list of dictionaries with server information about the servers that were started or stopped
|
|
|
|
"""
|
|
|
|
p = module.params
|
|
|
|
state = p.get('state')
|
|
|
|
changed = False
|
|
|
|
changed_servers = []
|
|
|
|
server_dict_array = []
|
|
|
|
result_server_ids = []
|
|
|
|
request_list = []
|
|
|
|
|
|
|
|
if not isinstance(server_ids, list) or len(server_ids) < 1:
|
|
|
|
return module.fail_json(
|
|
|
|
msg='server_ids should be a list of servers, aborting')
|
|
|
|
|
|
|
|
servers = clc.v2.Servers(server_ids).Servers()
|
|
|
|
for server in servers:
|
|
|
|
if server.powerState != state:
|
|
|
|
changed_servers.append(server)
|
|
|
|
if not module.check_mode:
|
|
|
|
request_list.append(
|
|
|
|
ClcServer._change_server_power_state(
|
|
|
|
module,
|
|
|
|
server,
|
|
|
|
state))
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
ClcServer._wait_for_requests(module, request_list)
|
|
|
|
ClcServer._refresh_servers(module, changed_servers)
|
|
|
|
|
|
|
|
for server in set(changed_servers + servers):
|
|
|
|
try:
|
|
|
|
server.data['ipaddress'] = server.details[
|
|
|
|
'ipAddresses'][0]['internal']
|
|
|
|
server.data['publicip'] = str(
|
|
|
|
server.PublicIPs().public_ips[0])
|
|
|
|
except (KeyError, IndexError):
|
|
|
|
pass
|
|
|
|
|
|
|
|
server_dict_array.append(server.data)
|
|
|
|
result_server_ids.append(server.id)
|
|
|
|
|
|
|
|
return changed, server_dict_array, result_server_ids
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _change_server_power_state(module, server, state):
|
|
|
|
"""
|
|
|
|
Change the server powerState
|
|
|
|
:param module: the module to check for intended state
|
|
|
|
:param server: the server to start or stop
|
|
|
|
:param state: the intended powerState for the server
|
|
|
|
:return: the request object from clc-sdk call
|
|
|
|
"""
|
|
|
|
result = None
|
|
|
|
try:
|
|
|
|
if state == 'started':
|
|
|
|
result = server.PowerOn()
|
|
|
|
else:
|
2015-10-21 19:36:51 +02:00
|
|
|
# Try to shut down the server and fall back to power off when unable to shut down.
|
|
|
|
result = server.ShutDown()
|
|
|
|
if result and hasattr(result, 'requests') and result.requests[0]:
|
|
|
|
return result
|
|
|
|
else:
|
|
|
|
result = server.PowerOff()
|
2015-07-27 20:59:16 +02:00
|
|
|
except CLCException:
|
|
|
|
module.fail_json(
|
|
|
|
msg='Unable to change power state for server {0}'.format(
|
|
|
|
server.id))
|
|
|
|
return result
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _find_running_servers_by_group(module, datacenter, count_group):
|
|
|
|
"""
|
|
|
|
Find a list of running servers in the provided group
|
|
|
|
:param module: the AnsibleModule object
|
|
|
|
:param datacenter: the clc-sdk.Datacenter instance to use to lookup the group
|
|
|
|
:param count_group: the group to count the servers
|
|
|
|
:return: list of servers, and list of running servers
|
|
|
|
"""
|
|
|
|
group = ClcServer._find_group(
|
|
|
|
module=module,
|
|
|
|
datacenter=datacenter,
|
|
|
|
lookup_group=count_group)
|
|
|
|
|
|
|
|
servers = group.Servers().Servers()
|
|
|
|
running_servers = []
|
|
|
|
|
|
|
|
for server in servers:
|
|
|
|
if server.status == 'active' and server.powerState == 'started':
|
|
|
|
running_servers.append(server)
|
|
|
|
|
|
|
|
return servers, running_servers
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _find_group(module, datacenter, lookup_group=None):
|
|
|
|
"""
|
|
|
|
Find a server group in a datacenter by calling the CLC API
|
|
|
|
:param module: the AnsibleModule instance
|
|
|
|
:param datacenter: clc-sdk.Datacenter instance to search for the group
|
|
|
|
:param lookup_group: string name of the group to search for
|
|
|
|
:return: clc-sdk.Group instance
|
|
|
|
"""
|
|
|
|
if not lookup_group:
|
|
|
|
lookup_group = module.params.get('group')
|
|
|
|
try:
|
|
|
|
return datacenter.Groups().Get(lookup_group)
|
|
|
|
except CLCException:
|
|
|
|
pass
|
|
|
|
|
|
|
|
# The search above only acts on the main
|
|
|
|
result = ClcServer._find_group_recursive(
|
|
|
|
module,
|
|
|
|
datacenter.Groups(),
|
|
|
|
lookup_group)
|
|
|
|
|
|
|
|
if result is None:
|
|
|
|
module.fail_json(
|
|
|
|
msg=str(
|
|
|
|
"Unable to find group: " +
|
|
|
|
lookup_group +
|
|
|
|
" in location: " +
|
|
|
|
datacenter.id))
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _find_group_recursive(module, group_list, lookup_group):
|
|
|
|
"""
|
|
|
|
Find a server group by recursively walking the tree
|
|
|
|
:param module: the AnsibleModule instance to use
|
|
|
|
:param group_list: a list of groups to search
|
|
|
|
:param lookup_group: the group to look for
|
|
|
|
:return: list of groups
|
|
|
|
"""
|
|
|
|
result = None
|
|
|
|
for group in group_list.groups:
|
|
|
|
subgroups = group.Subgroups()
|
|
|
|
try:
|
|
|
|
return subgroups.Get(lookup_group)
|
|
|
|
except CLCException:
|
|
|
|
result = ClcServer._find_group_recursive(
|
|
|
|
module,
|
|
|
|
subgroups,
|
|
|
|
lookup_group)
|
|
|
|
|
|
|
|
if result is not None:
|
|
|
|
break
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _create_clc_server(
|
|
|
|
clc,
|
|
|
|
module,
|
|
|
|
server_params):
|
|
|
|
"""
|
|
|
|
Call the CLC Rest API to Create a Server
|
|
|
|
:param clc: the clc-python-sdk instance to use
|
|
|
|
:param module: the AnsibleModule instance to use
|
|
|
|
:param server_params: a dictionary of params to use to create the servers
|
|
|
|
:return: clc-sdk.Request object linked to the queued server request
|
|
|
|
"""
|
|
|
|
|
|
|
|
try:
|
|
|
|
res = clc.v2.API.Call(
|
|
|
|
method='POST',
|
|
|
|
url='servers/%s' %
|
|
|
|
(server_params.get('alias')),
|
|
|
|
payload=json.dumps(
|
|
|
|
{
|
|
|
|
'name': server_params.get('name'),
|
|
|
|
'description': server_params.get('description'),
|
|
|
|
'groupId': server_params.get('group_id'),
|
|
|
|
'sourceServerId': server_params.get('template'),
|
|
|
|
'isManagedOS': server_params.get('managed_os'),
|
|
|
|
'primaryDNS': server_params.get('primary_dns'),
|
|
|
|
'secondaryDNS': server_params.get('secondary_dns'),
|
|
|
|
'networkId': server_params.get('network_id'),
|
|
|
|
'ipAddress': server_params.get('ip_address'),
|
|
|
|
'password': server_params.get('password'),
|
|
|
|
'sourceServerPassword': server_params.get('source_server_password'),
|
|
|
|
'cpu': server_params.get('cpu'),
|
|
|
|
'cpuAutoscalePolicyId': server_params.get('cpu_autoscale_policy_id'),
|
|
|
|
'memoryGB': server_params.get('memory'),
|
|
|
|
'type': server_params.get('type'),
|
|
|
|
'storageType': server_params.get('storage_type'),
|
|
|
|
'antiAffinityPolicyId': server_params.get('anti_affinity_policy_id'),
|
|
|
|
'customFields': server_params.get('custom_fields'),
|
|
|
|
'additionalDisks': server_params.get('additional_disks'),
|
|
|
|
'ttl': server_params.get('ttl'),
|
2015-10-21 19:36:51 +02:00
|
|
|
'packages': server_params.get('packages'),
|
|
|
|
'configurationId': server_params.get('configuration_id'),
|
|
|
|
'osType': server_params.get('os_type')}))
|
2015-07-27 20:59:16 +02:00
|
|
|
|
|
|
|
result = clc.v2.Requests(res)
|
|
|
|
except APIFailedResponse as ex:
|
|
|
|
return module.fail_json(msg='Unable to create the server: {0}. {1}'.format(
|
|
|
|
server_params.get('name'),
|
|
|
|
ex.response_text
|
|
|
|
))
|
|
|
|
|
|
|
|
#
|
|
|
|
# Patch the Request object so that it returns a valid server
|
|
|
|
|
|
|
|
# Find the server's UUID from the API response
|
|
|
|
server_uuid = [obj['id']
|
|
|
|
for obj in res['links'] if obj['rel'] == 'self'][0]
|
|
|
|
|
|
|
|
# Change the request server method to a _find_server_by_uuid closure so
|
|
|
|
# that it will work
|
|
|
|
result.requests[0].Server = lambda: ClcServer._find_server_by_uuid_w_retry(
|
|
|
|
clc,
|
|
|
|
module,
|
|
|
|
server_uuid,
|
|
|
|
server_params.get('alias'))
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _get_anti_affinity_policy_id(clc, module, alias, aa_policy_name):
|
|
|
|
"""
|
|
|
|
retrieves the anti affinity policy id of the server based on the name of the policy
|
|
|
|
:param clc: the clc-sdk instance to use
|
|
|
|
:param module: the AnsibleModule object
|
|
|
|
:param alias: the CLC account alias
|
|
|
|
:param aa_policy_name: the anti affinity policy name
|
|
|
|
:return: aa_policy_id: The anti affinity policy id
|
|
|
|
"""
|
|
|
|
aa_policy_id = None
|
|
|
|
try:
|
|
|
|
aa_policies = clc.v2.API.Call(method='GET',
|
|
|
|
url='antiAffinityPolicies/%s' % alias)
|
|
|
|
except APIFailedResponse as ex:
|
|
|
|
return module.fail_json(msg='Unable to fetch anti affinity policies for account: {0}. {1}'.format(
|
|
|
|
alias, ex.response_text))
|
|
|
|
for aa_policy in aa_policies.get('items'):
|
|
|
|
if aa_policy.get('name') == aa_policy_name:
|
|
|
|
if not aa_policy_id:
|
|
|
|
aa_policy_id = aa_policy.get('id')
|
|
|
|
else:
|
|
|
|
return module.fail_json(
|
|
|
|
msg='multiple anti affinity policies were found with policy name : %s' % aa_policy_name)
|
|
|
|
return aa_policy_id
|
|
|
|
|
|
|
|
#
|
|
|
|
# This is the function that gets patched to the Request.server object using a lamda closure
|
|
|
|
#
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _find_server_by_uuid_w_retry(
|
|
|
|
clc, module, svr_uuid, alias=None, retries=5, back_out=2):
|
|
|
|
"""
|
|
|
|
Find the clc server by the UUID returned from the provisioning request. Retry the request if a 404 is returned.
|
|
|
|
:param clc: the clc-sdk instance to use
|
|
|
|
:param module: the AnsibleModule object
|
|
|
|
:param svr_uuid: UUID of the server
|
|
|
|
:param retries: the number of retry attempts to make prior to fail. default is 5
|
|
|
|
:param alias: the Account Alias to search
|
|
|
|
:return: a clc-sdk.Server instance
|
|
|
|
"""
|
|
|
|
if not alias:
|
|
|
|
alias = clc.v2.Account.GetAlias()
|
|
|
|
|
|
|
|
# Wait and retry if the api returns a 404
|
|
|
|
while True:
|
|
|
|
retries -= 1
|
|
|
|
try:
|
|
|
|
server_obj = clc.v2.API.Call(
|
|
|
|
method='GET', url='servers/%s/%s?uuid=true' %
|
|
|
|
(alias, svr_uuid))
|
|
|
|
server_id = server_obj['id']
|
|
|
|
server = clc.v2.Server(
|
|
|
|
id=server_id,
|
|
|
|
alias=alias,
|
|
|
|
server_obj=server_obj)
|
|
|
|
return server
|
|
|
|
|
|
|
|
except APIFailedResponse as e:
|
|
|
|
if e.response_status_code != 404:
|
|
|
|
return module.fail_json(
|
|
|
|
msg='A failure response was received from CLC API when '
|
|
|
|
'attempting to get details for a server: UUID=%s, Code=%i, Message=%s' %
|
|
|
|
(svr_uuid, e.response_status_code, e.message))
|
|
|
|
if retries == 0:
|
|
|
|
return module.fail_json(
|
|
|
|
msg='Unable to reach the CLC API after 5 attempts')
|
|
|
|
sleep(back_out)
|
|
|
|
back_out *= 2
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _set_user_agent(clc):
|
|
|
|
if hasattr(clc, 'SetRequestsSession'):
|
|
|
|
agent_string = "ClcAnsibleModule/" + __version__
|
|
|
|
ses = requests.Session()
|
|
|
|
ses.headers.update({"Api-Client": agent_string})
|
|
|
|
ses.headers['User-Agent'] += " " + agent_string
|
|
|
|
clc.SetRequestsSession(ses)
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
"""
|
|
|
|
The main function. Instantiates the module and calls process_request.
|
|
|
|
:return: none
|
|
|
|
"""
|
|
|
|
argument_dict = ClcServer._define_module_argument_spec()
|
|
|
|
module = AnsibleModule(supports_check_mode=True, **argument_dict)
|
|
|
|
clc_server = ClcServer(module)
|
|
|
|
clc_server.process_request()
|
|
|
|
|
|
|
|
from ansible.module_utils.basic import * # pylint: disable=W0614
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|