Julia Kreger 4f0b5ea593 Retry rebuild status check once
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
2015-01-27 09:43:32 -08:00

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()