cloudscale_server: refactor to implement update (#52683)

pull/53316/head
René Moser 6 years ago committed by GitHub
parent d1c0df9e92
commit aafc5538bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -6,6 +6,7 @@
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
from copy import deepcopy
from ansible.module_utils.basic import env_fallback from ansible.module_utils.basic import env_fallback
from ansible.module_utils.urls import fetch_url from ansible.module_utils.urls import fetch_url
from ansible.module_utils._text import to_text from ansible.module_utils._text import to_text
@ -27,6 +28,10 @@ class AnsibleCloudscaleBase(object):
def __init__(self, module): def __init__(self, module):
self._module = module self._module = module
self._auth_header = {'Authorization': 'Bearer %s' % module.params['api_token']} self._auth_header = {'Authorization': 'Bearer %s' % module.params['api_token']}
self._result = {
'changed': False,
'diff': dict(before=dict(), after=dict()),
}
def _get(self, api_call): def _get(self, api_call):
resp, info = fetch_url(self._module, API_URL + api_call, resp, info = fetch_url(self._module, API_URL + api_call,
@ -44,6 +49,13 @@ class AnsibleCloudscaleBase(object):
def _post_or_patch(self, api_call, method, data): def _post_or_patch(self, api_call, method, data):
headers = self._auth_header.copy() headers = self._auth_header.copy()
if data is not None: if data is not None:
# Sanitize data dictionary
# Deepcopy: Duplicate the data object for iteration, because
# iterating an object and changing it at the same time is insecure
for k, v in deepcopy(data).items():
if v is None:
del data[k]
data = self._module.jsonify(data) data = self._module.jsonify(data)
headers['Content-type'] = 'application/json' headers['Content-type'] = 'application/json'
@ -80,3 +92,9 @@ class AnsibleCloudscaleBase(object):
else: else:
self._module.fail_json(msg='Failure while calling the cloudscale.ch API with DELETE for ' self._module.fail_json(msg='Failure while calling the cloudscale.ch API with DELETE for '
'"%s".' % api_call, fetch_url_info=info) '"%s".' % api_call, fetch_url_info=info)
def get_result(self, resource):
if resource:
for k, v in resource.items():
self._result[k] = v
return self._result

@ -1,7 +1,8 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# (c) 2017, Gaudenz Steinlin <gaudenz.steinlin@cloudscale.ch> # Copyright: (c) 2017, Gaudenz Steinlin <gaudenz.steinlin@cloudscale.ch>
# Copyright: (c) 2019, René Moser <mail@renemoser.net>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
@ -18,32 +19,38 @@ DOCUMENTATION = '''
module: cloudscale_server module: cloudscale_server
short_description: Manages servers on the cloudscale.ch IaaS service short_description: Manages servers on the cloudscale.ch IaaS service
description: description:
- Create, start, stop and delete servers on the cloudscale.ch IaaS service. - Create, update, start, stop and delete servers on the cloudscale.ch IaaS service.
notes: notes:
- To create a new server at least the C(name), C(ssh_key), C(image) and C(flavor) options are required. - Since version 2.8, I(uuid) and I(name) or not mututally exclusive anymore.
- If more than one server with the name given by the C(name) option exists, execution is aborted. - If I(uuid) option is provided, it takes precedence over I(name) for server selection. This allows to update the server's name.
- Once a server is created all parameters except C(state) are read-only. You can't change the name, flavor or any other property. This is a limitation - If no I(uuid) option is provided, I(name) is used for server selection. If more than one server with this name exists, execution is aborted.
of the cloudscale.ch API. The module will silently ignore differences between the configured parameters and the running server if a server with the - Only the I(name) and I(flavor) are evaluated for the update.
correct name or UUID exists. Only state changes will be applied. - The option I(force=true) must be given to allow the reboot of existing running servers for applying the changes.
version_added: 2.3 version_added: '2.3'
author: "Gaudenz Steinlin (@gaudenz) <gaudenz.steinlin@cloudscale.ch>" author:
- Gaudenz Steinlin (@gaudenz)
- René Moser (@resmo)
options: options:
state: state:
description: description:
- State of the server - State of the server.
default: running
choices: [ running, stopped, absent ] choices: [ running, stopped, absent ]
default: running
type: str
name: name:
description: description:
- Name of the Server. - Name of the Server.
- Either C(name) or C(uuid) are required. These options are mutually exclusive. - Either I(name) or I(uuid) are required.
type: str
uuid: uuid:
description: description:
- UUID of the server. - UUID of the server.
- Either C(name) or C(uuid) are required. These options are mutually exclusive. - Either I(name) or I(uuid) are required.
type: str
flavor: flavor:
description: description:
- Flavor of the server. - Flavor of the server.
type: str
image: image:
description: description:
- Image used to create the server. - Image used to create the server.
@ -51,46 +58,53 @@ options:
description: description:
- Size of the root volume in GB. - Size of the root volume in GB.
default: 10 default: 10
type: int
bulk_volume_size_gb: bulk_volume_size_gb:
description: description:
- Size of the bulk storage volume in GB. - Size of the bulk storage volume in GB.
- No bulk storage volume if not set. - No bulk storage volume if not set.
type: int
ssh_keys: ssh_keys:
description: description:
- List of SSH public keys. - List of SSH public keys.
- Use the full content of your .pub file here. - Use the full content of your .pub file here.
type: list
use_public_network: use_public_network:
description: description:
- Attach a public network interface to the server. - Attach a public network interface to the server.
default: True default: yes
type: bool type: bool
use_private_network: use_private_network:
description: description:
- Attach a private network interface to the server. - Attach a private network interface to the server.
default: False default: no
type: bool type: bool
use_ipv6: use_ipv6:
description: description:
- Enable IPv6 on the public network interface. - Enable IPv6 on the public network interface.
default: True default: yes
type: bool type: bool
anti_affinity_with: anti_affinity_with:
description: description:
- UUID of another server to create an anti-affinity group with. - UUID of another server to create an anti-affinity group with.
type: str
user_data: user_data:
description: description:
- Cloud-init configuration (cloud-config) data to use for the server. - Cloud-init configuration (cloud-config) data to use for the server.
type: str
api_timeout: api_timeout:
version_added: '2.5'
force:
description: description:
- Timeout in seconds for calls to the cloudscale.ch API. - Allow to stop the running server for updating if necessary.
default: 30 default: no
version_added: "2.5" type: bool
version_added: '2.8'
extends_documentation_fragment: cloudscale extends_documentation_fragment: cloudscale
''' '''
EXAMPLES = ''' EXAMPLES = '''
# Start a server (if it does not exist) and register the server details # Start a server (if it does not exist) and register the server details
- name: Start cloudscale.ch server - name: Start cloudscale.ch server
cloudscale_server: cloudscale_server:
name: my-shiny-cloudscale-server name: my-shiny-cloudscale-server
@ -112,6 +126,19 @@ EXAMPLES = '''
anti_affinity_with: '{{ server1.uuid }}' anti_affinity_with: '{{ server1.uuid }}'
api_token: xxxxxx api_token: xxxxxx
# Force to update the flavor of a running server
- name: Start cloudscale.ch server
cloudscale_server:
name: my-shiny-cloudscale-server
image: debian-8
flavor: flex-8
force: yes
ssh_keys: ssh-rsa XXXXXXXXXX...XXXX ansible@cloudscale
use_private_network: True
bulk_volume_size_gb: 100
api_token: xxxxxx
register: server1
# Stop the first server # Stop the first server
- name: Stop my first server - name: Stop my first server
cloudscale_server: cloudscale_server:
@ -135,7 +162,7 @@ EXAMPLES = '''
ssh_keys: ssh-rsa XXXXXXXXXXX ansible@cloudscale ssh_keys: ssh-rsa XXXXXXXXXXX ansible@cloudscale
api_token: xxxxxx api_token: xxxxxx
register: server register: server
until: server.ssh_fingerprints until: server.ssh_fingerprints is defined and server.ssh_fingerprints
retries: 60 retries: 60
delay: 2 delay: 2
''' '''
@ -198,7 +225,6 @@ anti_affinity_with:
sample: [] sample: []
''' '''
import os
from datetime import datetime, timedelta from datetime import datetime, timedelta
from time import sleep from time import sleep
from copy import deepcopy from copy import deepcopy
@ -217,31 +243,44 @@ class AnsibleCloudscaleServer(AnsibleCloudscaleBase):
def __init__(self, module): def __init__(self, module):
super(AnsibleCloudscaleServer, self).__init__(module) super(AnsibleCloudscaleServer, self).__init__(module)
# Check if server already exists and load properties
uuid = self._module.params['uuid']
name = self._module.params['name']
# Initialize server dictionary # Initialize server dictionary
self.info = {'uuid': uuid, 'name': name, 'state': 'absent'} self._info = {}
servers = self.list_servers() def _init_server_container(self):
matching_server = [] return {
for s in servers: 'uuid': self._module.params.get('uuid') or self._info.get('uuid'),
if uuid: 'name': self._module.params.get('name') or self._info.get('name'),
# Look for server by UUID if given 'state': 'absent',
if s['uuid'] == uuid: }
self.info = self._transform_state(s)
break def _get_server_info(self, refresh=False):
else: if self._info and not refresh:
# Look for server by name return self._info
if s['name'] == name:
matching_server.append(s) self._info = self._init_server_container()
uuid = self._info.get('uuid')
if uuid is not None:
server_info = self._get('servers/%s' % uuid)
if server_info:
self._info = self._transform_state(server_info)
else: else:
if len(matching_server) == 1: name = self._info.get('name')
self.info = self._transform_state(matching_server[0]) if name is not None:
elif len(matching_server) > 1: servers = self._get('servers') or []
self._module.fail_json(msg="More than one server with name '%s' exists. " matching_server = []
"Use the 'uuid' parameter to identify the server." % name) for server in servers:
if server['name'] == name:
matching_server.append(server)
if len(matching_server) == 1:
self._info = self._transform_state(matching_server[0])
elif len(matching_server) > 1:
self._module.fail_json(msg="More than one server with name '%s' exists. "
"Use the 'uuid' parameter to identify the server." % name)
return self._info
@staticmethod @staticmethod
def _transform_state(server): def _transform_state(server):
@ -252,80 +291,153 @@ class AnsibleCloudscaleServer(AnsibleCloudscaleBase):
server['state'] = 'absent' server['state'] = 'absent'
return server return server
def update_info(self): def _wait_for_state(self, states):
# If we don't have a UUID (yet) there is nothing to update
if 'uuid' not in self.info:
return
url_path = 'servers/' + self.info['uuid']
resp = self._get(url_path)
if resp:
self.info = self._transform_state(resp)
else:
self.info = {'uuid': self.info['uuid'],
'name': self.info.get('name', None),
'state': 'absent'}
def wait_for_state(self, states):
start = datetime.now() start = datetime.now()
timeout = self._module.params['api_timeout'] * 2 timeout = self._module.params['api_timeout'] * 2
while datetime.now() - start < timedelta(seconds=timeout): while datetime.now() - start < timedelta(seconds=timeout):
self.update_info() server_info = self._get_server_info(refresh=True)
if self.info['state'] in states: if server_info.get('state') in states:
return True return server_info
sleep(1) sleep(1)
self._module.fail_json(msg='Timeout while waiting for a state change on server %s to states %s. Current state is %s.' # Timeout succeeded
% (self.info['name'], states, self.info['state'])) if server_info.get('name') is not None:
msg = "Timeout while waiting for a state change on server %s to states %s. " \
def create_server(self): "Current state is %s." % (server_info.get('name'), states, server_info.get('state'))
data = self._module.params.copy() else:
name_uuid = self._module.params.get('name') or self._module.params.get('uuid')
# check for required parameters to create a server msg = 'Timeout while waiting to find the server %s' % name_uuid
missing_parameters = []
for p in ('name', 'ssh_keys', 'image', 'flavor'): self._module.fail_json(msg=msg)
if p not in data or not data[p]:
missing_parameters.append(p) def _start_stop_server(self, server_info, target_state="running", ignore_diff=False):
actions = {
if len(missing_parameters) > 0: 'stopped': 'stop',
self._module.fail_json(msg='Missing required parameter(s) to create a new server: %s.' % 'running': 'start',
' '.join(missing_parameters)) }
# Deepcopy: Duplicate the data object for iteration, because server_state = server_info.get('state')
# iterating an object and changing it at the same time is insecure if server_state != target_state:
self._result['changed'] = True
# Sanitize data dictionary
for k, v in deepcopy(data).items(): if not ignore_diff:
self._result['diff']['before'].update({
# Remove items not relevant to the create server call 'state': server_info.get('state'),
if k in ('api_token', 'api_timeout', 'uuid', 'state'): })
del data[k] self._result['diff']['after'].update({
continue 'state': target_state,
})
# Remove None values, these don't get correctly translated by urlencode if not self._module.check_mode:
if v is None: self._post('servers/%s/%s' % (server_info['uuid'], actions[target_state]))
del data[k] server_info = self._wait_for_state((target_state, ))
continue
return server_info
self.info = self._transform_state(self._post('servers', data))
self.wait_for_state(('running', )) def _update_param(self, param_key, server_info, requires_stop=False):
param_value = self._module.params.get(param_key)
def delete_server(self): if param_value is None:
self._delete('servers/%s' % self.info['uuid']) return server_info
self.wait_for_state(('absent', ))
if 'slug' in server_info[param_key]:
def start_server(self): server_v = server_info[param_key]['slug']
self._post('servers/%s/start' % self.info['uuid']) else:
self.wait_for_state(('running', )) server_v = server_info[param_key]
if server_v != param_value:
# Set the diff output
self._result['diff']['before'].update({param_key: server_v})
self._result['diff']['after'].update({param_key: param_value})
if server_info.get('state') == "running":
if requires_stop and not self._module.params.get('force'):
self._module.warn("Some changes won't be applied to running servers. "
"Use force=yes to allow the server '%s' to be stopped/started." % server_info['name'])
return server_info
# Either the server is stopped or change is forced
self._result['changed'] = True
if not self._module.check_mode:
if requires_stop:
self._start_stop_server(server_info, target_state="stopped", ignore_diff=True)
patch_data = {
param_key: param_value,
}
# Response is 204: No Content
self._patch('servers/%s' % server_info['uuid'], patch_data)
# State changes to "changing" after update, waiting for stopped/running
server_info = self._wait_for_state(('stopped', 'running'))
return server_info
def _create_server(self, server_info):
self._result['changed'] = True
required_params = ('name', 'ssh_keys', 'image', 'flavor')
self._module.fail_on_missing_params(required_params)
params = self._module.params
data = {
'name': params['name'],
'image': params['image'],
'flavor': params['flavor'],
'volume_size_gb': params['volume_size_gb'],
'bulk_volume_size_gb': params['bulk_volume_size_gb'],
'ssh_keys': params['ssh_keys'],
'use_public_network': params['use_public_network'],
'use_ipv6': params['use_ipv6'],
'anti_affinity_with': params['anti_affinity_with'],
'user_data': params['user_data'],
}
self._result['diff']['before'] = self._init_server_container()
self._result['diff']['after'] = deepcopy(data)
if not self._module.check_mode:
self._post('servers', data)
server_info = self._wait_for_state(('running', ))
return server_info
def _update_server(self, server_info):
previous_state = server_info.get('state')
server_info = self._update_param('flavor', server_info, requires_stop=True)
server_info = self._update_param('name', server_info)
if previous_state == "running":
server_info = self._start_stop_server(server_info, target_state="running", ignore_diff=True)
return server_info
def present_server(self):
server_info = self._get_server_info()
if server_info.get('state') != "absent":
# If target state is stopped, stop before an potential update and force would not be required
if self._module.params.get('state') == "stopped":
server_info = self._start_stop_server(server_info, target_state="stopped")
server_info = self._update_server(server_info)
if self._module.params.get('state') == "running":
server_info = self._start_stop_server(server_info, target_state="running")
else:
server_info = self._create_server(server_info)
server_info = self._start_stop_server(server_info, target_state=self._module.params.get('state'))
def stop_server(self): return server_info
self._post('servers/%s/stop' % self.info['uuid'])
self.wait_for_state(('stopped', ))
def list_servers(self): def absent_server(self):
return self._get('servers') or [] server_info = self._get_server_info()
if server_info.get('state') != "absent":
self._result['changed'] = True
self._result['diff']['before'] = deepcopy(server_info)
self._result['diff']['after'] = self._init_server_container()
if not self._module.check_mode:
self._delete('servers/%s' % server_info['uuid'])
server_info = self._wait_for_state(('absent', ))
return server_info
def main(): def main():
@ -344,47 +456,23 @@ def main():
use_ipv6=dict(type='bool', default=True), use_ipv6=dict(type='bool', default=True),
anti_affinity_with=dict(), anti_affinity_with=dict(),
user_data=dict(), user_data=dict(),
force=dict(type='bool', default=False)
)) ))
module = AnsibleModule( module = AnsibleModule(
argument_spec=argument_spec, argument_spec=argument_spec,
required_one_of=(('name', 'uuid'),), required_one_of=(('name', 'uuid'),),
mutually_exclusive=(('name', 'uuid'),),
supports_check_mode=True, supports_check_mode=True,
) )
target_state = module.params['state'] cloudscale_server = AnsibleCloudscaleServer(module)
server = AnsibleCloudscaleServer(module) if module.params['state'] == "absent":
# The server could be in a changing or error state. server = cloudscale_server.absent_server()
# Wait for one of the allowed states before doing anything. else:
# If an allowed state can't be reached, this module fails. server = cloudscale_server.present_server()
if server.info['state'] not in ALLOWED_STATES:
server.wait_for_state(ALLOWED_STATES) result = cloudscale_server.get_result(server)
current_state = server.info['state'] module.exit_json(**result)
if module.check_mode:
module.exit_json(changed=not target_state == current_state,
**server.info)
changed = False
if current_state == 'absent' and target_state == 'running':
server.create_server()
changed = True
elif current_state == 'absent' and target_state == 'stopped':
server.create_server()
server.stop_server()
changed = True
elif current_state == 'stopped' and target_state == 'running':
server.start_server()
changed = True
elif current_state in ('running', 'stopped') and target_state == 'absent':
server.delete_server()
changed = True
elif current_state == 'running' and target_state == 'stopped':
server.stop_server()
changed = True
module.exit_json(changed=changed, **server.info)
if __name__ == '__main__': if __name__ == '__main__':

@ -0,0 +1,2 @@
---
cloudscale_test_flavor_2: flex-4

@ -39,6 +39,153 @@
- server is not changed - server is not changed
- server.state == 'running' - server.state == 'running'
- name: Test update flavor of a running server without force in check mode
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: no
register: server
check_mode: yes
- name: Verify update flavor of a running server without force in check mode
assert:
that:
- server is not changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- name: Test update flavor of a running server without force
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: no
register: server
- name: Verify update flavor of a running server without force
assert:
that:
- server is not changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- name: Test update flavor of a running server without force idempotence
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: no
register: server
- name: Verify update flavor of a running server without force idempotence
assert:
that:
- server is not changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- name: Test update flavor and name of a running server without force in check mode
cloudscale_server:
uuid: '{{ server.uuid }}'
name: '{{ cloudscale_resource_prefix }}-test-renamed'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: no
register: server
check_mode: yes
- name: Verify update flavor and name of a running server without force in check mode
assert:
that:
- server is changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- server.name == '{{ cloudscale_resource_prefix }}-test'
- name: Test update flavor and name of a running server without force
cloudscale_server:
uuid: '{{ server.uuid }}'
name: '{{ cloudscale_resource_prefix }}-test-renamed'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: no
register: server
- name: Verify update flavor and name of a running server without force
assert:
that:
- server is changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- server.name == '{{ cloudscale_resource_prefix }}-test-renamed'
- name: Test update flavor and name of a running server without force idempotence
cloudscale_server:
uuid: '{{ server.uuid }}'
name: '{{ cloudscale_resource_prefix }}-test-renamed'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: no
register: server
- name: Verify update flavor and name of a running server without force idempotence
assert:
that:
- server is not changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- server.name == '{{ cloudscale_resource_prefix }}-test-renamed'
- name: Test update flavor of a running server with force in check mode
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-renamed'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: yes
register: server
check_mode: yes
- name: Verify update flavor of a running server with force in check mode
assert:
that:
- server is changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- server.name == '{{ cloudscale_resource_prefix }}-test-renamed'
- name: Test update flavor of a running server with force
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-renamed'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: yes
register: server
- name: Verify update flavor of a running server with force
assert:
that:
- server is changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor_2 }}'
- server.name == '{{ cloudscale_resource_prefix }}-test-renamed'
- name: Test update a running server with force idempotence
cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-renamed'
flavor: '{{ cloudscale_test_flavor_2 }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
force: yes
register: server
- name: Verify update flavor of a running server with force idempotence
assert:
that:
- server is not changed
- server.state == 'running'
- server.flavor.slug == '{{ cloudscale_test_flavor_2 }}'
- server.name == '{{ cloudscale_resource_prefix }}-test-renamed'
- name: Test create server stopped in check mode - name: Test create server stopped in check mode
cloudscale_server: cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test-stopped' name: '{{ cloudscale_resource_prefix }}-test-stopped'
@ -91,11 +238,11 @@
assert: assert:
that: that:
- server_failed is failed - server_failed is failed
- "'Missing required parameter(s)' in server_failed.msg" - "'missing required arguments' in server_failed.msg"
- name: Test stop running server in check mode - name: Test stop running server in check mode
cloudscale_server: cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test' name: '{{ cloudscale_resource_prefix }}-test-renamed'
state: stopped state: stopped
check_mode: yes check_mode: yes
register: server register: server
@ -107,7 +254,7 @@
- name: Test stop running server - name: Test stop running server
cloudscale_server: cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test' name: '{{ cloudscale_resource_prefix }}-test-renamed'
state: stopped state: stopped
register: server register: server
- name: Verify stop running server - name: Verify stop running server
@ -118,7 +265,7 @@
- name: Test stop running server idempotence - name: Test stop running server idempotence
cloudscale_server: cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test' name: '{{ cloudscale_resource_prefix }}-test-renamed'
state: 'stopped' state: 'stopped'
register: server register: server
- name: Verify stop running server idempotence - name: Verify stop running server idempotence
@ -127,6 +274,59 @@
- server is not changed - server is not changed
- server.state == 'stopped' - server.state == 'stopped'
- name: Test update a stopped server in check mode
cloudscale_server:
uuid: '{{ server.uuid }}'
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
state: stopped
register: server
check_mode: yes
- name: Verify update a stopped server in check mode
assert:
that:
- server is changed
- server.state == 'stopped'
- server.flavor.slug == '{{ cloudscale_test_flavor_2 }}'
- server.name == '{{ cloudscale_resource_prefix }}-test-renamed'
- name: Test update a stopped server without force
cloudscale_server:
uuid: '{{ server.uuid }}'
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
state: stopped
register: server
- name: Verify update a stopped server without force
assert:
that:
- server is changed
- server.state == 'stopped'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- server.name == '{{ cloudscale_resource_prefix }}-test'
- name: Test update a stopped server idempotence
cloudscale_server:
uuid: '{{ server.uuid }}'
name: '{{ cloudscale_resource_prefix }}-test'
flavor: '{{ cloudscale_test_flavor }}'
image: '{{ cloudscale_test_image }}'
ssh_keys: '{{ cloudscale_test_ssh_key }}'
state: stopped
register: server
- name: Verify update a stopped server idempotence
assert:
that:
- server is not changed
- server.state == 'stopped'
- server.flavor.slug == '{{ cloudscale_test_flavor }}'
- server.name == '{{ cloudscale_resource_prefix }}-test'
- name: Test server running in check mode - name: Test server running in check mode
cloudscale_server: cloudscale_server:
name: '{{ cloudscale_resource_prefix }}-test' name: '{{ cloudscale_resource_prefix }}-test'

Loading…
Cancel
Save