[WIP] cobbling together a command line - NOT READY FOR REVIEW

Change-Id: I9caefe557ac827ca7a3b8f9a1693d623cf369080
This commit is contained in:
Davanum Srinivas 2016-05-08 22:10:46 -04:00
parent 2100868fab
commit 5b64dcd17f
14 changed files with 1005 additions and 1 deletions

View File

View File

@ -0,0 +1,46 @@
# 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 cliff import command
from oslo_config import cfg
from oslo_log import log
from kolla_kubernetes 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):
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):
service.kill_service(parsed_args.service)

View File

View File

@ -0,0 +1,160 @@
# 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 os.path
import shlex
import sys
from cliff import app
from cliff import commandmanager
from cliff import interactive
from oslo_config import cfg
from oslo_log import log
from kolla_kubernetes.common import file_utils
from kolla_kubernetes.common import utils
PROJECT = 'kolla_kubernetes'
VERSION = '1.0'
CONF = cfg.CONF
CONF.import_group('kolla', 'kolla_kubernetes.config')
CONF.import_group('kolla-kubernetes', 'kolla_kubernetes.config')
log.register_options(CONF)
log.set_defaults(
default_log_levels='requests.packages.urllib3.connectionpool=WARNING')
cli_opts = [
cfg.StrOpt('service-dir',
default=utils.env(
'KM_SERVICE_DIR', default=os.path.join(
file_utils.find_base_dir(), 'services')),
help='Directory with services, (Env: KM_SERVICE_DIR)'),
]
CONF.register_cli_opts(cli_opts)
class KollaKubernetesInteractiveApp(interactive.InteractiveApp):
def do_run(self, arg):
self.default(arg)
def do_help(self, arg):
line_parts = shlex.split(arg)
try:
self.command_manager.find_command(line_parts)
return self.default(self.parsed('help ' + arg))
except ValueError:
# There is a builtin cmd2 command
pass
return interactive.InteractiveApp.do_help(self, arg)
class KollaKubernetesShell(app.App):
def __init__(self):
super(KollaKubernetesShell, self).__init__(
description='Kolla-kubernetes command-line interface',
version=VERSION,
command_manager=commandmanager.CommandManager(
'kolla_kubernetes.cli'),
deferred_help=True,
interactive_app_factory=KollaKubernetesInteractiveApp
)
def configure_logging(self):
return
def initialize_app(self, argv):
self.options.service_dir = CONF.service_dir
def print_help(self):
outputs = []
max_len = 0
self.stdout.write('\nCommands :\n')
for name, ep in sorted(self.command_manager):
factory = ep.load()
cmd = factory(self, None)
one_liner = cmd.get_description().split('\n')[0]
outputs.append((name, one_liner))
max_len = max(len(name), max_len)
for name, one_liner in outputs:
self.stdout.write(' %s %s\n' % (name.ljust(max_len), one_liner))
def _separate_args(argv):
conf_opts = _config_opts_map()
config_args = []
command_args = argv[:]
while command_args:
nargs = conf_opts.get(command_args[0])
if nargs:
config_args.extend(command_args[:nargs])
command_args = command_args[nargs:]
else:
break
return config_args, command_args
def _config_opts_map():
opts = {'--help': 1, '-h': 1, '--config-dir': 2, '--config-file': 2,
'--version': 1}
for opt in CONF._all_cli_opts():
if opt[1]:
arg = '%s-%s' % (opt[1].name, opt[0].name)
else:
arg = opt[0].name
if isinstance(opt[0], cfg.BoolOpt):
nargs = 1
opts['--no%s' % arg] = 1
else:
nargs = 2
opts['--%s' % arg] = nargs
if opt[0].short:
opts['-%s' % opt[0].short] = nargs
for dep_opt in opt[0].deprecated_opts:
if getattr(dep_opt, 'group'):
opts['--%s-%s' % (dep_opt.group, dep_opt.name)] = nargs
else:
opts['--%s' % dep_opt.name] = nargs
return opts
def main(argv=sys.argv[1:]):
config_args, command_args = _separate_args(argv)
need_help = (['help'] == command_args or '-h' in config_args or
'--help' in config_args)
if need_help:
CONF([], project=PROJECT, version=VERSION)
CONF.print_help()
return KollaKubernetesShell().print_help()
CONF(config_args, project=PROJECT, version=VERSION)
log.setup(CONF, PROJECT, VERSION)
if '-d' in config_args or '--debug' in config_args:
command_args.insert(0, '--debug')
CONF.log_opt_values(
log.getLogger(PROJECT), log.INFO)
return KollaKubernetesShell().run(command_args)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

