Jakob Meng 1b38b7c500 Properly documented openstacksdk version requirements
With "extends_documentation_fragment: ['openstack.cloud.openstack']"
it is not necessary to list required Python libraries in section
'requirements' of DOCUMENTATION docstring in modules. Ansible will
merge requirements from doc fragments and DOCUMENTATION docstring
which previously resulted in duplicates such as in server module [0]:

* openstacksdk
* openstacksdk >= 0.36, < 0.99.0
* python >= 3.6

When removing the 'requirements' section from server module, then
Ansible will list openstacksdk once only:

* openstacksdk >= 0.36, < 0.99.0
* python >= 3.6

To see what documentation Ansible will produce for server module run:

  ansible-doc --type module openstack.cloud.server

[0] https://docs.ansible.com/ansible/latest/collections/openstack/\
    cloud/server_module.html

Change-Id: I727ed95ee480bb644b5a533f6a9526973677064c
2023-01-16 13:51:01 +01:00

246 lines
9.3 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Jesse Keating <jlk@derpops.bike>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
---
module: server_action
short_description: Perform actions on Compute Instances from OpenStack
author: OpenStack Ansible SIG
description:
- Perform server actions on an existing compute instance from OpenStack.
This module does not return any data other than changed true/false.
When I(action) is 'rebuild', then I(image) parameter is required.
options:
server:
description:
- Name or ID of the instance
required: true
type: str
wait:
description:
- If the module should wait for the instance action to be performed.
type: bool
default: 'yes'
timeout:
description:
- The amount of time the module should wait for the instance to perform
the requested action.
default: 180
type: int
action:
description:
- Perform the given action. The lock and unlock actions always return
changed as the servers API does not provide lock status.
choices: [stop, start, pause, unpause, lock, unlock, suspend,
reboot_soft, reboot_hard, resume, rebuild, shelve,
shelve_offload, unshelve]
type: str
required: true
image:
description:
- Image the server should be rebuilt with
type: str
admin_password:
description:
- Admin password for server to rebuild
type: str
all_projects:
description:
- Whether to search for server in all projects or just the current
auth scoped project.
type: bool
default: 'no'
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Pauses a compute instance
- openstack.cloud.server_action:
action: pause
auth:
auth_url: https://identity.example.com
username: admin
password: admin
project_name: admin
server: vm1
timeout: 200
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
# If I(action) is set to C(shelve) then according to OpenStack's Compute API, the shelved
# server is in one of two possible states:
#
# SHELVED: The server is in shelved state. Depends on the shelve offload time,
# the server will be automatically shelved off loaded.
# SHELVED_OFFLOADED: The shelved server is offloaded (removed from the compute host) and
# it needs unshelved action to be used again.
#
# But wait_for_server can only wait for a single server state. If a shelved server is offloaded
# immediately, then a exceptions.ResourceTimeout will be raised if I(action) is set to C(shelve).
# This is likely to happen because shelved_offload_time in Nova's config is set to 0 by default.
# This also applies if you boot the server from volumes.
#
# Calling C(shelve_offload) instead of C(shelve) will also fail most likely because the default
# policy does not allow C(shelve_offload) for non-admin users while C(shelve) is allowed for
# admin users and server owners.
#
# As we cannot retrieve shelved_offload_time from Nova's config, we fall back to waiting for
# one state and if that fails then we fetch the server's state and match it against the other
# valid states from _action_map.
#
# Ref.: https://docs.openstack.org/api-guide/compute/server_concepts.html
_action_map = {'stop': ['SHUTOFF'],
'start': ['ACTIVE'],
'pause': ['PAUSED'],
'unpause': ['ACTIVE'],
'lock': ['ACTIVE'], # API doesn't show lock/unlock status
'unlock': ['ACTIVE'],
'suspend': ['SUSPENDED'],
'reboot_soft': ['ACTIVE'],
'reboot_hard': ['ACTIVE'],
'resume': ['ACTIVE'],
'rebuild': ['ACTIVE'],
'shelve': ['SHELVED_OFFLOADED', 'SHELVED'],
'shelve_offload': ['SHELVED_OFFLOADED'],
'unshelve': ['ACTIVE']}
_admin_actions = ['pause', 'unpause', 'suspend', 'resume', 'lock', 'unlock', 'shelve_offload']
class ServerActionModule(OpenStackModule):
argument_spec = dict(
server=dict(required=True),
action=dict(required=True,
choices=['stop', 'start', 'pause', 'unpause',
'lock', 'unlock', 'suspend', 'reboot_soft', 'reboot_hard', 'resume',
'rebuild', 'shelve', 'shelve_offload', 'unshelve']),
image=dict(),
admin_password=dict(no_log=True),
all_projects=dict(type='bool', default=False),
)
module_kwargs = dict(
required_if=[('action', 'rebuild', ['image'])],
supports_check_mode=True,
)
def run(self):
os_server = self._preliminary_checks()
self._execute_server_action(os_server)
# for some reason we don't wait for lock and unlock before exit
if self.params['action'] not in ('lock', 'unlock'):
if self.params['wait']:
self._wait(os_server)
self.exit_json(changed=True)
def _preliminary_checks(self):
# Using Munch object for getting information about a server
os_server = self.conn.get_server(
self.params['server'],
all_projects=self.params['all_projects'],
)
if not os_server:
self.fail_json(msg='Could not find server %s' % self.params['server'])
# check mode
if self.ansible.check_mode:
self.exit_json(changed=self.__system_state_change(os_server))
# examine special cases
# lock, unlock and rebuild don't depend on state, just do it
if self.params['action'] not in ('lock', 'unlock', 'rebuild'):
if not self.__system_state_change(os_server):
self.exit_json(changed=False)
return os_server
def _execute_server_action(self, os_server):
if self.params['action'] == 'rebuild':
return self._rebuild_server(os_server)
if self.params['action'] == 'shelve_offload':
# shelve_offload is not supported in OpenstackSDK
return self._action(os_server, json={'shelveOffload': None})
action_name = self.params['action'] + "_server"
# reboot_* actions are using reboot_server method with an extra param
if self.params['action'] == 'reboot_soft' or self.params['action'] == 'reboot_hard':
action_name = 'reboot_server'
# Try to grab method from OpenstackSDK
try:
func_name = getattr(self.conn.compute, action_name)
except AttributeError:
self.fail_json(
msg="Method %s wasn't found in OpenstackSDK compute" % action_name)
# Do the action
if self.params['action'] == 'reboot_soft':
func_name(os_server, 'SOFT')
elif self.params['action'] == 'reboot_hard':
func_name(os_server, 'HARD')
else:
func_name(os_server)
def _rebuild_server(self, os_server):
# rebuild should ensure images exists
try:
image = self.conn.get_image(self.params['image'])
except Exception as e:
self.fail_json(
msg="Can't find the image %s: %s" % (self.params['image'], e))
if not image:
self.fail_json(msg="Image %s was not found!" % self.params['image'])
# admin_password is required by SDK, but not required by Nova API
if self.params['admin_password']:
self.conn.compute.rebuild_server(
server=os_server,
name=os_server['name'],
image=image['id'],
admin_password=self.params['admin_password']
)
else:
self._action(os_server, json={'rebuild': {'imageRef': image['id']}})
def _action(self, os_server, json):
response = self.conn.compute.post(
'/servers/{server_id}/action'.format(server_id=os_server['id']),
json=json)
self.sdk.exceptions.raise_from_response(response)
return response
def _wait(self, os_server):
"""Wait for the server to reach the desired state for the given action."""
# The wait_for_server function needs a Server object instead of the
# Munch object returned by self.conn.get_server
server = self.conn.compute.get_server(os_server['id'])
states = _action_map[self.params['action']]
try:
self.conn.compute.wait_for_server(
server,
status=states[0],
wait=self.params['timeout'])
except self.sdk.exceptions.ResourceTimeout:
# raise if there is only one valid state
if len(states) < 2:
raise
# fetch current server status and compare to other valid states
server = self.conn.compute.get_server(os_server['id'])
if server.status not in states:
raise
def __system_state_change(self, os_server):
"""Check if system state would change."""
return os_server.status not in _action_map[self.params['action']]
def main():
module = ServerActionModule()
module()
if __name__ == '__main__':
main()