Generate random passwords instead of hard-coding

* The prototype stage hard-coding of passwords is replaced by random
  generation of passwords for:
  * all API services;
  * RabbitMQ;
  * MySQL;
  * OpenStack admin user;
  * OpenStack service users;
* Passwords are not replaced upon successive microstack.init calls to
  preserve idempotency.

Change-Id: Ic3d6108a81d09bdd09e986f80b3040b030605178
This commit is contained in:
Dmitrii Shcherbakov 2020-10-07 18:35:07 +00:00
parent 71ad68d36a
commit 32ad5af7f4
27 changed files with 273 additions and 178 deletions

View File

@ -1,107 +0,0 @@
#!/bin/bash
set -ex
# Initialize Config
# TODO: put this in a nice yaml format, and parse it.
# General snap-wide settings
snapctl set \
config.clustered=false \
config.post-setup=true \
;
snapctl set \
config.keystone.region-name=microstack \
;
# Networking related settings.
snapctl set \
config.network.dns-servers=1.1.1.1 \
config.network.dns-domain=microstack.example. \
config.network.ext-gateway=10.20.20.1 \
config.network.control-ip=10.20.20.1 \
config.network.node-fqdn=`hostname -f` \
config.network.compute-ip=10.20.20.1 \
config.network.ext-cidr=10.20.20.1/24 \
config.network.security-rules=true \
config.network.dashboard-allowed-hosts="*" \
config.network.ports.dashboard=80 \
config.network.ports.mysql=3306 \
config.network.ports.rabbit=5672 \
config.network.external-bridge-name=br-ex \
config.network.physnet-name=physnet1 \
;
# Passwords, certs, etc.
snapctl set \
config.credentials.os-password=keystone \
config.credentials.key-pair="/home/{USER}/snap/{SNAP_NAME}/common/.ssh/id_microstack" \
config.credentials.nova-password=nova \
config.credentials.cinder-password=cinder \
config.credentials.neutron-password=neutron \
config.credentials.placement-password=placement \
config.credentials.glance-password=glance \
;
# Cinder volume backend config.
snapctl set \
config.cinder.setup-loop-based-cinder-lvm-backend=false \
config.cinder.loop-device-file-size=32G \
config.cinder.lvm-backend-volume-group=cinder-volumes \
;
# Host optimizations and fixes.
snapctl set \
config.host.ip-forwarding=false \
config.host.check-qemu=true \
;
# Enable or disable groups of services.
snapctl set \
config.services.control-plane=true \
config.services.hypervisor=true \
config.services.spice-console=true \
;
# Clustering roles
snapctl set \
config.cluster.role=control \
config.cluster.password=null \
;
# Uninstall stuff
snapctl set \
config.cleanup.delete-bridge=true \
config.cleanup.remove=true \
;
# Filebeat
snapctl set \
config.logging.datatag="" \
config.logging.host="localhost:5044" \
config.logging.custom-config="$SNAP_COMMON/etc/filebeat/filebeat-microstack.yaml" \
config.services.extra.enabled=false \
config.services.extra.filebeat=false \
;
# services won't start if disabled in install hook
# see https://github.com/snapcore/snapd/blob/53dd10a71d1754610eda2d3d776465b81b3281cd/wrappers/services.go#L139
snapctl stop --disable ${SNAP_NAME}.filebeat
# NRPE
snapctl set \
config.alerting.custom-config="$SNAP_COMMON/etc/nrpe/nrpe-microstack.cfg" \
config.services.extra.nrpe=false \
;
snapctl stop --disable ${SNAP_NAME}.telegraf
# Telegraf
snapctl set \
config.monitoring.ipmi="" \
config.monitoring.custom-config="$SNAP_COMMON/etc/telegraf/telegraf-microstack.conf" \
config.services.extra.telegraf=false \
;
snapctl stop --disable ${SNAP_NAME}.nrpe

View File