View File

View File

@ -0,0 +1,94 @@
# 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 errno
import logging
import os
import platform
import sys
from oslo_utils import importutils
from kolla_kubernetes import exception
LOG = logging.getLogger(__name__)
def find_os_type():
return platform.linux_distribution()[0]
def mkdir_p(path):
try:
os.makedirs(path)
except OSError as exc: # Python >2.5
if exc.errno == errno.EEXIST and os.path.isdir(path):
pass
else:
raise
def get_src_dir():
kolla_kubernetes = importutils.import_module('kolla_kubernetes')
mod_path = os.path.abspath(kolla_kubernetes.__file__)
# remove the file and module to get to the base.
return os.path.dirname(os.path.dirname(mod_path))
def find_base_dir():
script_path = os.path.dirname(os.path.realpath(sys.argv[0]))
base_script_path = os.path.basename(script_path)
if base_script_path == 'kolla-kubernetes':
return script_path
if base_script_path == 'kolla_kubernetes':
return os.path.join(script_path, '..')
if base_script_path == 'cmd':
return os.path.join(script_path, '..', '..')
if base_script_path == 'subunit':
return get_src_dir()
if base_script_path == 'bin':
if find_os_type() in ['Ubuntu', 'debian']:
base_dir = '/usr/local/share/kolla-kubernetes'
else:
base_dir = '/usr/share/kolla-kubernetes'
if os.path.exists(base_dir):
return base_dir
else:
return get_src_dir()
raise exception.KollaDirNotFoundException(
'Unable to detect kolla-kubernetes directory'
)
def find_config_file(filename):
filepath = os.path.join('/etc/kolla-kubernetes', filename)
if os.access(filepath, os.R_OK):
config_file = filepath
else:
config_file = os.path.join(find_base_dir(),
'etc', filename)
return config_file
POSSIBLE_PATHS = {'/usr/share/kolla-kubernetes',
get_src_dir(),
find_base_dir()}
def find_file(filename):
for path in POSSIBLE_PATHS:
file_path = os.path.join(path, filename)
if os.path.exists(file_path):
return file_path
raise exception.KollaNotFoundException(filename, entity='file')

View File

@ -0,0 +1,102 @@
# 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 collections
import logging
import os
import jinja2
from jinja2 import meta
import six
import yaml
from kolla_kubernetes.common import type_utils
LOG = logging.getLogger(__name__)
# Customize PyYAML library to return the OrderedDict. That is needed, because
# when iterating on dict, we reuse its previous values when processing the
# next values and the order has to be preserved.
def ordered_dict_constructor(loader, node):
"""OrderedDict constructor for PyYAML."""
return collections.OrderedDict(loader.construct_pairs(node))
def ordered_dict_representer(dumper, data):
"""Representer for PyYAML which is able to work with OrderedDict."""
return dumper.represent_dict(data.items())
yaml.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
ordered_dict_constructor)
yaml.add_representer(collections.OrderedDict, ordered_dict_representer)
def jinja_render(fullpath, global_config, extra=None):
variables = global_config
if extra:
variables.update(extra)
myenv = jinja2.Environment(
loader=jinja2.FileSystemLoader(
os.path.dirname(fullpath)))
myenv.filters['bool'] = type_utils.str_to_bool
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'] = type_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)))
myenv.filters['bool'] = type_utils.str_to_bool
template_source = myenv.loader.get_source(myenv,
os.path.basename(fullpath))[0]
parsed_content = myenv.parse(template_source)
return meta.find_undeclared_variables(parsed_content)
def dict_jinja_render(raw_dict, jvars):
"""Renders dict with jinja2 using provided variables and itself.
By using itself, we mean reusing the previous values from dict for the
potential render of the next value in dict.
"""
for key, value in raw_dict.items():
if isinstance(value, six.string_types):
value = jinja_render_str(value, jvars)
elif isinstance(value, dict):
value = dict_jinja_render(value, jvars)
jvars[key] = value
def yaml_jinja_render(filename, jvars):
"""Parses YAML file and templates it with jinja2.
1. YAML file is rendered by jinja2 based on the provided variables.
2. Rendered file is parsed.
3. The every element dictionary being a result of parsing is rendered again
with itself.
"""
with open(filename, 'r') as yaml_file:
raw_dict = yaml.load(yaml_file)
dict_jinja_render(raw_dict, jvars)

