Support --heat-type=full
Add a new "full" ephemeral heat type which in addition to launching the heat pod, will also launch an infra pod containing ephemeral instances of mysql and rabbitmq. When using --heat-type=full, no undercloud installation is necessary at all (if using deployed-server). Change-Id: Ia6d823aa3f994d4ce05ea5c8940ec43c291d1add Signed-off-by: James Slagle <jslagle@redhat.com>
This commit is contained in:
parent
723bf34caf
commit
6378a4d833
|
@ -0,0 +1,69 @@
|
|||
[client]
|
||||
port = 3306
|
||||
socket = /var/lib/mysql/mysql.sock
|
||||
|
||||
[isamchk]
|
||||
key_buffer_size = 16M
|
||||
|
||||
[mysqld]
|
||||
wsrep_provider=none
|
||||
basedir = /usr
|
||||
bind-address = DATABASE_IP
|
||||
connect_timeout = 60
|
||||
datadir = /var/lib/mysql
|
||||
expire_logs_days = 10
|
||||
innodb_buffer_pool_size = 1G
|
||||
innodb_file_per_table = ON
|
||||
innodb_strict_mode = OFF
|
||||
key_buffer_size = 16M
|
||||
log-error = /var/log/mariadb/mariadb.log
|
||||
log_warnings = 1
|
||||
max_allowed_packet = 16M
|
||||
max_binlog_size = 100M
|
||||
max_connections = 4096
|
||||
open_files_limit = 65536
|
||||
pid-file = /var/lib/mysql/mariadb.pid
|
||||
plugin_load_add = auth_ed25519
|
||||
port = 3306
|
||||
query_cache_size = 0
|
||||
query_cache_type = 0
|
||||
skip-external-locking
|
||||
socket = /var/lib/mysql/mysql.sock
|
||||
ssl = false
|
||||
thread_cache_size = 8
|
||||
thread_stack = 256K
|
||||
tmpdir = /tmp
|
||||
user = mysql
|
||||
|
||||
[mysqld-5.0]
|
||||
myisam-recover = BACKUP
|
||||
|
||||
[mysqld-5.1]
|
||||
myisam-recover = BACKUP
|
||||
|
||||
[mysqld-5.5]
|
||||
myisam-recover = BACKUP
|
||||
query_cache_limit = 1M
|
||||
query_cache_size = 16M
|
||||
|
||||
[mysqld-5.6]
|
||||
myisam-recover-options = BACKUP
|
||||
query_cache_limit = 1M
|
||||
query_cache_size = 16M
|
||||
|
||||
[mysqld-5.7]
|
||||
myisam-recover-options = BACKUP
|
||||
query_cache_limit = 1M
|
||||
query_cache_size = 16M
|
||||
|
||||
[mysqld_safe]
|
||||
log-error = /var/log/mariadb/mariadb.log
|
||||
nice = 0
|
||||
pid-file = /var/lib/mysql/mariadb.pid
|
||||
socket = /var/lib/mysql/mysql.sock
|
||||
|
||||
[mysqldump]
|
||||
max_allowed_packet = 16M
|
||||
quick
|
||||
quote-names
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
labels:
|
||||
app: ephemeral-heat-infra
|
||||
name: ephemeral-heat-infra
|
||||
spec:
|
||||
containers:
|
||||
- name: rabbitmq
|
||||
command:
|
||||
- /rabbitmq-init.sh
|
||||
env:
|
||||
- name: PATH
|
||||
value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
- name: TERM
|
||||
value: xterm
|
||||
- name: container
|
||||
value: oci
|
||||
- name: LANG
|
||||
value: en_US.UTF-8
|
||||
image: quay.io/tripleomaster/openstack-rabbitmq:current-tripleo
|
||||
resources: {}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: true
|
||||
capabilities:
|
||||
drop:
|
||||
- CAP_MKNOD
|
||||
- CAP_AUDIT_WRITE
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: false
|
||||
runAsGroup: 42439
|
||||
runAsUser: 42439
|
||||
seLinuxOptions: {}
|
||||
volumeMounts:
|
||||
- mountPath: /rabbitmq-init.sh
|
||||
name: rabbitmq-init
|
||||
- mountPath: /rabbitmq.config
|
||||
name: rabbitmq-config
|
||||
workingDir: /
|
||||
- name: mysql
|
||||
command:
|
||||
- /mysql-init.sh
|
||||
args: []
|
||||
env:
|
||||
- name: PATH
|
||||
value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
- name: TERM
|
||||
value: xterm
|
||||
- name: container
|
||||
value: oci
|
||||
- name: LANG
|
||||
value: en_US.UTF-8
|
||||
- name: DB_ROOT_PASSWORD
|
||||
value: {{ db_password }}
|
||||
- name: DB_MAX_TIMEOUT
|
||||
value: {{ db_timeout }}
|
||||
image: quay.io/tripleomaster/openstack-mariadb:current-tripleo
|
||||
resources: {}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: true
|
||||
capabilities:
|
||||
drop:
|
||||
- CAP_MKNOD
|
||||
- CAP_AUDIT_WRITE
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: false
|
||||
runAsGroup: 0
|
||||
runAsUser: 0
|
||||
seLinuxOptions: {}
|
||||
volumeMounts:
|
||||
- mountPath: /etc/my.cnf.d/galera.cnf
|
||||
name: mysql-config
|
||||
- mountPath: /mysql-init.sh
|
||||
name: mysql-init
|
||||
- mountPath: /root/.my.cnf
|
||||
name: my-cnf
|
||||
workingDir: /
|
||||
volumes:
|
||||
- hostPath:
|
||||
path: {{ heat_dir }}/galera.cnf
|
||||
type: File
|
||||
name: mysql-config
|
||||
- hostPath:
|
||||
path: {{ heat_dir }}/mysql-init.sh
|
||||
type: File
|
||||
name: mysql-init
|
||||
- hostPath:
|
||||
path: {{ heat_dir }}/my.cnf
|
||||
type: File
|
||||
name: my-cnf
|
||||
- hostPath:
|
||||
path: {{ heat_dir }}/rabbitmq.config
|
||||
type: File
|
||||
name: rabbitmq-config
|
||||
- hostPath:
|
||||
path: {{ heat_dir }}/rabbitmq-init.sh
|
||||
type: File
|
||||
name: rabbitmq-init
|
||||
status: {}
|
|
@ -0,0 +1,5 @@
|
|||
[mysql]
|
||||
user=root
|
||||
host=localhost
|
||||
password={{ db_password }}
|
||||
socket=/var/lib/mysql/mysql.sock
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -eux
|
||||
|
||||
if [ ! -d /var/lib/mysql/mysql ]; then
|
||||
mysql_install_db
|
||||
fi
|
||||
DATABASE_IP=$(ip -f inet addr show eth0 | awk '/inet / {print $2}' | cut -d/ -f1)
|
||||
sed -e "s/DATABASE_IP/$DATABASE_IP/" /etc/my.cnf.d/galera.cnf > /tmp/galera.cnf
|
||||
cat /tmp/galera.cnf > /etc/my.cnf.d/galera.cnf
|
||||
mysqld_safe --wsrep-on=OFF &
|
||||
timeout ${DB_MAX_TIMEOUT}s /bin/bash -c 'until mysqladmin -uroot ping 2>/dev/null; do sleep 1; done'
|
||||
mysqladmin -u root password $DB_ROOT_PASSWORD
|
||||
mysql -uroot -p"${DB_ROOT_PASSWORD}" -e "CREATE USER 'mysql'@'localhost';"
|
||||
mysql -uroot -p"${DB_ROOT_PASSWORD}" -e "REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'mysql'@'localhost';"
|
||||
sleep infinity
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -eux
|
||||
|
||||
RABBITMQ_IP=$(ip -f inet addr show eth0 | awk '/inet / {print $2}' | cut -d/ -f1)
|
||||
sed -e "s/RABBITMQ_IP/$RABBITMQ_IP/" /rabbitmq.config > /etc/rabbitmq/rabbitmq.config
|
||||
/usr/lib/rabbitmq/bin/rabbitmq-server &
|
||||
sleep infinity
|
|
@ -0,0 +1,31 @@
|
|||
[
|
||||
{rabbit, [
|
||||
{loopback_users, []},
|
||||
{tcp_listen_options, [
|
||||
{keepalive, true},
|
||||
{backlog, 4096},
|
||||
{nodelay, true},
|
||||
{linger, {true, 0}},
|
||||
{exit_on_close, false}
|
||||
]},
|
||||
{collect_statistics_interval, 30000},
|
||||
{tcp_listeners, [{"RABBITMQ_IP", 5672}]},
|
||||
{cluster_partition_handling, ignore},
|
||||
{queue_master_locator, <<"min-masters">>},
|
||||
{default_user, <<"guest">>},
|
||||
{default_pass, <<"guest">>}
|
||||
]},
|
||||
{kernel, [
|
||||
{inet_dist_listen_max, 25672},
|
||||
{inet_dist_listen_min, 25672},
|
||||
{net_ticktime, 15}
|
||||
]}
|
||||
,
|
||||
{rabbitmq_management, [
|
||||
{rates_mode, none}
|
||||
, {listener, [
|
||||
{ip, "127.0.0.1"},
|
||||
{port, 15672}
|
||||
]}
|
||||
]}
|
||||
].
|
|
@ -37,6 +37,7 @@ STANDALONE_NETWORKS_FILE = "/dev/null"
|
|||
UNDERCLOUD_NETWORKS_FILE = "network_data_undercloud.yaml"
|
||||
ANSIBLE_HOSTS_FILENAME = "hosts.yaml"
|
||||
EPHEMERAL_HEAT_POD_NAME = "ephemeral-heat"
|
||||
EPHEMERAL_HEAT_INFRA_POD_NAME = "ephemeral-heat-infra"
|
||||
ANSIBLE_CWL = "tripleo_dense,tripleo_profile_tasks,tripleo_states"
|
||||
CONTAINER_IMAGE_PREPARE_LOG_FILE = "container_image_prepare.log"
|
||||
DEFAULT_CONTAINER_REGISTRY = "quay.io"
|
||||
|
|
|
@ -143,3 +143,7 @@ class HeatPodMessageQueueException(Base):
|
|||
|
||||
class InvalidPlaybook(Base):
|
||||
"""Invalid playbook path specified"""
|
||||
|
||||
|
||||
class HeatPodDatabaseException(Base):
|
||||
"""Heat database does not seem to be available"""
|
||||
|
|
|
@ -41,8 +41,10 @@ from tripleoclient.constants import (DEFAULT_HEAT_CONTAINER,
|
|||
DEFAULT_EPHEMERAL_HEAT_API_CONTAINER,
|
||||
DEFAULT_EPHEMERAL_HEAT_ENGINE_CONTAINER,
|
||||
DEFAULT_TEMPLATES_DIR,
|
||||
EPHEMERAL_HEAT_INFRA_POD_NAME,
|
||||
EPHEMERAL_HEAT_POD_NAME)
|
||||
from tripleoclient.exceptions import HeatPodMessageQueueException
|
||||
from tripleoclient.exceptions import (HeatPodMessageQueueException,
|
||||
HeatPodDatabaseException)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -198,7 +200,8 @@ class HeatBaseLauncher(object):
|
|||
|
||||
self.user = user
|
||||
self._write_fake_keystone_token(self.api_port, self.token_file)
|
||||
self._write_heat_config()
|
||||
if getattr(self, 'do_write_heat_config', True):
|
||||
self._write_heat_config()
|
||||
self._write_api_paste_config()
|
||||
if use_root:
|
||||
uid = int(self.get_heat_uid())
|
||||
|
@ -455,6 +458,9 @@ class HeatNativeLauncher(HeatBaseLauncher):
|
|||
class HeatPodLauncher(HeatContainerLauncher):
|
||||
|
||||
heat_type = 'pod'
|
||||
mysql_container = 'mysql'
|
||||
rabbitmq_container = 'rabbitmq'
|
||||
db_password = ''
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(HeatPodLauncher, self).__init__(*args, **kwargs)
|
||||
|
@ -494,10 +500,19 @@ class HeatPodLauncher(HeatContainerLauncher):
|
|||
raise Exception('Unable to fetch container image {}.'
|
||||
'Error: {}'.format(image, e))
|
||||
|
||||
def get_pod_state(self):
|
||||
def get_container_state(self, container='ephemeral-heat-api'):
|
||||
inspect = subprocess.run([
|
||||
'sudo', 'podman', 'inspect', '--format',
|
||||
'{{.State.Status}}', container],
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
return self._decode(inspect.stdout)
|
||||
|
||||
def get_pod_state(self, pod='ephemeral-heat'):
|
||||
inspect = subprocess.run([
|
||||
'sudo', 'podman', 'pod', 'inspect', '--format',
|
||||
'"{{.State}}"', EPHEMERAL_HEAT_POD_NAME],
|
||||
'"{{.State}}"', pod],
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
|
@ -518,22 +533,22 @@ class HeatPodLauncher(HeatContainerLauncher):
|
|||
if not self.database_exists():
|
||||
subprocess.check_call([
|
||||
'sudo', 'podman', 'exec', '-u', 'root',
|
||||
'mysql', 'mysql', '-e', 'create database heat'
|
||||
self.mysql_container, 'mysql', '-e', 'create database heat'
|
||||
])
|
||||
subprocess.check_call([
|
||||
'sudo', 'podman', 'exec', '-u', 'root',
|
||||
'mysql', 'mysql', '-e',
|
||||
self.mysql_container, 'mysql', '-e',
|
||||
'create user if not exists '
|
||||
'\'heat\'@\'%\' identified by \'heat\''
|
||||
])
|
||||
subprocess.check_call([
|
||||
'sudo', 'podman', 'exec', '-u', 'root',
|
||||
'mysql', 'mysql', 'heat', '-e',
|
||||
self.mysql_container, 'mysql', 'heat', '-e',
|
||||
'grant all privileges on heat.* to \'heat\'@\'%\''
|
||||
])
|
||||
subprocess.check_call([
|
||||
'sudo', 'podman', 'exec', '-u', 'root',
|
||||
'mysql', 'mysql', '-e', 'flush privileges;'
|
||||
self.mysql_container, 'mysql', '-e', 'flush privileges;'
|
||||
])
|
||||
cmd = [
|
||||
'sudo', 'podman', 'run', '--rm',
|
||||
|
@ -580,17 +595,20 @@ class HeatPodLauncher(HeatContainerLauncher):
|
|||
"Remove it first." % db_dump_path)
|
||||
log.info("Starting back up of heat db")
|
||||
with open(db_dump_path, 'w') as out:
|
||||
subprocess.run([
|
||||
backup_command = [
|
||||
'sudo', 'podman', 'exec', '-u', 'root',
|
||||
'mysql', 'mysqldump', 'heat'], stdout=out,
|
||||
check=True)
|
||||
self.mysql_container, 'mysqldump', '-uroot']
|
||||
if self.db_password:
|
||||
backup_command.extend(['-p{}'.format(self.db_password)])
|
||||
backup_command.extend(['heat'])
|
||||
subprocess.run(backup_command, stdout=out, check=True)
|
||||
|
||||
self.tar_file(db_dump_path)
|
||||
|
||||
def pod_exists(self):
|
||||
def pod_exists(self, pod_name='ephemeral-heat'):
|
||||
try:
|
||||
subprocess.check_call(
|
||||
['sudo', 'podman', 'pod', 'inspect', EPHEMERAL_HEAT_POD_NAME],
|
||||
['sudo', 'podman', 'pod', 'inspect', pod_name],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL)
|
||||
return True
|
||||
|
@ -633,53 +651,69 @@ class HeatPodLauncher(HeatContainerLauncher):
|
|||
])
|
||||
log.info("Stopped pod: %s", EPHEMERAL_HEAT_POD_NAME)
|
||||
|
||||
@retry(retry=retry_if_exception_type(HeatPodMessageQueueException),
|
||||
reraise=True,
|
||||
stop=(stop_after_delay(10) | stop_after_attempt(10)),
|
||||
wait=wait_fixed(0.5))
|
||||
def check_message_bus(self):
|
||||
log.info("Checking that message bus (rabbitmq) is up")
|
||||
log.info("Checking that message bus (rabbitmq) is available...")
|
||||
try:
|
||||
subprocess.check_call([
|
||||
'sudo', 'podman', 'exec', '-u', 'root', 'rabbitmq',
|
||||
'sudo', 'podman', 'exec', '-u', 'root',
|
||||
self.rabbitmq_container,
|
||||
'rabbitmqctl', 'list_queues'],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL)
|
||||
log.info("Message bus (rabbitmq) is available")
|
||||
return True
|
||||
except subprocess.CalledProcessError as cpe:
|
||||
log.error("The message bus (rabbitmq) does not seem "
|
||||
"to be available")
|
||||
log.error(cpe)
|
||||
raise
|
||||
except subprocess.CalledProcessError:
|
||||
msg = "The message bus (rabbitmq) is not available"
|
||||
raise HeatPodMessageQueueException(msg)
|
||||
|
||||
def _get_database_ip(self):
|
||||
return self._get_database_ip()
|
||||
|
||||
@retry(retry=retry_if_exception_type(HeatPodDatabaseException),
|
||||
reraise=True,
|
||||
stop=(stop_after_delay(10) | stop_after_attempt(10)),
|
||||
wait=wait_fixed(0.5))
|
||||
def check_database(self):
|
||||
log.info("Checking that database is up")
|
||||
log.info("Checking that database is available...")
|
||||
try:
|
||||
subprocess.check_call([
|
||||
'sudo', 'podman', 'exec', '-u', 'root', 'mysql',
|
||||
'mysql', '-h', self._get_ctlplane_ip(), '-e',
|
||||
'sudo', 'podman', 'exec', '-u', 'root', self.mysql_container,
|
||||
'mysql', '-e',
|
||||
'show databases;'],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL)
|
||||
log.info("Database available.")
|
||||
return True
|
||||
except subprocess.CalledProcessError as cpe:
|
||||
log.error("The database does not seem to be available")
|
||||
log.error(cpe)
|
||||
raise
|
||||
except subprocess.CalledProcessError:
|
||||
msg = "The database is not available"
|
||||
raise HeatPodDatabaseException(msg)
|
||||
|
||||
def database_exists(self):
|
||||
if self.get_container_state(self.mysql_container).lower() != "running":
|
||||
return False
|
||||
|
||||
output = subprocess.check_output([
|
||||
'sudo', 'podman', 'exec', '-u', 'root', 'mysql',
|
||||
'sudo', 'podman', 'exec', '-u', 'root', self.mysql_container,
|
||||
'mysql', '-e', 'show databases like "heat"'
|
||||
])
|
||||
return 'heat' in str(output)
|
||||
|
||||
def kill_heat(self, pid):
|
||||
if self.pod_exists():
|
||||
log.info("Killing pod: %s", EPHEMERAL_HEAT_POD_NAME)
|
||||
def kill_pod(self, pod_name=EPHEMERAL_HEAT_POD_NAME):
|
||||
if self.pod_exists(pod_name):
|
||||
log.info("Killing pod: {}".format(pod_name))
|
||||
subprocess.call([
|
||||
'sudo', 'podman', 'pod', 'kill',
|
||||
EPHEMERAL_HEAT_POD_NAME
|
||||
'sudo', 'podman', 'pod', 'kill', pod_name
|
||||
])
|
||||
log.info("Killed pod: %s", EPHEMERAL_HEAT_POD_NAME)
|
||||
log.info("Killed pod: {}".format(pod_name))
|
||||
else:
|
||||
log.info("Pod does not exist: %s", EPHEMERAL_HEAT_POD_NAME)
|
||||
log.info("Pod does not exist: {}".format(pod_name))
|
||||
|
||||
def kill_heat(self, pid):
|
||||
self.kill_pod()
|
||||
|
||||
def _decode(self, encoded):
|
||||
if not encoded:
|
||||
|
@ -687,7 +721,7 @@ class HeatPodLauncher(HeatContainerLauncher):
|
|||
decoded = encoded.decode('utf-8')
|
||||
if decoded.endswith('\n'):
|
||||
decoded = decoded[:-1]
|
||||
return decoded
|
||||
return decoded.strip('"')
|
||||
|
||||
def _get_transport_url(self):
|
||||
user = self._decode(subprocess.check_output(
|
||||
|
@ -727,7 +761,7 @@ class HeatPodLauncher(HeatContainerLauncher):
|
|||
def wait_for_message_queue(self):
|
||||
queue_name = 'engine.' + EPHEMERAL_HEAT_POD_NAME
|
||||
output = subprocess.check_output([
|
||||
'sudo', 'podman', 'exec', 'rabbitmq',
|
||||
'sudo', 'podman', 'exec', self.rabbitmq_container,
|
||||
'rabbitmqctl', 'list_queues'])
|
||||
if str(output).count(queue_name) < 1:
|
||||
msg = "Message queue for ephemeral heat not created in time."
|
||||
|
@ -742,42 +776,156 @@ class HeatPodLauncher(HeatContainerLauncher):
|
|||
return config
|
||||
|
||||
def _write_heat_config(self):
|
||||
heat_config_tmpl_path = os.path.join(DEFAULT_TEMPLATES_DIR,
|
||||
EPHEMERAL_HEAT_POD_NAME,
|
||||
"heat.conf.j2")
|
||||
with open(heat_config_tmpl_path) as tmpl:
|
||||
heat_config_tmpl = jinja2.Template(tmpl.read())
|
||||
self.render_template(
|
||||
'heat.conf.j2', 'heat.conf',
|
||||
transport_url=self._get_transport_url(),
|
||||
db_connection=self._get_db_connection(),
|
||||
api_port=self.api_port,
|
||||
num_engine_workers=self._get_num_engine_workers(),
|
||||
log_file=self.log_file)
|
||||
|
||||
config_vars = {
|
||||
"transport_url": self._get_transport_url(),
|
||||
"db_connection": self._get_db_connection(),
|
||||
"api_port": self.api_port,
|
||||
"num_engine_workers": self._get_num_engine_workers(),
|
||||
"log_file": self.log_file,
|
||||
def _write_heat_pod(self, template='heat-pod.yaml.j2',
|
||||
rendered='heat-pod.yaml', **template_vars):
|
||||
if not template_vars:
|
||||
pod_vars = {
|
||||
"install_dir": self.install_dir,
|
||||
"heat_dir": self.heat_dir,
|
||||
"ctlplane_ip": self.host,
|
||||
"api_port": self.api_port,
|
||||
"api_image": self.api_container_image,
|
||||
"engine_image": self.engine_container_image,
|
||||
"heat_pod_name": EPHEMERAL_HEAT_POD_NAME
|
||||
}
|
||||
else:
|
||||
pod_vars = template_vars
|
||||
self.render_template(template, rendered, **pod_vars)
|
||||
|
||||
@retry(stop=(stop_after_delay(20) | stop_after_attempt(20)),
|
||||
wait=wait_fixed(0.5))
|
||||
def wait_for_container_running(self, container):
|
||||
container_state = self.get_container_state(container)
|
||||
if container_state.lower() != 'running':
|
||||
msg = ("Container {} not running. State is {}".format(
|
||||
container, container_state))
|
||||
log.warning(msg)
|
||||
raise Exception(msg)
|
||||
|
||||
@retry(stop=(stop_after_delay(20) | stop_after_attempt(20)),
|
||||
wait=wait_fixed(0.5))
|
||||
def wait_for_pod_running(self, pod):
|
||||
pod_state = self.get_pod_state(pod)
|
||||
if pod_state.lower() != 'running':
|
||||
msg = ("Pod {} not running. State is {}".format(
|
||||
pod, pod_state))
|
||||
log.warning(msg)
|
||||
raise Exception(msg)
|
||||
|
||||
def render_template(self, templ_name, final_name, **templ_vars):
|
||||
templ_path = os.path.join(
|
||||
DEFAULT_TEMPLATES_DIR, EPHEMERAL_HEAT_POD_NAME,
|
||||
templ_name)
|
||||
with open(templ_path) as tmpl:
|
||||
templ = jinja2.Template(tmpl.read())
|
||||
|
||||
rendered = templ.render(**templ_vars)
|
||||
|
||||
with open(os.path.join(self.heat_dir, final_name), 'w') as out:
|
||||
out.write(rendered)
|
||||
|
||||
|
||||
class HeatFullLauncher(HeatPodLauncher):
|
||||
|
||||
heat_type = 'full'
|
||||
mysql_container = 'ephemeral-heat-infra-mysql'
|
||||
rabbitmq_container = 'ephemeral-heat-infra-rabbitmq'
|
||||
db_password = 'root'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.do_write_heat_config = False
|
||||
super(HeatFullLauncher, self).__init__(*args, **kwargs)
|
||||
self.host = "127.0.0.1"
|
||||
|
||||
def launch_heat_infra(self):
|
||||
if "Running" in self.get_pod_state(EPHEMERAL_HEAT_INFRA_POD_NAME):
|
||||
log.info("%s pod already running, skipping launch",
|
||||
EPHEMERAL_HEAT_INFRA_POD_NAME)
|
||||
return
|
||||
|
||||
infra_vars = {
|
||||
'db_password': self.db_password,
|
||||
'db_timeout': 60,
|
||||
'heat_dir': self.heat_dir,
|
||||
}
|
||||
heat_config = heat_config_tmpl.render(**config_vars)
|
||||
self._write_heat_pod(
|
||||
'heat-infra-pod.yaml.j2', 'heat-infra-pod.yaml', **infra_vars)
|
||||
|
||||
with open(self.config_file, 'w') as conf:
|
||||
conf.write(heat_config)
|
||||
mysql_config_path = os.path.join(
|
||||
DEFAULT_TEMPLATES_DIR, "ephemeral-heat",
|
||||
"galera.cnf")
|
||||
mysql_init_path = os.path.join(
|
||||
DEFAULT_TEMPLATES_DIR, "ephemeral-heat",
|
||||
"mysql-init.sh")
|
||||
shutil.copy(mysql_config_path, self.heat_dir)
|
||||
shutil.copy(mysql_init_path, self.heat_dir)
|
||||
|
||||
def _write_heat_pod(self):
|
||||
heat_pod_tmpl_path = os.path.join(DEFAULT_TEMPLATES_DIR,
|
||||
EPHEMERAL_HEAT_POD_NAME,
|
||||
"heat-pod.yaml.j2")
|
||||
with open(heat_pod_tmpl_path) as tmpl:
|
||||
heat_pod_tmpl = jinja2.Template(tmpl.read())
|
||||
rabbitmq_init_path = os.path.join(
|
||||
DEFAULT_TEMPLATES_DIR, "ephemeral-heat",
|
||||
"rabbitmq-init.sh")
|
||||
rabbitmq_config_path = os.path.join(
|
||||
DEFAULT_TEMPLATES_DIR, "ephemeral-heat",
|
||||
"rabbitmq.config")
|
||||
shutil.copy(rabbitmq_init_path, self.heat_dir)
|
||||
shutil.copy(rabbitmq_config_path, self.heat_dir)
|
||||
|
||||
pod_vars = {
|
||||
"install_dir": self.install_dir,
|
||||
"heat_dir": self.heat_dir,
|
||||
"ctlplane_ip": self.host,
|
||||
"api_port": self.api_port,
|
||||
"api_image": self.api_container_image,
|
||||
"engine_image": self.engine_container_image,
|
||||
"heat_pod_name": EPHEMERAL_HEAT_POD_NAME
|
||||
}
|
||||
heat_pod = heat_pod_tmpl.render(**pod_vars)
|
||||
self.render_template("my.cnf.j2", "my.cnf",
|
||||
db_password=self.db_password)
|
||||
|
||||
heat_pod_path = os.path.join(self.heat_dir, "heat-pod.yaml")
|
||||
with open(heat_pod_path, 'w') as conf:
|
||||
conf.write(heat_pod)
|
||||
subprocess.check_call([
|
||||
'sudo', 'podman', 'play', 'kube',
|
||||
os.path.join(self.heat_dir, 'heat-infra-pod.yaml')
|
||||
])
|
||||
self.wait_for_pod_running(EPHEMERAL_HEAT_INFRA_POD_NAME)
|
||||
self.wait_for_container_running("ephemeral-heat-infra-rabbitmq")
|
||||
self.wait_for_container_running("ephemeral-heat-infra-mysql")
|
||||
|
||||
def _get_infra_ip(self):
|
||||
ip = self._decode(subprocess.check_output(
|
||||
['sudo', 'podman', 'inspect', '--format',
|
||||
'{{.NetworkSettings.IPAddress}}',
|
||||
'ephemeral-heat-infra-rabbitmq']))
|
||||
return ip
|
||||
|
||||
def _get_database_ip(self):
|
||||
return self._get_infra_ip()
|
||||
|
||||
def _get_transport_url(self):
|
||||
transport_url = "rabbit://%s:%s@%s:%s/?ssl=0" % \
|
||||
('guest', 'guest',
|
||||
self._get_infra_ip(), 5672)
|
||||
return transport_url
|
||||
|
||||
def _get_db_connection(self):
|
||||
return ('mysql+pymysql://'
|
||||
'heat:heat@{}/heat?read_default_file='
|
||||
'/etc/my.cnf.d/tripleo.cnf&read_default_group=tripleo'.format(
|
||||
self._get_infra_ip()))
|
||||
|
||||
def kill_heat(self, pid):
|
||||
self.kill_pod(EPHEMERAL_HEAT_POD_NAME)
|
||||
|
||||
def rm_heat(self):
|
||||
super(HeatFullLauncher, self).rm_heat()
|
||||
self.kill_pod(EPHEMERAL_HEAT_INFRA_POD_NAME)
|
||||
if self.pod_exists(EPHEMERAL_HEAT_INFRA_POD_NAME):
|
||||
log.info("Removing pod: %s", EPHEMERAL_HEAT_INFRA_POD_NAME)
|
||||
subprocess.call([
|
||||
'sudo', 'podman', 'pod', 'rm', '-f',
|
||||
EPHEMERAL_HEAT_INFRA_POD_NAME
|
||||
])
|
||||
|
||||
def launch_heat(self):
|
||||
self.launch_heat_infra()
|
||||
self._write_heat_config()
|
||||
self.check_database()
|
||||
self.heat_db_sync()
|
||||
super(HeatFullLauncher, self).launch_heat()
|
||||
|
|
|
@ -22,7 +22,8 @@ from unittest import mock
|
|||
|
||||
from tripleoclient import constants
|
||||
from tripleoclient import heat_launcher
|
||||
from tripleoclient.exceptions import HeatPodMessageQueueException
|
||||
from tripleoclient.exceptions import (HeatPodDatabaseException,
|
||||
HeatPodMessageQueueException)
|
||||
from tripleoclient.tests import base
|
||||
from tripleoclient import utils
|
||||
|
||||
|
@ -264,7 +265,7 @@ class TestHeatPodLauncher(base.TestCase):
|
|||
mock_tar.assert_called_with(str(p))
|
||||
self.run.assert_called_once_with(['sudo', 'podman', 'exec', '-u',
|
||||
'root', 'mysql', 'mysqldump',
|
||||
'heat'],
|
||||
'-uroot', 'heat'],
|
||||
check=True, stdout=mock.ANY)
|
||||
|
||||
def test_pod_exists(self):
|
||||
|
@ -378,6 +379,7 @@ class TestHeatPodLauncher(base.TestCase):
|
|||
def test_check_message_bus(self):
|
||||
launcher = self.get_launcher()
|
||||
self.check_call.reset_mock()
|
||||
check_mb = launcher.check_message_bus.__wrapped__
|
||||
launcher.check_message_bus()
|
||||
self.check_call.assert_called_once_with(['sudo', 'podman', 'exec',
|
||||
'-u', 'root', 'rabbitmq',
|
||||
|
@ -387,35 +389,34 @@ class TestHeatPodLauncher(base.TestCase):
|
|||
|
||||
self.check_call.reset_mock()
|
||||
self.check_call.side_effect = subprocess.CalledProcessError(1, 'test')
|
||||
self.assertRaises(subprocess.CalledProcessError,
|
||||
launcher.check_message_bus)
|
||||
self.assertRaises(HeatPodMessageQueueException, check_mb, launcher)
|
||||
|
||||
@mock.patch(
|
||||
'tripleoclient.heat_launcher.HeatPodLauncher._get_ctlplane_ip')
|
||||
def test_check_database(self, mock_ctlplane_ip):
|
||||
launcher = self.get_launcher()
|
||||
self.check_call.reset_mock()
|
||||
check_db = launcher.check_database.__wrapped__
|
||||
|
||||
mock_ctlplane_ip.return_value = '1.1.1.1'
|
||||
self.assertTrue(launcher.check_database())
|
||||
mock_ctlplane_ip.assert_called()
|
||||
self.check_call.assert_called_once_with(['sudo', 'podman', 'exec',
|
||||
'-u', 'root', 'mysql',
|
||||
'mysql', '-h', '1.1.1.1',
|
||||
'mysql',
|
||||
'-e', 'show databases;'],
|
||||
stderr=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL)
|
||||
|
||||
self.check_call.reset_mock()
|
||||
mock_ctlplane_ip.reset_mock()
|
||||
self.check_call.side_effect = subprocess.CalledProcessError(1, '/test')
|
||||
self.assertRaises(subprocess.CalledProcessError,
|
||||
launcher.check_database)
|
||||
self.assertRaises(HeatPodDatabaseException, check_db, launcher)
|
||||
|
||||
def test_database_exists(self):
|
||||
launcher = self.get_launcher()
|
||||
self.check_output.reset_mock()
|
||||
self.check_output.return_value = 'heat'
|
||||
mock_running = mock.Mock()
|
||||
mock_running.stdout = 'Running'.encode()
|
||||
self.run.return_value = mock_running
|
||||
self.assertTrue(launcher.database_exists())
|
||||
self.check_output.assert_called_once_with([
|
||||
'sudo', 'podman', 'exec', '-u', 'root', 'mysql', 'mysql', '-e',
|
||||
|
|
|
@ -2755,10 +2755,12 @@ def launch_heat(launcher=None, restore_db=False, heat_type='pod'):
|
|||
_heat_pid = 0
|
||||
if launcher.heat_type == 'native':
|
||||
_heat_pid = os.fork()
|
||||
if _heat_pid == 0:
|
||||
if _heat_pid == 0 and launcher.heat_type != 'full':
|
||||
launcher.check_database()
|
||||
launcher.check_message_bus()
|
||||
launcher.heat_db_sync(restore_db)
|
||||
|
||||
if _heat_pid == 0:
|
||||
launcher.launch_heat()
|
||||
|
||||
# Wait for the API to be listening
|
||||
|
@ -2783,6 +2785,9 @@ def get_heat_launcher(heat_type, *args, **kwargs):
|
|||
return heat_launcher.HeatNativeLauncher(*args, **kwargs)
|
||||
if heat_type == 'container':
|
||||
return heat_launcher.HeatContainerLauncher(*args, **kwargs)
|
||||
if heat_type == 'full':
|
||||
return heat_launcher.HeatFullLauncher(*args, **kwargs)
|
||||
|
||||
return heat_launcher.HeatPodLauncher(*args, **kwargs)
|
||||
|
||||
|
||||
|
|
|
@ -973,6 +973,8 @@ class DeployOvercloud(command.Command):
|
|||
help=_('The type of Heat process to use to execute '
|
||||
'the deployment.\n'
|
||||
'pod (Default): Use an ephemeral Heat pod.\n'
|
||||
'full: Use a full ephemeral Heat pod, including rabbitmq '
|
||||
'and mariadb.\n'
|
||||
'installed: Use the system installed Heat.\n'
|
||||
'container: Use an ephemeral Heat container.\n'
|
||||
'native: Use an ephemeral Heat process.')
|
||||
|
|
|
@ -56,7 +56,6 @@ class LaunchHeat(command.Command):
|
|||
return 0
|
||||
|
||||
def _launch_heat(self, parsed_args):
|
||||
self.log.info("Launching Heat %s" % parsed_args.heat_type)
|
||||
utils.launch_heat(self.heat_launcher, parsed_args.restore_db)
|
||||
return 0
|
||||
|
||||
|
@ -135,7 +134,7 @@ class LaunchHeat(command.Command):
|
|||
'--rm-heat',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('If specified and --heat-type is container or pod '
|
||||
help=_('If specified and --heat-type is container, pod, or full '
|
||||
'any existing container or pod of a previous '
|
||||
'ephemeral Heat process will be deleted first. '
|
||||
'Ignored if --heat-type is native or --kill.')
|
||||
|
@ -144,7 +143,7 @@ class LaunchHeat(command.Command):
|
|||
'--skip-heat-pull',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('When --heat-type is pod or container, assume '
|
||||
help=_('When --heat-type is container, pod, or full assume '
|
||||
'the container image has already been pulled ')
|
||||
)
|
||||
parser.add_argument(
|
||||
|
@ -173,12 +172,16 @@ class LaunchHeat(command.Command):
|
|||
'--heat-type',
|
||||
dest='heat_type',
|
||||
default='native',
|
||||
choices=['native', 'container', 'pod'],
|
||||
choices=['native', 'container', 'pod', 'full'],
|
||||
help=_('Type of ephemeral Heat process to launch. One of:\n'
|
||||
'native: Execute heat-all directly on the host.\n'
|
||||
'container: Execute heat-all in a container.\n'
|
||||
'pod: Execute separate heat api and engine processes in '
|
||||
'a podman pod.')
|
||||
'pod: Execute separate heat api and engine processes in\n'
|
||||
' a podman pod.\n'
|
||||
'full: Execute separate heat api and engine processes in\n'
|
||||
' a pod, and also start an infra pod for the '
|
||||
' database\n'
|
||||
' and message bus.')
|
||||
)
|
||||
return parser
|
||||
|
||||
|
@ -196,6 +199,7 @@ class LaunchHeat(command.Command):
|
|||
else:
|
||||
rm_heat = parsed_args.rm_heat
|
||||
|
||||
self.log.info("Launching Heat %s" % parsed_args.heat_type)
|
||||
self.heat_launcher = utils.get_heat_launcher(
|
||||
heat_type, parsed_args.heat_api_port,
|
||||
parsed_args.heat_container_image,
|
||||
|
|
Loading…
Reference in New Issue