crowbar/crowbar-openstack

View on GitHub
chef/cookbooks/nova/files/default/crowbar-nova-set-availability-zone

Summary

Maintainability
Test Coverage
#! /usr/bin/env python
# vim: sw=4 et
#
# Copyright 2014, SUSE
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import argparse
import os
import random
import string
import sys

from novaclient import client as nova_client


debug = False


parser = argparse.ArgumentParser(description='Add nova compute host to availability zone.')

parser.add_argument('host',
                    help='Compute host')
parser.add_argument('availability_zone',
                    help='Availability zone')

parser.add_argument('--os-username',
                    metavar='<auth-user-name>',
                    default=os.environ.get('OS_USERNAME', None),
                    help='Defaults to env[OS_USERNAME].')
parser.add_argument('--os_username',
                    help=argparse.SUPPRESS)

parser.add_argument('--os-password',
                    metavar='<auth-password>',
                    default=os.environ.get('OS_PASSWORD', None),
                    help='Defaults to env[OS_PASSWORD].')
parser.add_argument('--os_password',
                    help=argparse.SUPPRESS)

parser.add_argument('--os-tenant-name',
                    metavar='<auth-tenant-name>',
                    default=os.environ.get('OS_TENANT_NAME', None),
                    help='Defaults to env[OS_TENANT_NAME].')
parser.add_argument('--os_tenant_name',
                    help=argparse.SUPPRESS)

parser.add_argument('--os-auth-url',
                    metavar='<auth-url>',
                    default=os.environ.get('OS_AUTH_URL', None),
                    help='Defaults to env[OS_AUTH_URL].')
parser.add_argument('--os_auth_url',
                    help=argparse.SUPPRESS)

parser.add_argument('--os-region-name',
                    metavar='<region-name>',
                    default=os.environ.get('OS_REGION_NAME', None),
                    help='Name of the region.')

parser.add_argument('--os_region_name',
                    help=argparse.SUPPRESS)

parser.add_argument('--endpoint-type',
                    metavar='<endpoint-type>',
                    default='internalURL',
                    help='Defaults to internalURL.')

parser.add_argument('--insecure',
                    default=False,
                    action='store_true',
                    help="Explicitly allow novaclient to perform \"insecure\" "
                         "SSL (https) requests. The server's certificate will "
                         "not be verified against any certificate authorities. "
                         "This option should be used with caution.")

parser.add_argument('--debug',
                    default=False,
                    action='store_true',
                    help="Print debugging output")


args = parser.parse_args()
debug = args.debug
target_host_name = args.host
target_availability_zone = args.availability_zone

if not args.os_username:
    print >> sys.stderr, 'You must provide a username via either --os-username or env[OS_USERNAME]'
    sys.exit(1)
if not args.os_password:
    print >> sys.stderr, 'You must provide a password via either --os-password or env[OS_PASSWORD]'
    sys.exit(1)
if not args.os_tenant_name:
    print >> sys.stderr, 'You must provide a tenant via either --os-tenant-name or via env[OS_TENANT_NAME]'
    sys.exit(1)
if not args.os_auth_url:
    print >> sys.stderr, 'You must provide an auth url via either --os-auth-url or via env[OS_AUTH_URL]'
    sys.exit(1)

c = nova_client.Client("2",
                       args.os_username,
                       args.os_password,
                       args.os_tenant_name,
                       auth_url=args.os_auth_url,
                       region_name=args.os_region_name,
                       endpoint_type=args.endpoint_type,
                       insecure=args.insecure,
                       http_log_debug=args.debug)


def debug_print(s):
    if debug:
        print s


def random_name(basis, length=6):
    rand = ''.join(random.choice(string.letters + string.digits) for i in range(length))
    return '%s-%s' % (basis, rand)


if target_availability_zone:
    debug_print("Goal: move %s to availability zone \'%s\'" % (target_host_name, target_availability_zone))
else:
    debug_print("Goal: move %s to default availability zone (or leave in non-Crowbar-owned availability zone)" % target_host_name)

# Find host
try:
    hosts = c.hosts.list()
except Exception as e:
    print >> sys.stderr, 'Cannot fetch list of nova hosts: %s' % e
    # Treat this as temp failure as likely its just the API is not up right now
    sys.exit(69)

try:
    host = [host for host in hosts if host.service == 'compute' and host.host_name == target_host_name][0]
except IndexError:
    print >> sys.stderr, 'Host %s not known as compute host (yet?)' % target_host_name
    sys.exit(68)

if host.zone == target_availability_zone:
    debug_print("%s is already in availability zone \'%s\'" % (host.host_name, host.zone))
    sys.exit(0)

debug_print("%s is in availability zone \'%s\'..." % (host.host_name, host.zone))

# Get info about aggregates
old_aggregate = None
target_aggregate = None

aggregates = c.aggregates.list()
for aggregate in aggregates:
    if aggregate.availability_zone == target_availability_zone:
        target_aggregate = aggregate
    elif aggregate.availability_zone == host.zone:
        old_aggregate = aggregate


# If no target availability zone, then we don't do anything if node is in the default AZ or in a AZ not by created by crowbar
if not target_availability_zone and (old_aggregate is None or not old_aggregate.name.startswith('crowbar-AZ-')):
    sys.exit(0)


# Create target aggregate if it doesn't exist yet
if target_aggregate is None and target_availability_zone:
    debug_print("No aggregate existing for availability zone \'%s\'; creating one..." % target_availability_zone)
    name = 'crowbar-AZ-%s' % random_name(target_availability_zone)
    try:
        target_aggregate = c.aggregates.create(name, target_availability_zone)
    except Exception as e:
        print >> sys.stderr, 'Cannot create aggregate for availability zone \'%s\': %s' % (target_availability_zone, e)
        sys.exit(1)


# Remove host from old aggregate
if old_aggregate is None:
    debug_print("Availability zone \'%s\' does not match any aggregate; possibly default availability zone..." % host.zone)
else:
    debug_print("Removing %s from availability zone \'%s\'..." % (host.host_name, host.zone))
    try:
        old_aggregate = c.aggregates.remove_host(old_aggregate.id, host.host_name)
    except Exception as e:
        print >> sys.stderr, 'Cannot remove %s from availability zone \'%s\': %s' % (host.host_name, host.zone, e)
        sys.exit(1)

    # remove aggregate if empty and created by crowbar
    if old_aggregate.name.startswith('crowbar-AZ-') and len(old_aggregate.hosts) == 0:
        debug_print("Removing empty aggregate for availability zone \'%s\'..." % old_aggregate.availability_zone)
        try:
            c.aggregates.delete(old_aggregate.id)
        except Exception as e:
            print >> sys.stderr, 'Cannot remove empty aggregate for availability zone \'%s\': %s' % (old_aggregate.availability_zone, e)
            # do not exit, go on happily


# Add host to target aggregate (if we don't want default availability zone)
if target_availability_zone:
    debug_print("Adding %s to availability zone \'%s\'..." % (host.host_name, target_availability_zone))
    try:
        c.aggregates.add_host(target_aggregate.id, host.host_name)
    except Exception as e:
        print >> sys.stderr, 'Cannot add %s to availability zone \'%s\': %s' % (host.host_name, target_availability_zone, e)
        sys.exit(1)