@ -0,0 +1,88 @@
#!/usr/bin/env python3
import os
import socket
from init import shell
from init import credentials
def _get_default_config():
snap_common = os.getenv('SNAP_COMMON')
return {
'config.clustered': False,
'config.post-setup': True,
'config.keystone.region-name': 'microstack',
'config.credentials.key-pair': '/home/{USER}/snap/{SNAP_NAME}'
'/common/.ssh/id_microstack',
'config.network.node-fqdn': socket.getfqdn(),
'config.network.dns-servers': '1.1.1.1',
'config.network.dns-domain': 'microstack.example.',
'config.network.ext-gateway': '10.20.20.1',
'config.network.control-ip': '10.20.20.1',
'config.network.compute-ip': '10.20.20.1',
'config.network.ext-cidr': '10.20.20.1/24',
'config.network.security-rules': True,
'config.network.dashboard-allowed-hosts': '*',
'config.network.ports.dashboard': 80,
'config.network.ports.mysql': 3306,
'config.network.ports.rabbit': 5672,
'config.network.external-bridge-name': 'br-ex',
'config.network.physnet-name': 'physnet1',
'config.cinder.setup-loop-based-cinder-lvm-backend': False,
'config.cinder.loop-device-file-size': '32G',
'config.cinder.lvm-backend-volume-group': 'cinder-volumes',
'config.host.ip-forwarding': False,
'config.host.check-qemu': True,
'config.services.control-plane': True,
'config.services.hypervisor': True,
'config.services.spice-console': True,
'config.cluster.role': 'control',
'config.cluster.password': 'null',
'config.cleanup.delete-bridge': True,
'config.cleanup.remove': True,
'config.logging.custom-config': f'{snap_common}/etc/filebeat'
'/filebeat-microstack.yaml',
'config.logging.datatag': '',
'config.logging.host': 'localhost:5044',
'config.services.extra.enabled': False,
'config.services.extra.filebeat': False,
'config.alerting.custom-config': f'{snap_common}/etc/nrpe'
'/nrpe-microstack.cfg',
'config.services.extra.nrpe': False,
'config.monitoring.ipmi': '',
'config.services.extra.telegraf': False,
'config.monitoring.custom-config': f'{snap_common}/etc/telegraf'
'/telegraf-microstack.conf'
}
def _set_default_config():
shell.config_set(**_get_default_config())
def _setup_secrets():
# If a user runs init multiple times we do not want to generate
# new credentials to keep the init operation idempotent.
existing_creds = shell.config_get('config.credentials')
if isinstance(existing_creds, dict):
existing_cred_keys = existing_creds.keys()
else:
existing_cred_keys = []
shell.config_set(**{
k: credentials.generate_password() for k in [
'config.credentials.mysql-root-password',
'config.credentials.rabbitmq-password',
'config.credentials.keystone-password',
'config.credentials.nova-password',
'config.credentials.cinder-password',
'config.credentials.neutron-password',
'config.credentials.placement-password',
'config.credentials.glance-password',
] if k not in existing_cred_keys
})
if __name__ == '__main__':
_set_default_config()
_setup_secrets()

View File

@ -4,5 +4,7 @@ set -ex
export HOME=$SNAP_COMMON/lib/rabbitmq
$SNAP/usr/sbin/rabbitmqctl add_user openstack rabbitmq || true
rabbitmq_password=`snapctl get config.credentials.rabbitmq-password`
$SNAP/usr/sbin/rabbitmqctl add_user openstack $rabbitmq_password || true
$SNAP/usr/sbin/rabbitmqctl set_permissions openstack ".*" ".*" ".*"

View File

@ -83,13 +83,13 @@ setup:
"{snap_common}/etc/microstack.json": 0644
snap-config-keys:
region_name: 'config.keystone.region-name'
ospassword: 'config.credentials.os-password'
keystone_password: 'config.credentials.keystone-password'
nova_password: 'config.credentials.nova-password'
cinder_password: 'config.credentials.cinder-password'
neutron_password: 'config.credentials.neutron-password'
placement_password: 'config.credentials.placement-password'
glance_password: 'config.credentials.glance-password'
placement_password: 'config.credentials.placement-password'
rabbitmq_password: 'config.credentials.rabbitmq-password'
control_ip: 'config.network.control-ip'
node_fqdn: 'config.network.node-fqdn'
compute_ip: 'config.network.compute-ip'

View File