View File

@ -0,0 +1,19 @@
# 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.
def str_to_bool(text):
if not text:
return False
if text.lower() in ['true', 'yes']:
return True
return False

View File

@ -0,0 +1,46 @@
# 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 collections
import os
from six.moves.urllib import parse
def env(*args, **kwargs):
for arg in args:
value = os.environ.get(arg)
if value:
return value
return kwargs.get('default', '')
def dict_update(d, u):
"""Recursively update 'd' with 'u' and return the result."""
if not isinstance(u, collections.Mapping):
return u
for k, v in u.items():
if isinstance(v, collections.Mapping):
d[k] = dict_update(d.get(k, {}), v)
else:
d[k] = u[k]
return d
def get_query_string(search_opts):
if search_opts:
qparams = sorted(search_opts.items(), key=lambda x: x[0])
query_string = "?%s" % parse.urlencode(qparams, doseq=True)
else:
query_string = ""
return query_string

View File

@ -0,0 +1,56 @@
# 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 oslo_config import cfg
from kolla_kubernetes.common import utils
CONF = cfg.CONF
kolla_opts = [
cfg.StrOpt('namespace',
default='kollaglue',
help='The Docker namespace name'),
cfg.StrOpt('tag',
default='1.0.0',
help='The Docker tag'),
cfg.StrOpt('base',
default='centos',
help='The base distro which was used to build images'),
cfg.StrOpt('base-tag',
default='latest',
help='The base distro image tag'),
cfg.StrOpt('install-type',
default='binary',
help='The method of the OpenStack install'),
cfg.StrOpt('deployment-id',
default=utils.env('USER', default='default'),
help='Uniq name for deployment'),
cfg.StrOpt('profile',
default='default',
help='Build profile which was used to build images')
]
kolla_opt_group = cfg.OptGroup(name='kolla',
title='Options for Kolla Docker images')
CONF.register_group(kolla_opt_group)
CONF.register_cli_opts(kolla_opts, kolla_opt_group)
CONF.register_opts(kolla_opts, kolla_opt_group)
kubernetes_opts = [
cfg.StrOpt('host', default='localhost:8080'),
cfg.StrOpt('kubectl_path'),
cfg.StrOpt('yml_dir_path', )
]
kubernetes_opt_group = cfg.OptGroup(name='kolla-kubernetes')
CONF.register_group(kubernetes_opt_group)
CONF.register_cli_opts(kubernetes_opts, kubernetes_opt_group)
CONF.register_opts(kubernetes_opts, kubernetes_opt_group)

View File

@ -0,0 +1,31 @@
# 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.
class KollaException(Exception):
pass
class KollaDirNotFoundException(KollaException):
pass
class KollaNotFoundException(KollaException):
def __init__(self, message, entity='file'):
super(KollaNotFoundException, self).__init__(
'The %s "%s" was not found' % (entity, message))
class KollaNotSupportedException(KollaNotFoundException):
def __init__(self, operation='update', entity='kubernetes'):
super(KollaNotFoundException, self).__init__(
'Operation "%s" is not supported by "%s"' % (operation, entity))

309
kolla_kubernetes/service.py Normal file
View File

