Removed tasklib directory

Looks like we don't need it any more.
Besides there is a separate repository
https://github.com/stackforge/fuel-tasklib

Change-Id: I25b7f6d4ab9d6e3ff1d77d262913f4697557528e
This commit is contained in:
Vladimir Kozhukalov 2015-07-17 18:17:47 +03:00
parent 38d0e7db78
commit 53801c201b
44 changed files with 0 additions and 1321 deletions

View File

@ -21,8 +21,6 @@ function usage {
echo "Run Fuel-Web test suite(s)"
echo ""
echo " -h, --help Print this usage message"
echo " -k, --tasklib Run tasklib unit and functional tests"
echo " -K, --no-tasklib Don't run tasklib unit and functional tests"
echo " -n, --nailgun Run NAILGUN unit/integration tests"
echo " -N, --no-nailgun Don't run NAILGUN unit/integration tests"
echo " -x, --performance Run NAILGUN performance tests"
@ -56,8 +54,6 @@ function process_options {
-n|--nailgun) nailgun_tests=1;;
-N|--no-nailgun) no_nailgun_tests=1;;
-x|--performance) performance_tests=1;;
-k|--tasklib) tasklib_tests=1;;
-K|--no-tasklib) no_tasklib_tests=1;;
-u|--upgrade) upgrade_system=1;;
-U|--no-upgrade) no_upgrade_system=1;;
-s|--shotgun) shotgun_tests=1;;
@ -126,8 +122,6 @@ no_ui_unit_tests=0
ui_func_tests=0
no_ui_func_tests=0
certain_tests=0
tasklib_tests=0
no_tasklib_tests=0
ui_func_selenium_tests=0
function run_tests {
@ -154,7 +148,6 @@ function run_tests {
# Enable all tests if none was specified skipping all explicitly disabled tests.
if [[ $nailgun_tests -eq 0 && \
$performance_tests -eq 0 && \
$tasklib_tests -eq 0 && \
$ui_lint_checks -eq 0 && \
$ui_unit_tests -eq 0 && \
$ui_func_tests -eq 0 && \
@ -164,7 +157,6 @@ function run_tests {
$flake8_checks -eq 0 ]]; then
if [ $no_nailgun_tests -ne 1 ]; then nailgun_tests=1; fi
if [ $no_tasklib_tests -ne 1 ]; then tasklib_tests=1; fi
if [ $no_ui_lint_checks -ne 1 ]; then ui_lint_checks=1; fi
if [ $no_ui_unit_tests -ne 1 ]; then ui_unit_tests=1; fi
if [ $no_ui_func_tests -ne 1 ]; then ui_func_tests=1; fi
@ -184,11 +176,6 @@ function run_tests {
run_nailgun_tests || errors+=" nailgun_tests"
fi
if [ $tasklib_tests -eq 1 ]; then
echo "Starting Tasklib tests"
run_tasklib_tests || errors+=" tasklib tests"
fi
if [ $ui_lint_checks -eq 1 ]; then
echo "Starting UI lint checks..."
run_lint_ui || errors+=" ui_lint_checks"
@ -444,19 +431,6 @@ function run_shotgun_tests {
return $result
}
function run_tasklib_tests {
local result=0
pushd $ROOT/tasklib >> /dev/null
# run tests
tox -epy26 || result=1
popd >> /dev/null
return $result
}
function run_flake8_subproject {
local DIRECTORY=$1
local result=0
@ -477,7 +451,6 @@ function run_flake8_subproject {
function run_flake8 {
local result=0
run_flake8_subproject nailgun && \
run_flake8_subproject tasklib && \
run_flake8_subproject fuelmenu && \
run_flake8_subproject network_checker && \
run_flake8_subproject fuel_upgrade_system/fuel_upgrade && \

5
tasklib/.gitignore vendored
View File

@ -1,5 +0,0 @@
tmp/*
*.egg-info
*.pyc
tasklib/tests/functional/tmp/
tasklib.log

View File

@ -1 +0,0 @@
include *requirements.txt

View File

@ -1,72 +0,0 @@
Tasklib
=======
Tasklib is mediator between different configuration management providers
and orchestration mechanism in Fuel.
It will try to cover next areas:
- part of the plugable fuel architecture
See tasklib/tasklib/tests/functional for detailed descriptionof
how tasklib plugability will work
- Control mechanism for tasks in fuel
To support different types of workflow we will provide
ability to terminate, list all running, stop/pause task
- Abstraction layer between tasks and orchestration, which will allow
easier development and debuging of tasks
- General reporting solution for tasks
Executions drivers
==================
- puppet
- exec
Puppet
--------
Puppet executor supports general metadata for running puppet manifests.
Example of such metadata (task.yaml):
type: puppet - required
puppet_manifest: file.pp - default is site.pp
puppet_moduels: /etc/puppet/modules
puppet_options: --debug
All defaults you can find in:
>> taskcmd conf
It works next way:
After task.yaml is found - executor will look for puppet_manifest
and run:
puppet apply --modulepath=/etc/puppet/modules file.pp
with additional options you will provide
Exec
-----
type: exec - required
cmd: echo 12 - required
will execute any cmd provided as subprocess
EXAMPLES:
=========
taskcmd -c tasklib/tests/functional/conf.yaml conf
taskcmd -c tasklib/tests/functional/conf.yaml list
taskcmd -c tasklib/tests/functional/conf.yaml daemon puppet/sleep
taskcmd -c tasklib/tests/functional/conf.yaml status puppet/sleep
taskcmd -c tasklib/tests/functional/conf.yaml run puppet/cmd
taskcmd -c tasklib/tests/functional/conf.yaml run puppet/invalid
HOW TO RUN TESTS:
==================
python setup.py develop
pip install -r test-requires.txt
nosetests tasklib/tests
For some functional tests installed puppet is required,
so if you dont have puppet - they will be skipped

View File

@ -1,3 +0,0 @@
argparse
daemonize
pyyaml

View File

@ -1,55 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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
import setuptools
def requirements():
dir_path = os.path.dirname(os.path.realpath(__file__))
requirements = []
with open('{0}/requirements.txt'.format(dir_path), 'r') as reqs:
requirements = reqs.readlines()
return requirements
name = 'tasklib'
version = '7.0.0'
setuptools.setup(
name=name,
version=version,
description='Tasklib package',
long_description="""Tasklib is intended to be mediator between different
configuration management solutions and orchestrator.
This is required to support plugable tasks/actions with good
amount of control, such as stop/pause/poll state.
""",
classifiers=[
"Development Status :: 4 - Beta",
"Programming Language :: Python",
],
author='Mirantis Inc.',
author_email='product@mirantis.com',
url='http://mirantis.com',
keywords='tasklib mirantis',
packages=setuptools.find_packages(),
zip_safe=False,
install_requires=requirements(),
include_package_data=True,
entry_points={
'console_scripts': [
'taskcmd = tasklib.cli:main',
]})

View File

@ -1,13 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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.

View File

@ -1,13 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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.

View File

@ -1,35 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 logging
from tasklib import exceptions
log = logging.getLogger(__name__)
class Action(object):
def __init__(self, task, config):
self.task = task
self.config = config
log.debug('Init action with task %s', self.task.name)
def verify(self):
if 'type' not in self.task.metadata:
raise exceptions.NotValidMetadata()
def run(self):
raise NotImplementedError('Should be implemented by action driver.')

View File

@ -1,44 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 logging
from tasklib.actions import action
from tasklib import exceptions
from tasklib import utils
log = logging.getLogger(__name__)
class ExecAction(action.Action):
def verify(self):
super(ExecAction, self).verify()
if 'cmd' not in self.task.metadata:
raise exceptions.NotValidMetadata()
@property
def command(self):
return self.task.metadata['cmd']
def run(self):
log.debug('Running task %s with command %s',
self.task.name, self.command)
exit_code, stdout, stderr = utils.execute(self.command)
log.debug(
'Task %s with command %s\n returned code %s\n out %s err%s',
self.task.name, self.command, exit_code, stdout, stderr)
if exit_code != 0:
raise exceptions.Failed()
return exit_code

View File

@ -1,68 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 logging
import os
from tasklib.actions import action
from tasklib import exceptions
from tasklib import utils
log = logging.getLogger(__name__)
class PuppetAction(action.Action):
def run(self):
log.debug('Running puppet task %s with command %s',
self.task.name, self.command)
exit_code, stdout, stderr = utils.execute(self.command)
log.debug(
'Task %s with command %s\n returned code %s\n out %s err%s',
self.task.name, self.command, exit_code, stdout, stderr)
# 0 - no changes
# 2 - was some changes but successfull
# 4 - failures during transaction
# 6 - changes and failures
if exit_code not in [0, 2]:
raise exceptions.Failed()
return exit_code
@property
def manifest(self):
return (self.task.metadata.get('puppet_manifest') or
self.config['puppet_manifest'])
@property
def puppet_options(self):
if 'puppet_options' in self.task.metadata:
return self.task.metadata['puppet_options']
return self.config['puppet_options']
@property
def puppet_modules(self):
return (self.task.metadata.get('puppet_modules') or
self.config['puppet_modules'])
@property
def command(self):
cmd = ['puppet', 'apply', '--detailed-exitcodes']
if self.puppet_modules:
cmd.append('--modulepath={0}'.format(self.puppet_modules))
if self.puppet_options:
cmd.append(self.puppet_options)
if self.config['debug']:
cmd.append('--debug --verbose --evaltrace')
cmd.append(os.path.join(self.task.dir, self.manifest))
return ' '.join(cmd)

View File

@ -1,94 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 logging
import os
import daemonize
from tasklib import exceptions
from tasklib import task
from tasklib import utils
log = logging.getLogger(__name__)
class TaskAgent(object):
def __init__(self, task_name, config):
log.debug('Initializing task agent for task %s', task_name)
self.config = config
self.task = task.Task(task_name, self.config)
self.init_directories()
def init_directories(self):
utils.ensure_dir_created(self.config['pid_dir'])
utils.ensure_dir_created(self.config['report_dir'])
utils.ensure_dir_created(self.task.pid_dir)
utils.ensure_dir_created(self.task.report_dir)
def run(self):
try:
self.set_status(utils.STATUS.running.name)
result = self.task.run()
self.set_status(utils.STATUS.end.name)
return result
except exceptions.NotFound as exc:
log.warning('Cant find task %s with path %s',
self.task.name, self.task.file)
self.set_status(utils.STATUS.notfound.name)
except exceptions.Failed as exc:
log.error('Task %s failed with msg %s', self.task.name, exc.msg)
self.set_status(utils.STATUS.failed.name)
except Exception:
log.exception('Task %s erred', self.task.name)
self.set_status(utils.STATUS.error.name)
finally:
self.clean()
def __str__(self):
return 'tasklib agent - {0}'.format(self.task.name)
def report(self):
return 'placeholder'
def status(self):
if not os.path.exists(self.task.status_file):
return utils.STATUS.notfound.name
with open(self.task.status_file) as f:
return f.read()
def set_status(self, status):
with open(self.task.status_file, 'w') as f:
f.write(status)
def code(self):
status = self.status()
return getattr(utils.STATUS, status).code
@property
def pid(self):
return os.path.join(self.task.pid_dir, 'run.pid')
def daemon(self):
log.debug('Daemonizing task %s with pidfile %s',
self.task.name, self.pid)
daemon = daemonize.Daemonize(
app=str(self), pid=self.pid, action=self.run)
daemon.start()
def clean(self):
if os.path.exists(self.pid):
os.unlink(self.pid)

View File

@ -1,115 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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.
"""Tasklib cmd interface
Exit Codes:
ended successfully - 0
running - 1
valid but failed - 2
unexpected error - 3
notfound such task - 4
"""
import argparse
import sys
import textwrap
import yaml
from tasklib import agent
from tasklib import config
from tasklib import logger
from tasklib import task
from tasklib import utils
class CmdApi(object):
def __init__(self):
self.parser = argparse.ArgumentParser(
description=textwrap.dedent(__doc__),
formatter_class=argparse.RawDescriptionHelpFormatter)
self.subparser = self.parser.add_subparsers(
title='actions',
description='Supported actions',
help='Provide of one valid actions')
self.config = config.Config()
self.register_options()
self.register_actions()
def register_options(self):
self.parser.add_argument(
'--config', '-c', dest='config', default=None,
help='Path to configuration file')
self.parser.add_argument(
'--debug', '-d', dest='debug', action='store_true', default=None)
def register_actions(self):
task_arg = [(('task',), {'type': str})]
self.register_parser('list')
self.register_parser('conf')
for name in ('run', 'daemon', 'report', 'status', 'show'):
self.register_parser(name, task_arg)
def register_parser(self, func_name, arguments=()):
parser = self.subparser.add_parser(func_name)
parser.set_defaults(func=getattr(self, func_name))
for args, kwargs in arguments:
parser.add_argument(*args, **kwargs)
def parse(self, args):
parsed = self.parser.parse_args(args)
if parsed.config:
self.config.update_from_file(parsed.config)
if parsed.debug is not None:
self.config['debug'] = parsed.debug
logger.setup_logging(self.config)
return parsed.func(parsed)
def list(self, args):
for task_dir in utils.find_all_tasks(self.config):
print(task.Task.task_from_dir(task_dir, self.config))
def show(self, args):
meta = task.Task(args.task, self.config).metadata
print(yaml.dump(meta, default_flow_style=False))
def run(self, args):
task_agent = agent.TaskAgent(args.task, self.config)
task_agent.run()
status = task_agent.status()
print(status)
return task_agent.code()
def daemon(self, args):
task_agent = agent.TaskAgent(args.task, self.config)
task_agent.daemon()
def report(self, args):
task_agent = agent.TaskAgent(args.task, self.config)
print(task_agent.report())
def status(self, args):
task_agent = agent.TaskAgent(args.task, self.config)
exit_code = task_agent.code()
print(task_agent.status())
return exit_code
def conf(self, args):
print(self.config)
def main():
api = CmdApi()
exit_code = api.parse(sys.argv[1:])
exit(exit_code)

View File

@ -1,57 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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
import yaml
class Config(object):
def __init__(self, config_file=None):
self.curdir = os.getcwd()
self.config = self.default_config
if config_file:
self.update_from_file(config_file)
@property
def default_config(self):
return {
'library_dir': '/etc/puppet/tasks',
'puppet_modules': '/etc/puppet/modules',
'puppet_options': ('--logdest syslog '
'--logdest /var/log/puppet.log'
'--trace --no-report'),
'report_dir': '/var/tmp/task_report',
'pid_dir': '/var/tmp/task_run',
'puppet_manifest': 'site.pp',
'status_file': 'status',
'debug': True,
'task_file': 'task.yaml',
'log_file': '/var/log/tasklib.log'}
def update_from_file(self, config_file):
if os.path.exists(config_file):
with open(config_file) as f:
loaded = yaml.load(f.read())
self.config.update(loaded)
def __getitem__(self, key):
return self.config.get(key, None)
def __setitem__(self, key, value):
self.config[key] = value
def __repr__(self):
return yaml.dump(self.config, default_flow_style=False)

View File

@ -1,33 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 TasklibException(Exception):
msg = 'Tasklib generic error'
class NotFound(TasklibException):
msg = 'No task with provided name'
class NotValidMetadata(TasklibException):
msg = 'Missing critical items in metadata'
class Failed(TasklibException):
msg = 'Task failed'

View File

@ -1,37 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 logging
import sys
def setup_logging(config):
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter(
'%(asctime)s %(levelname)s %(process)d (%(module)s) %(message)s',
"%Y-%m-%d %H:%M:%S")
if sys.stdout.isatty():
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.DEBUG)
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
if config['log_file']:
file_handler = logging.FileHandler(config['log_file'])
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
return logger

View File

@ -1,79 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 logging
import os
import yaml
from tasklib.actions import exec_action
from tasklib.actions import puppet
from tasklib import exceptions
# use stevedore here
type_mapping = {'exec': exec_action.ExecAction,
'puppet': puppet.PuppetAction}
log = logging.getLogger(__name__)
class Task(object):
"""Unit of execution. Contains pre/post/run subtasks."""
def __init__(self, task_name, config):
self.config = config
self.name = task_name
self.dir = os.path.abspath(
os.path.join(config['library_dir'], self.name))
self.file = os.path.abspath(
os.path.join(self.dir, config['task_file']))
self.pid_dir = os.path.abspath(
os.path.join(self.config['pid_dir'], self.name))
self.report_dir = os.path.abspath(
os.path.join(self.config['report_dir'], self.name))
self.status_file = os.path.abspath(os.path.join(
self.report_dir, self.config['status_file']))
self._metadata = {}
log.debug('Init task %s with task file %s', self.name, self.file)
def verify(self):
if not os.path.exists(self.file):
raise exceptions.NotFound()
@property
def metadata(self):
if self._metadata:
return self._metadata
with open(self.file) as f:
self._metadata = yaml.load(f.read())
return self._metadata
@classmethod
def task_from_dir(cls, task_dir, config):
path = task_dir.replace(config['library_dir'], '').split('/')
task_name = '/'.join((p for p in path if p))
return cls(task_name, config)
def __repr__(self):
return "{0:10} | {1:15}".format(self.name, self.dir)
def run(self):
"""Will be used to run a task."""
self.verify()
action_class = type_mapping.get(self.metadata.get('type'))
if action_class is None:
raise exceptions.NotValidMetadata()
action = action_class(self, self.config)
action.verify()
return action.run()

View File

@ -1,13 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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.

View File

@ -1,52 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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
import time
import unittest
from tasklib import utils
from tasklib.utils import STATUS
class BaseUnitTest(unittest.TestCase):
"""Tasklib base unittest."""
class BaseFunctionalTest(BaseUnitTest):
def setUp(self):
self.dir_path = os.path.dirname(os.path.realpath(__file__))
self.conf_path = os.path.join(self.dir_path, 'functional', 'conf.yaml')
self.base_command = ['taskcmd', '-c', self.conf_path]
def check_puppet_installed(self):
exit_code, out, err = utils.execute('which puppet')
if exit_code == 1:
self.skipTest('Puppet is not installed')
def execute(self, add_command):
command = self.base_command + add_command
cmd = ' '.join(command)
return utils.execute(cmd)
def wait_ready(self, cmd, timeout):
started = time.time()
while time.time() - started < timeout:
exit_code, out, err = self.execute(cmd)
if out.strip('\n') != STATUS.running.name:
return exit_code, out, err
self.fail('Command {0} failed to finish with timeout {1}'.format(
cmd, timeout))

View File

@ -1,13 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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.

View File

@ -1,5 +0,0 @@
library_dir: tasklib/tests/functional
pid_dir: tmp/task_run
report_dir: tmp/task_report
debug: true
log_file: tasklib.log

View File

@ -1,2 +0,0 @@
type: exec
description: "Should fail on validation"

View File

@ -1,3 +0,0 @@
type: exec
cmd: 'echo 42 | grep 100'
description: "grep will return 1, if nothing will be found"

View File

@ -1,3 +0,0 @@
type: exec
cmd: sleep 1.5
description: "Will be used for testing stop action"

View File

@ -1,3 +0,0 @@
type: exec
cmd: echo 1
description: "Most primitive task available"

View File

@ -1,3 +0,0 @@
exec { "which which":
path => ["/usr/bin", "/usr/sbin"]
}

View File

@ -1 +0,0 @@
type: puppet

View File

@ -1,6 +0,0 @@
file {"tasklibtest":
path => "/tmp/tasklibtest",
ensure => present,
mode => 0640,
content => "I'm a file created created by tasklib test",
}

View File

@ -1,2 +0,0 @@
type: puppet
puppet_manifest: file.pp

View File

@ -1 +0,0 @@
file {"invalid":

View File

@ -1,2 +0,0 @@
type: puppet
description: "site.pp is default manifest provided by config"

View File

@ -1,3 +0,0 @@
exec { "echo 15":
path => ["/bin", "sbin"]
}

View File

@ -1,2 +0,0 @@
type: puppet
description: 'Just sleep fot 1 seconds'

View File

@ -1,26 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 tasklib.tests import base
from tasklib.utils import STATUS
class TestDaemon(base.BaseFunctionalTest):
def test_exec_long_task(self):
exit_code, out, err = self.execute(['daemon', 'exec/long'])
self.assertEqual(exit_code, 0)
exit_code, out, err = self.wait_ready(['status', 'exec/long'], 2)
self.assertEqual(exit_code, 0)
self.assertEqual(out.strip('\n'), STATUS.end.name)

View File

@ -1,51 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 tasklib.tests import base
from tasklib.utils import STATUS
class TestFunctionalExecTasks(base.BaseFunctionalTest):
"""Each test will follow next pattern:
1. Run test with provided name - taskcmd -c conf.yaml run test/test
2. check status of task
"""
def test_simple_run(self):
exit_code, out, err = self.execute(['run', 'exec/simple'])
self.assertEqual(exit_code, 0)
exit_code, out, err = self.execute(['status', 'exec/simple'])
self.assertEqual(out.strip('\n'), STATUS.end.name)
self.assertEqual(exit_code, 0)
def test_failed_run(self):
exit_code, out, err = self.execute(['run', 'exec/fail'])
self.assertEqual(exit_code, 2)
exit_code, out, err = self.execute(['status', 'exec/fail'])
self.assertEqual(out.strip('\n'), STATUS.failed.name)
self.assertEqual(exit_code, 2)
def test_error(self):
exit_code, out, err = self.execute(['run', 'exec/error'])
self.assertEqual(exit_code, 3)
exit_code, out, err = self.execute(['status', 'exec/error'])
self.assertEqual(out.strip('\n'), STATUS.error.name)
self.assertEqual(exit_code, 3)
def test_notfound(self):
exit_code, out, err = self.execute(['run', 'exec/notfound'])
self.assertEqual(exit_code, 4)
exit_code, out, err = self.execute(['status', 'exec/notfound'])
self.assertEqual(out.strip('\n'), STATUS.notfound.name)
self.assertEqual(exit_code, 4)

View File

@ -1,39 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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
from tasklib.tests import base
class TestFunctionalExecTasks(base.BaseFunctionalTest):
"""Each test will follow next pattern:
1. Run test with provided name - taskcmd -c conf.yaml run test/test
2. check status of task
"""
def test_puppet_file(self):
test_file = '/tmp/tasklibtest'
if os.path.exists(test_file):
os.unlink(test_file)
exit_code, out, err = self.execute(['run', 'puppet/file'])
self.assertEqual(exit_code, 0)
def test_puppet_invalid(self):
exit_code, out, err = self.execute(['run', 'puppet/invalid'])
self.assertEqual(exit_code, 2)
def test_puppet_cmd(self):
exit_code, out, err = self.execute(['run', 'puppet/cmd'])
self.assertEqual(exit_code, 0)

View File

@ -1,37 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 mock
from tasklib import cli
from tasklib.tests import base
class TestCmdApi(base.BaseUnitTest):
def setUp(self):
self.api = cli.CmdApi()
self.api.config['log_file'] = None
@mock.patch('tasklib.cli.task.Task.task_from_dir')
@mock.patch('tasklib.cli.utils.find_all_tasks')
def test_list(self, mfind, mtask):
tasks = ['/etc/library/test/deploy', '/etc/library/test/rollback']
mfind.return_value = tasks
self.api.parse(['list'])
mfind.assert_called_once_with(self.api.config)
expected_calls = []
for t in tasks:
expected_calls.append(mock.call(t, self.api.config))
self.assertEqual(expected_calls, mtask.call_args_list)

View File

@ -1,49 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 mock
import yaml
from tasklib import config
from tasklib.tests import base
@mock.patch('tasklib.task.os.path.exists')
class TestConfig(base.BaseUnitTest):
def test_default_config_when_no_file_exists(self, mexists):
mexists.return_value = False
conf = config.Config(config_file='/etc/tasklib/test.yaml')
self.assertEqual(conf.default_config, conf.config)
def test_default_when_no_file_provided(self, mexists):
conf = config.Config()
self.assertEqual(conf.default_config, conf.config)
def test_non_default_config_from_valid_yaml(self, mexists):
mexists.return_value = True
provided = {'library_dir': '/var/run/tasklib',
'puppet_manifest': 'init.pp'}
mopen = mock.mock_open(read_data=yaml.dump(provided))
with mock.patch('tasklib.config.open', mopen, create=True):
conf = config.Config(config_file='/etc/tasklib/test.yaml')
self.assertNotEqual(
conf.config['library_dir'], conf.default_config['library_dir'])
self.assertEqual(
conf.config['library_dir'], provided['library_dir'])
self.assertNotEqual(
conf.config['puppet_manifest'],
conf.default_config['puppet_manifest'])
self.assertEqual(
conf.config['puppet_manifest'], provided['puppet_manifest'])

View File

@ -1,41 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 mock
import yaml
from tasklib import config
from tasklib import task
from tasklib.tests import base
@mock.patch('tasklib.task.os.path.exists')
@mock.patch('tasklib.utils.execute')
class TestExecTask(base.BaseUnitTest):
def setUp(self):
self.meta = {'cmd': 'echo 1',
'type': 'exec'}
self.only_required = {'type': 'puppet'}
self.config = config.Config()
def test_base_cmd_task(self, mexecute, mexists):
mexists.return_value = True
mexecute.return_value = (0, '', '')
mopen = mock.mock_open(read_data=yaml.dump(self.meta))
puppet_task = task.Task('test/cmd', self.config)
with mock.patch('tasklib.task.open', mopen, create=True):
puppet_task.run()
expected_cmd = 'echo 1'
mexecute.assert_called_once_with(expected_cmd)

View File

@ -1,63 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 mock
import yaml
from tasklib import config
from tasklib import task
from tasklib.tests import base
@mock.patch('tasklib.task.os.path.exists')
@mock.patch('tasklib.utils.execute')
class TestPuppetTask(base.BaseUnitTest):
def setUp(self):
self.meta = {'puppet_manifest': 'init.pp',
'puppet_options': '--debug',
'puppet_modules': '/etc/my_puppet_modules',
'type': 'puppet'}
self.only_required = {'type': 'puppet'}
self.config = config.Config()
def test_basic_puppet_action(self, mexecute, mexists):
mexists.return_value = True
mexecute.return_value = (0, '', '')
mopen = mock.mock_open(read_data=yaml.dump(self.meta))
puppet_task = task.Task('test/puppet', self.config)
with mock.patch('tasklib.task.open', mopen, create=True):
puppet_task.run()
expected_cmd = ['puppet', 'apply', '--detailed-exitcodes',
'--modulepath=/etc/my_puppet_modules',
'--debug']
expected = ' '.join(expected_cmd)
self.assertEqual(mexecute.call_count, 1)
received = mexecute.call_args[0][0]
self.assertTrue(expected in received)
def test_default_puppet_action(self, mexecute, mexists):
mexists.return_value = True
mexecute.return_value = (0, '', '')
mopen = mock.mock_open(read_data=yaml.dump(self.only_required))
puppet_task = task.Task('test/puppet/only_required', self.config)
with mock.patch('tasklib.task.open', mopen, create=True):
puppet_task.run()
expected_cmd = [
'puppet', 'apply', '--detailed-exitcodes',
'--modulepath={0}'.format(self.config['puppet_modules'])]
expected = ' '.join(expected_cmd)
self.assertEqual(mexecute.call_count, 1)
received = mexecute.call_args[0][0]
self.assertTrue(expected in received)

View File

@ -1,57 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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
import mock
import yaml
from tasklib import config
from tasklib import exceptions
from tasklib import task
from tasklib.tests import base
@mock.patch('tasklib.task.os.path.exists')
class TestBaseTask(base.BaseUnitTest):
"""Basic task tests."""
def setUp(self):
self.conf = config.Config()
def test_create_task_from_path(self, mexists):
name = 'ceph/deploy'
task_dir = os.path.join(self.conf['library_dir'], name)
test_task = task.Task.task_from_dir(task_dir, self.conf)
self.assertEqual(test_task.name, name)
self.assertEqual(test_task.dir, task_dir)
def test_verify_raises_not_found(self, mexists):
mexists.return_value = False
test_task = task.Task('ceph/deploy', self.conf)
self.assertRaises(exceptions.NotFound, test_task.verify)
def test_verify_nothing_happens_if_file_exists(self, mexists):
mexists.return_value = True
test_task = task.Task('ceph/deploy', self.conf)
test_task.verify()
def test_read_metadata_from_valid_yaml(self, mexists):
mexists.return_value = True
meta = {'report_dir': '/tmp/report_dir',
'pid_dir': '/tmp/pid_dir'}
mopen = mock.mock_open(read_data=yaml.dump(meta))
test_task = task.Task('ceph/deploy', self.conf)
with mock.patch('tasklib.task.open', mopen, create=True):
self.assertEqual(meta, test_task.metadata)

View File

@ -1,51 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 collections import namedtuple
import fnmatch
import os
import subprocess
Status = namedtuple('Status', ['name', 'code'])
def key_value_enum(enums):
enums = dict([(k, Status(k, v)) for k, v in enums.iteritems()])
return type('Enum', (), enums)
STATUS = key_value_enum({'running': 1,
'end': 0,
'error': 3,
'notfound': 4,
'failed': 2})
def find_all_tasks(config):
for root, dirnames, filenames in os.walk(config['library_dir']):
for filename in fnmatch.filter(filenames, config['task_file']):
yield os.path.dirname(os.path.join(root, filename))
def ensure_dir_created(path):
if not os.path.exists(path):
os.makedirs(path)
def execute(cmd):
command = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
stdout, stderr = command.communicate()
return command.returncode, stdout, stderr

View File

@ -1,4 +0,0 @@
-r requirements.txt
mock==1.0.1
nose
tox

View File

@ -1,38 +0,0 @@
[tox]
minversion = 1.6
skipsdist = True
envlist = py26,py27,pep8
[testenv]
usedevelop = True
install_command = pip install {packages}
setenv = VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/test-requirements.txt
commands =
nosetests {posargs:tasklib}
[tox:jenkins]
downloadcache = ~/cache/pip
[testenv:pep8]
deps = hacking==0.7
usedevelop = False
commands =
flake8 {posargs:tasklib}
[testenv:venv]
commands = {posargs:}
[testenv:devenv]
envdir = devenv
usedevelop = True
[flake8]
ignore = H234,H302,H802
exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools,__init__.py,docs
show-pep8 = True
show-source = True
count = True
[hacking]
import_exceptions = testtools.matchers