@ -1,2 +1,2 @@
[database]
connection = mysql+pymysql://cinder:cinder@{{ control_ip }}:{{ mysql_port }}/cinder
connection = mysql+pymysql://cinder:{{ cinder_password }}@{{ control_ip }}:{{ mysql_port }}/cinder

View File

@ -1,2 +1,2 @@
[DEFAULT]
transport_url = rabbit://openstack:rabbitmq@{{ control_ip }}:{{ rabbit_port }}
transport_url = rabbit://openstack:{{ rabbitmq_password }}@{{ control_ip }}:{{ rabbit_port }}

View File

@ -1,2 +1,2 @@
[database]
connection = mysql+pymysql://glance:glance@{{ control_ip }}:{{ mysql_port }}/glance
connection = mysql+pymysql://glance:{{ glance_password }}@{{ control_ip }}:{{ mysql_port }}/glance

View File

@ -1,2 +1,2 @@
[database]
connection = mysql+pymysql://keystone:keystone@{{ control_ip }}:{{ mysql_port }}/keystone
connection = mysql+pymysql://keystone:{{ keystone_password }}@{{ control_ip }}:{{ mysql_port }}/keystone

View File

@ -1,7 +1,7 @@
{
"openstack": {
"admin": {
"password": "{{ ospassword }}",
"password": "{{ keystone_password }}",
"project_domain_name": "default",
"project_name": "admin",
"user_domain_name": "default",

View File

@ -2,7 +2,7 @@ export OS_PROJECT_DOMAIN_NAME=default
export OS_USER_DOMAIN_NAME=default
export OS_PROJECT_NAME=admin
export OS_USERNAME=admin
export OS_PASSWORD={{ ospassword }}
export OS_PASSWORD={{ keystone_password }}
export OS_AUTH_URL=http://{{ control_ip }}:5000
export OS_IDENTITY_API_VERSION=3
export OS_IMAGE_API_VERSION=2

View File

@ -1,2 +1,2 @@
[DEFAULT]
transport_url = rabbit://openstack:rabbitmq@{{ control_ip }}:{{ rabbit_port }}
transport_url = rabbit://openstack:{{ rabbitmq_password }}@{{ control_ip }}:{{ rabbit_port }}

View File

@ -1,2 +1,2 @@
[database]
connection = mysql+pymysql://neutron:neutron@{{ control_ip }}:{{ mysql_port }}/neutron
connection = mysql+pymysql://neutron:{{ neutron_password }}@{{ control_ip }}:{{ mysql_port }}/neutron

View File

@ -7,6 +7,7 @@ use_journal = True
# Set a hostname to be an FQDN to avoid issues with port binding for
# which a hostname of a Nova node must match a hostname of an OVN chassis.
host = {{ node_fqdn }}
my_ip = {{ compute_ip }}
[oslo_concurrency]
# Oslo Concurrency lock path

View File

@ -1,5 +1,5 @@
[database]
connection = mysql+pymysql://nova:nova@{{ control_ip }}:{{ mysql_port }}/nova
connection = mysql+pymysql://nova:{{ nova_password }}@{{ control_ip }}:{{ mysql_port }}/nova
[api_database]
connection = mysql+pymysql://nova_api:nova_api@{{ control_ip }}:{{ mysql_port }}/nova_api
connection = mysql+pymysql://nova:{{ nova_password }}@{{ control_ip }}:{{ mysql_port }}/nova_api

View File

@ -1,2 +1,4 @@
[glance]
api_servers = http://{{ control_ip }}:9292
service_type = image
service_name = glance
region_name = {{ region_name }}

View File

@ -1,2 +1,2 @@
[DEFAULT]
transport_url = rabbit://openstack:rabbitmq@{{ control_ip }}:{{ rabbit_port }}
transport_url = rabbit://openstack:{{ rabbitmq_password }}@{{ control_ip }}:{{ rabbit_port }}

View File

@ -1,2 +1,2 @@
[placement_database]
connection = mysql+pymysql://placement:placement@{{ control_ip }}:{{ mysql_port }}/placement
connection = mysql+pymysql://placement:{{ placement_password }}@{{ control_ip }}:{{ mysql_port }}/placement

View File

@ -46,10 +46,25 @@ port=${PORT}
EOF
}
init_sql() {
# Write out the initial SQL statements to execute on mysql initialization.
mkdir -p "${CONFDIR}"
touch ${INIT_SQL_FILE}
chmod 600 ${INIT_SQL_FILE}
echo "Setting a root user password..."
root_password=`snapctl get config.credentials.mysql-root-password`
cat > ${INIT_SQL_FILE} <<EOF
ALTER USER 'root'@'localhost' IDENTIFIED BY '$root_password';
EOF
}
init_database() {
echo "Initializing new database in ${DATADIR}..."
mkdir -p ${DATADIR}
mysqld --defaults-file=${CONFFILE} --initialize-insecure --user=root
init_sql
mysqld --defaults-file=${CONFFILE} --init-file=${INIT_SQL_FILE} --initialize-insecure --user=root
# Get rid of the file with the root password after initialization.
rm -f ${INIT_SQL_FILE}
echo "Done"
}
@ -65,6 +80,8 @@ RUNDIR="${MYSQL_SNAPDIR}/run/mysql"
LOGDIR="${MYSQL_SNAPDIR}/log/mysql"
CONFDIR="${MYSQL_SNAPDIR}/etc/mysql"
CONFFILE="${CONFDIR}/my.cnf"
# A snap-specific temporary directory is used.
INIT_SQL_FILE="/tmp/init.sql"
FILESDIR="${MYSQL_SNAPDIR}/lib/mysql-files"
BASEDIR="${SNAP}/usr"
PORT=$(snapctl get config.network.ports.mysql)

View File

@ -3,7 +3,7 @@ set -ex
# Initialize config
set-default-config
set-default-config.py
# TODO(dmitriis): disable other services and only enable them once the
# prerequisites are met instead of allowing snapd to start them and make them fail.
@ -14,6 +14,23 @@ set-default-config
# 3. Interfaces that do not have auto-connection enabled are manually connected by
# an operator (connecting openvswitch-support loads the openvswitch kernel module
# but auto-connection is not enabled for openvswitch-support).
snapctl stop --disable $SNAP_INSTANCE_NAME.nginx
snapctl stop --disable $SNAP_INSTANCE_NAME.keystone-uwsgi
snapctl stop --disable $SNAP_INSTANCE_NAME.placement-uwsgi
snapctl stop --disable $SNAP_INSTANCE_NAME.nova-api
snapctl stop --disable $SNAP_INSTANCE_NAME.nova-compute
snapctl stop --disable $SNAP_INSTANCE_NAME.nova-conductor
snapctl stop --disable $SNAP_INSTANCE_NAME.nova-api-metadata
snapctl stop --disable $SNAP_INSTANCE_NAME.nova-spicehtml5proxy
snapctl stop --disable $SNAP_INSTANCE_NAME.nova-scheduler
snapctl stop --disable $SNAP_INSTANCE_NAME.neutron-api
snapctl stop --disable $SNAP_INSTANCE_NAME.glance-api
snapctl stop --disable $SNAP_INSTANCE_NAME.registry
snapctl stop --disable $SNAP_INSTANCE_NAME.cinder-uwsgi
snapctl stop --disable $SNAP_INSTANCE_NAME.ovsdb-server
snapctl stop --disable $SNAP_INSTANCE_NAME.neutron-ovn-metadata-agent
snapctl stop --disable $SNAP_INSTANCE_NAME.ovn-ovsdb-server-sb
@ -32,6 +49,9 @@ snapctl stop --disable $SNAP_INSTANCE_NAME.setup-lvm-loopdev
# Will only be enabled if a backend is chosen to be configured by the user.
snapctl stop --disable $SNAP_INSTANCE_NAME.cinder-volume
snapctl stop --disable $SNAP_INSTANCE_NAME.filebeat
snapctl stop --disable $SNAP_INSTANCE_NAME.nrpe
snapctl stop --disable $SNAP_INSTANCE_NAME.telegraf
mkdir -p $SNAP_DATA/lib/libvirt/images
mkdir -p ${SNAP_COMMON}/log/libvirt/qemu

View File

@ -11,7 +11,7 @@ if [ -z "$(snapctl get config)" ]; then
# config values under a config tree. Set the default values now (the
# old values were not documented, and we assume that they were not
# set).
set-default-config
set-default-config.py
# Make a place for our horizon config overrides to live. We piggy
# back on the above check, because the changes were made

View File

@ -252,6 +252,10 @@ class Framework(unittest.TestCase):
dashboard_port = check_output(*host.prefix, 'sudo', 'snap', 'get',
'microstack',
'config.network.ports.dashboard')
keystone_password = check_output(
*host.prefix, 'sudo', 'snap', 'get',
'microstack',
'config.credentials.keystone-password')
self.driver.get("http://{}:{}/".format(
host.horizon_ip,
dashboard_port
@ -259,7 +263,8 @@ class Framework(unittest.TestCase):
# Login to horizon!
self.driver.find_element(By.ID, "id_username").click()
self.driver.find_element(By.ID, "id_username").send_keys("admin")
self.driver.find_element(By.ID, "id_password").send_keys("keystone")
self.driver.find_element(By.ID, "id_password").send_keys(
keystone_password)
self.driver.find_element(By.CSS_SELECTOR, "#loginBtn > span").click()
# Verify that we can click something on the dashboard -- e.g.,
# we're still not sitting at the login screen.

View File

@ -4,7 +4,8 @@ import json
import requests
from cluster.shell import check, check_output
from cluster import shell
from cluster.shell import check_output
def join():
@ -27,12 +28,10 @@ def join():
resp.json))
resp = resp.json()
# TODO: add better error handling to the below
os_password = resp['config']['credentials']['os-password']
# Set passwords and such
check('snapctl', 'set', 'config.credentials.os-password={}'.format(
os_password))
credentials = resp['config']['credentials']
control_creds = {f'config.credentials.{k}': v
for k, v in credentials.items()}
shell.config_set(**control_creds)
if __name__ == '__main__':

View File

@ -1,6 +1,7 @@
import os
import pymysql
import subprocess
import json
def sql(cmd) -> None:
@ -16,7 +17,9 @@ def sql(cmd) -> None:
"""
mysql_conf = '${SNAP_USER_COMMON}/etc/mysql/my.cnf'.format(**os.environ)
root_pasword = config_get('config.credentials.mysql-root-password')
connection = pymysql.connect(host='localhost', user='root',
password=root_pasword,
read_default_file=mysql_conf)
with connection.cursor() as cursor:
@ -36,3 +39,19 @@ def check(*args):
"""
return subprocess.check_call(args, env=os.environ)
def config_get(*keys):
"""Get snap config keys via snapctl.
:param keys list[str]: Keys to retrieve from the snap configuration.
"""
return json.loads(check_output('snapctl', 'get', '-t', *keys))
def config_set(**kwargs):
"""Get snap config keys via snapctl.
:param kwargs dict[str, str]: Values to set in the snap configuration.
"""
check_output('snapctl', 'set', *[f'{k}={v}' for k, v in kwargs.items()])

View File

@ -0,0 +1,9 @@
import string
import secrets
DEFAULT_PASSWORD_LENGTH = 32
def generate_password(length=DEFAULT_PASSWORD_LENGTH):
return ''.join(secrets.choice(
string.ascii_letters + string.digits) for i in range(length))

View File

@ -150,8 +150,8 @@ def init() -> None:
questions.RabbitMq(),
questions.DatabaseSetup(),
questions.PlacementSetup(),
questions.NovaHypervisor(),
questions.NovaControlPlane(),
questions.NovaHypervisor(),
questions.NovaSpiceConsoleSetup(),
questions.NeutronControlPlane(),
questions.GlanceSetup(),

View File

@ -7,7 +7,7 @@ for now, we're keeping things simple (if a big lengthy)
----------------------------------------------------------------------
Copyright 2019 Canonical Ltd
Copyright 2020 Canonical Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -27,6 +27,7 @@ import json
from time import sleep
from os import path
from init import shell
from init.shell import (check, call, check_output, sql, nc_wait, log_wait,
start, restart, download, disable, enable)
from init.config import Env, log
@ -247,12 +248,10 @@ class NetworkSettings(Question):
class OsPassword(ConfigQuestion):
_type = 'string'
_question = 'Openstack Admin Password'
config_key = 'config.credentials.os-password'
config_key = 'config.credentials.keystone-password'
def yes(self, answer):
_env['ospassword'] = answer
# TODO obfuscate the password!
_env['keystone_password'] = answer
class ForceQemu(Question):
@ -367,19 +366,24 @@ class DatabaseSetup(Question):
'mysqld: ready for connections.')
def _create_dbs(self) -> None:
# TODO: actually use passwords here.
for db in ('neutron', 'nova', 'nova_api', 'nova_cell0', 'cinder',
'glance', 'keystone', 'placement'):
sql("CREATE USER IF NOT EXISTS '{db}'@'{control_ip}'"
" IDENTIFIED BY '{db}';".format(db=db, **_env))
sql("CREATE DATABASE IF NOT EXISTS `{db}`;".format(db=db))
sql("GRANT ALL PRIVILEGES ON {db}.* TO '{db}'@'{control_ip}';"
"".format(db=db, **_env))
# Grant nova user access to cell0
sql(
"GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'{control_ip}';"
"".format(**_env))
db_creds = shell.config_get('config.credentials')
for service_user, db_name in (
('neutron', 'neutron'),
('nova', 'nova'),
('nova', 'nova_api'),
('nova', 'nova_cell0'),
('cinder', 'cinder'),
('glance', 'glance'),
('keystone', 'keystone'),
('placement', 'placement')
):
db_password = db_creds[f'{service_user}-password']
sql("CREATE USER IF NOT EXISTS '{user}'@'{control_ip}'"
" IDENTIFIED BY '{db_password}';".format(
user=service_user, db_password=db_password, **_env))
sql("CREATE DATABASE IF NOT EXISTS `{db}`;".format(db=db_name))
sql("GRANT ALL PRIVILEGES ON {db}.* TO '{user}'@'{control_ip}';"
"".format(db=db_name, user=service_user, **_env))
def _bootstrap(self) -> None:
@ -389,7 +393,7 @@ class DatabaseSetup(Question):
bootstrap_url = 'http://{control_ip}:5000/v3/'.format(**_env)
check('snap-openstack', 'launch', 'keystone-manage', 'bootstrap',
'--bootstrap-password', _env['ospassword'],
'--bootstrap-password', _env['keystone_password'],
'--bootstrap-admin-url', bootstrap_url,
'--bootstrap-internal-url', bootstrap_url,
'--bootstrap-public-url', bootstrap_url,
@ -449,17 +453,6 @@ class NovaHypervisor(Question):
def yes(self, answer):
log.info('Configuring nova compute hypervisor ...')
if not call('openstack', 'service', 'show', 'compute'):
check('openstack', 'service', 'create', '--name', 'nova',
'--description', '"Openstack Compute"', 'compute')
# TODO make sure that we are the control plane before executing
# TODO if control plane is not hypervisor, still create this
for endpoint in ['public', 'internal', 'admin']:
call('openstack', 'endpoint', 'create', '--region',
'microstack', 'compute', endpoint,
'http://{compute_ip}:8774/v2.1'.format(**_env))
start('nova-compute')
def no(self, answer):
@ -492,8 +485,12 @@ class PlacementSetup(Question):
log.info('Configuring the Placement service...')
if not call('openstack', 'user', 'show', 'placement'):
check('openstack', 'user', 'create', '--domain', 'default',
'--password', 'placement', 'placement')
check(
'openstack', 'user', 'create', '--domain', 'default',
'--password',
shell.config_get('config.credentials.placement-password'),
'placement',
)
check('openstack', 'role', 'add', '--project', 'service',
'--user', 'placement', 'admin')
@ -549,8 +546,12 @@ class NovaControlPlane(Question):
log.info('Configuring nova control plane services ...')
if not call('openstack', 'user', 'show', 'nova'):
check('openstack', 'user', 'create', '--domain',
'default', '--password', 'nova', 'nova')
check(
'openstack', 'user', 'create', '--domain', 'default',
'--password',
shell.config_get('config.credentials.nova-password'),
'nova'
)
check('openstack', 'role', 'add', '--project',
'service', '--user', 'nova', 'admin')
@ -561,7 +562,7 @@ class NovaControlPlane(Question):
start('nova-api')
log.info('Running Nova API DB migrations'
' (this will take a lot of time)...')
' (this may take a lot of time)...')
check('snap-openstack', 'launch', 'nova-manage', 'api_db', 'sync')
if 'cell0' not in check_output('snap-openstack', 'launch',
@ -577,7 +578,7 @@ class NovaControlPlane(Question):
'create_cell', '--name=cell1', '--verbose')
log.info('Running Nova DB migrations'
' (this will take a lot of time)...')
' (this may take a lot of time)...')
check('snap-openstack', 'launch', 'nova-manage', 'db', 'sync')
restart('nova-api')
@ -594,7 +595,16 @@ class NovaControlPlane(Question):
sleep(5) # TODO: log_wait
if not call('openstack', 'service', 'show', 'compute'):
check('openstack', 'service', 'create', '--name', 'nova',
'--description', '"Openstack Compute"', 'compute')
for endpoint in ['public', 'internal', 'admin']:
call('openstack', 'endpoint', 'create', '--region',
'microstack', 'compute', endpoint,
'http://{control_ip}:8774/v2.1'.format(**_env))
log.info('Creating default flavors...')
self._flavors()
def no(self, answer):
@ -618,8 +628,12 @@ class CinderSetup(Question):
log.info('Configuring the Cinder services...')
if not call('openstack', 'user', 'show', 'cinder'):
check('openstack', 'user', 'create', '--domain', 'default',
'--password', 'cinder', 'cinder')
check(
'openstack', 'user', 'create', '--domain', 'default',
'--password',
shell.config_get('config.credentials.cinder-password'),
'cinder'
)
check('openstack', 'role', 'add', '--project', 'service',
'--user', 'cinder', 'admin')
@ -699,8 +713,11 @@ class NeutronControlPlane(Question):
log.info('Configuring Neutron')
if not call('openstack', 'user', 'show', 'neutron'):
check('openstack', 'user', 'create', '--domain', 'default',
'--password', 'neutron', 'neutron')
check(
'openstack', 'user', 'create', '--domain', 'default',
'--password',
shell.config_get('config.credentials.neutron-password'),
'neutron')
check('openstack', 'role', 'add', '--project', 'service',
'--user', 'neutron', 'admin')
@ -810,8 +827,12 @@ class GlanceSetup(Question):
log.info('Configuring Glance ...')
if not call('openstack', 'user', 'show', 'glance'):
check('openstack', 'user', 'create', '--domain', 'default',
'--password', 'glance', 'glance')
check(
'openstack', 'user', 'create', '--domain', 'default',
'--password',
shell.config_get('config.credentials.glance-password'),
'glance'
)
check('openstack', 'role', 'add', '--project', 'service',
'--user', 'glance', 'admin')

View File

@ -32,6 +32,7 @@ import netifaces
import pymysql
import socket
import wget
import json
from init.config import Env, log
@ -105,7 +106,9 @@ def sql(cmd: str) -> None:
"""
mysql_conf = '{SNAP_COMMON}/etc/mysql/my.cnf'.format(**_env)
connection = pymysql.connect(read_default_file=mysql_conf)
root_pasword = config_get('config.credentials.mysql-root-password')
connection = pymysql.connect(read_default_file=mysql_conf,
password=root_pasword)
with connection.cursor() as cursor:
cursor.execute(cmd)
@ -169,6 +172,22 @@ def disable(service: str) -> None:
check('snapctl', 'stop', '--disable', 'microstack.{}'.format(service))
def config_get(*keys):
"""Get snap config keys via snapctl.
:param keys list[str]: Keys to retrieve from the snap configuration.
"""
return json.loads(check_output('snapctl', 'get', '-t', *keys))
def config_set(**kwargs):
"""Get snap config keys via snapctl.
:param kwargs dict[str, str]: Values to set in the snap configuration.
"""
check_output('snapctl', 'set', *[f'{k}={v}' for k, v in kwargs.items()])
def download(url: str, output: str) -> None:
"""Download a file to a path"""
wget.download(url, output)