CLI: implement service commands

- list
- show
- run
- kill
- scale
- update
- snapshot

partial blueprint per-service-cli
Change-Id: I7b3d7bf9fdb5d9a88bc6ef9a93500a36c506f330
This commit is contained in:
Angus Salkeld 2016-03-15 15:47:27 +10:00
parent ba4f1082a4
commit 4dfab4c7f6
9 changed files with 685 additions and 26 deletions

View File

@ -13,51 +13,122 @@
from cliff import command
from cliff import lister
from cliff import show
from oslo_config import cfg
from oslo_log import log
from kolla_mesos.common import cli_utils
from kolla_mesos import service
CONF = cfg.CONF
LOG = log.getLogger(__name__)
class Run(command.Command):
"""Run a service."""
def get_parser(self, prog_name):
parser = super(Run, self).get_parser(prog_name)
parser.add_argument('service')
return parser
def take_action(self, parsed_args):
LOG.info('sending greeting')
LOG.debug('debugging')
self.app.stdout.write('hi!\n')
service.run_service(parsed_args.service,
CONF.service_dir)
class Kill(command.Command):
"""Kill a service."""
def get_parser(self, prog_name):
parser = super(Kill, self).get_parser(prog_name)
parser.add_argument('service')
return parser
def take_action(self, parsed_args):
LOG.info('sending greeting')
LOG.debug('debugging')
LOG.stdout.write('hi!\n')
service.kill_service(parsed_args.service)
class Show(show.ShowOne):
"""Show the live status of the task or service."""
def get_parser(self, prog_name):
parser = super(Show, self).get_parser(prog_name)
parser.add_argument('service')
return parser
def take_action(self, parsed_args):
LOG.info('sending greeting')
LOG.debug('debugging')
self.app.stdout.write('hi!\n')
data = service.get_service(parsed_args.service)
return cli_utils.dict2columns(data, id_col='service')
class List(lister.Lister):
"""List all deployed services for this deployment_id."""
def take_action(self, parsed_args):
LOG.info('sending greeting')
LOG.debug('debugging')
self.app.stdout.write('hi!\n')
apps = service.list_services()
values = []
cols = ('service', 'type', 'instances', 'tasksUnhealthy',
'tasksHealthy', 'tasksRunning', 'tasksStaged', 'version')
for app in apps:
values.append([app[field] for field in cols])
return (cols, values)
class Scale(command.Command):
"""Scale the service."""
def get_parser(self, prog_name):
parser = super(Scale, self).get_parser(prog_name)
parser.add_argument('service')
parser.add_argument('instances')
parser.add_argument('--force', action='store_true',
default=False)
return parser
def take_action(self, parsed_args):
service.scale_service(parsed_args.service,
parsed_args.instances,
parsed_args.force)
class Log(command.Command):
"""Dump the logs for this task or service."""
def get_parser(self, prog_name):
parser = super(Show, self).get_parser(prog_name)
parser.add_argument('service')
return parser
def take_action(self, parsed_args):
LOG.info('sending greeting')
LOG.debug('debugging')
LOG.stdout.write('hi!\n')
self.app.stdout.write(service.get_service_logs(parsed_args.service))
class Snapshot(command.Command):
"""Snapshot the service configuration and deployment file.
This will produce a tarball that can be later used with
'kolla-mesos update <service> --snapshot <file>'
"""
def get_parser(self, prog_name):
parser = super(Snapshot, self).get_parser(prog_name)
parser.add_argument('service')
parser.add_argument('output_dir')
return parser
def take_action(self, parsed_args):
service.snapshot_service(parsed_args.service,
parsed_args.output_dir)
class Update(command.Command):
"""Update the service configuration and deployment file."""
def get_parser(self, prog_name):
parser = super(Update, self).get_parser(prog_name)
parser.add_argument('service')
return parser
def take_action(self, parsed_args):
service.update_service(parsed_args.service, CONF.service_dir)

View File