@ -0,0 +1,309 @@
# 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 functools
import os.path
import subprocess
import time
from oslo_config import cfg
from oslo_log import log as logging
import yaml
from kolla_kubernetes.common import file_utils
from kolla_kubernetes.common import jinja_utils
from kolla_kubernetes import service_definition
LOG = logging.getLogger()
CONF = cfg.CONF
CONF.import_group('kolla', 'kolla_kubernetes.config')
CONF.import_group('kolla-kubernetes', 'kolla_kubernetes.config')
def execute_if_enabled(f):
"""Decorator for executing methods only if runner is enabled."""
@functools.wraps(f)
def wrapper(self, *args, **kwargs):
if not self._enabled:
return
return f(self, *args, **kwargs)
return wrapper
class File(object):
def __init__(self, conf, name, service_name):
self._conf = conf
self._name = name
self._service_name = service_name
self.base_dir = os.path.abspath(file_utils.find_base_dir())
# def merge_ini_files(self, source_files):
# config_p = configparser.ConfigParser()
# for src_file in source_files:
# if not src_file.startswith('/'):
# src_file = os.path.join(self.base_dir, src_file)
# if not os.path.exists(src_file):
# LOG.warning('path missing %s' % src_file)
# continue
# config_p.read(src_file)
# merged_f = cStringIO()
# config_p.write(merged_f)
# return merged_f.getvalue()
#
# def write_to_zookeeper(self, zk, base_node):
# dest_node = os.path.join(base_node, self._service_name,
# 'files', self._name)
# zk.ensure_path(dest_node)
# if isinstance(self._conf['source'], list):
# content = self.merge_ini_files(self._conf['source'])
# else:
# src_file = self._conf['source']
# if not src_file.startswith('/'):
# src_file = file_utils.find_file(src_file)
# with open(src_file) as fp:
# content = fp.read()
# zk.set(dest_node, content.encode('utf-8'))
class Command(object):
def __init__(self, conf, name, service_name):
self._conf = conf
self._name = name
self._service_name = service_name
# def write_to_zookeeper(self, zk, base_node):
# for fn in self._conf.get('files', []):
# fo = File(self._conf['files'][fn], fn, self._service_name)
# fo.write_to_zookeeper(zk, base_node)
class Runner(object):
def __init__(self, conf):
self._conf = conf
self.base_dir = os.path.abspath(file_utils.find_base_dir())
self.type_name = None
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."""
# Call is already for a subclass, so pass it through
RunnerClass = cls
return super(Runner, cls).__new__(RunnerClass)
@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:
yield 'daemon', self._conf['service']['daemon']
for key in self._conf.get('commands', []):
yield key, self._conf['commands'][key]
# @execute_if_enabled
# def write_to_zookeeper(self, zk, base_node):
# for cmd_name, cmd_conf in self._list_commands():
# cmd = Command(cmd_conf, cmd_name, self._conf['name'])
# # cmd.write_to_zookeeper(zk, base_node)
#
# dest_node = os.path.join(base_node, self._conf['name'])
# # zk.ensure_path(dest_node)
# # try:
# # zk.set(dest_node, json.dumps(self._conf).encode('utf-8'))
# # except Exception as te:
# # LOG.error('%s=%s -> %s' % (dest_node, self._conf, te))
# @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)
# dest_node = os.path.join(base_node, "openstack",
# service_name.split('-')[0], service_name)
# try:
# conf_raw, _st = zk.get(dest_node)
# except Exception as te:
# LOG.error('%s -> %s' % (dest_node, te))
# raise NameError(te)
# return Runner(yaml.load(
# jinja_utils.jinja_render_str(conf_raw.decode('utf-8'),
# variables)))
class JvarsDict(dict):
"""Dict which can contain the 'global_vars' which are always preserved.
They cannot be be overriden by any update nor single item setting.
"""
def __init__(self, *args, **kwargs):
super(JvarsDict, self).__init__(*args, **kwargs)
self.global_vars = {}
def __setitem__(self, key, value, force=False):
if not force and key in self.global_vars:
return
return super(JvarsDict, self).__setitem__(key, value)
def set_force(self, key, value):
"""Sets the variable even if it will override a global variable."""
return self.__setitem__(key, value, force=True)
def update(self, other_dict, force=False):
if not force:
other_dict = {key: value for key, value in other_dict.items()
if key not in self.global_vars}
super(JvarsDict, self).update(other_dict)
def set_global_vars(self, global_vars):
self.update(global_vars)
self.global_vars = global_vars
def _load_variables_from_file(service_dir, project_name):
config_dir = os.path.join(service_dir, '..', 'config')
jvars = JvarsDict()
with open(file_utils.find_config_file('globals.yml'), 'r') as gf:
jvars.set_global_vars(yaml.load(gf))
with open(file_utils.find_config_file('passwords.yml'), 'r') as gf:
jvars.update(yaml.load(gf))
# Apply the basic variables that aren't defined in any config file.
jvars.update({
'deployment_id': CONF.kolla.deployment_id,
'node_config_directory': '',
'timestamp': str(time.time())
})
# Get the exact marathon framework name.
# config.get_marathon_framework(jvars)
# 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')
jinja_utils.yaml_jinja_render(all_yml_name, jvars)
# Apply the dynamic deployment variables.
# config.apply_deployment_vars(jvars)
proj_yml_name = os.path.join(config_dir, project_name,
'defaults', 'main.yml')
if os.path.exists(proj_yml_name):
jinja_utils.yaml_jinja_render(proj_yml_name, jvars)
else:
LOG.warning('Path missing %s' % proj_yml_name)
return jvars
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]
# is this a snapshot or from original src?
variables = _load_variables_from_file(service_dir, proj_name)
# 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)
# # 3. write common config and start script
# config.write_common_config_to_zookeeper(config_dir, zk, variables)
#
# # 4. write files/config to zk
# runner.write_to_zookeeper(zk, base_node)
# 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 Exception:
# var_names = []
# for var in var_names:
# value, _stat = zk.get(os.path.join(path, var))
# variables[var] = value.decode('utf-8')
# # Add deployment_id
# variables.update({'deployment_id': CONF.kolla.deployment_id})
# # override node_config_directory to empty
# variables.update({'node_config_directory': ''})
# return variables
# Public API below
##################
def run_service(service_name, service_dir, variables=None):
# generate zk variables
if service_name == 'nova-compute':
service_list = ['nova-compute', 'nova-libvirt', 'openvswitch-vswitchd',
'neutron-openvswitch-agent', 'openvswitch-db']
elif service_name == 'network-node':
service_list = ['neutron-openvswitch-agent', 'neutron-dhcp-agent',
'neutron-metadata-agent', 'openvswitch-vswitchd',
'openvswitch-db neutron-l3-agent']
# TODO(dims): load this service _list from config
elif service_name == 'all':
service_list = ['keystone-init', 'keystone-api', 'keystone-db-sync',
'glance-init', 'mariadb', 'rabbitmq',
'glance-registry',
'glance-api', 'nova-init', 'nova-api',
'nova-scheduler',
'nova-conductor', 'nova-consoleauth', 'neutron-init',
'neutron-server', 'horizon', 'nova-compute',
'nova-libvirt', 'openvswitch-vswitchd',
'neutron-openvswitch-agent', 'openvswitch-db',
'neutron-dhcp-agent', 'neutron-metadata-agent',
'openvswitch-db neutron-l3-agent']
elif service_name == 'zookeeper':
service_list = []
else:
service_list = [service_name]
for service in service_list:
_build_runner(service, service_dir, variables=variables)
_deploy_instance(service_name)
def kill_service(service_name):
# if service_name == "all":
# with zk_utils.connection() as zk:
# status_node = os.path.join('kolla', CONF.kolla.deployment_id,
# 'status')
# zk.delete(status_node, recursive=True)
_delete_instance(service_name)
def _deploy_instance(service_name):
server = "--server=" + CONF.kubernetes.host
if service_name == 'all':
service_path = CONF.kubernetes.yml_dir_path
else:
service_path = CONF.kubernetes.yml_dir_path + service_name + ".yml"
cmd = [CONF.kubernetes.kubectl_path, server, "create", "-f", service_path]
# print cmd
subprocess.call(cmd)
def _delete_instance(service_name):
server = "--server=" + CONF.kubernetes.host
if service_name == 'all':
service_path = CONF.kubernetes.yml_dir_path
else:
service_path = CONF.kubernetes.yml_dir_path + service_name + ".yml"
cmd = [CONF.kubernetes.kubectl_path, server, "delete", "-f", service_path]
# print cmd
subprocess.call(cmd)

View File

@ -0,0 +1,131 @@
# 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 os.path
import socket
import yaml
import jinja2
from oslo_log import log
from kolla_kubernetes.common import jinja_utils
from kolla_kubernetes import exception
CNF_FIELDS = ('source', 'dest', 'owner', 'perm')
CMD_FIELDS = ('run_once', 'dependencies', 'command', 'env',
'delay', 'retries', 'files')
DEP_FIELDS = ('path', 'scope')
SCOPE_OPTS = ('global', 'local')
LOG = log.getLogger()
def find_service_file(service_name, service_dir):
# let's be flexible with the input, to make life easy
# for users.
if not os.path.exists(service_dir):
raise exception.KollaNotFoundException(service_dir,
entity='service directory')
short_name = service_name.split('/')[-1].replace('_ansible_tasks', '-init')
for root, dirs, names in os.walk(service_dir):
for name in names:
if short_name in name:
return os.path.join(root, name)
raise exception.KollaNotFoundException(service_name,
entity='service definition')
def inspect(service_name, service_dir):
filename = find_service_file(service_name, service_dir)
try:
required_variables = set.union(
jinja_utils.jinja_find_required_variables(filename))
except jinja2.exceptions.TemplateNotFound:
raise exception.KollaNotFoundException(filename,
entity='service definition')
return dict(required_variables=list(required_variables))
def validate(service_name, service_dir, variables=None, deps=None):
if variables is None:
variables = {}
if deps is None:
deps = {}
filename = find_service_file(service_name, service_dir)
try:
cnf = yaml.load(jinja_utils.jinja_render(filename, variables))
except jinja2.exceptions.TemplateNotFound:
raise exception.KollaNotFoundException(filename,
entity='service definition')
def get_commands():
for cmd in cnf.get('commands', {}):
yield cmd, cnf['commands'][cmd]
if 'service' in cnf:
yield 'daemon', cnf['service']['daemon']
LOG.debug('%s: file found at %s' % (cnf['name'], filename))
for cmd, cmd_info in get_commands():
_validate_command(filename, cmd, cmd_info, deps,
cnf['name'], service_dir)
return deps
def _validate_config(filename, conf, service_dir):
for file in conf:
for key in conf[file]:
assert key in CNF_FIELDS, '%s: %s not in %s' % (filename,
key, CNF_FIELDS)
srcs = conf[file]['source']
if isinstance(srcs, str):
srcs = [srcs]
for src in srcs:
file_path = os.path.join(service_dir, '..', src)
if not file_path.startswith('/etc'):
assert os.path.exists(file_path), '%s missing' % file_path
def _validate_command(filename, cmd, cmd_info, deps,
service_name, service_dir):
for key in cmd_info:
assert key in CMD_FIELDS, '%s not in %s' % (key, CMD_FIELDS)
_, group, role = service_name.split('/')
regs = ['%s/%s' % (role, cmd),
'%s/%s/%s' % (socket.gethostname(), role, cmd)]
reqs = cmd_info.get('dependencies', [])
for reg in regs:
if reg not in deps:
deps[reg] = {'waiters': {}}
deps[reg]['registered_by'] = cmd
deps[reg]['name'] = cmd
deps[reg]['run_by'] = filename
for req in reqs:
for key in req:
assert key in DEP_FIELDS, '%s: %s not in %s' % (filename,
key, DEP_FIELDS)
scope = req.get('scope', 'global')
assert scope in SCOPE_OPTS, '%s: %s not in %s' % (filename,
scope, SCOPE_OPTS)
req_path = req['path']
if scope == 'local':
req_path = os.path.join(socket.gethostname(), req_path)
if req_path not in deps:
deps[req_path] = {'waiters': {}}
for reg in regs:
deps[req_path]['waiters'][cmd] = reg
if 'files' in cmd_info:
_validate_config(filename, cmd_info['files'], service_dir)
LOG.debug('%s: command "%s" validated' % (service_name, cmd))

View File

@ -21,7 +21,17 @@ classifier =
[files]
packages =
kolla-kubernetes
kolla_kubernetes
data_files =
share/kolla-kubernetes/services = services/*
[entry_points]
console_scripts =
kolla-kubernetes = kolla_kubernetes.cmd.shell:main
kolla_kubernetes.cli =
run = kolla_kubernetes.cli.service:Run
kill = kolla_kubernetes.cli.service:Kill
[build_sphinx]
source-dir = doc/source