diff --git a/files/join.conf.template b/files/join.conf.template index bebe1dc..f78d049 100644 --- a/files/join.conf.template +++ b/files/join.conf.template @@ -12,8 +12,18 @@ cacert = /etc/ipa/ca.crt connect_retries = 1 [keystone_authtoken] -auth_uri = $KEYSTONE_AUTH +auth_uri = $KEYSTONE_AUTH_URI admin_password = $NOVA_PASSWORD admin_user = nova admin_tenant_name = services identity_uri = $KEYSTONE_IDENTITY + +[service_credentials] +region_name = RegionOne +auth_type = password +auth_url = $KEYSTONE_AUTH_URL +project_name = services +project_domain_name = Default +username = nova +user_domain_name = Default +password = $NOVA_PASSWORD diff --git a/novajoin/glance.py b/novajoin/glance.py index 8ad5553..48e826e 100644 --- a/novajoin/glance.py +++ b/novajoin/glance.py @@ -27,6 +27,7 @@ from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import timeutils from novajoin import exception +from novajoin import keystone_client import six @@ -37,27 +38,23 @@ LOG = logging.getLogger(__name__) GLANCE_APIVERSION = 2 -def generate_identity_headers(context, status='Confirmed'): - return { - 'X-Auth-Token': getattr(context, 'auth_token', None), - 'X-User-Id': getattr(context, 'user', None), - 'X-Tenant-Id': getattr(context, 'tenant', None), - 'X-Roles': ','.join(getattr(context, 'roles', [])), - 'X-Identity-Status': status, - } - - def get_api_servers(): """Return iterator of glance api_servers to cycle through the list, looping around to the beginning if necessary. """ api_servers = [] - if not CONF.glance_api_servers: - return None + ks = keystone_client.get_client() + catalog = keystone_client.get_service_catalog(ks) + + image_service = catalog.url_for(service_type='image') + if image_service: + api_servers.append(image_service) + + if CONF.glance_api_servers: + for api_server in CONF.glance_api_servers: + api_servers.append(api_server) - for api_server in CONF.glance_api_servers: - api_servers.append(api_server) random.shuffle(api_servers) return itertools.cycle(api_servers) @@ -82,9 +79,9 @@ class GlanceClient(object): params = {} - params['identity_headers'] = generate_identity_headers(context) + session = keystone_client.get_session() return glanceclient.Client(str(self.version), self.api_server, - **params) + session=session, **params) def call(self, context, method, *args, **kwargs): """Call a glance client method.""" diff --git a/novajoin/keystone_client.py b/novajoin/keystone_client.py new file mode 100644 index 0000000..82d99da --- /dev/null +++ b/novajoin/keystone_client.py @@ -0,0 +1,62 @@ +# Copyright 2016 Red Hat, Inc. +# +# 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. + + +from keystoneauth1 import loading as ks_loading +from keystoneclient.v3 import client as ks_client_v3 +from oslo_config import cfg + +CFG_GROUP = "service_credentials" + +_SESSION = None +_AUTH = None + + +def get_session(): + """Get a service credentials auth session.""" + + global _SESSION + global _AUTH + + if not _AUTH: + _AUTH = ks_loading.load_auth_from_conf_options(cfg.CONF, CFG_GROUP) + if not _SESSION: + _SESSION = ks_loading.load_session_from_conf_options( + cfg.CONF, CFG_GROUP, auth=_AUTH, session=_SESSION + ) + return _SESSION + + +def get_client(trust_id=None): + """Return a client for keystone v3 endpoint, optionally using a trust.""" + session = get_session() + return ks_client_v3.Client(session=session, trust_id=trust_id) + + +def get_service_catalog(client): + return client.session.auth.get_access(client.session).service_catalog + + +def get_auth_token(client): + return client.session.auth.get_access(client.session).auth_token + + +def register_keystoneauth_opts(conf): + ks_loading.register_auth_conf_options(conf, CFG_GROUP) + ks_loading.register_session_conf_options( + conf, CFG_GROUP, + deprecated_opts={'cacert': [ + cfg.DeprecatedOpt('os-cacert', group=CFG_GROUP), + cfg.DeprecatedOpt('os-cacert', group="DEFAULT")] + }) diff --git a/novajoin/notifications.py b/novajoin/notifications.py index 62214f4..0987fd0 100644 --- a/novajoin/notifications.py +++ b/novajoin/notifications.py @@ -21,6 +21,9 @@ import sys import time import json import oslo_messaging +from neutronclient.v2_0 import client as neutron_client +from novaclient import client as nova_client +from novajoin.keystone_client import get_session, register_keystoneauth_opts from oslo_serialization import jsonutils from oslo_log import log as logging @@ -34,18 +37,38 @@ CONF = config.CONF LOG = logging.getLogger(__name__) +def novaclient(): + session = get_session() + return nova_client.Client('2.1', session=session) + + +def neutronclient(): + session = get_session() + return neutron_client.Client(session=session) + + class NotificationEndpoint(object): filter_rule = oslo_messaging.notify.filter.NotificationFilter( publisher_id='^compute.*|^network.*', event_type='^compute.instance.create.end|' '^compute.instance.delete.end|' - '^network.floating_ip.(dis)?associate',) + '^network.floating_ip.(dis)?associate|' + '^floatingip.update.end') def __init__(self): self.uuidcache = cache.Cache() self.ipaclient = IPAClient() + def _generate_hostname(self, hostname): + # FIXME: Don't re-calculate the hostname, fetch it from somewhere + project = 'foo' + if CONF.project_subdomain: + host = '%s.%s.%s' % (hostname, project, CONF.domain) + else: + host = '%s.%s' % (hostname, CONF.domain) + return host + def info(self, ctxt, publisher_id, event_type, payload, metadata): LOG.debug('notification:') LOG.debug(json.dumps(payload, indent=4)) @@ -54,17 +77,13 @@ class NotificationEndpoint(object): event_type, metadata) if event_type == 'compute.instance.create.end': - LOG.info("Add new host") + hostname = self._generate_hostname(payload.get('hostname')) + id = payload.get('instance_id') + LOG.info("Add new host %s (%s)", id, hostname) elif event_type == 'compute.instance.delete.end': - LOG.info("Delete host") - hostname = payload.get('hostname') - # FIXME: Don't re-calculate the hostname, fetch it from somewhere - project = 'foo' - if CONF.project_subdomain: - hostname = '%s.%s.%s' % (hostname, project, CONF.domain) - else: - hostname = '%s.%s' % (hostname, CONF.domain) - + hostname = self._generate_hostname(payload.get('hostname')) + id = payload.get('instance_id') + LOG.info("Delete host %s (%s)", id, hostname) self.ipaclient.delete_host(hostname, {}) elif event_type == 'network.floating_ip.associate': floating_ip = payload.get('floating_ip') @@ -86,26 +105,41 @@ class NotificationEndpoint(object): else: LOG.error("Could not resolve %s into a hostname", payload.get('instance_id')) + elif event_type == 'floatingip.update.end': # Neutron + floatingip = payload.get('floatingip') + floating_ip = floatingip.get('floating_ip_address') + port_id = floatingip.get('port_id') + LOG.info("Neutron floating IP associate: %s" % floating_ip) + nova = novaclient() + neutron = neutronclient() + search_opts = {'id': port_id} + ports = neutron.list_ports(**search_opts).get('ports') + if len(ports) == 1: + device_id = ports[0].get('device_id') + if device_id: + server = nova.servers.get(device_id) + if server: + self.ipaclient.add_ip(server.name, floating_ip) + else: + LOG.error("Expected 1 port, got %d", len(ports)) else: LOG.error("Status update or unknown") def main(): - + register_keystoneauth_opts(CONF) CONF(sys.argv[1:], project='join', version='1.0.0') logging.setup(CONF, 'join') transport = oslo_messaging.get_transport(CONF) targets = [oslo_messaging.Target(topic='notifications')] endpoints = [NotificationEndpoint()] - pool = 'listener-novajoin' server = oslo_messaging.get_notification_listener(transport, targets, endpoints, executor='threading', - allow_requeue=True, - pool=pool) + allow_requeue=True) LOG.info("Starting") server.start() try: diff --git a/novajoin/wsgi.py b/novajoin/wsgi.py index 4d18d5c..2c2e261 100644 --- a/novajoin/wsgi.py +++ b/novajoin/wsgi.py @@ -18,6 +18,7 @@ from oslo_service import service from oslo_service import wsgi from oslo_log import log from novajoin import config +from novajoin import keystone_client from novajoin import exception @@ -103,6 +104,7 @@ def process_launcher(): def main(): + keystone_client.register_keystoneauth_opts(CONF) CONF(sys.argv[1:], project='join', version='1.0.0') log.setup(CONF, 'join') launcher = process_launcher() diff --git a/scripts/novajoin-install b/scripts/novajoin-install index a05efdf..3d1787b 100755 --- a/scripts/novajoin-install +++ b/scripts/novajoin-install @@ -145,7 +145,8 @@ def install(args): confopts = {'FQDN': args['hostname'], 'MASTER': api.env.server, # pylint: disable=no-member 'DOMAIN': api.env.domain, # pylint: disable=no-member - 'KEYSTONE_AUTH': args['keystone_auth'], + 'KEYSTONE_AUTH_URI': args['keystone_auth_uri'], + 'KEYSTONE_AUTH_URL': args['keystone_auth_url'], 'KEYSTONE_IDENTITY': args['keystone_identity'], 'NOVA_PASSWORD': args['nova_password'], } @@ -215,8 +216,10 @@ def parse_args(): parser.add_argument('--password-file', dest='passwordfile', help='path to file containing password for ' 'the principal') - parser.add_argument('--keystone-auth', dest='keystone_auth', + parser.add_argument('--keystone-auth-uri', dest='keystone_auth_uri', help='Keystone auth URI') + parser.add_argument('--keystone-auth-url', dest='keystone_auth_url', + help='Keystone auth URL') parser.add_argument('--keystone-identity', dest='keystone_identity', help='Keystone identity URI') parser.add_argument('--nova-password', dest='nova_password', @@ -251,8 +254,12 @@ def parse_args(): raise ConfigurationError('Hostname: %s is not a FQDN' % args['hostname']) - if not args['keystone_auth']: - args['keystone_auth'] = user_input("Keysone auth URI", "", + if not args['keystone_auth_uri']: + args['keystone_auth_uri'] = user_input("Keysone auth URI", "", + allow_empty=False) + + if not args['keystone_auth_url']: + args['keystone_auth_url'] = user_input("Keysone auth URL", "", allow_empty=False) if not args['keystone_identity']: