OpenStack in a snap!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

410 lines
15 KiB

"""questions.py
All of our subclasses of Question live here.
We might break this file up into multiple pieces at some point, but
for now, we're keeping things simple (if a big lengthy)
----------------------------------------------------------------------
Copyright 2019 Canonical Ltd
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 time import sleep
from os import path
from init.shell import (check, call, check_output, shell, sql, nc_wait,
log_wait, restart, download)
from init.config import Env, log
from init.question import Question
_env = Env().get_env()
class ConfigError(Exception):
"""Suitable error to raise in case there is an issue with the snapctl
config or environment vars.
"""
class Setup(Question):
"""Prepare our environment.
Check to make sure that everything is in place, and populate our
config object and os.environ with the correct values.
"""
def yes(self, answer: str) -> None:
"""Since this is an auto question, we always execute yes."""
log.info('Loading config and writing templates ...')
log.info('Validating config ...')
for key in ['ospassword', 'extgateway', 'extcidr', 'dns']:
val = check_output('snapctl', 'get', key)
if not val:
raise ConfigError(
'Expected config value {} is not set.'.format(key))
_env[key] = val
log.info('Writing out templates ...')
check('snap-openstack', 'setup')
# Parse microstack.rc, and load into _env
# TODO: write something more robust (this breaks on comments
# at end of line.)
mstackrc = '{SNAP_COMMON}/etc/microstack.rc'.format(**_env)
with open(mstackrc, 'r') as rc_file:
for line in rc_file.readlines():
if not line.startswith('export'):
continue
key, val = line[7:].split('=')
_env[key.strip()] = val.strip()
class IpForwarding(Question):
"""Possibly setup IP forwarding."""
_type = 'auto' # Auto for now, to maintain old behavior.
_question = 'Do you wish to setup ip forwarding? (recommended)'
def yes(self, answer: str) -> None:
"""Use sysctl to setup ip forwarding."""
log.info('Setting up ipv4 forwarding...')
check('sysctl', 'net.ipv4.ip_forward=1')
class VmSwappiness(Question):
_type = 'binary'
_question = 'Do you wish to set vm swappiness to 1? (recommended)'
def yes(self, answer: str) -> None:
# TODO
pass
class FileHandleLimits(Question):
_type = 'binary'
_question = 'Do you wish to increase file handle limits? (recommended)'
def yes(self, answer: str) -> None:
# TODO
pass
class RabbitMQ(Question):
"""Wait for Rabbit to start, then setup permissions."""
def _wait(self) -> None:
nc_wait(_env['extgateway'], '5672')
log_file = '{SNAP_COMMON}/log/rabbitmq/startup_log'.format(**_env)
log_wait(log_file, 'completed')
def _configure(self) -> None:
"""Configure RabbitMQ
(actions may have already been run, in which case we fail silently).
"""
# Add Erlang HOME to env.
env = dict(**_env)
env['HOME'] = '{SNAP_COMMON}/lib/rabbitmq'.format(**_env)
# Configure RabbitMQ
call('rabbitmqctl', 'add_user', 'openstack', 'rabbitmq', env=env)
shell('rabbitmqctl set_permissions openstack ".*" ".*" ".*"', env=env)
def yes(self, answer: str) -> None:
log.info('Waiting for RabbitMQ to start ...')
self._wait()
log.info('RabbitMQ started!')
log.info('Configuring RabbitMQ ...')
self._configure()
log.info('RabbitMQ Configured!')
class DatabaseSetup(Question):
"""Setup keystone permissions, then setup all databases."""
def _wait(self) -> None:
nc_wait(_env['extgateway'], '3306')
log_wait('{SNAP_COMMON}/log/mysql/error.log'.format(**_env),
'mysqld: ready for connections.')
def _create_dbs(self) -> None:
for db in ('neutron', 'nova', 'nova_api', 'nova_cell0', 'cinder',
'glance', 'keystone'):
sql("CREATE DATABASE IF NOT EXISTS {db};".format(db=db))
sql(
"GRANT ALL PRIVILEGES ON {db}.* TO {db}@{extgateway} \
IDENTIFIED BY '{db}';".format(db=db, **_env))
def _bootstrap(self) -> None:
if call('openstack', 'user', 'show', 'admin'):
return
bootstrap_url = 'http://{extgateway}:5000/v3/'.format(**_env)
check('snap-openstack', 'launch', 'keystone-manage', 'bootstrap',
'--bootstrap-password', _env['ospassword'],
'--bootstrap-admin-url', bootstrap_url,
'--bootstrap-internal-url', bootstrap_url,
'--bootstrap-public-url', bootstrap_url)
def yes(self, answer: str) -> None:
"""Setup Databases.
Create all the MySQL databases we require, then setup the
fernet keys and create the service project.
"""
log.info('Waiting for MySQL server to start ...')
self._wait()
log.info('Mysql server started! Creating databases ...')
self._create_dbs()
log.info('Configuring Keystone Fernet Keys ...')
check('snap-openstack', 'launch', 'keystone-manage',
'fernet_setup', '--keystone-user', 'root',
'--keystone-group', 'root')
check('snap-openstack', 'launch', 'keystone-manage', 'db_sync')
restart('keystone-*')
log.info('Bootstrapping Keystone ...')
self._bootstrap()
log.info('Creating service project ...')
if not call('openstack', 'project', 'show', 'service'):
check('openstack', 'project', 'create', '--domain',
'default', '--description', 'Service Project',
'service')
log.info('Keystone configured!')
class NovaSetup(Question):
"""Create all relevant nova users and services."""
def _flavors(self) -> None:
"""Create default flavors."""
if not call('openstack', 'flavor', 'show', 'm1.tiny'):
check('openstack', 'flavor', 'create', '--id', '1',
'--ram', '512', '--disk', '1', '--vcpus', '1', 'm1.tiny')
if not call('openstack', 'flavor', 'show', 'm1.small'):
check('openstack', 'flavor', 'create', '--id', '2',
'--ram', '2048', '--disk', '20', '--vcpus', '1', 'm1.small')
if not call('openstack', 'flavor', 'show', 'm1.medium'):
check('openstack', 'flavor', 'create', '--id', '3',
'--ram', '4096', '--disk', '20', '--vcpus', '2', 'm1.medium')
if not call('openstack', 'flavor', 'show', 'm1.large'):
check('openstack', 'flavor', 'create', '--id', '4',
'--ram', '8192', '--disk', '20', '--vcpus', '4', 'm1.large')
if not call('openstack', 'flavor', 'show', 'm1.xlarge'):
check('openstack', 'flavor', 'create', '--id', '5',
'--ram', '16384', '--disk', '20', '--vcpus', '8',
'm1.xlarge')
def yes(self, answer: str) -> None:
log.info('Configuring nova ...')
if not call('openstack', 'user', 'show', 'nova'):
check('openstack', 'user', 'create', '--domain',
'default', '--password', 'nova', 'nova')
check('openstack', 'role', 'add', '--project',
'service', '--user', 'nova', 'admin')
if not call('openstack', 'user', 'show', 'placement'):
check('openstack', 'user', 'create', '--domain', 'default',
'--password', 'placement', 'placement')
check('openstack', 'role', 'add', '--project', 'service',
'--user', 'placement', 'admin')
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://{extgateway}:8774/v2.1'.format(**_env))
if not call('openstack', 'service', 'show', 'placement'):
check('openstack', 'service', 'create', '--name',
'placement', '--description', '"Placement API"',
'placement')
for endpoint in ['public', 'internal', 'admin']:
call('openstack', 'endpoint', 'create', '--region',
'microstack', 'placement', endpoint,
'http://{extgateway}:8778'.format(**_env))
# Grant nova user access to cell0
sql(
"GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'{extgateway}' \
IDENTIFIED BY \'nova';".format(**_env))
check('snap-openstack', 'launch', 'nova-manage', 'api_db', 'sync')
if 'cell0' not in check_output('snap-openstack', 'launch',
'nova-manage', 'cell_v2',
'list_cells'):
check('snap-openstack', 'launch', 'nova-manage',
'cell_v2', 'map_cell0')
if 'cell1' not in check_output('snap-openstack', 'launch',
'nova-manage', 'cell_v2', 'list_cells'):
check('snap-openstack', 'launch', 'nova-manage', 'cell_v2',
'create_cell', '--name=cell1', '--verbose')
check('snap-openstack', 'launch', 'nova-manage', 'db', 'sync')
restart('nova-*')
nc_wait(_env['extgateway'], '8774')
sleep(5) # TODO: log_wait
log.info('Creating default flavors...')
self._flavors()
class NeutronSetup(Question):
"""Create all relevant neutron services and users."""
def yes(self, answer: str) -> None:
log.info('Configuring Neutron')
if not call('openstack', 'user', 'show', 'neutron'):
check('openstack', 'user', 'create', '--domain', 'default',
'--password', 'neutron', 'neutron')
check('openstack', 'role', 'add', '--project', 'service',
'--user', 'neutron', 'admin')
if not call('openstack', 'service', 'show', 'network'):
check('openstack', 'service', 'create', '--name', 'neutron',
'--description', '"OpenStack Network"', 'network')
for endpoint in ['public', 'internal', 'admin']:
call('openstack', 'endpoint', 'create', '--region',
'microstack', 'network', endpoint,
'http://{extgateway}:9696'.format(**_env))
check('snap-openstack', 'launch', 'neutron-db-manage', 'upgrade',
'head')
restart('neutron-*')
nc_wait(_env['extgateway'], '9696')
sleep(5) # TODO: log_wait
if not call('openstack', 'network', 'show', 'test'):
check('openstack', 'network', 'create', 'test')
if not call('openstack', 'subnet', 'show', 'test-subnet'):
check('openstack', 'subnet', 'create', '--network', 'test',
'--subnet-range', '192.168.222.0/24', 'test-subnet')
if not call('openstack', 'network', 'show', 'external'):
check('openstack', 'network', 'create', '--external',
'--provider-physical-network=physnet1',
'--provider-network-type=flat', 'external')
if not call('openstack', 'subnet', 'show', 'external-subnet'):
check('openstack', 'subnet', 'create', '--network', 'external',
'--subnet-range', _env['extcidr'], '--no-dhcp',
'external-subnet')
if not call('openstack', 'router', 'show', 'test-router'):
check('openstack', 'router', 'create', 'test-router')
check('openstack', 'router', 'add', 'subnet', 'test-router',
'test-subnet')
check('openstack', 'router', 'set', '--external-gateway',
'external', 'test-router')
class GlanceSetup(Question):
"""Setup glance, and download an initial Cirros image."""
def _fetch_cirros(self) -> None:
if call('openstack', 'image', 'show', 'cirros'):
return
env = dict(**_env)
env['VER'] = '0.4.0'
env['IMG'] = 'cirros-{VER}-x86_64-disk.img'.format(**env)
log.info('Fetching cirros image ...')
cirros_path = '{SNAP_COMMON}/images/{IMG}'.format(**env)
if not path.exists(cirros_path):
check('mkdir', '-p', '{SNAP_COMMON}/images'.format(**env))
download(
'http://download.cirros-cloud.net/{VER}/{IMG}'.format(**env),
'{SNAP_COMMON}/images/{IMG}'.format(**env))
check('openstack', 'image', 'create', '--file',
'{SNAP_COMMON}/images/{IMG}'.format(**env),
'--public', '--container-format=bare',
'--disk-format=qcow2', 'cirros')
def yes(self, answer: str) -> None:
log.info('Configuring Glance ...')
if not call('openstack', 'user', 'show', 'glance'):
check('openstack', 'user', 'create', '--domain', 'default',
'--password', 'glance', 'glance')
check('openstack', 'role', 'add', '--project', 'service',
'--user', 'glance', 'admin')
if not call('openstack', 'service', 'show', 'image'):
check('openstack', 'service', 'create', '--name', 'glance',
'--description', '"OpenStack Image"', 'image')
for endpoint in ['internal', 'admin', 'public']:
check('openstack', 'endpoint', 'create', '--region',
'microstack', 'image', endpoint,
'http://{extgateway}:9292'.format(**_env))
check('snap-openstack', 'launch', 'glance-manage', 'db_sync')
restart('glance*')
nc_wait(_env['extgateway'], '9292')
sleep(5) # TODO: log_wait
self._fetch_cirros()
class PostSetup(Question):
"""Sneak in any additional cleanup, then set the initialized state."""
def yes(self, answer: str) -> None:
log.info('restarting libvirt and virtlogd ...')
# This fixes an issue w/ logging not getting set.
# TODO: fix issue.
restart('*virt*')
check('snapctl', 'set', 'initialized=true')
log.info('Complete. Marked microstack as initialized!')