Update tripleo launch heat for HeatPodLauncher
Updates the "openstack tripleo launch heat" command to support launching ephemeral Heat with any of the now supported options: native, container, or pod. The command also can be used to start the Heat process and reuse an existing environment with --heat-dir. Change-Id: Ie665bc518751343fd4824f10c6efa6b5ca1c991d Signed-off-by: James Slagle <jslagle@redhat.com>
This commit is contained in:
parent
6ab4d15863
commit
d8e4878e2e
|
@ -27,7 +27,10 @@ import tempfile
|
|||
import jinja2
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from tripleoclient import constants
|
||||
from tripleoclient.constants import (DEFAULT_HEAT_CONTAINER,
|
||||
DEFAULT_HEAT_API_CONTAINER,
|
||||
DEFAULT_HEAT_ENGINE_CONTAINER,
|
||||
DEFAULT_TEMPLATES_DIR)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -115,13 +118,15 @@ class HeatBaseLauncher(object):
|
|||
|
||||
# The init function will need permission to touch these files
|
||||
# and chown them accordingly for the heat user
|
||||
def __init__(
|
||||
self, api_port=8006,
|
||||
all_container_image=constants.DEFAULT_HEAT_CONTAINER,
|
||||
api_container_image=constants.DEFAULT_HEAT_API_CONTAINER,
|
||||
engine_container_image=constants.DEFAULT_HEAT_ENGINE_CONTAINER,
|
||||
user='heat', heat_dir='/var/log/heat-launcher', use_tmp_dir=True):
|
||||
|
||||
def __init__(self, api_port=8006,
|
||||
all_container_image=DEFAULT_HEAT_CONTAINER,
|
||||
api_container_image=DEFAULT_HEAT_API_CONTAINER,
|
||||
engine_container_image=DEFAULT_HEAT_ENGINE_CONTAINER,
|
||||
user='heat',
|
||||
heat_dir='/var/log/heat-launcher',
|
||||
use_tmp_dir=True,
|
||||
rm_heat=False,
|
||||
skip_heat_pull=False):
|
||||
self.api_port = api_port
|
||||
self.all_container_image = all_container_image
|
||||
self.api_container_image = api_container_image
|
||||
|
@ -130,6 +135,11 @@ class HeatBaseLauncher(object):
|
|||
self.host = "127.0.0.1"
|
||||
self.db_dump_path = os.path.join(
|
||||
self.heat_dir, 'heat-db-dump.sql')
|
||||
self.skip_heat_pull = skip_heat_pull
|
||||
|
||||
if rm_heat:
|
||||
self.kill_heat(None)
|
||||
self.rm_heat()
|
||||
|
||||
if os.path.isdir(self.heat_dir):
|
||||
# This one may fail but it's just cleanup.
|
||||
|
@ -293,6 +303,9 @@ class HeatContainerLauncher(HeatBaseLauncher):
|
|||
self.host = "127.0.0.1"
|
||||
|
||||
def _fetch_container_image(self):
|
||||
if self.skip_heat_pull:
|
||||
log.info("Skipping container image pull.")
|
||||
return
|
||||
# force pull of latest container image
|
||||
cmd = ['podman', 'pull', self.all_container_image]
|
||||
log.debug(' '.join(cmd))
|
||||
|
@ -370,6 +383,12 @@ class HeatContainerLauncher(HeatBaseLauncher):
|
|||
# We don't want to hear from this command..
|
||||
subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
def rm_heat(self, pid):
|
||||
cmd = ['podman', 'rm', 'heat_all']
|
||||
log.debug(' '.join(cmd))
|
||||
# We don't want to hear from this command..
|
||||
subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
|
||||
class HeatNativeLauncher(HeatBaseLauncher):
|
||||
|
||||
|
@ -408,6 +427,9 @@ class HeatPodLauncher(HeatContainerLauncher):
|
|||
'-l', 's0', self.heat_dir])
|
||||
|
||||
def _fetch_container_image(self):
|
||||
if self.skip_heat_pull:
|
||||
log.info("Skipping container image pull.")
|
||||
return
|
||||
# force pull of latest container image
|
||||
for image in self.api_container_image, self.engine_container_image:
|
||||
log.info("Pulling conatiner image {}.".format(image))
|
||||
|
@ -584,7 +606,7 @@ class HeatPodLauncher(HeatContainerLauncher):
|
|||
return int(multiprocessing.cpu_count() / 2)
|
||||
|
||||
def _write_heat_config(self):
|
||||
heat_config_tmpl_path = os.path.join(constants.DEFAULT_TEMPLATES_DIR,
|
||||
heat_config_tmpl_path = os.path.join(DEFAULT_TEMPLATES_DIR,
|
||||
"ephemeral-heat",
|
||||
"heat.conf.j2")
|
||||
with open(heat_config_tmpl_path) as tmpl:
|
||||
|
@ -602,7 +624,7 @@ class HeatPodLauncher(HeatContainerLauncher):
|
|||
conf.write(heat_config)
|
||||
|
||||
def _write_heat_pod(self):
|
||||
heat_pod_tmpl_path = os.path.join(constants.DEFAULT_TEMPLATES_DIR,
|
||||
heat_pod_tmpl_path = os.path.join(DEFAULT_TEMPLATES_DIR,
|
||||
"ephemeral-heat",
|
||||
"heat-pod.yaml.j2")
|
||||
with open(heat_pod_tmpl_path) as tmpl:
|
||||
|
|
|
@ -52,6 +52,7 @@ from heatclient.common import template_utils
|
|||
from heatclient.common import utils as heat_utils
|
||||
from heatclient.exc import HTTPNotFound
|
||||
from osc_lib import exceptions as oscexc
|
||||
from osc_lib import utils as osc_lib_utils
|
||||
from osc_lib.i18n import _
|
||||
from oslo_concurrency import processutils
|
||||
from six.moves import configparser
|
||||
|
@ -60,13 +61,20 @@ from heatclient import exc as hc_exc
|
|||
from six.moves.urllib import error as url_error
|
||||
from six.moves.urllib import request
|
||||
|
||||
from tenacity import retry
|
||||
from tenacity.stop import stop_after_attempt, stop_after_delay
|
||||
from tenacity.wait import wait_fixed
|
||||
|
||||
from tripleo_common.utils import stack as stack_utils
|
||||
from tripleo_common import update
|
||||
from tripleoclient import constants
|
||||
from tripleoclient import exceptions
|
||||
from tripleoclient import heat_launcher
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__ + ".utils")
|
||||
_local_orchestration_client = None
|
||||
_heat_pid = None
|
||||
|
||||
|
||||
class Pushd(object):
|
||||
|
@ -2541,3 +2549,94 @@ def write_user_environment(env_map, abs_env_path, tht_root,
|
|||
LOG.debug("Writing user environment %s" % user_env_path)
|
||||
f.write(contents)
|
||||
return user_env_path
|
||||
|
||||
|
||||
def launch_heat(launcher=None, restore_db=False):
|
||||
|
||||
global _local_orchestration_client
|
||||
global _heat_pid
|
||||
|
||||
if _local_orchestration_client:
|
||||
print("returning cached")
|
||||
return _local_orchestration_client
|
||||
|
||||
if not launcher:
|
||||
launcher = get_heat_launcher()
|
||||
|
||||
_heat_pid = 0
|
||||
if launcher.heat_type == 'native':
|
||||
_heat_pid = os.fork()
|
||||
if _heat_pid == 0:
|
||||
launcher.check_database()
|
||||
launcher.check_message_bus()
|
||||
launcher.heat_db_sync(restore_db)
|
||||
launcher.launch_heat()
|
||||
|
||||
# Wait for the API to be listening
|
||||
heat_api_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
test_heat_api_port(heat_api_socket, launcher.host, int(launcher.api_port))
|
||||
|
||||
_local_orchestration_client = local_orchestration_client(
|
||||
launcher.host, launcher.api_port)
|
||||
return _local_orchestration_client
|
||||
|
||||
|
||||
@retry(stop=(stop_after_delay(10) | stop_after_attempt(10)),
|
||||
wait=wait_fixed(0.5))
|
||||
def test_heat_api_port(heat_api_socket, host, port):
|
||||
heat_api_socket.connect((host, port))
|
||||
|
||||
|
||||
def get_heat_launcher(heat_type, *args, **kwargs):
|
||||
if heat_type == 'native':
|
||||
return heat_launcher.HeatNativeLauncher(*args, **kwargs)
|
||||
elif heat_type == 'container':
|
||||
return heat_launcher.HeatContainerLauncher(*args, **kwargs)
|
||||
else:
|
||||
return heat_launcher.HeatPodLauncher(*args, **kwargs)
|
||||
|
||||
|
||||
def local_orchestration_client(host="127.0.0.1", api_port=8006):
|
||||
"""Returns a local orchestration service client"""
|
||||
|
||||
API_VERSIONS = {
|
||||
'1': 'heatclient.v1.client.Client',
|
||||
}
|
||||
|
||||
heat_client = osc_lib_utils.get_client_class(
|
||||
'tripleoclient',
|
||||
'1',
|
||||
API_VERSIONS)
|
||||
LOG.debug('Instantiating local_orchestration client for '
|
||||
'host %s, port %s: %s',
|
||||
host, api_port, heat_client)
|
||||
|
||||
endpoint = 'http://%s:%s/v1/admin' % (host, api_port)
|
||||
client = heat_client(
|
||||
endpoint=endpoint,
|
||||
username='admin',
|
||||
password='fake',
|
||||
region_name='regionOne',
|
||||
token='fake',
|
||||
)
|
||||
|
||||
for v in ('OS_USER_DOMAIN_NAME',
|
||||
'OS_PROJECT_DOMAIN_NAME',
|
||||
'OS_PROJECT_NAME'):
|
||||
os.environ.pop(v, None)
|
||||
|
||||
os.environ['OS_AUTH_TYPE'] = "none"
|
||||
os.environ['OS_ENDPOINT'] = endpoint
|
||||
|
||||
return client
|
||||
|
||||
|
||||
def kill_heat(launcher, backup_db=True):
|
||||
global _heat_pid
|
||||
if _heat_pid:
|
||||
LOG.debug("Attempting to kill heat pid %s" % _heat_pid)
|
||||
launcher.kill_heat(_heat_pid, backup_db)
|
||||
|
||||
|
||||
def rm_heat(launcher, backup_db=False):
|
||||
launcher.rm_heat(backup_db)
|
||||
|
|
|
@ -21,13 +21,15 @@ import os
|
|||
from cliff import command
|
||||
from osc_lib.i18n import _
|
||||
|
||||
from tripleoclient.constants import DEFAULT_HEAT_CONTAINER
|
||||
from tripleoclient.constants import (DEFAULT_HEAT_CONTAINER,
|
||||
DEFAULT_HEAT_API_CONTAINER,
|
||||
DEFAULT_HEAT_ENGINE_CONTAINER)
|
||||
from tripleoclient import exceptions
|
||||
from tripleoclient import heat_launcher
|
||||
from tripleoclient import utils
|
||||
|
||||
|
||||
class LaunchHeat(command.Command):
|
||||
"""Launch all-in-one Heat process and run in the foreground."""
|
||||
"""Launch ephemeral Heat process."""
|
||||
|
||||
log = logging.getLogger(__name__ + ".Deploy")
|
||||
auth_required = False
|
||||
|
@ -41,32 +43,23 @@ class LaunchHeat(command.Command):
|
|||
when cleanup is requested.
|
||||
|
||||
"""
|
||||
if self.heat_pid:
|
||||
self.heat_launch.kill_heat(self.heat_pid)
|
||||
pid, ret = os.waitpid(self.heat_pid, 0)
|
||||
self.heat_pid = None
|
||||
self.log.info("Attempting to kill ephemeral heat")
|
||||
if parsed_args.heat_type == "native":
|
||||
if self.heat_pid:
|
||||
self.log.info("Using heat pid: %s" % self.heat_pid)
|
||||
self.heat_launcher.kill_heat(self.heat_pid)
|
||||
pid, ret = os.waitpid(self.heat_pid, 0)
|
||||
self.heat_pid = None
|
||||
else:
|
||||
self.log.info("No heat pid set, can't kill.")
|
||||
else:
|
||||
self.heat_launcher.kill_heat(None, backup_db=True)
|
||||
|
||||
return 0
|
||||
|
||||
def _launch_heat(self, parsed_args):
|
||||
# we do this as root to chown config files properly for docker, etc.
|
||||
if parsed_args.heat_native is not None and \
|
||||
parsed_args.heat_native.lower() == "false":
|
||||
self.heat_launch = heat_launcher.HeatContainerLauncher(
|
||||
parsed_args.heat_api_port,
|
||||
parsed_args.heat_container_image,
|
||||
parsed_args.heat_user,
|
||||
parsed_args.heat_dir)
|
||||
else:
|
||||
self.heat_launch = heat_launcher.HeatNativeLauncher(
|
||||
parsed_args.heat_api_port,
|
||||
parsed_args.heat_container_image,
|
||||
parsed_args.heat_user,
|
||||
parsed_args.heat_dir)
|
||||
|
||||
self.heat_launch.heat_db_sync()
|
||||
self.heat_launch.launch_heat()
|
||||
|
||||
self.log.info("Launching Heat %s" % parsed_args.heat_type)
|
||||
utils.launch_heat(self.heat_launcher, parsed_args.restore_db)
|
||||
return 0
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
|
@ -90,7 +83,9 @@ class LaunchHeat(command.Command):
|
|||
'Defaults to current user. '
|
||||
'If the configuration files /etc/heat/heat.conf or '
|
||||
'/usr/share/heat/heat-dist.conf exist, the user '
|
||||
'must have read access to those files.')
|
||||
'must have read access to those files.\n'
|
||||
'This option is ignored when using --heat-type=container '
|
||||
'or --heat-type=pod')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--heat-container-image', metavar='<HEAT_CONTAINER_IMAGE>',
|
||||
|
@ -100,16 +95,22 @@ class LaunchHeat(command.Command):
|
|||
'process. Defaults to: {}'.format(DEFAULT_HEAT_CONTAINER))
|
||||
)
|
||||
parser.add_argument(
|
||||
'--heat-native',
|
||||
dest='heat_native',
|
||||
nargs='?',
|
||||
default=None,
|
||||
const="true",
|
||||
help=_('Execute the heat-all process natively on this host. '
|
||||
'This option requires that the heat-all binaries '
|
||||
'be installed locally on this machine. '
|
||||
'This option is enabled by default which means heat-all is '
|
||||
'executed on the host OS directly.')
|
||||
'--heat-container-api-image',
|
||||
metavar='<HEAT_CONTAINER_API_IMAGE>',
|
||||
dest='heat_container_api_image',
|
||||
default=DEFAULT_HEAT_API_CONTAINER,
|
||||
help=_('The container image to use when launching the heat-api '
|
||||
'process. Only used when --heat-type=pod. '
|
||||
'Defaults to: {}'.format(DEFAULT_HEAT_API_CONTAINER))
|
||||
)
|
||||
parser.add_argument(
|
||||
'--heat-container-engine-image',
|
||||
metavar='<HEAT_CONTAINER_ENGINE_IMAGE>',
|
||||
dest='heat_container_engine_image',
|
||||
default=DEFAULT_HEAT_ENGINE_CONTAINER,
|
||||
help=_('The container image to use when launching the heat-engine '
|
||||
'process. Only used when --heat-type=pod. '
|
||||
'Defaults to: {}'.format(DEFAULT_HEAT_ENGINE_CONTAINER))
|
||||
)
|
||||
parser.add_argument(
|
||||
'--kill', '-k',
|
||||
|
@ -125,12 +126,82 @@ class LaunchHeat(command.Command):
|
|||
default=os.path.join(os.getcwd(), 'heat-launcher'),
|
||||
help=_("Directory to use for file storage and logs of the "
|
||||
"running heat process. Defaults to 'heat-launcher' "
|
||||
"in the current directory.")
|
||||
"in the current directory. Can be set to an already "
|
||||
"existing directory to reuse the environment from a "
|
||||
"previos Heat process.")
|
||||
)
|
||||
parser.add_argument(
|
||||
'--rm-heat',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('If specified and --heat-type is container or pod '
|
||||
'any existing container or pod of a previous '
|
||||
'ephemeral Heat process will be deleted first. '
|
||||
'Ignored if --heat-type is native.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--skip-heat-pull',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('When --heat-type is pod or container, assume '
|
||||
'the container image has already been pulled ')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--restore-db',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Restore a database dump if it exists '
|
||||
'within the directory specified by --heat-dir')
|
||||
)
|
||||
heat_type_group = parser.add_mutually_exclusive_group()
|
||||
heat_type_group.add_argument(
|
||||
'--heat-native',
|
||||
dest='heat_native',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('(DEPRECATED): Execute the heat-all process natively on '
|
||||
'this host. '
|
||||
'This option requires that the heat-all binaries '
|
||||
'be installed locally on this machine. '
|
||||
'This option is enabled by default which means heat-all is '
|
||||
'executed on the host OS directly.\n'
|
||||
'Conflicts with --heat-type, which deprecates '
|
||||
'--heat-native.')
|
||||
)
|
||||
heat_type_group.add_argument(
|
||||
'--heat-type',
|
||||
dest='heat_type',
|
||||
default='native',
|
||||
choices=['native', 'container', 'pod'],
|
||||
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.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self._configure_logging(parsed_args)
|
||||
self.log.debug("take_action(%s)" % parsed_args)
|
||||
|
||||
if parsed_args.heat_native:
|
||||
heat_type = "native"
|
||||
else:
|
||||
heat_type = parsed_args.heat_type
|
||||
|
||||
self.heat_launcher = utils.get_heat_launcher(
|
||||
heat_type, parsed_args.heat_api_port,
|
||||
parsed_args.heat_container_image,
|
||||
parsed_args.heat_container_api_image,
|
||||
parsed_args.heat_container_engine_image,
|
||||
parsed_args.heat_user,
|
||||
parsed_args.heat_dir,
|
||||
False,
|
||||
False,
|
||||
parsed_args.rm_heat,
|
||||
parsed_args.skip_heat_pull)
|
||||
|
||||
if parsed_args.kill:
|
||||
if self._kill_heat(parsed_args) != 0:
|
||||
msg = _('Heat kill failed.')
|
||||
|
@ -141,3 +212,14 @@ class LaunchHeat(command.Command):
|
|||
msg = _('Heat launch failed.')
|
||||
self.log.error(msg)
|
||||
raise exceptions.DeploymentError(msg)
|
||||
|
||||
def _configure_logging(self, parsed_args):
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
handler = logging.StreamHandler()
|
||||
handler.setFormatter(formatter)
|
||||
self.log.addHandler(handler)
|
||||
if self.app_args.verbose_level >= 2:
|
||||
handler.setLevel(logging.DEBUG)
|
||||
else:
|
||||
handler.setLevel(logging.INFO)
|
||||
|
|
Loading…
Reference in New Issue