chef/cookbooks/nova/files/default/crowbar-nova-set-availability-zone
#! /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)