
In a VM testing environment, there is a possibility for transient network connectivity errors to cause an exception for the rebuild status check, where in reality it will continue without issues. We will now attempt to retry once in the event of an exception occuring. Change-Id: I3e4d841a17aa53362710b32e86ee24f0c32a27da
269 lines
11 KiB
Python
269 lines
11 KiB
Python
#!/usr/bin/env python
|
|
# This code is part of Ansible, but is an independent component.
|
|
# This particular file snippet, and this file snippet only, is BSD licensed.
|
|
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
|
# still belong to the author of the module, and may assign their own license
|
|
# to the complete work.
|
|
#
|
|
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without modification,
|
|
# are permitted provided that the following conditions are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
# this list of conditions and the following disclaimer in the documentation
|
|
# and/or other materials provided with the distribution.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import os
|
|
import sys
|
|
import traceback
|
|
|
|
try:
|
|
from novaclient.v1_1 import client as nova_client
|
|
from novaclient import exceptions
|
|
import time
|
|
except ImportError:
|
|
print("failed=True msg='novaclient is required for this module'")
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: nova_rebuild
|
|
short_description: Rebuild VMs from OpenStack
|
|
description:
|
|
- Rebuild (re-image) virtual machines from Openstack.
|
|
options:
|
|
login_username:
|
|
description:
|
|
- login username to authenticate to keystone
|
|
required: true
|
|
default: admin
|
|
login_password:
|
|
description:
|
|
- Password of login user
|
|
required: true
|
|
default: 'yes'
|
|
login_tenant_name:
|
|
description:
|
|
- The tenant name of the login user
|
|
required: true
|
|
default: 'yes'
|
|
auth_url:
|
|
description:
|
|
- The keystone url for authentication
|
|
required: false
|
|
default: 'http://127.0.0.1:35357/v2.0/'
|
|
region_name:
|
|
description:
|
|
- Name of the region
|
|
required: false
|
|
default: None
|
|
name:
|
|
description:
|
|
- Name of the instance to be rebuilt
|
|
default: None
|
|
instance_id:
|
|
description:
|
|
- Instance ID of the instance to be rebuilt
|
|
default: None
|
|
image_id:
|
|
description:
|
|
- The id of the image that has to be cloned
|
|
required: true
|
|
default: None
|
|
preserve_ephemeral:
|
|
description:
|
|
- Whether to preserve ephemeral storage on the instance
|
|
required: false
|
|
default: false
|
|
wait:
|
|
description:
|
|
- If the module should wait for the VM to be created.
|
|
required: false
|
|
default: 'yes'
|
|
wait_for:
|
|
description:
|
|
- Number of seconds the module should wait for the VM to get into active state
|
|
required: false
|
|
default: 600
|
|
requirements: ["novaclient"]
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
# Rebuilds an existing VM with a new image
|
|
- nova_rebuild:
|
|
login_username: admin
|
|
login_password: admin
|
|
login_tenant_name: admin
|
|
name: vm1
|
|
image_id: 4f905f38-e52a-43d2-b6ec-754a13ffb529
|
|
wait_for: 200
|
|
'''
|
|
|
|
|
|
# The following two openstack_ are copy paste from an upcoming
|
|
# core module "lib/ansible/module_utils/openstack.py" Once that's landed,
|
|
# these should be replaced with a line at the bottom of the file:
|
|
# from ansible.module_utils.openstack import *
|
|
def openstack_argument_spec():
|
|
# Consume standard OpenStack environment variables.
|
|
# This is mainly only useful for ad-hoc command line operation as
|
|
# in playbooks one would assume variables would be used appropriately
|
|
OS_AUTH_URL=os.environ.get('OS_AUTH_URL', 'http://127.0.0.1:35357/v2.0/')
|
|
OS_PASSWORD=os.environ.get('OS_PASSWORD', None)
|
|
OS_REGION_NAME=os.environ.get('OS_REGION_NAME', None)
|
|
OS_USERNAME=os.environ.get('OS_USERNAME', 'admin')
|
|
OS_TENANT_NAME=os.environ.get('OS_TENANT_NAME', OS_USERNAME)
|
|
|
|
spec = dict(
|
|
login_username = dict(default=OS_USERNAME),
|
|
auth_url = dict(default=OS_AUTH_URL),
|
|
region_name = dict(default=OS_REGION_NAME),
|
|
availability_zone = dict(default=None),
|
|
)
|
|
if OS_PASSWORD:
|
|
spec['login_password'] = dict(default=OS_PASSWORD)
|
|
else:
|
|
spec['login_password'] = dict(required=True)
|
|
if OS_TENANT_NAME:
|
|
spec['login_tenant_name'] = dict(default=OS_TENANT_NAME)
|
|
else:
|
|
spec['login_tenant_name'] = dict(required=True)
|
|
return spec
|
|
|
|
|
|
def openstack_find_nova_addresses(addresses, ext_tag, key_name=None):
|
|
|
|
ret = []
|
|
for (k, v) in addresses.iteritems():
|
|
if key_name and k == key_name:
|
|
ret.extend([addrs['addr'] for addrs in v])
|
|
else:
|
|
for interface_spec in v:
|
|
if 'OS-EXT-IPS:type' in interface_spec and interface_spec['OS-EXT-IPS:type'] == ext_tag:
|
|
ret.append(interface_spec['addr'])
|
|
return ret
|
|
|
|
|
|
def _rebuild_server(module, nova):
|
|
server = None
|
|
last_error = None
|
|
last_stacktrace = None
|
|
try:
|
|
if module.params.get('instance_id') is None:
|
|
servers = nova.servers.list(True, {'name': module.params['name']})
|
|
if servers:
|
|
# the {'name': module.params['name']} will also return servers
|
|
# with names that partially match the server name, so we have to
|
|
# strictly filter here
|
|
servers = [x for x in servers if x.name == module.params['name']]
|
|
if servers:
|
|
server = servers[0]
|
|
else:
|
|
server = nova.servers.get(module.params['instance_id'])
|
|
except Exception, e:
|
|
module.fail_json(msg = "Error in getting the server list: %s" % e.message)
|
|
if not server:
|
|
module.exit_json(changed = False, result = "not present")
|
|
|
|
bootargs = [server, module.params['image_id']]
|
|
bootkwargs = {
|
|
'preserve_ephemeral' : module.params['preserve_ephemeral'],
|
|
}
|
|
try:
|
|
server = nova.servers.rebuild(*bootargs, **bootkwargs)
|
|
server = nova.servers.get(server.id)
|
|
except Exception, e:
|
|
module.fail_json( msg = "Error in rebuilding instance: %s " % e.message)
|
|
if module.params['wait'] == 'yes':
|
|
expire = time.time() + int(module.params['wait_for'])
|
|
while time.time() < expire:
|
|
try:
|
|
server = nova.servers.get(server.id)
|
|
except Exception, last_error:
|
|
# This logic attempts to retry a failed status query
|
|
# once before aborting and returning an error to
|
|
# Ansible. This helps prevent momentarilly transient
|
|
# issues from creating spurious issues.
|
|
#
|
|
# Ideally, we would change this logic to something
|
|
# along the lines of wait_for, although logging
|
|
# would need to be addressed so issues can be
|
|
# identified should errors occur.
|
|
last_error = traceback.format_exc
|
|
time.sleep(2)
|
|
try:
|
|
server = nova.servers.get(server.id)
|
|
except Exception, error:
|
|
module.fail_json(
|
|
msg = ("Error in getting info for instance: %s\n"
|
|
"Initial error: %s\n"
|
|
"Stack backtrace: %s\n"
|
|
"Terminal error: %s\n"
|
|
"Terminal stack backtrace: %s") % (
|
|
last_stracetrace,
|
|
last_error.message,
|
|
error.message,
|
|
traceback.format_exc
|
|
)
|
|
)
|
|
|
|
if server.status == 'ACTIVE':
|
|
private = openstack_find_nova_addresses(getattr(server, 'addresses'), 'fixed', 'private')
|
|
public = openstack_find_nova_addresses(getattr(server, 'addresses'), 'floating', 'public')
|
|
module.exit_json(changed = True, id = server.id, private_ip=''.join(private), public_ip=''.join(public), status = server.status, info = server._info)
|
|
if server.status == 'ERROR':
|
|
module.fail_json(msg = "Instance exited Nova Rebuild in an ERROR state, please check OpenStack logs")
|
|
time.sleep(2)
|
|
|
|
module.fail_json(msg = "Timeout waiting for the server to come up.. Please check manually")
|
|
if server.status == 'ERROR':
|
|
module.fail_json(msg = "Error in rebuilding the server.. Please check manually")
|
|
private = openstack_find_nova_addresses(getattr(server, 'addresses'), 'fixed', 'private')
|
|
public = openstack_find_nova_addresses(getattr(server, 'addresses'), 'floating', 'public')
|
|
module.exit_json(changed = True, id = info['id'], private_ip=''.join(private), public_ip=''.join(public), status = server.status, info = server._info)
|
|
|
|
|
|
def main():
|
|
argument_spec = openstack_argument_spec()
|
|
argument_spec.update(dict(
|
|
name = dict(),
|
|
instance_id = dict(),
|
|
image_id = dict(required=True),
|
|
preserve_ephemeral = dict(default=False, choices=[True, False]),
|
|
wait = dict(default='yes', choices=['yes', 'no']),
|
|
wait_for = dict(default=600),
|
|
))
|
|
module = AnsibleModule(argument_spec=argument_spec)
|
|
|
|
nova = nova_client.Client(module.params['login_username'],
|
|
module.params['login_password'],
|
|
module.params['login_tenant_name'],
|
|
module.params['auth_url'],
|
|
region_name=module.params['region_name'],
|
|
service_type='compute')
|
|
try:
|
|
nova.authenticate()
|
|
except exceptions.Unauthorized, e:
|
|
module.fail_json(msg = "Invalid OpenStack Nova credentials.: %s" % e.message)
|
|
except exceptions.AuthorizationFailure, e:
|
|
module.fail_json(msg = "Unable to authorize user: %s" % e.message)
|
|
|
|
_rebuild_server(module, nova)
|
|
|
|
# this is magic, see lib/ansible/module_common.py
|
|
from ansible.module_utils.basic import *
|
|
main()
|