#!/usr/bin/env python # 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. import argparse import logging import os import pwd import shutil import sys import time import getpass from ipapython.ipautil import run, user_input from ipalib import api from ipalib import errors from novajoin.errors import ConfigurationError from novajoin import configure_ipa from six.moves.configparser import SafeConfigParser, NoOptionError from urllib3.util import parse_url IPACONF = '/etc/ipa/default.conf' NOVACONF = '/etc/nova/nova.conf' NOVACPUCONF = '/etc/nova/nova-cpu.conf' NEUTRONCONF = '/etc/neutron/neutron.conf' JOINCONF = '/etc/novajoin/join.conf' LOGFILE = '/var/log/novajoin-install.log' logger = logging.getLogger() def openlogs(): global logger # pylint: disable=W0603 if os.path.isfile(LOGFILE): try: created = '%s' % time.strftime( '%Y%m%d%H%M%SZ', time.gmtime(os.path.getctime(LOGFILE))) shutil.move(LOGFILE, '%s.%s' % (LOGFILE, created)) except IOError: pass logger = logging.getLogger() try: lh = logging.FileHandler(LOGFILE) except IOError as e: print >> sys.stderr, 'Unable to open %s (%s)' % (LOGFILE, str(e)) lh = logging.StreamHandler(sys.stderr) formatter = logging.Formatter('[%(asctime)s] %(message)s') lh.setFormatter(formatter) lh.setLevel(logging.DEBUG) logger.addHandler(lh) logger.propagate = False ch = logging.StreamHandler(sys.stdout) formatter = logging.Formatter('%(message)s') ch.setFormatter(formatter) ch.setLevel(logging.INFO) logger.addHandler(ch) def install(opts): logger.info('Installation initiated') if not os.path.exists(opts.ipa_conf): raise ConfigurationError('Must be enrolled in IPA') try: os.environ['OS_PASSWORD'] os.environ['OS_USERNAME'] os.environ['OS_AUTH_URL'] except KeyError as e: raise ConfigurationError('%s environment variable not set.' % e) try: pwd.getpwnam(opts.user) except KeyError: raise ConfigurationError('User: %s not found on the system' % opts.user) api.bootstrap(context='novajoin') novajoin = configure_ipa.NovajoinRole(user=opts.user) if not opts.no_kinit: novajoin.kinit(opts.principal, api.env.realm, opts.password) api.finalize() try: api.Backend.rpcclient.connect() except errors.CCacheError: raise ConfigurationError("No Kerberos credentials") logger.info('Installing default config files') keystone_url = parse_url(opts.keystone_auth_url) config = SafeConfigParser() config.read(opts.novajoin_conf) config.set('DEFAULT', 'domain', api.env.domain) # pylint: disable=no-member config.set('DEFAULT', 'api_paste_config', '/etc/novajoin/join-api-paste.ini') config.set('DEFAULT', 'log_dir', '/var/log/novajoin') if not config.has_section('service_credentials'): config.add_section('service_credentials') config.set('service_credentials', 'auth_url', opts.keystone_auth_url) config.set('service_credentials', 'password', opts.nova_password) config.set('service_credentials', 'username', 'nova') config.set('service_credentials', 'auth_type', 'password') config.set('service_credentials', 'project_domain_name', 'default') config.set('service_credentials', 'project_name', opts.project_name) config.set('service_credentials', 'user_domain_id', 'default') if not config.has_section('keystone_authtoken'): config.add_section('keystone_authtoken') config.set('keystone_authtoken', 'memcached_servers', '%s:11211' % keystone_url.hostname) config.set('keystone_authtoken', 'project_name', opts.project_name) config.set('keystone_authtoken', 'password', opts.nova_password) config.set('keystone_authtoken', 'username', 'nova') config.set('keystone_authtoken', 'auth_url', opts.keystone_auth_url) config.set('keystone_authtoken', 'auth_type', 'password') config.set('keystone_authtoken', 'project_domain_name', 'default') config.set('keystone_authtoken', 'user_domain_id', 'default') if opts.notification_format == 'versioned': config.set('DEFAULT', 'notification_format', 'versioned') else: config.set('DEFAULT', 'notification_format', 'unversioned') config.set('DEFAULT', 'notifications_topic', 'novajoin_notifications') with open(opts.novajoin_conf, 'w') as f: config.write(f) config = SafeConfigParser() config.read(opts.nova_conf) config.set('DEFAULT', 'vendordata_jsonfile_path', '/etc/novajoin/cloud-config-novajoin.json') # set the default domain to the IPA domain. This is added to the # instance name to set the hostname. config.set('DEFAULT', 'dhcp_domain', api.env.domain) # Novajoin service config.set('DEFAULT', 'vendordata_providers', 'StaticJSON, DynamicJSON') config.set('DEFAULT', 'vendordata_dynamic_targets', 'join@http://127.0.0.1:9090/v1/') if not config.has_section('vendordata_dynamic_auth'): config.add_section('vendordata_dynamic_auth') config.set('vendordata_dynamic_auth', 'project_name', opts.project_name) config.set('vendordata_dynamic_auth', 'password', opts.nova_password) config.set('vendordata_dynamic_auth', 'username', 'nova') config.set('vendordata_dynamic_auth', 'auth_url', opts.keystone_auth_url) config.set('vendordata_dynamic_auth', 'auth_type', 'password') config.set('vendordata_dynamic_auth', 'project_domain_name', 'default') config.set('vendordata_dynamic_auth', 'user_domain_id', 'default') try: transport_url = config.get('DEFAULT', 'transport_url') except NoOptionError: transport_url = None with open(opts.nova_conf, 'w') as f: config.write(f) # Notifications for conf in set([opts.nova_conf, opts.nova_cpu_conf]): config = SafeConfigParser() config.read(conf) if not config.has_section('notifications'): config.add_section('notifications') config.set('notifications', 'notify_on_state_change', 'vm_state') if opts.notification_format == 'versioned': config.set('notifications', 'notification_format', 'versioned') config.set('notifications', 'versioned_notifications_topics', 'versioned_notifications,novajoin_notifications') else: config.set('notifications', 'notification_format', 'unversioned') config.set('oslo_messaging_notifications', 'topics', 'notifications,novajoin_notifications') with open(conf, 'w') as f: config.write(f) config = SafeConfigParser() config.read(opts.neutron_conf) config.set('oslo_messaging_notifications', 'driver', 'messagingv2') config.set('oslo_messaging_notifications', 'topics', 'notifications,novajoin_notifications') with open(opts.neutron_conf, 'w') as f: config.write(f) if transport_url: join_config = SafeConfigParser() join_config.read(opts.novajoin_conf) join_config.set('DEFAULT', 'transport_url', transport_url) with open(opts.novajoin_conf, 'w') as f: join_config.write(f) logger.info('Importing IPA metadata') (stdout, stderr, returncode) = run( ['glance', '--os-image-api-version', '2', 'md-namespace-import', '--file', '/usr/share/novajoin/freeipa.json'], raiseonerr=False) if returncode != 0: logger.error('Adding IPA metadata failed: %s' % stderr) logger.info('Creating IPA permissions') novajoin.configure_ipa(precreate=False) def parse_args(): parser = argparse.ArgumentParser(description='Nova join Install Options') parser.add_argument('--keystone-auth-url', dest='keystone_auth_url', help='Keystone auth URL', default=None) parser.add_argument('--nova-password', dest='nova_password', help='Nova service user password', default=None) parser.add_argument('--project', dest='project_name', help='Keystone project', default='service') parser.add_argument('--ipa-conf', dest='ipa_conf', help='IPA configuration file', default=IPACONF) parser.add_argument('--nova-conf', dest='nova_conf', help='nova configuration file', default=NOVACONF) parser.add_argument('--nova-cpu-conf', dest='nova_cpu_conf', help='nova compute configuration file', default=NOVACPUCONF) parser.add_argument('--neutron-conf', dest='neutron_conf', help='neutron configuration file', default=NEUTRONCONF) parser.add_argument('--novajoin-conf', dest='novajoin_conf', help='novajoin configuration file', default=JOINCONF) parser.add_argument('--notification-format', dest='notification_format', help='The format of notifications to emit and read.', choices=['versioned', 'unversioned'], default='versioned') parser = configure_ipa.ipa_options(parser) opts = parser.parse_args() configure_ipa.validate_options(opts) if not opts.keystone_auth_url: opts.keystone_auth_url = user_input("Keystone auth URL", "", allow_empty=False) if not opts.nova_password: try: opts.nova_password = getpass.getpass("nova service Password: ") except EOFError: opts.nova_password = None if not opts.nova_password: raise ConfigurationError('nova service user password required.') try: pwd.getpwnam(opts.user) except KeyError: raise ConfigurationError('User: %s not found on the system' % opts.user) return opts if __name__ == '__main__': opts = [] out = 0 openlogs() logger.setLevel(logging.DEBUG) try: opts = parse_args() logger.debug('Installation arguments:') logger.debug(opts) install(opts) except Exception as e: # pylint: disable=broad-except logger.info(str(e)) # emit message to console logger.debug(e, exc_info=1) # add backtrace information to logfile logger.info('Installation aborted.') logger.info('See log file %s for details' % LOGFILE) out = 1 except SystemExit: out = 1 raise finally: if out == 0: logger.info('Installation complete.') logger.info( 'Please restart nova-api to enable the join service.') sys.exit(out)