@ -28,3 +28,20 @@ def show(column_names, data, align='c'):
for column_name, data_row in six.moves.zip(column_names, data):
table.add_row((column_name, data_row,))
print(table.get_string())
def dict2columns(data, id_col=None):
"""Convert a dict-based object to two-column output.
Also make sure the id field is at top.
"""
if not data:
return ({}, {})
else:
if id_col is not None and id_col in data:
keys = [id_col]
[keys.append(key) for key in sorted(data) if key != id_col]
items = [(key, data[key]) for key in keys]
else:
items = sorted(data.items())
return zip(*items)

View File

@ -33,6 +33,16 @@ def jinja_render(fullpath, global_config, extra=None):
return myenv.get_template(os.path.basename(fullpath)).render(variables)
def jinja_render_str(content, global_config, name='dafault_name', extra=None):
variables = global_config
if extra:
variables.update(extra)
myenv = jinja2.Environment(loader=jinja2.DictLoader({name: content}))
myenv.filters['bool'] = yaml_utils.str_to_bool
return myenv.get_template(name).render(variables)
def jinja_find_required_variables(fullpath):
myenv = jinja2.Environment(loader=jinja2.FileSystemLoader(
os.path.dirname(fullpath)))

View File

@ -30,7 +30,7 @@ CONF.import_group('kolla', 'kolla_mesos.config.kolla')
CONF.import_group('kolla', 'kolla_mesos.config.zookeeper')
def write_variables_zookeeper(zk, variables, base_node=None):
def write_variables_zookeeper(zk, variables, base_node=None, overwrite=True):
if base_node is None:
base_node = os.path.join('kolla', CONF.kolla.deployment_id)
filter_out = ['groups', 'hostvars', 'kolla_config',
@ -44,6 +44,10 @@ def write_variables_zookeeper(zk, variables, base_node=None):
if isinstance(variables[var], dict):
var_value = json.dumps(variables[var])
var_path = os.path.join(base_node, 'variables', var)
if not overwrite and zk.exists(var_path):
LOG.debug('NOT Updating "%s" node in zookeeper(overwrite=False).',
var_path)
return
zk.ensure_path(var_path)
zk.set(var_path, "" if var_value is None else var_value)
LOG.debug('Updated "%s" node in zookeeper.' % var_path)
@ -61,7 +65,8 @@ def get_start_config(config_dir, jinja_vars):
return kolla_config
def write_common_config_to_zookeeper(config_dir, zk, jinja_vars):
def write_common_config_to_zookeeper(config_dir, zk, jinja_vars,
overwrite=True):
# 1. At first write global tools to ZK. FIXME: Make it a common profile
conf_path = os.path.join(config_dir, 'common',
'common_config.yml.j2')
@ -69,6 +74,11 @@ def write_common_config_to_zookeeper(config_dir, zk, jinja_vars):
common_node = os.path.join('kolla', 'common')
for script in common_cfg:
script_node = os.path.join(common_node, script)
if not overwrite and zk.exists(script_node):
LOG.debug('NOT Updating "%s" node in zookeeper(overwrite=False).',
script_node)
continue
zk.ensure_path(script_node)
source_path = common_cfg[script]['source']
src_file = source_path

View File

@ -43,3 +43,9 @@ class KollaNotFoundException(KollaException):
def __init__(self, message, entity='file'):
super(KollaNotFoundException, self).__init__(
'The %s "%s" was not found' % (entity, message))
class KollaNotSupportedException(KollaException):
def __init__(self, operation='update', entity='chronos'):
super(KollaNotFoundException, self).__init__(
'Operation "%s" is not supported by "%s"' % (operation, entity))

View File

@ -13,20 +13,29 @@
import json
import os.path
from kazoo import exceptions
from oslo_config import cfg
from oslo_log import log as logging
from six.moves import configparser
from six.moves import cStringIO
import yaml
from kolla_mesos import chronos
from kolla_mesos.common import file_utils
from kolla_mesos.common import jinja_utils
from kolla_mesos.common import utils
from kolla_mesos.common import zk_utils
from kolla_mesos import configuration as config
from kolla_mesos import exception
from kolla_mesos import marathon
from kolla_mesos import service_definition
LOG = logging.getLogger()
CONF = cfg.CONF
CONF.import_group('kolla', 'kolla_mesos.config.kolla')
CONF.import_group('zookeeper', 'kolla_mesos.config.zookeeper')
CONF.import_group('marathon', 'kolla_mesos.config.marathon')
CONF.import_group('chronos', 'kolla_mesos.config.chronos')
class File(object):
@ -84,6 +93,41 @@ class Runner(object):
self._enabled = self._conf.get('enabled', True)
if not self._enabled:
LOG.warn('Service %s disabled', self._conf['name'])
self.app_file = None
self.app_def = None
def __new__(cls, conf):
"""Create a new Runner of the appropriate class for its type."""
if cls != Runner:
# Call is already for a subclass, so pass it through
RunnerClass = cls
else:
if 'service' in conf and 'task' not in conf:
RunnerClass = MarathonApp
else:
RunnerClass = ChronosTask
return super(Runner, cls).__new__(RunnerClass)
@classmethod
def load_from_zk(cls, zk, service_name):
variables = _load_variables_from_zk(zk)
base_node = os.path.join('kolla', CONF.kolla.deployment_id)
if '/' not in service_name:
service_name = service_name.replace('-', '/')
dest_node = os.path.join(base_node, service_name)
try:
conf_raw, _st = zk.get(dest_node)
except Exception as te:
LOG.error('%s -> %s' % (dest_node, te))
raise exception.KollaNotFoundException(
service_name, entity='running service definition')
return Runner(yaml.load(
jinja_utils.jinja_render_str(conf_raw, variables)))
@classmethod
def load_from_file(cls, service_file, variables):
return Runner(yaml.load(
jinja_utils.jinja_render(service_file, variables)))
def _list_commands(self):
if 'service' in self._conf:
@ -108,7 +152,8 @@ class Runner(object):
def _apply_service_def(self, app_def):
"""Apply the specifics from the service definition."""
def generate_deployment_files(self, kolla_config, jinja_vars, temp_dir):
def generate_deployment_files(self, kolla_config, jinja_vars,
temp_dir=None):
if not self._enabled:
return
_, proj, service = self._conf['name'].split('/')
@ -125,20 +170,106 @@ class Runner(object):
'default.%s.j2' % self.type_name)
content = jinja_utils.jinja_render(app_file, jinja_vars,
extra=values)
app_def = yaml.load(content)
self._apply_service_def(app_def)
dest_file = os.path.join(temp_dir, proj,
'%s.%s' % (service, self.type_name))
file_utils.mkdir_p(os.path.dirname(dest_file))
LOG.info(dest_file)
with open(dest_file, 'w') as f:
f.write(json.dumps(app_def, indent=2))
self.app_def = yaml.load(content)
self._apply_service_def(self.app_def)
if temp_dir is not None:
self.app_file = os.path.join(temp_dir, proj,
'%s.%s' % (service, self.type_name))
file_utils.mkdir_p(os.path.dirname(self.app_file))
LOG.info(self.app_file)
with open(self.app_file, 'w') as f:
f.write(json.dumps(self.app_def, indent=2))
def get_live_deployment_file(self):
"""Get the current version of the deployment from Marathon."""
raise exception.KollaNotSupportedException(
operation='get_live_deployment_file', entity=self.type_name)
def run(self):
raise exception.KollaNotSupportedException(operation='run',
entity=self.type_name)
def update(self):
raise exception.KollaNotSupportedException(operation='update',
entity=self.type_name)
def scale(self, instances, force=False):
raise exception.KollaNotSupportedException(operation='scale',
entity=self.type_name)
def snapshot(self, zk, output_dir, variables):
"""Produce the required files to revert the service.
The files should have the same basic structure as the original
service definition:
<output_dir>/<project>/<service>.yml.j2
<output_dir>/<project>/templates/*
<output_dir>/<project>/<service>.{marathon|chronos}
<output_dir>/variables.yml
"""
service_name = self._conf['name'].split('/')[-1]
proj = self._conf['name'].split('/')[-2]
proj_dir = os.path.join(output_dir, proj)
file_utils.mkdir_p(proj_dir)
# variables
# Note: if this exists, then the variables will be merged.
var_path = os.path.join(output_dir, 'variables.yml')
if os.path.exists(var_path):
with open(var_path) as vfp:
global_vars = yaml.load(vfp)
LOG.info('global_vars: %s', yaml.dump(global_vars))
global_vars.update(variables)
else:
global_vars = variables
with open(var_path, 'w') as vfp:
vfp.write(yaml.dump(global_vars, default_flow_style=False))
# service definition and files
with open(os.path.join(proj_dir, '%s.yml.j2' % service_name),
'w') as sfp:
sfp.write(json.dumps(self._conf))
# get and write the files
files_node = os.path.join('kolla', CONF.kolla.deployment_id,
self._conf['name'], 'files')
if zk.exists(files_node):
file_utils.mkdir_p(os.path.join(proj_dir, 'templates'))
try:
files = zk.get_children(files_node)
except exceptions.NoNodeError:
files = []
for fn in files:
with open(os.path.join(proj_dir, 'templates', fn), 'w') as fp:
content, _st = zk.get(os.path.join(files_node, fn))
fp.write(content)
# deployment file
self.app_file = os.path.join(proj_dir, '%s.%s' % (service_name,
self.type_name))
self.app_def = self.get_live_deployment_file()
with open(self.app_file, 'w') as dfp:
dfp.write(json.dumps(self.app_def, indent=2))
def kill(self, zk=None):
if zk:
dest_node = os.path.join('kolla', CONF.kolla.deployment_id,
self._conf['name'])
zk.delete(dest_node, recursive=True)
class MarathonApp(Runner):
def __init__(self, conf):
super(MarathonApp, self).__init__(conf)
self.type_name = 'marathon'
self._marathon_client = None
def _client(self):
if self._marathon_client is None:
self._marathon_client = marathon.Client()
return self._marathon_client
def _apply_service_def(self, app_def):
"""Apply the specifics from the service definition."""
@ -170,11 +301,66 @@ class MarathonApp(Runner):
app_def[opt] = utils.dict_update(app_def.get(opt),
self._conf['service'][opt])
def run(self):
self._client().add_app(self.app_def)
LOG.info('Marathon app "%s" is started' %
self.app_def['id'])
def update(self):
self._client().update_job(self._get_app_id(), self.app_def)
def _get_app_id(self):
return '/%s/%s' % (CONF.kolla.deployment_id, self._conf['name'])
def kill(self, zk=None):
self._client().remove_app(self._get_app_id())
super(MarathonApp, self).kill(zk)
def get_live_deployment_file(self):
"""Get the current version of the deployment from Marathon."""
return self._client().get_app(self._get_app_id())
def scale(self, instances, force=False):
self._client().scale_app(self._get_app_id(),
instances, force=force)
@staticmethod
def _get_common_fields(app):
dep_id = '/%s/' % CONF.kolla.deployment_id
marathon_fields = ('instances', 'tasksUnhealthy', 'tasksHealthy',
'tasksRunning', 'tasksStaged',
'version', 'healthChecks')
mini_app = dict((field, app[field]) for field in marathon_fields)
mini_app.update({'service': app['id'].replace(dep_id, ''),
'type': 'marathon'})
return mini_app
def get_state(self, version=None):
app = self._client().get_app(self._get_app_id(), version=version)
mini_app = MarathonApp._get_common_fields(app)
mini_app['image'] = app['container']['docker']['image']
mini_app['privileged'] = app['container']['docker']['privileged']
return mini_app
@staticmethod
def list_all():
dep_id = '/%s/' % CONF.kolla.deployment_id
marathon_client = marathon.Client()
apps = marathon_client.get_apps()
return [MarathonApp._get_common_fields(app) for app in apps
if app['id'].startswith(dep_id)]
class ChronosTask(Runner):
def __init__(self, conf):
super(ChronosTask, self).__init__(conf)
self.type_name = 'chronos'
self._chronos_client = None
def _client(self):
if self._chronos_client is None:
self._chronos_client = chronos.Client()
return self._chronos_client
def _apply_service_def(self, task_def):
"""Apply the specifics from the service definition."""
@ -200,3 +386,187 @@ class ChronosTask(Runner):
cenv['value'] = value
return
chronos_env.append({"name": key, "value": value})
def run(self):
self._client().add_job(self.app_def)
LOG.info('Chronos job "%s" is started' %
self.app_def['name'])
def _get_job_name(self):
return '%s-%s' % (CONF.kolla.deployment_id,
self._conf['name'].replace('/', '-'))
def update(self):
self._client().update_job(self._get_job_name(), self.app_def)
def kill(self, zk=None):
self._client().remove_job(self._get_job_name())
super(ChronosTask, self).kill(zk)
@staticmethod
def _job_to_public_format(job):
dep_id = '%s-' % CONF.kolla.deployment_id
mini_job = {'service': job['name'].replace(dep_id, ''),
'type': 'chronos'}
mini_job['tasksHealthy'] = job['successCount']
mini_job['tasksUnhealthy'] = job['errorCount']
mini_job['instances'] = 0 if job['disabled'] else 1
mini_job['version'] = job['lastSuccess']
mini_job['tasksStaged'] = 'N/A'
mini_job['tasksRunning'] = 'N/A'
return mini_job
def get_state(self, version=None):
job = self._client().get_job(self._get_job_name())
mini_job = ChronosTask._job_to_public_format(job)
mini_job['image'] = job['container']['image']
return mini_job
@staticmethod
def list_all():
client = chronos.Client()
dep_id = '%s-' % CONF.kolla.deployment_id
jobs = []
for job in client.get_jobs():
if job['name'].startswith(dep_id):
jobs.append(ChronosTask._job_to_public_format(job))
return jobs
def _load_variables_from_zk(zk):
path = os.path.join('/kolla', CONF.kolla.deployment_id, 'variables')
variables = {}
try:
var_names = zk.get_children(path)
except exceptions.NoNodeError:
var_names = []
for var in var_names:
variables[str(var)], _stat = zk.get(os.path.join(path, var))
# Add deployment_id
variables.update({'deployment_id': CONF.kolla.deployment_id})
# override node_config_directory to empty
variables.update({'node_config_directory': ''})
return variables
def _load_variables_from_file(service_dir, project_name):
config_dir = os.path.join(service_dir, '..', 'config')
with open(file_utils.find_config_file('passwords.yml'), 'r') as gf:
global_vars = yaml.load(gf)
with open(file_utils.find_config_file('globals.yml'), 'r') as gf:
global_vars.update(yaml.load(gf))
# all.yml file uses some its variables to template itself by jinja2,
# so its raw content is used to template the file
all_yml_name = os.path.join(config_dir, 'all.yml')
with open(all_yml_name) as af:
raw_vars = yaml.load(af)
raw_vars.update(global_vars)
jvars = yaml.load(jinja_utils.jinja_render(all_yml_name, raw_vars))
jvars.update(global_vars)
proj_yml_name = os.path.join(config_dir, project_name,
'defaults', 'main.yml')
if os.path.exists(proj_yml_name):
proj_vars = yaml.load(jinja_utils.jinja_render(proj_yml_name,
jvars))
jvars.update(proj_vars)
else:
LOG.warning('Path missing %s' % proj_yml_name)
# Add deployment_id
jvars.update({'deployment_id': CONF.kolla.deployment_id})
# override node_config_directory to empty
jvars.update({'node_config_directory': ''})
config.apply_deployment_vars(jvars)
return jvars
def _load_variables_from_snapshot(service_dir):
var_path = os.path.join(service_dir, 'variables.yml')
with open(var_path) as vfp:
return yaml.load(vfp)
def _build_runner(service_name, service_dir, variables=None):
config_dir = os.path.join(service_dir, '..', 'config')
base_node = os.path.join('kolla', CONF.kolla.deployment_id)
filename = service_definition.find_service_file(service_name,
service_dir)
proj_name = filename.split('/')[-2]
proj_yml_name = os.path.join(config_dir, proj_name,
'defaults', 'main.yml')
# is this a snapshot or from original src?
var_path = os.path.join(service_dir, 'variables.yml')
is_snapshot = (os.path.exists(var_path) and
not os.path.exists(proj_yml_name))
if variables is None:
if not is_snapshot:
variables = _load_variables_from_file(service_dir, proj_name)
else:
variables = _load_variables_from_snapshot(service_dir)
# 1. validate the definition with the given variables
service_definition.validate(service_name, service_dir, variables)
runner = Runner.load_from_file(filename, variables)
with zk_utils.connection() as zk:
# 2. write variables to zk (globally)
config.write_variables_zookeeper(zk, variables,
overwrite=not is_snapshot)
# 3. write common config and start script
config.write_common_config_to_zookeeper(config_dir, zk, variables,
overwrite=not is_snapshot)
# 4. write files/config to zk
runner.write_to_zookeeper(zk, base_node)
# 5. generate the deployment files
kolla_config = config.get_start_config(config_dir, variables)
runner.generate_deployment_files(kolla_config, variables)
return runner
# Public API below
##################
def run_service(service_name, service_dir, variables=None):
runner = _build_runner(service_name, service_dir, variables=variables)
runner.run()
def update_service(service_name, service_dir, variables=None):
runner = _build_runner(service_name, service_dir, variables=variables)
runner.update()
def kill_service(service_name):
with zk_utils.connection() as zk:
runner = Runner.load_from_zk(zk, service_name)
runner.kill(zk)
def snapshot_service(service_name, output_dir):
with zk_utils.connection() as zk:
runner = Runner.load_from_zk(zk, service_name)
variables = _load_variables_from_zk(zk)
runner.snapshot(zk, output_dir, variables)
def get_service(service_name, version=None):
with zk_utils.connection() as zk:
runner = Runner.load_from_zk(zk, service_name)
return runner.get_state(version)
def scale_service(service_name, instances, force=False):
with zk_utils.connection() as zk:
runner = Runner.load_from_zk(zk, service_name)
return runner.scale(instances, force)
def list_services():
return ChronosTask.list_all() + MarathonApp.list_all()
def get_service_logs(service_name):
pass

