1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
|
#!/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 = r'''
---
module: server_action
short_description: Perform actions on OpenStack compute (Nova) instances
author: OpenStack Ansible SIG
description:
- Perform actions on OpenStack compute (Nova) instances aka servers.
options:
action:
description:
- Action to perform.
- By default, only server owners and administrators are allowed to
perform actions C(pause), C(unpause), C(suspend), C(resume), C(lock),
C(unlock) and C(shelve_offload).
choices: [lock, pause, reboot_hard, reboot_soft, rebuild, resume, shelve,
shelve_offload, start, stop, suspend, unlock, unpause, unshelve]
type: str
required: true
admin_password:
description:
- Admin password for server to rebuild.
type: str
all_projects:
description:
- Whether to search for server in all projects or the current project
only.
type: bool
default: false
image:
description:
- Image name or ID the server should be rebuilt with.
type: str
name:
description:
- Server name or ID.
required: true
type: str
aliases: ['server']
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = r'''
- name: Pauses a compute instance
openstack.cloud.server_action:
cloud: devstack-admin
action: pause
server: vm1
timeout: 200
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class ServerActionModule(OpenStackModule):
argument_spec = dict(
action=dict(required=True,
choices=['stop', 'start', 'pause', 'unpause',
'lock', 'unlock', 'suspend', 'reboot_soft',
'reboot_hard', 'resume', 'rebuild', 'shelve',
'shelve_offload', 'unshelve']),
admin_password=dict(no_log=True),
all_projects=dict(type='bool', default=False),
image=dict(),
name=dict(required=True, aliases=['server']),
)
module_kwargs = dict(
required_if=[('action', 'rebuild', ['image'])],
supports_check_mode=True,
)
# 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 offloaded.
# 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'],
'unlock': ['ACTIVE'],
'suspend': ['SUSPENDED'],
'reboot_soft': ['ACTIVE'],
'reboot_hard': ['ACTIVE'],
'resume': ['ACTIVE'],
'rebuild': ['ACTIVE'],
'shelve': ['SHELVED_OFFLOADED', 'SHELVED'],
'shelve_offload': ['SHELVED_OFFLOADED'],
'unshelve': ['ACTIVE']}
def run(self):
# TODO: Replace with self.conn.compute.find_server(
# self.params['name'], all_projects=self.params['all_projects'],
# ignore_missing=False) when [0] has been merged.
# [0] https://review.opendev.org/c/openstack/openstacksdk/+/857936/
server = self.conn.get_server(
name_or_id=self.params['name'],
detailed=True,
all_projects=self.params['all_projects'])
if not server:
self.fail_json(msg='No Server found for {0}'
.format(self.params['name']))
action = self.params['action']
# rebuild does not depend on state
will_change = (
(action == 'rebuild')
or (action == 'lock' and not server['is_locked'])
or (action == 'unlock' and server['is_locked'])
or server.status.lower() not in [a.lower()
for a
in self._action_map[action]])
if not will_change:
self.exit_json(changed=False)
elif self.ansible.check_mode:
self.exit_json(changed=True)
# else perform action
if action == 'rebuild':
# rebuild should ensure images exists
image = self.conn.image.find_image(self.params['image'],
ignore_missing=False)
kwargs = dict(server=server,
name=server['name'],
image=image['id'])
admin_password = self.params['admin_password']
if admin_password is not None:
kwargs['admin_password'] = admin_password
self.conn.compute.rebuild_server(**kwargs)
elif action == 'shelve_offload':
# TODO: Replace with shelve_offload function call when [0] has been
# merged.
# [0] https://review.opendev.org/c/openstack/openstacksdk/+/857947
# shelve_offload is not supported in openstacksdk <= 1.0.0
response = self.conn.compute.post(
'/servers/{server_id}/action'.format(server_id=server['id']),
json={'shelveOffload': None})
self.sdk.exceptions.raise_from_response(response)
else: # action != 'rebuild' and action != 'shelve_offload'
action_name = action + "_server"
# reboot_* actions are using reboot_server method with an
# additional argument
if action in ['reboot_soft', 'reboot_hard']:
action_name = 'reboot_server'
func_name = getattr(self.conn.compute, action_name)
# Do the action
if action == 'reboot_soft':
func_name(server, 'SOFT')
elif action == 'reboot_hard':
func_name(server, 'HARD')
else:
func_name(server)
if self.params['wait']:
for count in self.sdk.utils.iterate_timeout(
timeout=self.params['timeout'],
message='Timeout waiting for action {0} to be completed.'
.format(action)
):
server = self.conn.compute.get_server(server['id'])
if (action == 'lock' and server['is_locked']) \
or (action == 'unlock' and not server['is_locked']):
break
states = [s.lower() for s in self._action_map[action]]
if server.status.lower() in states:
break
self.exit_json(changed=True)
def main():
module = ServerActionModule()
module()
if __name__ == '__main__':
main()
|