You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
168 lines
6.2 KiB
168 lines
6.2 KiB
#!/usr/bin/env python |
|
|
|
import os |
|
import sys |
|
import time |
|
import inspect |
|
import logging |
|
import argparse |
|
import oslo_config.cfg |
|
import requests.exceptions |
|
|
|
def is_forced_down(connection, hostname): |
|
services = connection.services.list(host=hostname, binary="nova-compute") |
|
for service in services: |
|
if service.forced_down: |
|
return True |
|
return False |
|
|
|
def evacuations_done(connection, hostname): |
|
# Get a list of migrations. |
|
# :param host: (optional) filter migrations by host name. |
|
# :param status: (optional) filter migrations by status. |
|
# :param cell_name: (optional) filter migrations for a cell. |
|
# |
|
migrations = connection.migrations.list(host=hostname) |
|
|
|
print("Checking %d migrations" % len(migrations)) |
|
for migration in migrations: |
|
# print migration.to_dict() |
|
# |
|
# { |
|
# u'status': u'error', |
|
# u'dest_host': None, |
|
# u'new_instance_type_id': 2, |
|
# u'old_instance_type_id': 2, |
|
# u'updated_at': u'2018-04-22T20:55:29.000000', |
|
# u'dest_compute': |
|
# u'overcloud-novacompute-2.localdomain', |
|
# u'migration_type': u'live-migration', |
|
# u'source_node': |
|
# u'overcloud-novacompute-0.localdomain', |
|
# u'id': 8, |
|
# u'created_at': u'2018-04-22T20:52:58.000000', |
|
# u'instance_uuid': |
|
# u'd1c82ce8-3dc5-48db-b59f-854b3b984ef1', |
|
# u'dest_node': |
|
# u'overcloud-novacompute-2.localdomain', |
|
# u'source_compute': |
|
# u'overcloud-novacompute-0.localdomain' |
|
# } |
|
# Acceptable: done, completed, failed |
|
if migration.status in ["running", "accepted", "pre-migrating"]: |
|
return False |
|
return True |
|
|
|
def safe_to_start(connection, hostname): |
|
if is_forced_down(connection, hostname): |
|
print("Waiting for fence-down flag to be cleared") |
|
return False |
|
if not evacuations_done(connection, hostname): |
|
print("Waiting for evacuations to complete or fail") |
|
return False |
|
return True |
|
|
|
def create_nova_connection(options): |
|
try: |
|
from novaclient import client |
|
from novaclient.exceptions import NotAcceptable |
|
except ImportError: |
|
print("Nova not found or not accessible") |
|
sys.exit(1) |
|
|
|
from keystoneauth1 import loading |
|
from keystoneauth1 import session |
|
|
|
# Prefer the oldest and strip the leading 'v' |
|
kwargs = dict( |
|
auth_url=options["auth_url"][0], |
|
username=options["username"][0], |
|
password=options["password"][0], |
|
project_name=options["project_name"][0], |
|
user_domain_name=options["user_domain_name"][0], |
|
project_domain_name=options["project_domain_name"][0], |
|
) |
|
|
|
loader = loading.get_plugin_loader('password') |
|
keystone_auth = loader.load_from_options(**kwargs) |
|
keystone_session = session.Session(auth=keystone_auth, verify=(not options["insecure"])) |
|
|
|
nova_endpoint_type = 'internalURL' |
|
# We default to internalURL but we allow this to be overridden via |
|
# the [placement]/os_interface key. |
|
if 'os_interface' in options and len(options["os_interface"]) == 1: |
|
nova_endpoint_type = options["os_interface"][0] |
|
# Via https://review.opendev.org/#/c/492247/ os_interface has been deprecated in queens |
|
# and we need to use 'valid_interfaces' which is a: |
|
# "List of interfaces, in order of preference, for endpoint URL. (list value)" |
|
# Since it is not explicitely set in nova.conf we still keep the check for os_interface |
|
elif 'valid_interfaces' in options and len(options["valid_interfaces"]) >= 1: |
|
nova_endpoint_type = options["valid_interfaces"][0] |
|
|
|
# This mimicks the code in novaclient/shell.py |
|
if nova_endpoint_type in ['internal', 'public', 'admin']: |
|
nova_endpoint_type += 'URL' |
|
|
|
if 'region_name' in options: |
|
region = options['region_name'][0] |
|
elif 'os_region_name' in options: |
|
region = options['os_region_name'][0] |
|
else: # We actually try to make a client call even with an empty region |
|
region = None |
|
nova_versions = [ "2.23", "2" ] |
|
for version in nova_versions: |
|
nova = client.Client(version, |
|
region_name=region, |
|
session=keystone_session, auth=keystone_auth, |
|
http_log_debug="verbose" in options, |
|
endpoint_type=nova_endpoint_type) |
|
|
|
try: |
|
nova.hypervisors.list() |
|
return nova |
|
|
|
except NotAcceptable as e: |
|
logging.warning(e) |
|
|
|
except Exception as e: |
|
logging.warning("Nova connection failed. %s: %s" % (e.__class__.__name__, e)) |
|
|
|
print("Couldn't obtain a supported connection to nova, tried: %s\n" % repr(nova_versions)) |
|
return None |
|
|
|
|
|
parser = argparse.ArgumentParser(description='Process some integers.') |
|
parser.add_argument('--config-file', dest='nova_config', action='store', |
|
default="/etc/nova/nova.conf", |
|
help='path to nova configuration (default: /etc/nova/nova.conf)') |
|
parser.add_argument('--nova-binary', dest='nova_binary', action='store', |
|
default="/usr/bin/nova-compute", |
|
help='path to nova compute binary (default: /usr/bin/nova-compute)') |
|
parser.add_argument('--enable-file', dest='enable_file', action='store', |
|
default="/var/lib/nova/instanceha/enabled", |
|
help='file exists if instance HA is enabled on this host '\ |
|
'(default: /var/lib/nova/instanceha/enabled)') |
|
|
|
|
|
sections = {} |
|
(args, remaining) = parser.parse_known_args(sys.argv) |
|
|
|
config = oslo_config.cfg.ConfigParser(args.nova_config, sections) |
|
config.parse() |
|
config.sections["placement"]["insecure"] = 0 |
|
config.sections["placement"]["verbose"] = 1 |
|
|
|
if os.path.isfile(args.enable_file): |
|
connection = None |
|
while not connection: |
|
# Loop in case the control plane is recovering when we run |
|
connection = create_nova_connection(config.sections["placement"]) |
|
if not connection: |
|
time.sleep(10) |
|
|
|
while not safe_to_start(connection, config.sections["DEFAULT"]["host"][0]): |
|
time.sleep(10) |
|
|
|
real_args = [args.nova_binary, '--config-file', args.nova_config] |
|
real_args.extend(remaining[1:]) |
|
os.execv(args.nova_binary, real_args)
|
|
|