def ssh_authorized_peers(peer_interface, user, group=None, ensure_local_user=False): """ Main setup function, should be called from both peer -changed and -joined hooks with the same parameters. """ if ensure_local_user: ensure_user(user, group) priv_key, pub_key = get_keypair(user) hook = os.path.basename(sys.argv[0]) if hook == '%s-relation-joined' % peer_interface: utils.relation_set(ssh_pub_key=pub_key) print 'joined' elif hook == '%s-relation-changed' % peer_interface: hosts = [] keys = [] for r_id in utils.relation_ids(peer_interface): for unit in utils.relation_list(r_id): settings = utils.relation_get_dict(relation_id=r_id, remote_unit=unit) if 'ssh_pub_key' in settings: keys.append(settings['ssh_pub_key']) hosts.append(settings['private-address']) else: utils.juju_log('INFO', 'ssh_authorized_peers(): ssh_pub_key '\ 'missing for unit %s, skipping.' % unit) write_authorized_keys(user, keys) write_known_hosts(user, hosts) authed_hosts = ':'.join(hosts) utils.relation_set(ssh_authorized_hosts=authed_hosts)
def ha_relation_changed(): relation_data = utils.relation_get_dict() if ('clustered' in relation_data and cluster.is_leader(CLUSTER_RES)): utils.juju_log('INFO', 'Cluster configured, notifying other services' ' and updating keystone endpoint configuration') # Update keystone endpoint to point at VIP ensure_initial_admin(config) # Tell all related services to start using # the VIP and haproxy ports instead for r_id in utils.relation_ids('identity-service'): utils.relation_set(rid=r_id, auth_host=config['vip'], service_host=config['vip'])
def sync_to_peers(peer_interface, user, paths=[], verbose=False): base_cmd = [ 'unison', '-auto', '-batch=true', '-confirmbigdel=false', '-fastcheck=true', '-group=false', '-owner=false', '-prefer=newer', '-times=true' ] if not verbose: base_cmd.append('-silent') hosts = [] for r_id in (utils.relation_ids(peer_interface) or []): for unit in utils.relation_list(r_id): settings = utils.relation_get_dict(relation_id=r_id, remote_unit=unit) try: authed_hosts = settings['ssh_authorized_hosts'].split(':') except KeyError: print 'unison sync_to_peers: peer has not authorized *any* '\ 'hosts yet.' return unit_hostname = utils.unit_get('private-address') add_host = None for authed_host in authed_hosts: if unit_hostname == authed_host: add_host = settings['private-address'] if add_host: hosts.append(settings['private-address']) else: print 'unison sync_to_peers: peer (%s) has not authorized '\ '*this* host yet, skipping.' %\ settings['private-address'] for path in paths: # removing trailing slash from directory paths, unison # doesn't like these. if path.endswith('/'): path = path[:(len(path) - 1)] for host in hosts: try: cmd = base_cmd + [path, 'ssh://%s@%s/%s' % (user, host, path)] utils.juju_log( 'INFO', 'Syncing local path %s to %s@%s:%s' % (path, user, host, path)) run_as_user(user, cmd) except: # it may fail for permissions on some files pass
def sync_to_peers(peer_interface, user, paths=[], verbose=False): base_cmd = ['unison', '-auto', '-batch=true', '-confirmbigdel=false', '-fastcheck=true', '-group=false', '-owner=false', '-prefer=newer', '-times=true'] if not verbose: base_cmd.append('-silent') hosts = [] for r_id in (utils.relation_ids(peer_interface) or []): for unit in utils.relation_list(r_id): settings = utils.relation_get_dict(relation_id=r_id, remote_unit=unit) try: authed_hosts = settings['ssh_authorized_hosts'].split(':') except KeyError: print 'unison sync_to_peers: peer has not authorized *any* '\ 'hosts yet.' return unit_hostname = utils.unit_get('private-address') add_host = None for authed_host in authed_hosts: if unit_hostname == authed_host: add_host = settings['private-address'] if add_host: hosts.append(settings['private-address']) else: print 'unison sync_to_peers: peer (%s) has not authorized '\ '*this* host yet, skipping.' %\ settings['private-address'] for path in paths: # removing trailing slash from directory paths, unison # doesn't like these. if path.endswith('/'): path = path[:(len(path) - 1)] for host in hosts: try: cmd = base_cmd + [path, 'ssh://%s@%s/%s' % (user, host, path)] utils.juju_log('INFO', 'Syncing local path %s to %s@%s:%s' % (path, user, host, path)) run_as_user(user, cmd) except: # it may fail for permissions on some files pass
def db_changed(): relation_data = utils.relation_get_dict() if ('password' not in relation_data or 'db_host' not in relation_data): utils.juju_log('INFO', "db_host or password not set. Peer not ready, exit 0") return update_config_block('sql', connection="mysql://%s:%s@%s/%s" % (config["database-user"], relation_data["password"], relation_data["db_host"], config["database"])) if cluster.eligible_leader(CLUSTER_RES): utils.juju_log('INFO', 'Cluster leader, performing db-sync') execute("keystone-manage db_sync", echo=True) if config_dirty(): utils.restart('keystone') time.sleep(5) if cluster.eligible_leader(CLUSTER_RES): ensure_initial_admin(config) # If the backend database has been switched to something new and there # are existing identity-service relations,, service entries need to be # recreated in the new database. Re-executing identity-service-changed # will do this. for rid in utils.relation_ids('identity-service'): for unit in utils.relation_list(rid=rid): utils.juju_log('INFO', "Re-exec'ing identity-service-changed" " for: %s - %s" % (rid, unit)) identity_changed(relation_id=rid, remote_unit=unit)
def identity_changed(relation_id=None, remote_unit=None): """ A service has advertised its API endpoints, create an entry in the service catalog. Optionally allow this hook to be re-fired for an existing relation+unit, for context see see db_changed(). """ if not cluster.eligible_leader(CLUSTER_RES): utils.juju_log('INFO', 'Deferring identity_changed() to service leader.') return settings = utils.relation_get_dict(relation_id=relation_id, remote_unit=remote_unit) # the minimum settings needed per endpoint single = set(['service', 'region', 'public_url', 'admin_url', 'internal_url']) if single.issubset(settings): # other end of relation advertised only one endpoint if 'None' in [v for k, v in settings.iteritems()]: # Some backend services advertise no endpoint but require a # hook execution to update auth strategy. relation_data = {} # Check if clustered and use vip + haproxy ports if so if cluster.is_clustered(): relation_data["auth_host"] = config['vip'] relation_data["service_host"] = config['vip'] else: relation_data["auth_host"] = config['hostname'] relation_data["service_host"] = config['hostname'] relation_data["auth_port"] = config['admin-port'] relation_data["service_port"] = config['service-port'] if config['https-service-endpoints'] in ['True', 'true']: # Pass CA cert as client will need it to # verify https connections ca = get_ca(user=SSH_USER) ca_bundle = ca.get_ca_bundle() relation_data['https_keystone'] = 'True' relation_data['ca_cert'] = b64encode(ca_bundle) if relation_id: relation_data['rid'] = relation_id # Allow the remote service to request creation of any additional # roles. Currently used by Horizon for role in get_requested_roles(settings): utils.juju_log('INFO', "Creating requested role: %s" % role) create_role(role) utils.relation_set(**relation_data) return else: ensure_valid_service(settings['service']) add_endpoint(region=settings['region'], service=settings['service'], publicurl=settings['public_url'], adminurl=settings['admin_url'], internalurl=settings['internal_url']) service_username = settings['service'] https_cn = urlparse.urlparse(settings['internal_url']) https_cn = https_cn.hostname else: # assemble multiple endpoints from relation data. service name # should be prepended to setting name, ie: # realtion-set ec2_service=$foo ec2_region=$foo ec2_public_url=$foo # relation-set nova_service=$foo nova_region=$foo nova_public_url=$foo # Results in a dict that looks like: # { 'ec2': { # 'service': $foo # 'region': $foo # 'public_url': $foo # } # 'nova': { # 'service': $foo # 'region': $foo # 'public_url': $foo # } # } endpoints = {} for k, v in settings.iteritems(): ep = k.split('_')[0] x = '_'.join(k.split('_')[1:]) if ep not in endpoints: endpoints[ep] = {} endpoints[ep][x] = v services = [] https_cn = None for ep in endpoints: # weed out any unrelated relation stuff Juju might have added # by ensuring each possible endpiont has appropriate fields # ['service', 'region', 'public_url', 'admin_url', 'internal_url'] if single.issubset(endpoints[ep]): ep = endpoints[ep] ensure_valid_service(ep['service']) add_endpoint(region=ep['region'], service=ep['service'], publicurl=ep['public_url'], adminurl=ep['admin_url'], internalurl=ep['internal_url']) services.append(ep['service']) if not https_cn: https_cn = urlparse.urlparse(ep['internal_url']) https_cn = https_cn.hostname service_username = '******'.join(services) if 'None' in [v for k, v in settings.iteritems()]: return if not service_username: return token = get_admin_token() utils.juju_log('INFO', "Creating service credentials for '%s'" % service_username) service_password = get_service_password(service_username) create_user(service_username, service_password, config['service-tenant']) grant_role(service_username, config['admin-role'], config['service-tenant']) # Allow the remote service to request creation of any additional roles. # Currently used by Swift and Ceilometer. for role in get_requested_roles(settings): utils.juju_log('INFO', "Creating requested role: %s" % role) create_role(role, service_username, config['service-tenant']) # As of https://review.openstack.org/#change,4675, all nodes hosting # an endpoint(s) needs a service username and password assigned to # the service tenant and granted admin role. # note: config['service-tenant'] is created in utils.ensure_initial_admin() # we return a token, information about our API endpoints, and the generated # service credentials relation_data = { "admin_token": token, "service_host": config["hostname"], "service_port": config["service-port"], "auth_host": config["hostname"], "auth_port": config["admin-port"], "service_username": service_username, "service_password": service_password, "service_tenant": config['service-tenant'], "https_keystone": "False", "ssl_cert": "", "ssl_key": "", "ca_cert": "" } if relation_id: relation_data['rid'] = relation_id # Check if clustered and use vip + haproxy ports if so if cluster.is_clustered(): relation_data["auth_host"] = config['vip'] relation_data["service_host"] = config['vip'] # generate or get a new cert/key for service if set to manage certs. if config['https-service-endpoints'] in ['True', 'true']: ca = get_ca(user=SSH_USER) cert, key = ca.get_cert_and_key(common_name=https_cn) ca_bundle = ca.get_ca_bundle() relation_data['ssl_cert'] = b64encode(cert) relation_data['ssl_key'] = b64encode(key) relation_data['ca_cert'] = b64encode(ca_bundle) relation_data['https_keystone'] = 'True' unison.sync_to_peers(peer_interface='cluster', paths=[SSL_DIR], user=SSH_USER, verbose=True) utils.relation_set(**relation_data) synchronize_service_credentials()
def do_openstack_upgrade(install_src, packages): '''Upgrade packages from a given install src.''' config = config_get() old_vers = get_os_codename_package('keystone') new_vers = get_os_codename_install_source(install_src) utils.juju_log('INFO', "Beginning Keystone upgrade: %s -> %s" % \ (old_vers, new_vers)) # Backup previous config. utils.juju_log('INFO', "Backing up contents of /etc/keystone.") stamp = time.strftime('%Y%m%d%H%M') cmd = 'tar -pcf /var/lib/juju/keystone-backup-%s.tar /etc/keystone' % stamp execute(cmd, die=True, echo=True) configure_installation_source(install_src) execute('apt-get update', die=True, echo=True) os.environ['DEBIAN_FRONTEND'] = 'noninteractive' cmd = 'apt-get --option Dpkg::Options::=--force-confnew -y '\ 'install %s' % packages execute(cmd, echo=True, die=True) # we have new, fresh config files that need updating. # set the admin token, which is still stored in config. set_admin_token(config['admin-token']) # set the sql connection string if a shared-db relation is found. ids = utils.relation_ids('shared-db') if ids: for rid in ids: for unit in utils.relation_list(rid): utils.juju_log('INFO', 'Configuring new keystone.conf for ' 'database access on existing database' ' relation to %s' % unit) relation_data = utils.relation_get_dict(relation_id=rid, remote_unit=unit) update_config_block('sql', connection="mysql://%s:%s@%s/%s" % (config["database-user"], relation_data["password"], relation_data["private-address"], config["database"])) utils.stop('keystone') if (cluster.eligible_leader(CLUSTER_RES)): utils.juju_log('INFO', 'Running database migrations for %s' % new_vers) execute('keystone-manage db_sync', echo=True, die=True) else: utils.juju_log('INFO', 'Not cluster leader; snoozing whilst' ' leader upgrades DB') time.sleep(10) utils.start('keystone') time.sleep(5) utils.juju_log('INFO', 'Completed Keystone upgrade: ' '%s -> %s' % (old_vers, new_vers))