tripleo-heat-templates/extraconfig/tasks/instanceha/check-run-nova-compute
Michele Baldessari 1bdefbe59d IHA Default the compute endpoint check script to internal
Currently we instantiate a novaclient.client Client object without explicitely
passing any endpoint_type in kwargs. The Client object defaults to using
'publicURL': https://github.com/openstack/python-novaclient/blob/stable/queens/novaclient/client.py#L116

In some environments the access to publicURL is not desired and likely the wrong default.
So this needs to be a) configureable and b) default to internalURL when nothing is specified.

We make this configurable by leveraging the os_interface key in the
placement section of nova.conf as that is what specifies the endpoint
type since ocata: https://docs.openstack.org/releasenotes/nova/ocata.html#other-notes

We also check for the existance of the [placement]/valid_interface key
and will use that instead if it is present as it is the proper
recommended way to get this information as of queens (see
https://review.openstack.org/#/c/492247/). Since it is a list
of preferred endpoint URLs, we take the first one.

Tested by making sure via tcpdump that the internal_url was being hit
after restarting the nova_compute container with the patched code:
(overcloud) [stack@undercloud-0 ~]$ openstack endpoint list |grep comput
| 8ad225f34170467a84513c5b447662dc | regionOne | nova         | compute        | True    | admin     | http://172.17.1.16:8774/v2.1                   |
| 9a15e824601f43629b03ec99589c3d83 | regionOne | nova         | compute        | True    | internal  | http://172.17.1.16:8774/v2.1                   |
| c5b964700daf4abfac5060432debdbe3 | regionOne | nova         | compute        | True    | public    | https://10.0.0.101:13774/v2.1                  |

[root@compute-0 ~]# tcpdump -i any -nn host 172.17.1.16 and port 8774
09:29:57.824687 IP 172.17.1.10.37254 > 172.17.1.16.8774: Flags [S], seq 3520534439, win 29200, options [mss 1460,sackOK,TS val 564789919 ecr 0,nop,wscale 7], length 0
09:29:57.824946 ethertype IPv4, IP 172.17.1.16.8774 > 172.17.1.10.37254: Flags [S.], seq 3844540290, ack 3520534440, win 28960, options [mss 1460,sackOK,TS val 564810385 ecr 564789919,nop,wscale 7], length 0
09:29:57.824946 IP 172.17.1.16.8774 > 172.17.1.10.37254: Flags [S.], seq 3844540290, ack 3520534440, win 28960, options [mss 1460,sackOK,TS val 564810385 ecr 564789919,nop,wscale 7], length 0

Change-Id: Ifbb40e2a2222c229fd71eca2c4c36daa448e492d
Closes-Bug: #1788584
2018-08-23 12:14:40 +02:00

201 lines
7.8 KiB
Python
Executable File

#!/usr/bin/python -utt
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
from keystoneclient import discover
# Prefer the oldest and strip the leading 'v'
keystone_versions = discover.available_versions(options["auth_url"][0])
keystone_version = keystone_versions[0]['id'][1:]
kwargs = dict(
auth_url=options["auth_url"][0],
username=options["username"][0],
password=options["password"][0]
)
if discover.version_match("2", keystone_version):
kwargs["tenant_name"] = options["tenant_name"][0]
elif discover.version_match("3", keystone_version):
kwargs["project_name"] = options["project_name"][0]
kwargs["user_domain_name"] = options["user_domain_name"][0]
kwargs["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.openstack.org/#/c/492247/ os_interface has been deprecatd 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'
nova_versions = [ "2.23", "2" ]
for version in nova_versions:
clientargs = inspect.getargspec(client.Client).varargs
# Some versions of Openstack prior to Ocata only
# supported positional arguments for username,
# password, and tenant.
#
# Versions since Ocata only support named arguments.
#
# So we need to use introspection to figure out how to
# create a Nova client.
#
# Happy days
#
if clientargs:
# OSP < Ocata
# ArgSpec(args=['version', 'username', 'password', 'project_id', 'auth_url'],
# varargs=None,
# keywords='kwargs', defaults=(None, None, None, None))
nova = client.Client(version,
None, # User
None, # Password
None, # Tenant
None, # Auth URL
insecure=options["insecure"],
region_name=options["os_region_name"][0],
session=keystone_session, auth=keystone_auth,
http_log_debug=options.has_key("verbose"),
endpoint_type=nova_endpoint_type)
else:
# OSP >= Ocata
# ArgSpec(args=['version'], varargs='args', keywords='kwargs', defaults=None)
nova = client.Client(version,
region_name=options["os_region_name"][0],
session=keystone_session, auth=keystone_auth,
http_log_debug=options.has_key("verbose"),
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)