View File

@ -0,0 +1,32 @@
# 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 kolla_mesos.common import cli_utils
from kolla_mesos.tests import base
class DictToColTest(base.BaseTestCase):
scenarios = [
('normal',
dict(id_col=None,
input={'a': 'v1', 'b': 'v2'},
expect=[('a', 'b'), ('v1', 'v2')])),
('special',
dict(id_col='b',
input={'a': 'v1', 'b': 'v2'},
expect=[('b', 'a'), ('v2', 'v1')])),
]
def test_to_col(self):
self.assertEqual(self.expect,
cli_utils.dict2columns(self.input,
id_col=self.id_col))

View File

@ -0,0 +1,140 @@
# 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.
import json
import os.path
import mock
from oslo_config import cfg
from zake import fake_client
from kolla_mesos import service
from kolla_mesos.tests import base
class TestAPI(base.BaseTestCase):
def setUp(self):
super(TestAPI, self).setUp()
self.service_dir = os.path.join(self.project_dir, 'services')
self.client = fake_client.FakeClient()
self.client.start()
self.addCleanup(self.client.stop)
self.addCleanup(self.client.close)
cfg.CONF.set_override('deployment_id', 'did', group='kolla')
@mock.patch.object(service.config, 'apply_deployment_vars')
@mock.patch.object(service.MarathonApp, 'run')
def test_run_marathon(self, m_run, m_apply):
with mock.patch.object(service.zk_utils,
'connection') as m_zk_c:
m_zk_c.return_value.__enter__.return_value = self.client
service.run_service('openstack/keystone/keystone-api',
self.service_dir)
m_run.assert_called_once_with()
# assert that we have the basics in zk
exp_nodes = ['/kolla/common/kolla_mesos_start.py',
'/kolla/did/variables/keystone_admin_port',
'/kolla/did/openstack/keystone/keystone-api']
for node in exp_nodes:
self.assertTrue(self.client.exists(node), node)
@mock.patch.object(service.config, 'apply_deployment_vars')
@mock.patch.object(service.ChronosTask, 'run')
def test_run_chronos(self, m_run, m_apply):
with mock.patch.object(service.zk_utils,
'connection') as m_zk_c:
m_zk_c.return_value.__enter__.return_value = self.client
service.run_service('openstack/keystone/keystone-init',
self.service_dir)
m_run.assert_called_once_with()
# assert that we have the basics in zk
exp_nodes = ['/kolla/common/kolla_mesos_start.py',
'/kolla/did/variables/keystone_admin_port',
'/kolla/did/openstack/keystone/keystone_ansible_tasks']
for node in exp_nodes:
self.assertTrue(self.client.exists(node), node)
@mock.patch.object(service.MarathonApp, 'kill')
def test_kill(self, m_kill):
self.client.create('/kolla/did/openstack/nova/nova-api',
json.dumps({'name': 'openstack/nova/nova-api',
'service': {}}),
makepath=True)
with mock.patch.object(service.zk_utils,
'connection') as m_zk_c:
m_zk_c.return_value.__enter__.return_value = self.client
service.kill_service('openstack/nova/nova-api')
m_kill.assert_called_once_with(self.client)
@mock.patch.object(service.ChronosTask, 'get_state')
@mock.patch.object(service.MarathonApp, 'get_state')
def test_get_marathon(self, m_get_state, c_get_state):
self.client.create('/kolla/did/openstack/nova/nova-api',
json.dumps({'name': 'openstack/nova/nova-api',
'service': {}}),
makepath=True)
with mock.patch.object(service.zk_utils,
'connection') as m_zk_c:
m_zk_c.return_value.__enter__.return_value = self.client
service.get_service('openstack/nova/nova-api')
self.assertEqual([], c_get_state.mock_calls)
m_get_state.assert_called_once_with(None)
@mock.patch.object(service.ChronosTask, 'get_state')
@mock.patch.object(service.MarathonApp, 'get_state')
def test_get_chronos(self, m_get_state, c_get_state):
self.client.create('/kolla/did/openstack/nova/nova_init',
json.dumps({'name': 'openstack/nova/nova_init',
'task': {}}),
makepath=True)
with mock.patch.object(service.zk_utils,
'connection') as m_zk_c:
m_zk_c.return_value.__enter__.return_value = self.client
service.get_service('openstack-nova-nova_init')
self.assertEqual([], m_get_state.mock_calls)
c_get_state.assert_called_once_with(None)
@mock.patch.object(service.MarathonApp, 'scale')
def test_scale_marathon(self, m_scale):
self.client.create('/kolla/did/openstack/nova/nova-api',
json.dumps({'name': 'openstack/nova/nova-api',
'service': {}}),
makepath=True)
with mock.patch.object(service.zk_utils,
'connection') as m_zk_c:
m_zk_c.return_value.__enter__.return_value = self.client
service.scale_service('openstack/nova/nova-api', 2, force=False)
m_scale.assert_called_once_with(2, False)
@mock.patch.object(service.config, 'apply_deployment_vars')
@mock.patch.object(service.MarathonApp, 'update')
def test_update_marathon(self, m_update, m_apply):
self.client.create('/kolla/did/openstack/nova/nova-api',
json.dumps({'name': 'openstack/nova/nova-api',
'service': {}}),
makepath=True)
with mock.patch.object(service.zk_utils,
'connection') as m_zk_c:
m_zk_c.return_value.__enter__.return_value = self.client
service.update_service('openstack/nova/nova-api', self.service_dir)
m_update.assert_called_once_with()
@mock.patch.object(service.ChronosTask, 'list_all')
@mock.patch.object(service.MarathonApp, 'list_all')
def test_list(self, m_list, c_list):
service.list_services()
c_list.assert_called_once_with()
m_list.assert_called_once_with()

View File

@ -42,6 +42,9 @@ kolla_mesos.cli =
list = kolla_mesos.cli.service:List
show = kolla_mesos.cli.service:Show
log = kolla_mesos.cli.service:Log
scale = kolla_mesos.cli.service:Scale
snapshot = kolla_mesos.cli.service:Snapshot
update = kolla_mesos.cli.service:Update
chronos list = kolla_mesos.cli.chronos:List
chronos show = kolla_mesos.cli.chronos:Show
config list = kolla_mesos.cli.config:ConfigList