Delete fuelclient from fuel-web repo
After python-fuelclient was moved to its own repository the fuelclient folder can be deleted from fuel-web. New python-fuelclient's repository can be found here: https://github.com/stackforge/python-fuelclient Blueprint: re-thinking-fuel-client Change-Id: Ic579bfdaacbafefc4050a6ec489888ced33d1b37
This commit is contained in:
parent
e75ac13a46
commit
df66a77faa
|
@ -1 +0,0 @@
|
|||
include fuelclient/fuelclient_settings.yaml
|
|
@ -1,19 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2013-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 fuelclient.cli.parser import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,22 +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.
|
||||
try:
|
||||
import pkg_resources
|
||||
try:
|
||||
__version__ = pkg_resources.get_distribution(
|
||||
"fuelclient").version
|
||||
except pkg_resources.DistributionNotFound:
|
||||
__version__ = ""
|
||||
except ImportError:
|
||||
__version__ = ""
|
|
@ -1,19 +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.
|
||||
|
||||
"""fuelclient.cli sub-module contains functionality of
|
||||
fuelclient command line interface
|
||||
|
||||
|
||||
"""
|
|
@ -1,60 +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.
|
||||
"""fuelclient.cli.actions sub-module contains files with action classes
|
||||
which implement command line interface logic
|
||||
|
||||
All action classes must be added to action_tuple to be used by parser
|
||||
"""
|
||||
from fuelclient.cli.actions.deploy import DeployChangesAction
|
||||
from fuelclient.cli.actions.environment import EnvironmentAction
|
||||
from fuelclient.cli.actions.fact import DeploymentAction
|
||||
from fuelclient.cli.actions.fact import ProvisioningAction
|
||||
from fuelclient.cli.actions.health import HealthCheckAction
|
||||
from fuelclient.cli.actions.interrupt import ResetAction
|
||||
from fuelclient.cli.actions.interrupt import StopAction
|
||||
from fuelclient.cli.actions.network import NetworkAction
|
||||
from fuelclient.cli.actions.node import NodeAction
|
||||
from fuelclient.cli.actions.nodegroup import NodeGroupAction
|
||||
from fuelclient.cli.actions.release import ReleaseAction
|
||||
from fuelclient.cli.actions.role import RoleAction
|
||||
from fuelclient.cli.actions.settings import SettingsAction
|
||||
from fuelclient.cli.actions.snapshot import SnapshotAction
|
||||
from fuelclient.cli.actions.task import TaskAction
|
||||
from fuelclient.cli.actions.user import UserAction
|
||||
from fuelclient.cli.actions.plugins import PluginAction
|
||||
|
||||
actions_tuple = (
|
||||
ReleaseAction,
|
||||
RoleAction,
|
||||
EnvironmentAction,
|
||||
DeployChangesAction,
|
||||
NodeAction,
|
||||
DeploymentAction,
|
||||
ProvisioningAction,
|
||||
StopAction,
|
||||
ResetAction,
|
||||
SettingsAction,
|
||||
NetworkAction,
|
||||
TaskAction,
|
||||
SnapshotAction,
|
||||
HealthCheckAction,
|
||||
UserAction,
|
||||
PluginAction,
|
||||
NodeGroupAction
|
||||
)
|
||||
|
||||
actions = dict(
|
||||
(action.action_name, action())
|
||||
for action in actions_tuple
|
||||
)
|
|
@ -1,129 +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 functools import partial
|
||||
from functools import wraps
|
||||
from itertools import imap
|
||||
import os
|
||||
|
||||
from fuelclient.cli.error import ArgumentException
|
||||
from fuelclient.cli.formatting import quote_and_join
|
||||
from fuelclient.cli.serializers import Serializer
|
||||
from fuelclient.client import APIClient
|
||||
|
||||
|
||||
class Action(object):
|
||||
"""Action class generalizes logic of action execution
|
||||
method action_func - entry point of parser with parsed arguments
|
||||
|
||||
flag_func_map - is tuple of pairs ("flag", self.some_method) where
|
||||
"flag" is name of argument which causes "some_method" to be called.
|
||||
None is used as "flag" when method will be called without any flag.
|
||||
|
||||
serializer - is Serializer class instance which supposed to be the
|
||||
only way to read and write to output or file system.
|
||||
|
||||
args - tuple of function calls of functions from arguments module,
|
||||
is a manifest of all arguments used in action, and is used to initialize
|
||||
argparse subparser of that action.
|
||||
"""
|
||||
def __init__(self):
|
||||
# Mapping of flags to methods
|
||||
self.flag_func_map = None
|
||||
self.serializer = Serializer()
|
||||
|
||||
def action_func(self, params):
|
||||
"""Entry point for all actions subclasses
|
||||
"""
|
||||
APIClient.debug_mode(debug=params.debug)
|
||||
if getattr(params, 'user') and getattr(params, 'password'):
|
||||
APIClient.user = params.user
|
||||
APIClient.password = params.password
|
||||
APIClient.initialize_keystone_client()
|
||||
|
||||
self.serializer = Serializer.from_params(params)
|
||||
if self.flag_func_map is not None:
|
||||
for flag, method in self.flag_func_map:
|
||||
if flag is None or getattr(params, flag):
|
||||
method(params)
|
||||
break
|
||||
|
||||
@property
|
||||
def examples(self):
|
||||
"""examples property is concatenation of __doc__ strings from
|
||||
methods in child action classes, and is added as epilog of help
|
||||
output
|
||||
"""
|
||||
methods_with_docs = set(
|
||||
method
|
||||
for _, method in self.flag_func_map
|
||||
)
|
||||
return "Examples:\n\n" + \
|
||||
"\n".join(
|
||||
imap(
|
||||
lambda method: (
|
||||
"\t" + method.__doc__.replace("\n ", "\n")
|
||||
),
|
||||
methods_with_docs
|
||||
)
|
||||
).format(
|
||||
action_name=self.action_name
|
||||
)
|
||||
|
||||
def full_path_directory(self, directory, base_name):
|
||||
full_path = os.path.join(directory, base_name)
|
||||
if not os.path.exists(full_path):
|
||||
os.mkdir(full_path)
|
||||
return full_path
|
||||
|
||||
def default_directory(self, directory=None):
|
||||
return os.path.abspath(os.curdir if directory is None else directory)
|
||||
|
||||
|
||||
def wrap(method, args, f):
|
||||
"""wrap - is second order function, purpose of which is to
|
||||
generalize argument checking for methods in actions in form
|
||||
of decorator with arguments.
|
||||
|
||||
'check_all' and 'check_any' are partial function of wrap.
|
||||
"""
|
||||
@wraps(f)
|
||||
def wrapped_f(self, params):
|
||||
if method(getattr(params, _arg) for _arg in args):
|
||||
return f(self, params)
|
||||
else:
|
||||
raise ArgumentException(
|
||||
"{0} required!".format(
|
||||
quote_and_join(
|
||||
"--" + arg for arg in args
|
||||
)
|
||||
)
|
||||
)
|
||||
return wrapped_f
|
||||
|
||||
|
||||
def check_all(*args):
|
||||
"""check_all - decorator with arguments, which checks that
|
||||
all arguments are given before running action method, if
|
||||
not all arguments are given, it raises an ArgumentException.
|
||||
"""
|
||||
return partial(wrap, all, args)
|
||||
|
||||
|
||||
def check_any(*args):
|
||||
"""check_any - decorator with arguments, which checks that
|
||||
at least one arguments is given before running action method,
|
||||
if no arguments were given, it raises an ArgumentException.
|
||||
"""
|
||||
return partial(wrap, any, args)
|
|
@ -1,46 +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 fuelclient.cli.actions.base import Action
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.formatting import print_deploy_progress
|
||||
|
||||
|
||||
class DeployChangesAction(Action):
|
||||
"""Deploy changes to environments
|
||||
"""
|
||||
action_name = "deploy-changes"
|
||||
|
||||
def __init__(self):
|
||||
super(DeployChangesAction, self).__init__()
|
||||
self.args = (
|
||||
Args.get_env_arg(required=True),
|
||||
)
|
||||
|
||||
self.flag_func_map = (
|
||||
(None, self.deploy_changes),
|
||||
)
|
||||
|
||||
def deploy_changes(self, params):
|
||||
"""To deploy all applied changes to some environment:
|
||||
fuel --env 1 deploy-changes
|
||||
"""
|
||||
from fuelclient.objects.environment import Environment
|
||||
env = Environment(params.env)
|
||||
deploy_task = env.deploy_changes()
|
||||
self.serializer.print_to_output(
|
||||
deploy_task.data,
|
||||
deploy_task,
|
||||
print_method=print_deploy_progress)
|
|
@ -1,217 +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 fuelclient.cli.actions.base import Action
|
||||
from fuelclient.cli.actions.base import check_all
|
||||
from fuelclient.cli.actions.base import check_any
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.arguments import group
|
||||
from fuelclient.cli.formatting import format_table
|
||||
from fuelclient.objects.environment import Environment
|
||||
|
||||
|
||||
class EnvironmentAction(Action):
|
||||
"""Create, list and modify currently existing environments(clusters)
|
||||
"""
|
||||
action_name = "environment"
|
||||
|
||||
def __init__(self):
|
||||
super(EnvironmentAction, self).__init__()
|
||||
self.args = [
|
||||
Args.get_env_arg(),
|
||||
group(
|
||||
Args.get_list_arg(
|
||||
"List all available environments."
|
||||
),
|
||||
Args.get_set_arg(
|
||||
"Set environment parameters (e.g name, deployment mode)"
|
||||
),
|
||||
Args.get_delete_arg(
|
||||
"Delete environment with specific env or name"
|
||||
),
|
||||
Args.get_create_arg(
|
||||
"Create a new environment with "
|
||||
"specific release id and name."
|
||||
),
|
||||
Args.get_update_arg(
|
||||
"Update OS to specified release id for given env."
|
||||
)
|
||||
),
|
||||
Args.get_release_arg(
|
||||
"Release id"
|
||||
),
|
||||
Args.get_name_arg(
|
||||
"environment name"
|
||||
),
|
||||
Args.get_mode_arg(
|
||||
"Set deployment mode for specific environment."
|
||||
),
|
||||
Args.get_net_arg(
|
||||
"Set network mode for specific environment."
|
||||
),
|
||||
Args.get_nst_arg(
|
||||
"Set network segment type"
|
||||
),
|
||||
Args.get_deployment_tasks_arg("Environment tasks configuration."),
|
||||
group(
|
||||
Args.get_download_arg(
|
||||
"Download configuration of specific cluster"),
|
||||
Args.get_upload_arg(
|
||||
"Upload configuration to specific cluster")
|
||||
),
|
||||
Args.get_dir_arg(
|
||||
"Select directory to which download release attributes"),
|
||||
]
|
||||
self.flag_func_map = (
|
||||
("deployment-tasks", self.deployment_tasks),
|
||||
("create", self.create),
|
||||
("set", self.set),
|
||||
("delete", self.delete),
|
||||
("update", self.update),
|
||||
(None, self.list)
|
||||
)
|
||||
|
||||
@check_all("name", "release")
|
||||
def create(self, params):
|
||||
"""To create an environment with name MyEnv and release id=1 run:
|
||||
fuel env create --name MyEnv --rel 1
|
||||
|
||||
By default it creates environment in multinode mode, and nova
|
||||
network mode, to specify other modes add optional arguments:
|
||||
fuel env create --name MyEnv --rel 1 \\
|
||||
--mode ha --network-mode neutron
|
||||
"""
|
||||
env = Environment.create(
|
||||
params.name,
|
||||
params.release,
|
||||
params.net,
|
||||
net_segment_type=params.nst
|
||||
)
|
||||
|
||||
if params.mode:
|
||||
data = env.set({'mode': params.mode})
|
||||
else:
|
||||
data = env.get_fresh_data()
|
||||
|
||||
self.serializer.print_to_output(
|
||||
data,
|
||||
u"Environment '{name}' with id={id}, mode={mode}"
|
||||
u" and network-mode={net_provider} was created!"
|
||||
.format(**data)
|
||||
)
|
||||
|
||||
@check_all("env")
|
||||
def set(self, params):
|
||||
"""For changing environments name, mode
|
||||
or network mode exists set action:
|
||||
fuel --env 1 env set --name NewEmvName --mode ha_compact
|
||||
"""
|
||||
acceptable_params = ('mode', 'name', 'pending_release_id')
|
||||
|
||||
env = Environment(params.env, params=params)
|
||||
|
||||
# forming message for output and data structure for request body
|
||||
# TODO(aroma): make it less ugly
|
||||
msg_template = ("Following attributes are changed for "
|
||||
"the environment: {env_attributes}")
|
||||
|
||||
env_attributes = []
|
||||
update_kwargs = dict()
|
||||
for param_name in acceptable_params:
|
||||
attr_value = getattr(params, param_name, None)
|
||||
if attr_value:
|
||||
update_kwargs[param_name] = attr_value
|
||||
env_attributes.append(
|
||||
''.join([param_name, '=', str(attr_value)])
|
||||
)
|
||||
|
||||
data = env.set(update_kwargs)
|
||||
env_attributes = ', '.join(env_attributes)
|
||||
self.serializer.print_to_output(
|
||||
data,
|
||||
msg_template.format(env_attributes=env_attributes)
|
||||
)
|
||||
|
||||
@check_all("env")
|
||||
def delete(self, params):
|
||||
"""To delete the environment:
|
||||
fuel --env 1 env delete
|
||||
"""
|
||||
env = Environment(params.env, params=params)
|
||||
data = env.delete()
|
||||
self.serializer.print_to_output(
|
||||
data,
|
||||
"Environment with id={0} was deleted."
|
||||
.format(env.id)
|
||||
)
|
||||
|
||||
def list(self, params):
|
||||
"""Print all available environments:
|
||||
fuel env
|
||||
"""
|
||||
acceptable_keys = ("id", "status", "name", "mode",
|
||||
"release_id", "changes", "pending_release_id")
|
||||
data = Environment.get_all_data()
|
||||
if params.env:
|
||||
data = filter(
|
||||
lambda x: x[u"id"] == int(params.env),
|
||||
data
|
||||
)
|
||||
self.serializer.print_to_output(
|
||||
data,
|
||||
format_table(
|
||||
data,
|
||||
acceptable_keys=acceptable_keys
|
||||
)
|
||||
)
|
||||
|
||||
def update(self, params):
|
||||
"""Update environment to given OS release
|
||||
fuel env --env 1 --update --release 1
|
||||
"""
|
||||
params.pending_release_id = params.release
|
||||
self.set(params)
|
||||
|
||||
env = Environment(params.env, params=params)
|
||||
update_task = env.update_env()
|
||||
|
||||
msg = ("Update process for environment has been started. "
|
||||
"Update task id is {0}".format(update_task.id))
|
||||
|
||||
self.serializer.print_to_output(
|
||||
{},
|
||||
msg
|
||||
)
|
||||
|
||||
@check_all("env")
|
||||
@check_any("download", "upload")
|
||||
def deployment_tasks(self, params):
|
||||
"""Modify deployment_tasks for environment.
|
||||
fuel env --env 1 --deployment-tasks --download
|
||||
fuel env --env 1 --deployment-tasks --upload
|
||||
"""
|
||||
cluster = Environment(params.env)
|
||||
dir_path = self.full_path_directory(
|
||||
params.dir, 'cluster_{0}'.format(params.env))
|
||||
full_path = '{0}/deployment_tasks'.format(dir_path)
|
||||
if params.download:
|
||||
tasks = cluster.get_deployment_tasks()
|
||||
self.serializer.write_to_file(full_path, tasks)
|
||||
print("Deployment tasks for cluster {0} "
|
||||
"downloaded into {1}.yaml.".format(cluster.id, full_path))
|
||||
elif params.upload:
|
||||
tasks = self.serializer.read_from_file(full_path)
|
||||
cluster.update_deployment_tasks(tasks)
|
||||
print("Deployment tasks for release {0} "
|
||||
" uploaded from {1}.yaml".format(cluster.id, full_path))
|
|
@ -1,131 +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 fuelclient.cli.actions.base import Action
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.arguments import group
|
||||
from fuelclient.objects.environment import Environment
|
||||
|
||||
|
||||
class FactAction(Action):
|
||||
|
||||
action_name = None
|
||||
|
||||
def __init__(self):
|
||||
super(FactAction, self).__init__()
|
||||
self.args = [
|
||||
Args.get_env_arg(required=True),
|
||||
group(
|
||||
Args.get_delete_arg(
|
||||
"Delete current {0} data.".format(self.action_name)
|
||||
),
|
||||
Args.get_download_arg(
|
||||
"Download current {0} data.".format(self.action_name)
|
||||
),
|
||||
Args.get_upload_arg(
|
||||
"Upload current {0} data.".format(self.action_name)
|
||||
),
|
||||
Args.get_default_arg(
|
||||
"Download default {0} data.".format(self.action_name)
|
||||
),
|
||||
required=True
|
||||
),
|
||||
Args.get_dir_arg(
|
||||
"Directory with {0} data.".format(self.action_name)
|
||||
),
|
||||
Args.get_node_arg(
|
||||
"Node ids."
|
||||
),
|
||||
]
|
||||
self.flag_func_map = (
|
||||
("default", self.default),
|
||||
("upload", self.upload),
|
||||
("delete", self.delete),
|
||||
("download", self.download)
|
||||
)
|
||||
|
||||
def default(self, params):
|
||||
"""To get default {action_name} information for some environment:
|
||||
fuel --env 1 {action_name} --default
|
||||
|
||||
It's possible to get default {action_name} information
|
||||
just for some nodes:
|
||||
fuel --env 1 {action_name} --default --node 1,2,3
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
dir_name = env.write_facts_to_dir(
|
||||
self.action_name,
|
||||
env.get_default_facts(self.action_name, nodes=params.node),
|
||||
directory=params.dir,
|
||||
serializer=self.serializer
|
||||
)
|
||||
print(
|
||||
"Default {0} info was downloaded to {1}".format(
|
||||
self.action_name,
|
||||
dir_name
|
||||
)
|
||||
)
|
||||
|
||||
def upload(self, params):
|
||||
"""To upload {action_name} information for some environment:
|
||||
fuel --env 1 {action_name} --upload
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
facts = env.read_fact_info(
|
||||
self.action_name,
|
||||
directory=params.dir,
|
||||
serializer=self.serializer
|
||||
)
|
||||
env.upload_facts(self.action_name, facts)
|
||||
print("{0} facts were uploaded.".format(self.action_name))
|
||||
|
||||
def delete(self, params):
|
||||
"""Also {action_name} information can be left or
|
||||
taken from specific directory:
|
||||
fuel --env 1 {action_name} --upload \\
|
||||
--dir path/to/some/directory
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
env.delete_facts(self.action_name)
|
||||
print("{0} facts deleted.".format(self.action_name))
|
||||
|
||||
def download(self, params):
|
||||
"""To download {action_name} information for some environment:
|
||||
fuel --env 1 {action_name} --download
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
dir_name = env.write_facts_to_dir(
|
||||
self.action_name,
|
||||
env.get_facts(self.action_name, nodes=params.node),
|
||||
directory=params.dir,
|
||||
serializer=self.serializer
|
||||
)
|
||||
print(
|
||||
"Current {0} info was downloaded to {1}".format(
|
||||
self.action_name,
|
||||
dir_name
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class DeploymentAction(FactAction):
|
||||
"""Show computed deployment facts for orchestrator
|
||||
"""
|
||||
action_name = "deployment"
|
||||
|
||||
|
||||
class ProvisioningAction(FactAction):
|
||||
"""Show computed provisioning facts for orchestrator
|
||||
"""
|
||||
action_name = "provisioning"
|
|
@ -1,90 +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 fuelclient.cli.actions.base import Action
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.error import exit_with_error
|
||||
from fuelclient.cli.formatting import format_table
|
||||
from fuelclient.cli.formatting import print_health_check
|
||||
from fuelclient.objects.environment import Environment
|
||||
|
||||
|
||||
class HealthCheckAction(Action):
|
||||
"""Run health check on environment
|
||||
"""
|
||||
action_name = "health"
|
||||
|
||||
_allowed_statuses = (
|
||||
'error',
|
||||
'operational',
|
||||
'update_error',
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super(HealthCheckAction, self).__init__()
|
||||
self.args = (
|
||||
Args.get_env_arg(required=True),
|
||||
Args.get_list_arg("List all available checks"),
|
||||
Args.get_force_arg("Forced test run"),
|
||||
Args.get_check_arg("Run check for some testset.")
|
||||
)
|
||||
|
||||
self.flag_func_map = (
|
||||
("check", self.check),
|
||||
(None, self.list)
|
||||
)
|
||||
|
||||
def check(self, params):
|
||||
"""To run some health checks:
|
||||
fuel --env 1 health --check smoke,sanity
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
|
||||
if env.status not in self._allowed_statuses and not params.force:
|
||||
exit_with_error(
|
||||
"Environment is not ready to run health check "
|
||||
"because it is in {0} state. "
|
||||
"Health check is likely to fail because of "
|
||||
"this. Use --force flag to proceed anyway.". format(env.status)
|
||||
)
|
||||
|
||||
if env.is_customized and not params.force:
|
||||
exit_with_error(
|
||||
"Environment deployment facts were updated. "
|
||||
"Health check is likely to fail because of "
|
||||
"that. Use --force flag to proceed anyway."
|
||||
)
|
||||
test_sets_to_check = params.check or set(
|
||||
ts["id"] for ts in env.get_testsets())
|
||||
env.run_test_sets(test_sets_to_check)
|
||||
tests_state = env.get_state_of_tests()
|
||||
self.serializer.print_to_output(
|
||||
tests_state,
|
||||
env,
|
||||
print_method=print_health_check
|
||||
)
|
||||
|
||||
def list(self, params):
|
||||
"""To list all health check test sets:
|
||||
fuel health
|
||||
or:
|
||||
fuel --env 1 health --list
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
test_sets = env.get_testsets()
|
||||
self.serializer.print_to_output(
|
||||
test_sets,
|
||||
format_table(test_sets)
|
||||
)
|
|
@ -1,58 +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 fuelclient.cli.actions.base import Action
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.objects.environment import Environment
|
||||
|
||||
|
||||
class InterruptAction(Action):
|
||||
|
||||
def __init__(self):
|
||||
super(InterruptAction, self).__init__()
|
||||
self.args = [
|
||||
Args.get_env_arg(required=True)
|
||||
]
|
||||
self.flag_func_map = (
|
||||
(None, self.interrupt),
|
||||
)
|
||||
|
||||
def interrupt(self, params):
|
||||
"""To {action_name} some environment:
|
||||
fuel --env 1 {action_name}
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
intercept_task = getattr(env, self.action_name)()
|
||||
self.serializer.print_to_output(
|
||||
intercept_task.data,
|
||||
"{0} task of environment with id={1} started. "
|
||||
"To check task status run 'fuel task -t {2}'.".format(
|
||||
self.action_name.title(),
|
||||
params.env,
|
||||
intercept_task.data["id"]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class StopAction(InterruptAction):
|
||||
"""Stop deployment process for specific environment
|
||||
"""
|
||||
action_name = "stop"
|
||||
|
||||
|
||||
class ResetAction(InterruptAction):
|
||||
"""Reset deployed process for specific environment
|
||||
"""
|
||||
action_name = "reset"
|
|
@ -1,89 +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 fuelclient.cli.actions.base import Action
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.arguments import group
|
||||
from fuelclient.objects.environment import Environment
|
||||
|
||||
|
||||
class NetworkAction(Action):
|
||||
"""Show or modify network settings of specific environments
|
||||
"""
|
||||
action_name = "network"
|
||||
|
||||
def __init__(self):
|
||||
super(NetworkAction, self).__init__()
|
||||
self.args = (
|
||||
Args.get_env_arg(required=True),
|
||||
Args.get_dir_arg("Directory with network data."),
|
||||
group(
|
||||
Args.get_download_arg(
|
||||
"Download current network configuration."),
|
||||
Args.get_verify_arg(
|
||||
"Verify current network configuration."),
|
||||
Args.get_upload_arg(
|
||||
"Upload changed network configuration."),
|
||||
required=True
|
||||
)
|
||||
)
|
||||
self.flag_func_map = (
|
||||
("upload", self.upload),
|
||||
("verify", self.verify),
|
||||
("download", self.download)
|
||||
)
|
||||
|
||||
def upload(self, params):
|
||||
"""To upload network configuration from some
|
||||
directory for some environment:
|
||||
fuel --env 1 network --upload --dir path/to/directory
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
network_data = env.read_network_data(
|
||||
directory=params.dir,
|
||||
serializer=self.serializer
|
||||
)
|
||||
env.set_network_data(network_data)
|
||||
print(
|
||||
"Network configuration uploaded."
|
||||
)
|
||||
|
||||
def verify(self, params):
|
||||
"""To verify network configuration from some directory
|
||||
for some environment:
|
||||
fuel --env 1 network --verify --dir path/to/directory
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
response = env.verify_network()
|
||||
print(
|
||||
"Verification status is '{status}'. message: {message}"
|
||||
.format(**response)
|
||||
)
|
||||
|
||||
def download(self, params):
|
||||
"""To download network configuration in this
|
||||
directory for some environment:
|
||||
fuel --env 1 network --download
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
network_data = env.get_network_data()
|
||||
network_file_path = env.write_network_data(
|
||||
network_data,
|
||||
directory=params.dir,
|
||||
serializer=self.serializer)
|
||||
print(
|
||||
"Network configuration for environment with id={0}"
|
||||
" downloaded to {1}"
|
||||
.format(env.id, network_file_path)
|
||||
)
|
|
@ -1,266 +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 itertools import groupby
|
||||
from operator import attrgetter
|
||||
|
||||
from fuelclient.cli.actions.base import Action
|
||||
from fuelclient.cli.actions.base import check_all
|
||||
from fuelclient.cli.actions.base import check_any
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.arguments import group
|
||||
from fuelclient.cli.error import ActionException
|
||||
from fuelclient.cli.error import ArgumentException
|
||||
from fuelclient.cli.formatting import format_table
|
||||
from fuelclient.objects.environment import Environment
|
||||
from fuelclient.objects.node import Node
|
||||
from fuelclient.objects.node import NodeCollection
|
||||
|
||||
|
||||
class NodeAction(Action):
|
||||
"""List and assign available nodes to environments
|
||||
"""
|
||||
action_name = "node"
|
||||
acceptable_keys = ("id", "status", "name", "cluster", "ip",
|
||||
"mac", "roles", "pending_roles", "online", "group_id")
|
||||
|
||||
def __init__(self):
|
||||
super(NodeAction, self).__init__()
|
||||
self.args = [
|
||||
Args.get_env_arg(),
|
||||
group(
|
||||
Args.get_list_arg("List all nodes."),
|
||||
Args.get_set_arg("Set role for specific node."),
|
||||
Args.get_delete_arg("Delete specific node from environment."),
|
||||
Args.get_network_arg("Node network configuration."),
|
||||
Args.get_disk_arg("Node disk configuration."),
|
||||
Args.get_deploy_arg("Deploy specific nodes."),
|
||||
Args.get_delete_from_db_arg(
|
||||
"Delete specific nodes only from fuel db.\n"
|
||||
"User should still delete node from cobbler"),
|
||||
Args.get_provision_arg("Provision specific nodes."),
|
||||
),
|
||||
group(
|
||||
Args.get_default_arg(
|
||||
"Get default configuration of some node"),
|
||||
Args.get_download_arg(
|
||||
"Download configuration of specific node"),
|
||||
Args.get_upload_arg(
|
||||
"Upload configuration to specific node")
|
||||
),
|
||||
Args.get_dir_arg(
|
||||
"Select directory to which download node attributes"),
|
||||
Args.get_node_arg("Node id."),
|
||||
Args.get_force_arg("Bypassing parameter validation."),
|
||||
Args.get_all_arg("Select all nodes."),
|
||||
Args.get_role_arg("Role to assign for node.")
|
||||
]
|
||||
|
||||
self.flag_func_map = (
|
||||
("set", self.set),
|
||||
("delete", self.delete),
|
||||
("network", self.attributes),
|
||||
("disk", self.attributes),
|
||||
("deploy", self.start),
|
||||
("provision", self.start),
|
||||
("delete-from-db", self.delete_from_db),
|
||||
(None, self.list)
|
||||
)
|
||||
|
||||
@check_all("node", "role", "env")
|
||||
def set(self, params):
|
||||
"""Assign some nodes to environment with with specific roles:
|
||||
fuel --env 1 node set --node 1 --role controller
|
||||
fuel --env 1 node set --node 2,3,4 --role compute,cinder
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
nodes = Node.get_by_ids(params.node)
|
||||
roles = map(str.lower, params.role)
|
||||
env.assign(nodes, roles)
|
||||
self.serializer.print_to_output(
|
||||
{},
|
||||
"Nodes {0} with roles {1} "
|
||||
"were added to environment {2}"
|
||||
.format(params.node, roles, params.env)
|
||||
)
|
||||
|
||||
@check_any("node", "env")
|
||||
def delete(self, params):
|
||||
"""Remove some nodes from environment:
|
||||
fuel --env 1 node remove --node 2,3
|
||||
|
||||
Remove nodes no matter to which environment they were assigned:
|
||||
fuel node remove --node 2,3,6,7
|
||||
|
||||
Remove all nodes from some environment:
|
||||
fuel --env 1 node remove --all
|
||||
"""
|
||||
if params.env:
|
||||
env = Environment(params.env)
|
||||
if params.node:
|
||||
env.unassign(params.node)
|
||||
self.serializer.print_to_output(
|
||||
{},
|
||||
"Nodes with ids {0} were removed "
|
||||
"from environment with id {1}."
|
||||
.format(params.node, params.env))
|
||||
else:
|
||||
if params.all:
|
||||
env.unassign_all()
|
||||
else:
|
||||
raise ArgumentException(
|
||||
"You have to select which nodes to remove "
|
||||
"with --node-id. Try --all for removing all nodes."
|
||||
)
|
||||
self.serializer.print_to_output(
|
||||
{},
|
||||
"All nodes from environment with id {0} were removed."
|
||||
.format(params.env))
|
||||
else:
|
||||
nodes = map(Node, params.node)
|
||||
for env_id, _nodes in groupby(nodes, attrgetter("env_id")):
|
||||
list_of_nodes = [n.id for n in _nodes]
|
||||
if env_id:
|
||||
Environment(env_id).unassign(list_of_nodes)
|
||||
self.serializer.print_to_output(
|
||||
{},
|
||||
"Nodes with ids {0} were removed "
|
||||
"from environment with id {1}."
|
||||
.format(list_of_nodes, env_id)
|
||||
)
|
||||
else:
|
||||
self.serializer.print_to_output(
|
||||
{},
|
||||
"Nodes with ids {0} aren't added to "
|
||||
"any environment.".format(list_of_nodes)
|
||||
)
|
||||
|
||||
@check_all("node")
|
||||
@check_any("default", "download", "upload")
|
||||
def attributes(self, params):
|
||||
"""Download current or default disk, network,
|
||||
configuration for some node:
|
||||
fuel node --node-id 2 --disk --default
|
||||
fuel node --node-id 2 --network --download \\
|
||||
--dir path/to/directory
|
||||
|
||||
Upload disk, network, configuration for some node:
|
||||
fuel node --node-id 2 --network --upload
|
||||
fuel node --node-id 2 --disk --upload --dir path/to/directory
|
||||
"""
|
||||
nodes = Node.get_by_ids(params.node)
|
||||
attribute_type = "interfaces" if params.network else "disks"
|
||||
attributes = []
|
||||
files = []
|
||||
if params.default:
|
||||
for node in nodes:
|
||||
default_attribute = node.get_default_attribute(attribute_type)
|
||||
file_path = node.write_attribute(
|
||||
attribute_type,
|
||||
default_attribute,
|
||||
params.dir,
|
||||
serializer=self.serializer
|
||||
)
|
||||
files.append(file_path)
|
||||
attributes.append(default_attribute)
|
||||
message = "Default node attributes for {0} were written" \
|
||||
" to:\n{1}".format(attribute_type, "\n".join(files))
|
||||
elif params.upload:
|
||||
for node in nodes:
|
||||
attribute = node.read_attribute(
|
||||
attribute_type,
|
||||
params.dir,
|
||||
serializer=self.serializer
|
||||
)
|
||||
node.upload_node_attribute(
|
||||
attribute_type,
|
||||
attribute
|
||||
)
|
||||
attributes.append(attribute)
|
||||
message = "Node attributes for {0} were uploaded" \
|
||||
" from {1}".format(attribute_type, params.dir)
|
||||
else:
|
||||
for node in nodes:
|
||||
downloaded_attribute = node.get_attribute(attribute_type)
|
||||
file_path = node.write_attribute(
|
||||
attribute_type,
|
||||
downloaded_attribute,
|
||||
params.dir,
|
||||
serializer=self.serializer
|
||||
)
|
||||
attributes.append(downloaded_attribute)
|
||||
files.append(file_path)
|
||||
message = "Node attributes for {0} were written" \
|
||||
" to:\n{1}".format(attribute_type, "\n".join(files))
|
||||
print(message)
|
||||
|
||||
@check_all("env", "node")
|
||||
def start(self, params):
|
||||
"""Deploy/Provision some node:
|
||||
fuel node --node-id 2 --provision
|
||||
fuel node --node-id 2 --deploy
|
||||
"""
|
||||
node_collection = NodeCollection.init_with_ids(params.node)
|
||||
method_type = "deploy" if params.deploy else "provision"
|
||||
env_ids = set(n.env_id for n in node_collection)
|
||||
if len(env_ids) != 1:
|
||||
raise ActionException(
|
||||
"Inputed nodes assigned to multiple environments!")
|
||||
else:
|
||||
env_id_to_start = env_ids.pop()
|
||||
task = Environment(env_id_to_start).install_selected_nodes(
|
||||
method_type, node_collection.collection)
|
||||
self.serializer.print_to_output(
|
||||
task.data,
|
||||
"Started {0}ing {1}."
|
||||
.format(method_type, node_collection))
|
||||
|
||||
def list(self, params):
|
||||
"""To list all available nodes:
|
||||
fuel node
|
||||
|
||||
To filter them by environment:
|
||||
fuel --env-id 1 node
|
||||
|
||||
It's Possible to manipulate nodes with their short mac addresses:
|
||||
fuel node --node-id 80:ac
|
||||
fuel node remove --node-id 80:ac,5d:a2
|
||||
"""
|
||||
if params.node:
|
||||
node_collection = NodeCollection.init_with_ids(params.node)
|
||||
else:
|
||||
node_collection = NodeCollection.get_all()
|
||||
if params.env:
|
||||
node_collection.filter_by_env_id(int(params.env))
|
||||
self.serializer.print_to_output(
|
||||
node_collection.data,
|
||||
format_table(
|
||||
node_collection.data,
|
||||
acceptable_keys=self.acceptable_keys,
|
||||
column_to_join=("roles", "pending_roles")
|
||||
)
|
||||
)
|
||||
|
||||
@check_all("node")
|
||||
def delete_from_db(self, params):
|
||||
"""To delete nodes from fuel db:
|
||||
fuel node --node-id 1 --delete-from-db
|
||||
"""
|
||||
nodes = Node.get_by_ids(params.node)
|
||||
for node in nodes:
|
||||
node.delete()
|
||||
self.serializer.print_to_output(
|
||||
{},
|
||||
"Nodes with id {0} has been deleted from fuel db.\n"
|
||||
"You should still delete node from cobbler".format(params.node))
|
|
@ -1,106 +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 fuelclient.cli.actions.base import Action
|
||||
from fuelclient.cli.actions.base import check_all
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.arguments import group
|
||||
from fuelclient.cli.error import ActionException
|
||||
from fuelclient.cli.formatting import format_table
|
||||
from fuelclient.objects.node import Node
|
||||
from fuelclient.objects.nodegroup import NodeGroup
|
||||
from fuelclient.objects.nodegroup import NodeGroupCollection
|
||||
|
||||
|
||||
class NodeGroupAction(Action):
|
||||
"""Show or modify node groups
|
||||
"""
|
||||
action_name = "nodegroup"
|
||||
acceptable_keys = ("id", "cluster", "name")
|
||||
|
||||
def __init__(self):
|
||||
super(NodeGroupAction, self).__init__()
|
||||
self.args = (
|
||||
Args.get_env_arg(),
|
||||
Args.get_list_arg("List all node groups."),
|
||||
Args.get_name_arg("Name of new node group."),
|
||||
Args.get_group_arg("ID of node group."),
|
||||
Args.get_node_arg("List of nodes to assign specified group to."),
|
||||
group(
|
||||
Args.get_create_arg(
|
||||
"Create a new node group in the specified environment."
|
||||
),
|
||||
Args.get_assign_arg(
|
||||
"Download current network configuration."),
|
||||
Args.get_delete_arg(
|
||||
"Verify current network configuration."),
|
||||
)
|
||||
)
|
||||
self.flag_func_map = (
|
||||
("create", self.create),
|
||||
("delete", self.delete),
|
||||
("assign", self.assign),
|
||||
(None, self.list)
|
||||
)
|
||||
|
||||
def create(self, params):
|
||||
"""Create a new node group
|
||||
fuel --env 1 nodegroup --create --name "group 1"
|
||||
"""
|
||||
NodeGroup.create(params.name, int(params.env))
|
||||
|
||||
def delete(self, params):
|
||||
"""Delete the specified node groups
|
||||
fuel --env 1 nodegroup --delete --group 1
|
||||
fuel --env 1 nodegroup --delete --group 2,3,4
|
||||
"""
|
||||
ngs = NodeGroup.get_by_ids(params.group)
|
||||
for n in ngs:
|
||||
if n.name == "default":
|
||||
raise ActionException(
|
||||
"Default node groups cannot be deleted."
|
||||
)
|
||||
NodeGroup.delete(n.id)
|
||||
|
||||
@check_all("env")
|
||||
def assign(self, params):
|
||||
"""Assign nodes to specified node group:
|
||||
fuel --env 1 nodegroup --assign --node 1 --group 1
|
||||
fuel --env 1 nodegroup --assign --node 2,3,4 --group 1
|
||||
"""
|
||||
nodes = [n.id for n in map(Node, params.node)]
|
||||
ngs = map(NodeGroup, params.group)
|
||||
if len(ngs) > 1:
|
||||
raise ActionException(
|
||||
"Nodes can only be assigned to one node group."
|
||||
)
|
||||
NodeGroup.assign(ngs[0].id, nodes)
|
||||
|
||||
def list(self, params):
|
||||
"""To list all available node groups:
|
||||
fuel nodegroup
|
||||
|
||||
To filter them by environment:
|
||||
fuel --env-id 1 nodegroup
|
||||
"""
|
||||
group_collection = NodeGroupCollection.get_all()
|
||||
if params.env:
|
||||
group_collection.filter_by_env_id(int(params.env))
|
||||
self.serializer.print_to_output(
|
||||
group_collection.data,
|
||||
format_table(
|
||||
group_collection.data,
|
||||
acceptable_keys=self.acceptable_keys,
|
||||
)
|
||||
)
|
|
@ -1,67 +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 fuelclient.cli.actions.base import Action
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.formatting import format_table
|
||||
from fuelclient.objects.plugins import Plugins
|
||||
|
||||
|
||||
class PluginAction(Action):
|
||||
"""List and modify currently available releases
|
||||
"""
|
||||
action_name = "plugins"
|
||||
|
||||
acceptable_keys = (
|
||||
"id",
|
||||
"name",
|
||||
"version",
|
||||
"package_version",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super(PluginAction, self).__init__()
|
||||
self.args = [
|
||||
Args.get_list_arg("List all available plugins."),
|
||||
Args.get_plugin_install_arg("Install action"),
|
||||
Args.get_force_arg("Update action"),
|
||||
]
|
||||
self.flag_func_map = (
|
||||
("install", self.install),
|
||||
(None, self.list),
|
||||
)
|
||||
|
||||
def list(self, params):
|
||||
"""Print all available plugins:
|
||||
fuel plugins
|
||||
fuel plugins --list
|
||||
"""
|
||||
plugins = Plugins.get_all_data()
|
||||
self.serializer.print_to_output(
|
||||
plugins,
|
||||
format_table(
|
||||
plugins,
|
||||
acceptable_keys=self.acceptable_keys
|
||||
)
|
||||
)
|
||||
|
||||
def install(self, params):
|
||||
"""Enable plugin for environment
|
||||
fuel plugins --install /tmp/plugin_sample.fb
|
||||
"""
|
||||
results = Plugins.install_plugin(params.install, params.force)
|
||||
self.serializer.print_to_output(
|
||||
results,
|
||||
"Plugin {0} was successfully installed.".format(
|
||||
params.install))
|
|
@ -1,165 +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 defaultdict
|
||||
import os
|
||||
|
||||
from fuelclient.cli.actions.base import Action
|
||||
from fuelclient.cli.actions.base import check_all
|
||||
from fuelclient.cli.actions.base import check_any
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.arguments import group
|
||||
from fuelclient.cli.formatting import format_table
|
||||
from fuelclient.cli import utils
|
||||
from fuelclient.objects.release import Release
|
||||
|
||||
|
||||
class ReleaseAction(Action):
|
||||
"""List and modify currently available releases
|
||||
"""
|
||||
action_name = "release"
|
||||
|
||||
def __init__(self):
|
||||
super(ReleaseAction, self).__init__()
|
||||
self.args = [
|
||||
Args.get_release_arg('Specify particular release id'),
|
||||
Args.get_list_arg("List all available releases."),
|
||||
Args.get_network_arg("Release network configuration."),
|
||||
Args.get_deployment_tasks_arg("Release tasks configuration."),
|
||||
Args.get_sync_deployment_tasks_arg(),
|
||||
Args.get_dir_arg(
|
||||
"Select directory to which download release attributes"),
|
||||
group(
|
||||
Args.get_download_arg(
|
||||
"Download configuration of specific release"),
|
||||
Args.get_upload_arg(
|
||||
"Upload configuration to specific release")
|
||||
)
|
||||
]
|
||||
self.flag_func_map = (
|
||||
('sync-deployment-tasks', self.sync_deployment_tasks),
|
||||
('deployment-tasks', self.deployment_tasks),
|
||||
('network', self.network),
|
||||
(None, self.list),
|
||||
)
|
||||
|
||||
def list(self, params):
|
||||
"""Print all available releases:
|
||||
fuel release --list
|
||||
|
||||
Print release with specific id=1:
|
||||
fuel release --rel 1
|
||||
"""
|
||||
acceptable_keys = (
|
||||
"id",
|
||||
"name",
|
||||
"state",
|
||||
"operating_system",
|
||||
"version"
|
||||
)
|
||||
if params.release:
|
||||
release = Release(params.release)
|
||||
data = [release.get_fresh_data()]
|
||||
else:
|
||||
data = Release.get_all_data()
|
||||
self.serializer.print_to_output(
|
||||
data,
|
||||
format_table(
|
||||
data,
|
||||
acceptable_keys=acceptable_keys
|
||||
)
|
||||
)
|
||||
|
||||
@check_all("release")
|
||||
@check_any("download", "upload")
|
||||
def network(self, params):
|
||||
"""Modify release networks configuration.
|
||||
fuel rel --rel 1 --network --download
|
||||
fuel rel --rel 2 --network --upload
|
||||
"""
|
||||
release = Release(params.release)
|
||||
dir_path = self.full_path_directory(
|
||||
params.dir, 'release_{0}'.format(params.release))
|
||||
full_path = '{0}/networks'.format(dir_path)
|
||||
if params.download:
|
||||
networks = release.get_networks()
|
||||
self.serializer.write_to_file(full_path, networks)
|
||||
print("Networks for release {0} "
|
||||
"downloaded into {1}.yaml".format(release.id, full_path))
|
||||
elif params.upload:
|
||||
networks = self.serializer.read_from_file(full_path)
|
||||
release.update_networks(networks)
|
||||
print("Networks for release {0} uploaded from {1}.yaml".format(
|
||||
release.id, full_path))
|
||||
|
||||
@check_all("release")
|
||||
@check_any("download", "upload")
|
||||
def deployment_tasks(self, params):
|
||||
"""Modify deployment_tasks for release.
|
||||
fuel rel --rel 1 --deployment-tasks --download
|
||||
fuel rel --rel 1 --deployment-tasks --upload
|
||||
"""
|
||||
release = Release(params.release)
|
||||
dir_path = self.full_path_directory(
|
||||
params.dir, 'release_{0}'.format(params.release))
|
||||
full_path = '{0}/deployment_tasks'.format(dir_path)
|
||||
if params.download:
|
||||
tasks = release.get_deployment_tasks()
|
||||
self.serializer.write_to_file(full_path, tasks)
|
||||
print("Deployment tasks for release {0} "
|
||||
"downloaded into {1}.yaml.".format(release.id, full_path))
|
||||
elif params.upload:
|
||||
tasks = self.serializer.read_from_file(full_path)
|
||||
release.update_deployment_tasks(tasks)
|
||||
print("Deployment tasks for release {0}"
|
||||
" uploaded from {1}.yaml".format(release.id, dir_path))
|
||||
|
||||
@check_all("dir")
|
||||
def sync_deployment_tasks(self, params):
|
||||
"""Upload tasks for different releases based on directories.
|
||||
Unique identifier of the release should in the path, like:
|
||||
|
||||
/etc/puppet/2014.2-6.0/
|
||||
|
||||
fuel rel --sync-deployment-tasks --dir /etc/puppet/2014.2-6.0/
|
||||
|
||||
In case no directory will be provided:
|
||||
|
||||
fuel rel --sync-deployment-tasks
|
||||
|
||||
Current directory will be used
|
||||
"""
|
||||
all_rels = Release.get_all_data()
|
||||
real_path = os.path.realpath(params.dir)
|
||||
files = list(utils.iterfiles(real_path, ('tasks.yaml',)))
|
||||
serialized_tasks = defaultdict(list)
|
||||
versions = set([r['version'] for r in all_rels])
|
||||
|
||||
for file_name in files:
|
||||
for version in versions:
|
||||
if version in file_name:
|
||||
serialized_tasks[version].extend(
|
||||
self.serializer.read_from_full_path(file_name))
|
||||
|
||||
for rel in all_rels:
|
||||
release = Release(rel['id'])
|
||||
data = serialized_tasks.get(rel['version'])
|
||||
if data:
|
||||
release.update_deployment_tasks(data)
|
||||
print("Deployment tasks syncronized for release"
|
||||
" {0} of version {1}".format(rel['name'],
|
||||
rel['version']))
|
||||
else:
|
||||
print("No tasks found for release {0} "
|
||||
"of version {1}".format(rel['name'], rel['version']))
|
|
@ -1,58 +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 fuelclient.cli.actions.base import Action
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.formatting import format_table
|
||||
from fuelclient.objects.release import Release
|
||||
|
||||
|
||||
class RoleAction(Action):
|
||||
"""List all roles for specific release
|
||||
"""
|
||||
action_name = "role"
|
||||
|
||||
def __init__(self):
|
||||
super(RoleAction, self).__init__()
|
||||
self.args = [
|
||||
Args.get_list_arg("List all roles for specific release"),
|
||||
Args.get_release_arg("Release id", required=True)
|
||||
]
|
||||
self.flag_func_map = (
|
||||
(None, self.list),
|
||||
)
|
||||
|
||||
def list(self, params):
|
||||
"""Print all available roles and their
|
||||
conflicts for some release with id=1:
|
||||
fuel role --rel 1
|
||||
"""
|
||||
release = Release(params.release, params=params)
|
||||
data = release.get_fresh_data()
|
||||
acceptable_keys = ("name", "conflicts")
|
||||
roles = [
|
||||
{
|
||||
"name": role_name,
|
||||
"conflicts": ", ".join(
|
||||
metadata.get("conflicts", ["-"])
|
||||
)
|
||||
} for role_name, metadata in data["roles_metadata"].iteritems()]
|
||||
self.serializer.print_to_output(
|
||||
roles,
|
||||
format_table(
|
||||
roles,
|
||||
acceptable_keys=acceptable_keys
|
||||
)
|
||||
)
|
|
@ -1,85 +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 fuelclient.cli.actions.base import Action
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.arguments import group
|
||||
from fuelclient.objects.environment import Environment
|
||||
|
||||
|
||||
class SettingsAction(Action):
|
||||
"""Show or modify environment settings
|
||||
"""
|
||||
action_name = "settings"
|
||||
|
||||
def __init__(self):
|
||||
super(SettingsAction, self).__init__()
|
||||
self.args = (
|
||||
Args.get_env_arg(required=True),
|
||||
group(
|
||||
Args.get_download_arg("Modify current configuration."),
|
||||
Args.get_default_arg("Open default configuration."),
|
||||
Args.get_upload_arg("Save current changes in configuration."),
|
||||
required=True
|
||||
),
|
||||
Args.get_dir_arg("Directory with configuration data.")
|
||||
)
|
||||
self.flag_func_map = (
|
||||
("upload", self.upload),
|
||||
("default", self.default),
|
||||
("download", self.download)
|
||||
)
|
||||
|
||||
def upload(self, params):
|
||||
"""To upload settings for some environment from some directory:
|
||||
fuel --env 1 settings --upload --dir path/to/directory
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
settings_data = env.read_settings_data(
|
||||
directory=params.dir,
|
||||
serializer=self.serializer
|
||||
)
|
||||
env.set_settings_data(settings_data)
|
||||
print("Settings configuration uploaded.")
|
||||
|
||||
def default(self, params):
|
||||
"""To download default settings for some environment in some directory:
|
||||
fuel --env 1 settings --default --dir path/to/directory
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
default_data = env.get_default_settings_data()
|
||||
settings_file_path = env.write_settings_data(
|
||||
default_data,
|
||||
directory=params.dir,
|
||||
serializer=self.serializer)
|
||||
print(
|
||||
"Default settings configuration downloaded to {0}."
|
||||
.format(settings_file_path)
|
||||
)
|
||||
|
||||
def download(self, params):
|
||||
"""To download settings for some environment in this directory:
|
||||
fuel --env 1 settings --download
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
settings_data = env.get_settings_data()
|
||||
settings_file_path = env.write_settings_data(
|
||||
settings_data,
|
||||
directory=params.dir,
|
||||
serializer=self.serializer)
|
||||
print(
|
||||
"Settings configuration for environment with id={0}"
|
||||
" downloaded to {1}"
|
||||
.format(env.id, settings_file_path)
|
||||
)
|
|
@ -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 fuelclient.cli.actions.base import Action
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.formatting import download_snapshot_with_progress_bar
|
||||
from fuelclient.objects.task import SnapshotTask
|
||||
|
||||
|
||||
class SnapshotAction(Action):
|
||||
"""Generate and download snapshot.
|
||||
"""
|
||||
action_name = "snapshot"
|
||||
|
||||
def __init__(self):
|
||||
super(SnapshotAction, self).__init__()
|
||||
self.args = (
|
||||
Args.get_dir_arg("Directory to which download snapshot."),
|
||||
)
|
||||
self.flag_func_map = (
|
||||
(None, self.get_snapshot),
|
||||
)
|
||||
|
||||
def get_snapshot(self, params):
|
||||
"""To download diagnostic snapshot:
|
||||
fuel snapshot
|
||||
|
||||
To download diagnostic snapshot to specific directory:
|
||||
fuel snapshot --dir path/to/directory
|
||||
"""
|
||||
snapshot_task = SnapshotTask.start_snapshot_task()
|
||||
self.serializer.print_to_output(
|
||||
snapshot_task.data,
|
||||
"Generating dump..."
|
||||
)
|
||||
snapshot_task.wait()
|
||||
download_snapshot_with_progress_bar(
|
||||
snapshot_task.connection.root + snapshot_task.data["message"],
|
||||
directory=params.dir
|
||||
)
|
|
@ -1,81 +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 fuelclient.cli.actions.base import Action
|
||||
from fuelclient.cli.actions.base import check_all
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.arguments import group
|
||||
from fuelclient.cli.formatting import format_table
|
||||
from fuelclient.objects.task import Task
|
||||
|
||||
|
||||
class TaskAction(Action):
|
||||
"""Show tasks
|
||||
"""
|
||||
action_name = "task"
|
||||
|
||||
def __init__(self):
|
||||
super(TaskAction, self).__init__()
|
||||
self.args = [
|
||||
group(
|
||||
Args.get_list_arg("List all tasks"),
|
||||
Args.get_delete_arg("Delete task with some task-id.")
|
||||
),
|
||||
Args.get_force_arg("Force deletion"),
|
||||
Args.get_task_arg("Task id.")
|
||||
]
|
||||
self.flag_func_map = (
|
||||
("delete", self.delete),
|
||||
(None, self.list)
|
||||
)
|
||||
|
||||
@check_all("task")
|
||||
def delete(self, params):
|
||||
"""To delete some tasks:
|
||||
fuel task delete --task-id 1,2,3
|
||||
|
||||
To delete some tasks forcefully (without considering their state):
|
||||
fuel task delete -f --tid 1,6
|
||||
"""
|
||||
tasks = Task.get_by_ids(params.task)
|
||||
delete_response = map(
|
||||
lambda task: task.delete(force=params.force),
|
||||
tasks
|
||||
)
|
||||
self.serializer.print_to_output(
|
||||
delete_response,
|
||||
"Tasks with id's {0} deleted."
|
||||
.format(', '.join(map(str, params.task)))
|
||||
)
|
||||
|
||||
def list(self, params):
|
||||
"""To display all tasks:
|
||||
fuel task
|
||||
|
||||
To display tasks with some ids:
|
||||
fuel task --tid 1,2,3
|
||||
"""
|
||||
acceptable_keys = ("id", "status", "name",
|
||||
"cluster", "progress", "uuid")
|
||||
if params.task:
|
||||
tasks_data = map(
|
||||
Task.get_fresh_data,
|
||||
Task.get_by_ids(params.task)
|
||||
)
|
||||
else:
|
||||
tasks_data = Task.get_all_data()
|
||||
self.serializer.print_to_output(
|
||||
tasks_data,
|
||||
format_table(tasks_data, acceptable_keys=acceptable_keys)
|
||||
)
|
|
@ -1,45 +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 fuelclient.cli.actions.base import Action
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.error import ArgumentException
|
||||
from fuelclient.client import APIClient
|
||||
|
||||
|
||||
class UserAction(Action):
|
||||
"""Change password for user
|
||||
"""
|
||||
action_name = "user"
|
||||
|
||||
def __init__(self):
|
||||
super(UserAction, self).__init__()
|
||||
self.args = (
|
||||
Args.get_new_password_arg(),
|
||||
Args.get_change_password_arg("Change user password")
|
||||
)
|
||||
|
||||
self.flag_func_map = (
|
||||
("change-password", self.change_password),
|
||||
)
|
||||
|
||||
def change_password(self, params):
|
||||
"""To change user password:
|
||||
fuel user change-password
|
||||
"""
|
||||
if params.newpass:
|
||||
APIClient.update_own_password(params.newpass)
|
||||
else:
|
||||
raise ArgumentException(
|
||||
"Expect password [--newpass NEWPASS]")
|
|
@ -1,397 +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 argparse
|
||||
from itertools import chain
|
||||
import os
|
||||
|
||||
from fuelclient import __version__
|
||||
from fuelclient.cli.error import ArgumentException
|
||||
from fuelclient.client import APIClient
|
||||
|
||||
substitutions = {
|
||||
#replace from: to
|
||||
"env": "environment",
|
||||
"nodes": "node",
|
||||
"net": "network",
|
||||
"rel": "release",
|
||||
"list": "--list",
|
||||
"set": "--set",
|
||||
"delete": "--delete",
|
||||
"download": "--download",
|
||||
"upload": "--upload",
|
||||
"default": "--default",
|
||||
"create": "--create",
|
||||
"remove": "--delete",
|
||||
"config": "--config",
|
||||
"--roles": "--role",
|
||||
"help": "--help",
|
||||
"change-password": "--change-password",
|
||||
}
|
||||
|
||||
|
||||
def group(*args, **kwargs):
|
||||
required = kwargs.get("required", False)
|
||||
return (required, ) + args
|
||||
|
||||
|
||||
class TaskAction(argparse.Action):
|
||||
"""Custom argparse.Action subclass to store task ids
|
||||
|
||||
:returns: list of ids
|
||||
"""
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
setattr(namespace, self.dest, map(int, chain(*values)))
|
||||
|
||||
|
||||
class NodeAction(argparse.Action):
|
||||
"""Custom argparse.Action subclass to store node identity
|
||||
|
||||
:returns: list of ids
|
||||
"""
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
if values:
|
||||
node_identities = set(chain(*values))
|
||||
input_macs = set(n for n in node_identities if ":" in n)
|
||||
only_ids = set()
|
||||
for _id in (node_identities - input_macs):
|
||||
try:
|
||||
only_ids.add(int(_id))
|
||||
except ValueError:
|
||||
raise ArgumentException(
|
||||
"'{0}' is not valid node id.".format(_id))
|
||||
if input_macs:
|
||||
nodes_mac_to_id_map = dict(
|
||||
(n["mac"], n["id"])
|
||||
for n in APIClient.get_request("nodes/")
|
||||
)
|
||||
for short_mac in input_macs:
|
||||
target_node = None
|
||||
for mac in nodes_mac_to_id_map:
|
||||
if mac.endswith(short_mac):
|
||||
target_node = mac
|
||||
break
|
||||
if target_node:
|
||||
only_ids.add(nodes_mac_to_id_map[target_node])
|
||||
else:
|
||||
raise ArgumentException(
|
||||
'Node with mac endfix "{0}" was not found.'
|
||||
.format(short_mac)
|
||||
)
|
||||
setattr(namespace, self.dest, map(int, only_ids))
|
||||
|
||||
|
||||
class FuelVersionAction(argparse._VersionAction):
|
||||
"""Custom argparse._VersionAction subclass to compute fuel server version
|
||||
|
||||
:returns: prints fuel server version
|
||||
"""
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
parser.exit(message=APIClient.get_fuel_version())
|
||||
|
||||
|
||||
class SetAction(argparse.Action):
|
||||
"""Custom argparse.Action subclass to store distinct values
|
||||
|
||||
:returns: Set of arguments
|
||||
"""
|
||||
def __call__(self, _parser, namespace, values, option_string=None):
|
||||
try:
|
||||
getattr(namespace, self.dest).update(values)
|
||||
except AttributeError:
|
||||
setattr(namespace, self.dest, set(values))
|
||||
|
||||
|
||||
def get_debug_arg():
|
||||
return {
|
||||
"args": ["--debug"],
|
||||
"params": {
|
||||
"dest": "debug",
|
||||
"action": "store_true",
|
||||
"help": "prints details of all HTTP request",
|
||||
"default": False
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_version_arg():
|
||||
return {
|
||||
"args": ["-v", "--version"],
|
||||
"params": {
|
||||
"action": "version",
|
||||
"version": __version__
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_fuel_version_arg():
|
||||
return {
|
||||
"args": ["--fuel-version"],
|
||||
"params": {
|
||||
"action": FuelVersionAction,
|
||||
"help": "show Fuel server's version number and exit"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_arg(name, flags=None, aliases=None, help_=None, **kwargs):
|
||||
if "_" in name:
|
||||
name = name.replace("_", "-")
|
||||
args = ["--" + name, ]
|
||||
if flags is not None:
|
||||
args.extend(flags)
|
||||
if aliases is not None:
|
||||
substitutions.update(
|
||||
dict((alias, args[0]) for alias in aliases)
|
||||
)
|
||||
all_args = {
|
||||
"args": args,
|
||||
"params": {
|
||||
"dest": name,
|
||||
"help": help_ or name
|
||||
}
|
||||
}
|
||||
all_args["params"].update(kwargs)
|
||||
return all_args
|
||||
|
||||
|
||||
def get_boolean_arg(name, **kwargs):
|
||||
kwargs.update({
|
||||
"action": "store_true",
|
||||
"default": False
|
||||
})
|
||||
return get_arg(name, **kwargs)
|
||||
|
||||
|
||||
def get_env_arg(required=False):
|
||||
return get_int_arg(
|
||||
"env",
|
||||
flags=("--env-id",),
|
||||
help="environment id",
|
||||
required=required
|
||||
)
|
||||
|
||||
|
||||
def get_new_password_arg():
|
||||
return get_str_arg(
|
||||
"newpass",
|
||||
flags=("--new-pass",),
|
||||
help="new_password",
|
||||
required=False
|
||||
)
|
||||
|
||||
|
||||
def get_str_arg(name, **kwargs):
|
||||
default_kwargs = {
|
||||
"action": "store",
|
||||
"type": str,
|
||||
"default": None
|
||||
}
|
||||
default_kwargs.update(kwargs)
|
||||
return get_arg(name, **default_kwargs)
|
||||
|
||||
|
||||
def get_int_arg(name, **kwargs):
|
||||
default_kwargs = {
|
||||
"action": "store",
|
||||
"type": int,
|
||||
"default": None
|
||||
}
|
||||
default_kwargs.update(kwargs)
|
||||
return get_arg(name, **default_kwargs)
|
||||
|
||||
|
||||
def get_set_type_arg(name, **kwargs):
|
||||
default_kwargs = {
|
||||
"type": lambda v: v.split(','),
|
||||
"action": SetAction,
|
||||
"default": None
|
||||
}
|
||||
default_kwargs.update(kwargs)
|
||||
return get_arg(name, **default_kwargs)
|
||||
|
||||
|
||||
def get_delete_from_db_arg(help_msg):
|
||||
return get_boolean_arg("delete-from-db", help=help_msg)
|
||||
|
||||
|
||||
def get_deployment_tasks_arg(help_msg):
|
||||
return get_boolean_arg(
|
||||
"deployment-tasks", flags=("--deployment-tasks",), help=help_msg)
|
||||
|
||||
|
||||
def get_sync_deployment_tasks_arg():
|
||||
return get_boolean_arg(
|
||||
"sync-deployment-tasks",
|
||||
flags=("--sync-deployment-tasks",),
|
||||
help="Update tasks for each release.")
|
||||
|
||||
|
||||
def get_network_arg(help_msg):
|
||||
return get_boolean_arg("network", flags=("--net",), help=help_msg)
|
||||
|
||||
|
||||
def get_force_arg(help_msg):
|
||||
return get_boolean_arg("force", flags=("-f",), help=help_msg)
|
||||
|
||||
|
||||
def get_disk_arg(help_msg):
|
||||
return get_boolean_arg("disk", help=help_msg)
|
||||
|
||||
|
||||
def get_deploy_arg(help_msg):
|
||||
return get_boolean_arg("deploy", help=help_msg)
|
||||
|
||||
|
||||
def get_provision_arg(help_msg):
|
||||
return get_boolean_arg("provision", help=help_msg)
|
||||
|
||||
|
||||
def get_role_arg(help_msg):
|
||||
return get_set_type_arg("role", flags=("-r",), help=help_msg)
|
||||
|
||||
|
||||
def get_check_arg(help_msg):
|
||||
return get_set_type_arg("check", help=help_msg)
|
||||
|
||||
|
||||
def get_change_password_arg(help_msg):
|
||||
return get_boolean_arg("change-password", help=help_msg)
|
||||
|
||||
|
||||
def get_name_arg(help_msg):
|
||||
return get_str_arg("name", flags=("--env-name",), help=help_msg)
|
||||
|
||||
|
||||
def get_mode_arg(help_msg):
|
||||
return get_arg("mode",
|
||||
action="store",
|
||||
choices=("multinode", "ha"),
|
||||
default=False,
|
||||
flags=("-m", "--deployment-mode"),
|
||||
help_=help_msg)
|
||||
|
||||
|
||||
def get_net_arg(help_msg):
|
||||
return get_arg("net",
|
||||
flags=("-n", "--network-mode"),
|
||||
action="store",
|
||||
choices=("nova", "neutron"),
|
||||
help_=help_msg,
|
||||
default="nova")
|
||||
|
||||
|
||||
def get_nst_arg(help_msg):
|
||||
return get_arg("nst",
|
||||
flags=("--net-segment-type",),
|
||||
action="store",
|
||||
choices=("gre", "vlan"),
|
||||
help_=help_msg,
|
||||
default=None)
|
||||
|
||||
|
||||
def get_all_arg(help_msg):
|
||||
return get_boolean_arg("all", help=help_msg)
|
||||
|
||||
|
||||
def get_create_arg(help_msg):
|
||||
return get_boolean_arg(
|
||||
"create",
|
||||
flags=("-c", "--env-create"),
|
||||
help=help_msg)
|
||||
|
||||
|
||||
def get_download_arg(help_msg):
|
||||
return get_boolean_arg("download", flags=("-d",), help=help_msg)
|
||||
|
||||
|
||||
def get_list_arg(help_msg):
|
||||
return get_boolean_arg("list", flags=("-l",), help=help_msg)
|
||||
|
||||
|
||||
def get_update_arg(help_msg):
|
||||
return get_boolean_arg("update",
|
||||
flags=("--env-update",), help=help_msg)
|
||||
|
||||
|
||||
def get_dir_arg(help_msg):
|
||||
return get_str_arg("dir", default=os.curdir, help=help_msg)
|
||||
|
||||
|
||||
def get_verify_arg(help_msg):
|
||||
return get_boolean_arg("verify", flags=("-v",), help=help_msg)
|
||||
|
||||
|
||||
def get_upload_arg(help_msg):
|
||||
return get_boolean_arg("upload", flags=("-u",), help=help_msg)
|
||||
|
||||
|
||||
def get_default_arg(help_msg):
|
||||
return get_boolean_arg("default", help=help_msg)
|
||||
|
||||
|
||||
def get_set_arg(help_msg):
|
||||
return get_boolean_arg("set", flags=("-s",), help=help_msg)
|
||||
|
||||
|
||||
def get_delete_arg(help_msg):
|
||||
return get_boolean_arg("delete", help=help_msg)
|
||||
|
||||
|
||||
def get_assign_arg(help_msg):
|
||||
return get_boolean_arg("assign", help=help_msg)
|
||||
|
||||
|
||||
def get_group_arg(help_msg):
|
||||
return get_set_type_arg("group", help=help_msg)
|
||||
|
||||
|
||||
def get_release_arg(help_msg, required=False):
|
||||
return get_int_arg(
|
||||
"release",
|
||||
flags=("--rel",),
|
||||
required=required,
|
||||
help=help_msg)
|
||||
|
||||
|
||||
def get_node_arg(help_msg):
|
||||
default_kwargs = {
|
||||
"action": NodeAction,
|
||||
"flags": ("--node-id",),
|
||||
"nargs": '+',
|
||||
"type": lambda v: v.split(","),
|
||||
"default": None,
|
||||
"help": help_msg
|
||||
}
|
||||
return get_arg("node", **default_kwargs)
|
||||
|
||||
|
||||
def get_task_arg(help_msg):
|
||||
default_kwargs = {
|
||||
"action": TaskAction,
|
||||
"flags": ("--task-id", "--tid"),
|
||||
"nargs": '+',
|
||||
"type": lambda v: v.split(","),
|
||||
"default": None,
|
||||
"help": help_msg
|
||||
}
|
||||
return get_arg("task", **default_kwargs)
|
||||
|
||||
|
||||
def get_plugin_install_arg(help_msg):
|
||||
return get_str_arg(
|
||||
"install",
|
||||
flags=("--install",),
|
||||
help=help_msg
|
||||
)
|
|
@ -1,118 +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 functools import wraps
|
||||
from keystoneclient.exceptions import Unauthorized
|
||||
import requests
|
||||
import sys
|
||||
|
||||
|
||||
def exit_with_error(message):
|
||||
"""exit_with_error - writes message to stderr and exits with exit code 1.
|
||||
"""
|
||||
sys.stderr.write(message + "\n")
|
||||
exit(1)
|
||||
|
||||
|
||||
class FuelClientException(Exception):
|
||||
"""Base Exception for Fuel-Client
|
||||
|
||||
All child classes must be instantiated before raising.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FuelClientException, self).__init__(*args, **kwargs)
|
||||
self.message = args[0]
|
||||
|
||||
|
||||
class BadDataException(FuelClientException):
|
||||
"""Should be raised when user provides corrupted data."""
|
||||
|
||||
|
||||
class WrongEnvironmentError(FuelClientException):
|
||||
"""Raised when particular action is not supported on environment."""
|
||||
|
||||
|
||||
class ServerDataException(FuelClientException):
|
||||
"""ServerDataException - must be raised when
|
||||
data returned from server cannot be processed by Fuel-Client methods.
|
||||
"""
|
||||
|
||||
|
||||
class DeployProgressError(FuelClientException):
|
||||
"""DeployProgressError - must be raised when
|
||||
deployment process interrupted on server.
|
||||
"""
|
||||
|
||||
|
||||
class ArgumentException(FuelClientException):
|
||||
"""ArgumentException - must be raised when
|
||||
incorrect arguments inputted through argparse or some function.
|
||||
"""
|
||||
|
||||
|
||||
class ActionException(FuelClientException):
|
||||
"""ActionException - must be raised when
|
||||
though arguments inputted to action are correct but they contradict
|
||||
to logic in action.
|
||||
"""
|
||||
|
||||
|
||||
class ParserException(FuelClientException):
|
||||
"""ParserException - must be raised when
|
||||
some problem occurred in process of argument parsing,
|
||||
in argparse extension or in Fuel-Client Parser submodule.
|
||||
"""
|
||||
|
||||
|
||||
class ProfilingError(FuelClientException):
|
||||
"""Indicates errors and other issues related to performance profiling."""
|
||||
|
||||
|
||||
class SettingsException(FuelClientException):
|
||||
"""Indicates errors or unexpected behaviour in processing settings."""
|
||||
|
||||
|
||||
def exceptions_decorator(func):
|
||||
"""Handles HTTP errors and expected exceptions that may occur
|
||||
in methods of APIClient class
|
||||
"""
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
|
||||
# when server returns to us bad request check that
|
||||
# and print meaningful reason
|
||||
except requests.HTTPError as exc:
|
||||
error_body = exc.response.text
|
||||
exit_with_error("{0} ({1})".format(exc, error_body))
|
||||
except requests.ConnectionError:
|
||||
exit_with_error("""
|
||||
Can't connect to Nailgun server!
|
||||
Please modify "SERVER_ADDRESS" and "LISTEN_PORT"
|
||||
in the file /etc/fuel/client/config.yaml""")
|
||||
except Unauthorized:
|
||||
exit_with_error("""
|
||||
Unauthorized: need authentication!
|
||||
Please provide user and password via client
|
||||
fuel --user=user --password=pass [action]
|
||||
or modify "KEYSTONE_USER" and "KEYSTONE_PASS" in
|
||||
/etc/fuel/client/config.yaml""")
|
||||
except FuelClientException as exc:
|
||||
exit_with_error(exc.message)
|
||||
# not all responses return data
|
||||
except ValueError:
|
||||
return {}
|
||||
|
||||
return wrapper
|
|
@ -1,256 +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 curses
|
||||
from functools import partial
|
||||
from itertools import chain
|
||||
import math
|
||||
from operator import itemgetter
|
||||
import os
|
||||
import sys
|
||||
from time import sleep
|
||||
import urllib2
|
||||
|
||||
from fuelclient.cli.error import DeployProgressError
|
||||
from fuelclient.cli.error import exit_with_error
|
||||
|
||||
|
||||
def format_table(data, acceptable_keys=None, column_to_join=None):
|
||||
"""Format list of dicts to table in a string form
|
||||
|
||||
:acceptable_keys list(str): list of keys for which to create table
|
||||
also specifies their order
|
||||
"""
|
||||
if column_to_join is not None:
|
||||
for data_dict in data:
|
||||
for column_name in column_to_join:
|
||||
data_dict[column_name] = u", ".join(
|
||||
sorted(data_dict[column_name])
|
||||
)
|
||||
if acceptable_keys is not None:
|
||||
rows = [tuple(value[key] for key in acceptable_keys)
|
||||
for value in data]
|
||||
header = tuple(acceptable_keys)
|
||||
else:
|
||||
rows = [tuple(x.values()) for x in data]
|
||||
header = tuple(data[0].keys())
|
||||
number_of_columns = len(header)
|
||||
column_widths = dict(
|
||||
zip(
|
||||
range(number_of_columns),
|
||||
(len(str(x)) for x in header)
|
||||
)
|
||||
)
|
||||
for row in rows:
|
||||
column_widths.update(
|
||||
(index, max(column_widths[index], len(unicode(element))))
|
||||
for index, element in enumerate(row)
|
||||
)
|
||||
row_template = u' | '.join(
|
||||
u"{{{0}:{1}}}".format(idx, width)
|
||||
for idx, width in column_widths.iteritems()
|
||||
)
|
||||
|
||||
return u'\n'.join(
|
||||
(row_template.format(*header),
|
||||
u'-|-'.join(column_widths[column_index] * u'-'
|
||||
for column_index in range(number_of_columns)),
|
||||
u'\n'.join(row_template.format(*map(unicode, x))
|
||||
for x in rows))
|
||||
)
|
||||
|
||||
|
||||
def quote_and_join(words):
|
||||
"""quote_and_join - performs listing of objects and returns string.
|
||||
"""
|
||||
words = list(words)
|
||||
if len(words) > 1:
|
||||
return '{0} and "{1}"'.format(
|
||||
", ".join(
|
||||
map(
|
||||
lambda x: '"{0}"'.format(x),
|
||||
words
|
||||
)[0:-1]
|
||||
),
|
||||
words[-1]
|
||||
)
|
||||
else:
|
||||
return '"{0}"'.format(words[0])
|
||||
|
||||
|
||||
def get_bar_for_progress(full_width, progress):
|
||||
"""get_bar_for_progress - returns string with a width of 'full_width'
|
||||
which illustrates specific progress value.
|
||||
"""
|
||||
number_of_equal_signs = int(
|
||||
math.ceil(progress * float(full_width - 2) / 100)
|
||||
)
|
||||
return "[{0}{1}{2}]".format(
|
||||
"=" * number_of_equal_signs,
|
||||
">" if number_of_equal_signs < full_width - 2 else "",
|
||||
" " * (full_width - 3 - number_of_equal_signs)
|
||||
)
|
||||
|
||||
|
||||
def download_snapshot_with_progress_bar(url, directory=os.path.curdir):
|
||||
"""downloads file from specific 'url' with progress bar and save it
|
||||
to some 'directory'.
|
||||
"""
|
||||
if not os.path.exists(directory):
|
||||
exit_with_error("Folder {0} doesn't exist.".format(directory))
|
||||
file_name = os.path.join(
|
||||
os.path.abspath(directory),
|
||||
url.split('/')[-1]
|
||||
)
|
||||
download_handle = urllib2.urlopen(url)
|
||||
with open(file_name, 'wb') as file_handle:
|
||||
meta = download_handle.info()
|
||||
file_size = int(meta.getheaders("Content-Length")[0])
|
||||
print("Downloading: {0} Bytes: {1}".format(url, file_size))
|
||||
file_size_dl = 0
|
||||
block_size = 8192
|
||||
bar = partial(get_bar_for_progress, 80)
|
||||
while True:
|
||||
data_buffer = download_handle.read(block_size)
|
||||
if not data_buffer:
|
||||
break
|
||||
file_size_dl += len(data_buffer)
|
||||
file_handle.write(data_buffer)
|
||||
progress = int(100 * float(file_size_dl) / file_size)
|
||||
sys.stdout.write("\r{0}".format(
|
||||
bar(progress)
|
||||
))
|
||||
sys.stdout.flush()
|
||||
sleep(1 / 10)
|
||||
print()
|
||||
|
||||
|
||||
def print_deploy_progress(deploy_task):
|
||||
"""Receives 'deploy_task' and depending on terminal availability
|
||||
starts progress printing routines with or without curses.
|
||||
"""
|
||||
try:
|
||||
terminal_screen = curses.initscr()
|
||||
print_deploy_progress_with_terminal(deploy_task, terminal_screen)
|
||||
except curses.error:
|
||||
print_deploy_progress_without_terminal(deploy_task)
|
||||
|
||||
|
||||
def print_deploy_progress_without_terminal(deploy_task):
|
||||
print("Deploying changes to environment with id={0}".format(
|
||||
deploy_task.env.id
|
||||
))
|
||||
message_len = 0
|
||||
try:
|
||||
for progress, nodes in deploy_task:
|
||||
sys.stdout.write("\r" * message_len)
|
||||
message_len = 0
|
||||
deployment_message = "[Deployment: {0:3}%]".format(progress)
|
||||
sys.stdout.write(deployment_message)
|
||||
message_len += len(deployment_message)
|
||||
for index, node in enumerate(nodes):
|
||||
node_message = "[Node{id:2} {progress:3}%]".format(
|
||||
**node.data
|
||||
)
|
||||
message_len += len(node_message)
|
||||
sys.stdout.write(node_message)
|
||||
print("\nFinished deployment!")
|
||||
except DeployProgressError as de:
|
||||
print(de.message)
|
||||
|
||||
|
||||
def print_deploy_progress_with_terminal(deploy_task, terminal_screen):
|
||||
scr_width = terminal_screen.getmaxyx()[1]
|
||||
curses.noecho()
|
||||
curses.cbreak()
|
||||
total_progress_bar = partial(get_bar_for_progress, scr_width - 17)
|
||||
node_bar = partial(get_bar_for_progress, scr_width - 28)
|
||||
env_id = deploy_task.env.id
|
||||
try:
|
||||
for progress, nodes in deploy_task:
|
||||
terminal_screen.refresh()
|
||||
terminal_screen.addstr(
|
||||
0, 0,
|
||||
"Deploying changes to environment with id={0}".format(
|
||||
env_id
|
||||
)
|
||||
)
|
||||
terminal_screen.addstr(
|
||||
1, 0,
|
||||
"Deployment: {0} {1:3}%".format(
|
||||
total_progress_bar(progress),
|
||||
progress
|
||||
)
|
||||
)
|
||||
for index, node in enumerate(nodes):
|
||||
terminal_screen.addstr(
|
||||
index + 2, 0,
|
||||
"Node{id:3} {status:13}: {bar} {progress:3}%"
|
||||
.format(bar=node_bar(node.progress), **node.data)
|
||||
)
|
||||
except DeployProgressError as de:
|
||||
close_curses()
|
||||
print(de.message)
|
||||
finally:
|
||||
close_curses()
|
||||
|
||||
|
||||
def close_curses():
|
||||
curses.echo()
|
||||
curses.nocbreak()
|
||||
curses.endwin()
|
||||
|
||||
|
||||
def print_health_check(env):
|
||||
tests_states = [{"status": "not finished"}]
|
||||
finished_tests = set()
|
||||
test_counter, total_tests_count = 1, None
|
||||
while not all(map(
|
||||
lambda t: t["status"] == "finished",
|
||||
tests_states
|
||||
)):
|
||||
tests_states = env.get_state_of_tests()
|
||||
all_tests = list(chain(*map(
|
||||
itemgetter("tests"),
|
||||
filter(
|
||||
env.is_in_running_test_sets,
|
||||
tests_states
|
||||
))))
|
||||
if total_tests_count is None:
|
||||
total_tests_count = len(all_tests)
|
||||
all_finished_tests = filter(
|
||||
lambda t: "running" not in t["status"],
|
||||
all_tests
|
||||
)
|
||||
new_finished_tests = filter(
|
||||
lambda t: t["name"] not in finished_tests,
|
||||
all_finished_tests
|
||||
)
|
||||
finished_tests.update(
|
||||
map(
|
||||
itemgetter("name"),
|
||||
new_finished_tests
|
||||
)
|
||||
)
|
||||
for test in new_finished_tests:
|
||||
print(
|
||||
u"[{0:2} of {1}] [{status}] '{name}' "
|
||||
u"({taken:.4} s) {message}".format(
|
||||
test_counter,
|
||||
total_tests_count,
|
||||
**test
|
||||
)
|
||||
)
|
||||
test_counter += 1
|
||||
sleep(1)
|
|
@ -1,218 +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 argparse
|
||||
import sys
|
||||
|
||||
from fuelclient.cli.actions import actions
|
||||
from fuelclient.cli.arguments import get_fuel_version_arg
|
||||
from fuelclient.cli.arguments import get_version_arg
|
||||
from fuelclient.cli.arguments import substitutions
|
||||
from fuelclient.cli.error import exceptions_decorator
|
||||
from fuelclient.cli.error import ParserException
|
||||
from fuelclient.cli.serializers import Serializer
|
||||
from fuelclient import profiler
|
||||
|
||||
|
||||
class Parser:
|
||||
"""Parser class - encapsulates argparse's ArgumentParser
|
||||
and based on available actions, serializers and additional flags
|
||||
populates it.
|
||||
"""
|
||||
def __init__(self, argv):
|
||||
self.args = argv
|
||||
self.parser = argparse.ArgumentParser(
|
||||
usage="""
|
||||
Configuration for client you can find in
|
||||
/etc/fuel/client/config.yaml. If you don't have this file please
|
||||
create it i.e.:
|
||||
"SERVER_ADDRESS": "127.0.0.1",
|
||||
"LISTEN_PORT": "8000",
|
||||
"KEYSTONE_USER": "admin",
|
||||
"KEYSTONE_PASS": "admin"
|
||||
|
||||
fuel [optional args] <namespace> [action] [flags]"""
|
||||
)
|
||||
self.universal_flags = []
|
||||
self.credential_flags = []
|
||||
self.subparsers = self.parser.add_subparsers(
|
||||
title="Namespaces",
|
||||
metavar="",
|
||||
dest="action",
|
||||
help='actions'
|
||||
)
|
||||
self.generate_actions()
|
||||
self.add_version_args()
|
||||
self.add_debug_arg()
|
||||
self.add_keystone_credentials_args()
|
||||
self.add_serializers_args()
|
||||
|
||||
def generate_actions(self):
|
||||
for action, action_object in actions.iteritems():
|
||||
action_parser = self.subparsers.add_parser(
|
||||
action,
|
||||
prog="fuel {0}".format(action),
|
||||
help=action_object.__doc__,
|
||||
formatter_class=argparse.RawTextHelpFormatter,
|
||||
epilog=action_object.examples
|
||||
)
|
||||
for argument in action_object.args:
|
||||
if isinstance(argument, dict):
|
||||
action_parser.add_argument(
|
||||
*argument["args"],
|
||||
**argument["params"]
|
||||
)
|
||||
elif isinstance(argument, tuple):
|
||||
required = argument[0]
|
||||
group = action_parser.add_mutually_exclusive_group(
|
||||
required=required)
|
||||
for argument_in_group in argument[1:]:
|
||||
group.add_argument(
|
||||
*argument_in_group["args"],
|
||||
**argument_in_group["params"]
|
||||
)
|
||||
|
||||
def parse(self):
|
||||
self.prepare_args()
|
||||
if len(self.args) < 2:
|
||||
self.parser.print_help()
|
||||
sys.exit(0)
|
||||
parsed_params, _ = self.parser.parse_known_args(self.args[1:])
|
||||
if parsed_params.action not in actions:
|
||||
self.parser.print_help()
|
||||
sys.exit(0)
|
||||
|
||||
if profiler.profiling_enabled():
|
||||
handler_name = parsed_params.action
|
||||
method_name = ''.join([method for method in parsed_params.__dict__
|
||||
if getattr(parsed_params, method) is True])
|
||||
prof = profiler.Profiler(method_name, handler_name)
|
||||
|
||||
actions[parsed_params.action].action_func(parsed_params)
|
||||
|
||||
if profiler.profiling_enabled():
|
||||
prof.save_data()
|
||||
|
||||
def add_serializers_args(self):
|
||||
for format_name in Serializer.serializers.keys():
|
||||
serialization_flag = "--{0}".format(format_name)
|
||||
self.universal_flags.append(serialization_flag)
|
||||
self.parser.add_argument(
|
||||
serialization_flag,
|
||||
dest=format_name,
|
||||
action="store_true",
|
||||
help="prints only {0} to stdout".format(format_name),
|
||||
default=False
|
||||
)
|
||||
|
||||
def add_debug_arg(self):
|
||||
self.universal_flags.append("--debug")
|
||||
self.parser.add_argument(
|
||||
"--debug",
|
||||
dest="debug",
|
||||
action="store_true",
|
||||
help="prints details of all HTTP request",
|
||||
default=False
|
||||
)
|
||||
|
||||
def add_keystone_credentials_args(self):
|
||||
self.credential_flags.append('--user')
|
||||
self.credential_flags.append('--password')
|
||||
self.parser.add_argument(
|
||||
"--user",
|
||||
dest="user",
|
||||
type=str,
|
||||
help="credentials for keystone authentication user",
|
||||
default=None
|
||||
)
|
||||
self.parser.add_argument(
|
||||
"--password",
|
||||
dest="password",
|
||||
type=str,
|
||||
help="credentials for keystone authentication password",
|
||||
default=None
|
||||
)
|
||||
|
||||
def add_version_args(self):
|
||||
for args in (get_version_arg(), get_fuel_version_arg()):
|
||||
self.parser.add_argument(*args["args"], **args["params"])
|
||||
|
||||
def prepare_args(self):
|
||||
# replace some args from dict substitutions
|
||||
self.args = map(
|
||||
lambda x: substitutions.get(x, x),
|
||||
self.args
|
||||
)
|
||||
|
||||
# move general used flags before actions, otherwise they will be used
|
||||
# as a part of action by action_generator
|
||||
for flag in self.credential_flags:
|
||||
self.move_argument_before_action(flag)
|
||||
|
||||
for flag in self.universal_flags:
|
||||
self.move_argument_before_action(flag, has_value=False)
|
||||
|
||||
self.move_argument_after_action("--env", )
|
||||
|
||||
def move_argument_before_action(self, flag, has_value=True):
|
||||
"""We need to move general argument before action, we use them
|
||||
not directly in action but in APIClient.
|
||||
"""
|
||||
for arg in self.args:
|
||||
if flag in arg:
|
||||
if "=" in arg or not has_value:
|
||||
index_of_flag = self.args.index(arg)
|
||||
flag = self.args.pop(index_of_flag)
|
||||
self.args.insert(1, flag)
|
||||
else:
|
||||
try:
|
||||
index_of_flag = self.args.index(arg)
|
||||
flag = self.args.pop(index_of_flag)
|
||||
value = self.args.pop(index_of_flag)
|
||||
self.args.insert(1, value)
|
||||
self.args.insert(1, flag)
|
||||
except IndexError:
|
||||
raise ParserException(
|
||||
'Corresponding value must follow "{0}" flag'
|
||||
.format(arg)
|
||||
)
|
||||
break
|
||||
|
||||
def move_argument_after_action(self, flag):
|
||||
for arg in self.args:
|
||||
if flag in arg:
|
||||
# if declaration with '=' sign (e.g. --env-id=1)
|
||||
if "=" in arg:
|
||||
index_of_flag = self.args.index(arg)
|
||||
flag = self.args.pop(index_of_flag)
|
||||
self.args.append(flag)
|
||||
else:
|
||||
try:
|
||||
index_of_flag = self.args.index(arg)
|
||||
self.args.pop(index_of_flag)
|
||||
flag = self.args.pop(index_of_flag)
|
||||
self.args.append(arg)
|
||||
self.args.append(flag)
|
||||
except IndexError:
|
||||
raise ParserException(
|
||||
'Corresponding value must follow "{0}" flag'
|
||||
.format(arg)
|
||||
)
|
||||
break
|
||||
|
||||
|
||||
@exceptions_decorator
|
||||
def main(args=sys.argv):
|
||||
parser = Parser(args)
|
||||
parser.parse()
|
|
@ -1,96 +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 __future__ import print_function
|
||||
|
||||
from itertools import ifilter
|
||||
from itertools import imap
|
||||
import json
|
||||
import os
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
class Serializer(object):
|
||||
"""Serializer class - contains all logic responsible for
|
||||
printing to stdout, reading and writing files to file system.
|
||||
"""
|
||||
serializers = {
|
||||
"json": {
|
||||
"w": lambda d: json.dumps(d, indent=4),
|
||||
"r": lambda d: json.loads(d)
|
||||
},
|
||||
"yaml": {
|
||||
"w": lambda d: yaml.safe_dump(d, default_flow_style=False),
|
||||
"r": lambda d: yaml.load(d)
|
||||
}
|
||||
}
|
||||
|
||||
format_flags = False
|
||||
default_format = "yaml"
|
||||
format = default_format
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for f in self.serializers:
|
||||
if kwargs.get(f, False):
|
||||
self.format = f
|
||||
self.format_flags = True
|
||||
break
|
||||
|
||||
@property
|
||||
def serializer(self):
|
||||
return self.serializers[self.format]
|
||||
|
||||
@classmethod
|
||||
def from_params(cls, params):
|
||||
kwargs = dict((key, getattr(params, key)) for key in cls.serializers)
|
||||
return cls(**kwargs)
|
||||
|
||||
def print_formatted(self, data):
|
||||
print(self.serializer["w"](data))
|
||||
|
||||
def print_to_output(self, formatted_data, arg, print_method=print):
|
||||
if self.format_flags:
|
||||
self.print_formatted(formatted_data)
|
||||
else:
|
||||
if isinstance(arg, unicode):
|
||||
arg = arg.encode('utf-8')
|
||||
print_method(arg)
|
||||
|
||||
def prepare_path(self, path):
|
||||
return "{0}.{1}".format(
|
||||
path, self.format
|
||||
)
|
||||
|
||||
def write_to_file(self, path, data):
|
||||
full_path = self.prepare_path(path)
|
||||
with open(full_path, "w+") as file_to_write:
|
||||
file_to_write.write(self.serializer["w"](data))
|
||||
return full_path
|
||||
|
||||
def read_from_file(self, path):
|
||||
return self.read_from_full_path(self.prepare_path(path))
|
||||
|
||||
def read_from_full_path(self, full_path):
|
||||
with open(full_path, "r") as file_to_read:
|
||||
return self.serializer["r"](file_to_read.read())
|
||||
|
||||
|
||||
def listdir_without_extensions(dir_path):
|
||||
return ifilter(
|
||||
lambda f: f != "",
|
||||
imap(
|
||||
lambda f: f.split(".")[0],
|
||||
os.listdir(dir_path)
|
||||
)
|
||||
)
|
|
@ -1,28 +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
|
||||
|
||||
|
||||
def iterfiles(dir_path, file_patterns):
|
||||
"""Returns generator where each item is a path to file, that satisfies
|
||||
file_patterns condtion
|
||||
|
||||
:param dir_path: path to directory, e.g /etc/puppet/
|
||||
:param file_patterns: iterable with file name, e.g (tasks.yaml,)
|
||||
"""
|
||||
for root, dirs, file_names in os.walk(dir_path):
|
||||
for file_name in file_names:
|
||||
if file_name in file_patterns:
|
||||
yield os.path.join(root, file_name)
|
|
@ -1,192 +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 json
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
|
||||
import yaml
|
||||
|
||||
from keystoneclient.v2_0 import client as auth_client
|
||||
|
||||
from fuelclient.cli.error import exceptions_decorator
|
||||
from fuelclient.logs import NullHandler
|
||||
|
||||
|
||||
# configure logging to silent all logs
|
||||
# and prevent issues in keystoneclient logging
|
||||
logger = logging.getLogger()
|
||||
logger.addHandler(NullHandler())
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""This class handles API requests
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.debug = False
|
||||
path_to_config = "/etc/fuel/client/config.yaml"
|
||||
defaults = {
|
||||
"SERVER_ADDRESS": "127.0.0.1",
|
||||
"LISTEN_PORT": "8000",
|
||||
"KEYSTONE_USER": "admin",
|
||||
"KEYSTONE_PASS": "admin",
|
||||
}
|
||||
if os.access(path_to_config, os.R_OK):
|
||||
with open(path_to_config, "r") as fh:
|
||||
config = yaml.load(fh.read())
|
||||
defaults.update(config)
|
||||
defaults.update(os.environ)
|
||||
self.root = "http://{SERVER_ADDRESS}:{LISTEN_PORT}".format(**defaults)
|
||||
self.keystone_base = (
|
||||
"http://{SERVER_ADDRESS}:{LISTEN_PORT}/keystone/v2.0"
|
||||
.format(**defaults)
|
||||
)
|
||||
self.api_root = self.root + "/api/v1/"
|
||||
self.ostf_root = self.root + "/ostf/"
|
||||
self.user = defaults["KEYSTONE_USER"]
|
||||
self.password = defaults["KEYSTONE_PASS"]
|
||||
self._keystone_client = None
|
||||
self._auth_required = None
|
||||
|
||||
@property
|
||||
def auth_token(self):
|
||||
if self.auth_required:
|
||||
if not self.keystone_client.auth_token:
|
||||
self.keystone_client.authenticate()
|
||||
return self.keystone_client.auth_token
|
||||
return ''
|
||||
|
||||
@property
|
||||
@exceptions_decorator
|
||||
def auth_required(self):
|
||||
if self._auth_required is None:
|
||||
url = self.api_root + 'version'
|
||||
resp = requests.get(url)
|
||||
resp.raise_for_status()
|
||||
|
||||
self._auth_required = resp.json().get('auth_required', False)
|
||||
return self._auth_required
|
||||
|
||||
@property
|
||||
def keystone_client(self):
|
||||
if not self._keystone_client:
|
||||
self.initialize_keystone_client()
|
||||
return self._keystone_client
|
||||
|
||||
def update_own_password(self, new_pass):
|
||||
if self.auth_token:
|
||||
self.keystone_client.users.update_own_password(
|
||||
self.password, new_pass)
|
||||
|
||||
def initialize_keystone_client(self):
|
||||
if self.auth_required:
|
||||
self._keystone_client = auth_client.Client(
|
||||
username=self.user,
|
||||
password=self.password,
|
||||
auth_url=self.keystone_base,
|
||||
tenant_name="admin")
|
||||
self._keystone_client.session.auth = self._keystone_client
|
||||
self._keystone_client.authenticate()
|
||||
|
||||
def debug_mode(self, debug=False):
|
||||
self.debug = debug
|
||||
return self
|
||||
|
||||
def print_debug(self, message):
|
||||
if self.debug:
|
||||
print(message)
|
||||
|
||||
@exceptions_decorator
|
||||
def delete_request(self, api):
|
||||
"""Make DELETE request to specific API with some data
|
||||
"""
|
||||
url = self.api_root + api
|
||||
self.print_debug(
|
||||
"DELETE {0}".format(self.api_root + api)
|
||||
)
|
||||
|
||||
headers = {'content-type': 'application/json',
|
||||
'x-auth-token': self.auth_token}
|
||||
resp = requests.delete(url, headers=headers)
|
||||
resp.raise_for_status()
|
||||
|
||||
return resp.json()
|
||||
|
||||
@exceptions_decorator
|
||||
def put_request(self, api, data):
|
||||
"""Make PUT request to specific API with some data
|
||||
"""
|
||||
url = self.api_root + api
|
||||
data_json = json.dumps(data)
|
||||
self.print_debug(
|
||||
"PUT {0} data={1}"
|
||||
.format(self.api_root + api, data_json)
|
||||
)
|
||||
|
||||
headers = {'content-type': 'application/json',
|
||||
'x-auth-token': self.auth_token}
|
||||
resp = requests.put(url, data=data_json, headers=headers)
|
||||
resp.raise_for_status()
|
||||
|
||||
return resp.json()
|
||||
|
||||
@exceptions_decorator
|
||||
def get_request(self, api, ostf=False):
|
||||
"""Make GET request to specific API
|
||||
"""
|
||||
url = (self.ostf_root if ostf else self.api_root) + api
|
||||
self.print_debug(
|
||||
"GET {0}"
|
||||
.format(url)
|
||||
)
|
||||
|
||||
headers = {'x-auth-token': self.auth_token}
|
||||
resp = requests.get(url, headers=headers)
|
||||
resp.raise_for_status()
|
||||
|
||||
return resp.json()
|
||||
|
||||
def post_request_raw(self, api, data, ostf=False):
|
||||
url = (self.ostf_root if ostf else self.api_root) + api
|
||||
data_json = json.dumps(data)
|
||||
self.print_debug(
|
||||
"POST {0} data={1}"
|
||||
.format(url, data_json)
|
||||
)
|
||||
|
||||
headers = {'content-type': 'application/json',
|
||||
'x-auth-token': self.auth_token}
|
||||
return requests.post(url, data=data_json, headers=headers)
|
||||
|
||||
@exceptions_decorator
|
||||
def post_request(self, api, data, ostf=False):
|
||||
"""Make POST request to specific API with some data
|
||||
"""
|
||||
resp = self.post_request_raw(api, data, ostf=ostf)
|
||||
resp.raise_for_status()
|
||||
|
||||
return resp.json()
|
||||
|
||||
@exceptions_decorator
|
||||
def get_fuel_version(self):
|
||||
return yaml.safe_dump(
|
||||
self.get_request("version"),
|
||||
default_flow_style=False
|
||||
)
|
||||
|
||||
# This line is single point of instantiation for 'Client' class,
|
||||
# which intended to implement Singleton design pattern.
|
||||
APIClient = Client()
|
|
@ -1,90 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2013-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
|
||||
|
||||
from fuelclient.cli import error
|
||||
|
||||
|
||||
_SETTINGS = None
|
||||
|
||||
|
||||
class FuelClientSettings(object):
|
||||
"""Represents a model of Fuel Clients settings
|
||||
|
||||
Default settigs file are distributed with the source code in
|
||||
the <DIST_DIR>/fuelclient_settings.yaml.
|
||||
|
||||
User-specific settings may be stored in any YAML-formatted file
|
||||
the path to which should be supplied via the FUELCLIENT_CUSTOM_SETTINGS
|
||||
environment variable. Custom settins override the default ones.
|
||||
|
||||
NOTE: This is not to be confused with the API client settings which
|
||||
requires a different configuration file.
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
settings_files = []
|
||||
|
||||
# Look up for a default file distributed with the source code
|
||||
project_path = os.path.dirname(__file__)
|
||||
project_settings_file = os.path.join(project_path,
|
||||
'fuelclient_settings.yaml')
|
||||
settings_files.append(project_settings_file)
|
||||
|
||||
# Check whether a user specified a custom settings file
|
||||
test_config = os.environ.get('FUELCLIENT_CUSTOM_SETTINGS')
|
||||
if test_config:
|
||||
settings_files.append(test_config)
|
||||
|
||||
self.config = {}
|
||||
for sf in settings_files:
|
||||
try:
|
||||
self._update_from_file(sf)
|
||||
except Exception as e:
|
||||
msg = ('Error while reading config file '
|
||||
'%(file)s: %(err)s') % {'file': sf, 'err': str(e)}
|
||||
|
||||
raise error.SettingsException(msg)
|
||||
|
||||
def _update_from_file(self, path):
|
||||
with open(path, 'r') as custom_config:
|
||||
self.config.update(
|
||||
yaml.load(custom_config.read())
|
||||
)
|
||||
|
||||
def dump(self):
|
||||
return yaml.dump(self.config)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self.config.get(name, None)
|
||||
|
||||
def __repr__(self):
|
||||
return '<settings object>'
|
||||
|
||||
|
||||
def _init_settings():
|
||||
global _SETTINGS
|
||||
_SETTINGS = FuelClientSettings()
|
||||
|
||||
|
||||
def get_settings():
|
||||
if _SETTINGS is None:
|
||||
_init_settings()
|
||||
|
||||
return _SETTINGS
|
|
@ -1,6 +0,0 @@
|
|||
# performance tests settings
|
||||
PERFORMANCE_PROFILING_TESTS: 0
|
||||
PERF_TESTS_PATHS:
|
||||
perf_tests_base: "/tmp/fuelclient_performance_tests/tests/"
|
||||
last_performance_test: "/tmp/fuelclient_performance_tests/tests/last/"
|
||||
perf_tests_results: "/tmp/fuelclient_performance_tests/results/"
|
|
@ -1,38 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
"""This handler does nothing. It's intended to be used to avoid the
|
||||
"No handlers could be found for logger XXX" one-off warning. This
|
||||
important for library code, which may contain code to log events.
|
||||
of the library does not configure logging, the one-off warning mig
|
||||
produced; to avoid this, the library developer simply needs to ins
|
||||
a NullHandler and add it to the top-level logger of the library mo
|
||||
package.
|
||||
|
||||
Taken from Python 2.7
|
||||
"""
|
||||
def handle(self, record):
|
||||
pass
|
||||
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
def createLock(self):
|
||||
self.lock = None
|
|
@ -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.
|
||||
|
||||
"""fuelclient.objects sub-module contains classes that mirror
|
||||
functionality from nailgun objects.
|
||||
"""
|
||||
|
||||
from fuelclient.objects.base import BaseObject
|
||||
from fuelclient.objects.environment import Environment
|
||||
from fuelclient.objects.node import Node
|
||||
from fuelclient.objects.node import NodeCollection
|
||||
from fuelclient.objects.release import Release
|
||||
from fuelclient.objects.task import DeployTask
|
||||
from fuelclient.objects.task import SnapshotTask
|
||||
from fuelclient.objects.task import Task
|
|
@ -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.
|
||||
|
||||
from fuelclient.cli.serializers import Serializer
|
||||
from fuelclient.client import APIClient
|
||||
|
||||
|
||||
class BaseObject(object):
|
||||
"""BaseObject class - base class for fuelclient.objects object classes
|
||||
|
||||
'class_api_path' - url path to object handler on Nailgun server.
|
||||
'instance_api_path' - url path template which formatted with object id
|
||||
returns only one serialized object.
|
||||
'connection' - 'Client' class instance from fuelclient.client
|
||||
"""
|
||||
class_api_path = None
|
||||
instance_api_path = None
|
||||
connection = APIClient
|
||||
|
||||
def __init__(self, obj_id, **kwargs):
|
||||
self.connection = APIClient
|
||||
self.serializer = Serializer(**kwargs)
|
||||
self.id = obj_id
|
||||
self._data = None
|
||||
|
||||
@classmethod
|
||||
def init_with_data(cls, data):
|
||||
instance = cls(data["id"])
|
||||
instance._data = data
|
||||
return instance
|
||||
|
||||
@classmethod
|
||||
def get_by_ids(cls, ids):
|
||||
return map(cls, ids)
|
||||
|
||||
def update(self):
|
||||
self._data = self.connection.get_request(
|
||||
self.instance_api_path.format(self.id))
|
||||
|
||||
def get_fresh_data(self):
|
||||
self.update()
|
||||
return self.data
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
if self._data is None:
|
||||
return self.get_fresh_data()
|
||||
else:
|
||||
return self._data
|
||||
|
||||
@classmethod
|
||||
def get_all_data(cls):
|
||||
return cls.connection.get_request(cls.class_api_path)
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
return map(cls.init_with_data, cls.get_all_data())
|
|
@ -1,379 +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 operator import attrgetter
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from fuelclient.cli.error import ActionException
|
||||
from fuelclient.cli.error import ArgumentException
|
||||
from fuelclient.cli.error import ServerDataException
|
||||
from fuelclient.cli.serializers import listdir_without_extensions
|
||||
from fuelclient.objects.base import BaseObject
|
||||
from fuelclient.objects.task import DeployTask
|
||||
from fuelclient.objects.task import Task
|
||||
|
||||
|
||||
class Environment(BaseObject):
|
||||
|
||||
class_api_path = "clusters/"
|
||||
instance_api_path = "clusters/{0}/"
|
||||
deployment_tasks_path = 'clusters/{0}/deployment_tasks'
|
||||
|
||||
@classmethod
|
||||
def create(cls, name, release_id, net, net_segment_type=None):
|
||||
data = {
|
||||
"nodes": [],
|
||||
"tasks": [],
|
||||
"name": name,
|
||||
"release_id": release_id
|
||||
}
|
||||
if net.lower() == "nova":
|
||||
data["net_provider"] = "nova_network"
|
||||
else:
|
||||
data["net_provider"] = "neutron"
|
||||
|
||||
if net_segment_type is None:
|
||||
raise ArgumentException(
|
||||
'"--net-segment-type" must be specified!')
|
||||
|
||||
data["net_segment_type"] = net_segment_type
|
||||
|
||||
data = cls.connection.post_request("clusters/", data)
|
||||
return cls.init_with_data(data)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Environment, self).__init__(*args, **kwargs)
|
||||
self._testruns_ids = []
|
||||
|
||||
def set(self, data):
|
||||
if data.get('mode'):
|
||||
data["mode"] = "ha_compact" \
|
||||
if data['mode'].lower() == "ha" else "multinode"
|
||||
|
||||
return self.connection.put_request(
|
||||
"clusters/{0}/".format(self.id),
|
||||
data
|
||||
)
|
||||
|
||||
def update_env(self):
|
||||
return Task.init_with_data(
|
||||
self.connection.put_request(
|
||||
"clusters/{0}/update/".format(self.id),
|
||||
{}
|
||||
)
|
||||
)
|
||||
|
||||
def delete(self):
|
||||
return self.connection.delete_request(
|
||||
"clusters/{0}/".format(self.id)
|
||||
)
|
||||
|
||||
def assign(self, nodes, roles):
|
||||
return self.connection.post_request(
|
||||
"clusters/{0}/assignment/".format(self.id),
|
||||
[{'id': node.id, 'roles': roles} for node in nodes]
|
||||
)
|
||||
|
||||
def unassign(self, nodes):
|
||||
return self.connection.post_request(
|
||||
"clusters/{0}/unassignment/".format(self.id),
|
||||
[{"id": n} for n in nodes]
|
||||
)
|
||||
|
||||
def get_all_nodes(self):
|
||||
from fuelclient.objects.node import Node
|
||||
return sorted(map(
|
||||
Node.init_with_data,
|
||||
self.connection.get_request(
|
||||
"nodes/?cluster_id={0}".format(self.id)
|
||||
)
|
||||
), key=attrgetter)
|
||||
|
||||
def unassign_all(self):
|
||||
nodes = self.get_all_nodes()
|
||||
if not nodes:
|
||||
raise ActionException(
|
||||
"Environment with id={0} doesn't have nodes to remove."
|
||||
.format(self.id)
|
||||
)
|
||||
return self.connection.post_request(
|
||||
"clusters/{0}/unassignment/".format(self.id),
|
||||
[{"id": n.id} for n in nodes]
|
||||
)
|
||||
|
||||
def deploy_changes(self):
|
||||
deploy_data = self.connection.put_request(
|
||||
"clusters/{0}/changes".format(self.id),
|
||||
{}
|
||||
)
|
||||
return DeployTask.init_with_data(deploy_data)
|
||||
|
||||
def get_network_data_path(self, directory=os.curdir):
|
||||
return os.path.join(
|
||||
os.path.abspath(directory),
|
||||
"network_{0}".format(self.id)
|
||||
)
|
||||
|
||||
def get_settings_data_path(self, directory=os.curdir):
|
||||
return os.path.join(
|
||||
os.path.abspath(directory),
|
||||
"settings_{0}".format(self.id)
|
||||
)
|
||||
|
||||
def write_network_data(self, network_data, directory=os.curdir,
|
||||
serializer=None):
|
||||
return (serializer or self.serializer).write_to_file(
|
||||
self.get_network_data_path(directory),
|
||||
network_data
|
||||
)
|
||||
|
||||
def write_settings_data(self, settings_data, directory=os.curdir,
|
||||
serializer=None):
|
||||
return (serializer or self.serializer).write_to_file(
|
||||
self.get_settings_data_path(directory),
|
||||
settings_data
|
||||
)
|
||||
|
||||
def read_network_data(self, directory=os.curdir,
|
||||
serializer=None):
|
||||
network_file_path = self.get_network_data_path(directory)
|
||||
return (serializer or self.serializer).read_from_file(
|
||||
network_file_path)
|
||||
|
||||
def read_settings_data(self, directory=os.curdir, serializer=None):
|
||||
settings_file_path = self.get_settings_data_path(directory)
|
||||
return (serializer or self.serializer).read_from_file(
|
||||
settings_file_path)
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self.get_fresh_data()['status']
|
||||
|
||||
@property
|
||||
def settings_url(self):
|
||||
return "clusters/{0}/attributes".format(self.id)
|
||||
|
||||
@property
|
||||
def default_settings_url(self):
|
||||
return self.settings_url + "/defaults"
|
||||
|
||||
@property
|
||||
def network_url(self):
|
||||
return "clusters/{id}/network_configuration/{net_provider}".format(
|
||||
**self.data
|
||||
)
|
||||
|
||||
@property
|
||||
def network_verification_url(self):
|
||||
return self.network_url + "/verify"
|
||||
|
||||
def get_network_data(self):
|
||||
return self.connection.get_request(self.network_url)
|
||||
|
||||
def get_settings_data(self):
|
||||
return self.connection.get_request(self.settings_url)
|
||||
|
||||
def get_default_settings_data(self):
|
||||
return self.connection.get_request(self.default_settings_url)
|
||||
|
||||
def set_network_data(self, data):
|
||||
return self.connection.put_request(
|
||||
self.network_url, data)
|
||||
|
||||
def set_settings_data(self, data):
|
||||
return self.connection.put_request(
|
||||
self.settings_url, data)
|
||||
|
||||
def verify_network(self):
|
||||
return self.connection.put_request(
|
||||
self.network_verification_url, self.get_network_data())
|
||||
|
||||
def _get_fact_dir_name(self, fact_type, directory=os.path.curdir):
|
||||
return os.path.join(
|
||||
os.path.abspath(directory),
|
||||
"{0}_{1}".format(fact_type, self.id))
|
||||
|
||||
def _get_fact_default_url(self, fact_type, nodes=None):
|
||||
default_url = "clusters/{0}/orchestrator/{1}/defaults".format(
|
||||
self.id,
|
||||
fact_type
|
||||
)
|
||||
if nodes is not None:
|
||||
default_url += "/?nodes=" + ",".join(map(str, nodes))
|
||||
return default_url
|
||||
|
||||
def _get_fact_url(self, fact_type, nodes=None):
|
||||
fact_url = "clusters/{0}/orchestrator/{1}/".format(
|
||||
self.id,
|
||||
fact_type
|
||||
)
|
||||
if nodes is not None:
|
||||
fact_url += "/?nodes=" + ",".join(map(str, nodes))
|
||||
return fact_url
|
||||
|
||||
def get_default_facts(self, fact_type, nodes=None):
|
||||
facts = self.connection.get_request(
|
||||
self._get_fact_default_url(fact_type, nodes=nodes))
|
||||
if not facts:
|
||||
raise ServerDataException(
|
||||
"There is no {0} info for this "
|
||||
"environment!".format(fact_type)
|
||||
)
|
||||
return facts
|
||||
|
||||
def get_facts(self, fact_type, nodes=None):
|
||||
facts = self.connection.get_request(
|
||||
self._get_fact_url(fact_type, nodes=nodes))
|
||||
if not facts:
|
||||
raise ServerDataException(
|
||||
"There is no {0} info for this "
|
||||
"environment!".format(fact_type)
|
||||
)
|
||||
return facts
|
||||
|
||||
def upload_facts(self, fact_type, facts):
|
||||
self.connection.put_request(self._get_fact_url(fact_type), facts)
|
||||
|
||||
def delete_facts(self, fact_type):
|
||||
self.connection.delete_request(self._get_fact_url(fact_type))
|
||||
|
||||
def read_fact_info(self, fact_type, directory, serializer=None):
|
||||
return getattr(
|
||||
self, "read_{0}_info".format(fact_type)
|
||||
)(fact_type, directory=directory, serializer=serializer)
|
||||
|
||||
def write_facts_to_dir(self, fact_type, facts,
|
||||
directory=os.path.curdir, serializer=None):
|
||||
dir_name = self._get_fact_dir_name(fact_type, directory=directory)
|
||||
if os.path.exists(dir_name):
|
||||
shutil.rmtree(dir_name)
|
||||
os.makedirs(dir_name)
|
||||
if isinstance(facts, dict):
|
||||
engine_file_path = os.path.join(dir_name, "engine")
|
||||
(serializer or self.serializer).write_to_file(
|
||||
engine_file_path, facts["engine"])
|
||||
facts = facts["nodes"]
|
||||
name_template = u"{name}"
|
||||
else:
|
||||
name_template = "{role}_{uid}"
|
||||
for _fact in facts:
|
||||
fact_path = os.path.join(
|
||||
dir_name,
|
||||
name_template.format(**_fact)
|
||||
)
|
||||
(serializer or self.serializer).write_to_file(fact_path, _fact)
|
||||
return dir_name
|
||||
|
||||
def read_deployment_info(self, fact_type,
|
||||
directory=os.path.curdir, serializer=None):
|
||||
dir_name = self._get_fact_dir_name(fact_type, directory=directory)
|
||||
return map(
|
||||
lambda f: (serializer or self.serializer).read_from_file(f),
|
||||
[os.path.join(dir_name, json_file)
|
||||
for json_file in listdir_without_extensions(dir_name)]
|
||||
)
|
||||
|
||||
def read_provisioning_info(self, fact_type,
|
||||
directory=os.path.curdir, serializer=None):
|
||||
dir_name = self._get_fact_dir_name(fact_type, directory=directory)
|
||||
node_facts = map(
|
||||
lambda f: (serializer or self.serializer).read_from_file(f),
|
||||
[os.path.join(dir_name, fact_file)
|
||||
for fact_file in listdir_without_extensions(dir_name)
|
||||
if "engine" != fact_file]
|
||||
)
|
||||
engine = (serializer or self.serializer).read_from_file(
|
||||
os.path.join(dir_name, "engine"))
|
||||
return {
|
||||
"engine": engine,
|
||||
"nodes": node_facts
|
||||
}
|
||||
|
||||
def get_testsets(self):
|
||||
return self.connection.get_request(
|
||||
'testsets/{0}'.format(self.id),
|
||||
ostf=True
|
||||
)
|
||||
|
||||
@property
|
||||
def is_customized(self):
|
||||
data = self.get_fresh_data()
|
||||
return data["is_customized"]
|
||||
|
||||
def is_in_running_test_sets(self, test_set):
|
||||
return test_set["testset"] in self._test_sets_to_run
|
||||
|
||||
def run_test_sets(self, test_sets_to_run):
|
||||
self._test_sets_to_run = test_sets_to_run
|
||||
tests_data = map(
|
||||
lambda testset: {
|
||||
"testset": testset,
|
||||
"metadata": {
|
||||
"config": {},
|
||||
"cluster_id": self.id
|
||||
}
|
||||
},
|
||||
test_sets_to_run
|
||||
)
|
||||
|
||||
testruns = self.connection.post_request(
|
||||
"testruns", tests_data, ostf=True)
|
||||
self._testruns_ids = [tr['id'] for tr in testruns]
|
||||
return testruns
|
||||
|
||||
def get_state_of_tests(self):
|
||||
return [
|
||||
self.connection.get_request(
|
||||
"testruns/{0}".format(testrun_id), ostf=True)
|
||||
for testrun_id in self._testruns_ids
|
||||
]
|
||||
|
||||
def stop(self):
|
||||
return Task.init_with_data(
|
||||
self.connection.put_request(
|
||||
"clusters/{0}/stop_deployment/".format(self.id),
|
||||
{}
|
||||
)
|
||||
)
|
||||
|
||||
def reset(self):
|
||||
return Task.init_with_data(
|
||||
self.connection.put_request(
|
||||
"clusters/{0}/reset/".format(self.id),
|
||||
{}
|
||||
)
|
||||
)
|
||||
|
||||
def _get_method_url(self, method_type, nodes):
|
||||
return "clusters/{0}/{1}/?nodes={2}".format(
|
||||
self.id,
|
||||
method_type,
|
||||
','.join(map(lambda n: str(n.id), nodes)))
|
||||
|
||||
def install_selected_nodes(self, method_type, nodes):
|
||||
return Task.init_with_data(
|
||||
self.connection.put_request(
|
||||
self._get_method_url(method_type, nodes),
|
||||
{}
|
||||
)
|
||||
)
|
||||
|
||||
def get_deployment_tasks(self):
|
||||
url = self.deployment_tasks_path.format(self.id)
|
||||
return self.connection.get_request(url)
|
||||
|
||||
def update_deployment_tasks(self, data):
|
||||
url = self.deployment_tasks_path.format(self.id)
|
||||
return self.connection.put_request(url, data)
|
|
@ -1,156 +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 operator import attrgetter
|
||||
import os
|
||||
|
||||
from fuelclient.cli.error import exit_with_error
|
||||
from fuelclient.objects.base import BaseObject
|
||||
from fuelclient.objects.environment import Environment
|
||||
|
||||
|
||||
class Node(BaseObject):
|
||||
|
||||
class_api_path = "nodes/"
|
||||
instance_api_path = "nodes/{0}/"
|
||||
|
||||
attributes_urls = {
|
||||
"interfaces": ("interfaces", "default_assignment"),
|
||||
"disks": ("disks", "defaults")
|
||||
}
|
||||
|
||||
@property
|
||||
def env_id(self):
|
||||
return self.get_fresh_data()["cluster"]
|
||||
|
||||
@property
|
||||
def env(self):
|
||||
return Environment(self.env_id)
|
||||
|
||||
def get_attributes_path(self, directory):
|
||||
return os.path.join(
|
||||
os.path.abspath(
|
||||
os.curdir if directory is None else directory),
|
||||
"node_{0}".format(self.id)
|
||||
)
|
||||
|
||||
def is_finished(self, latest=True):
|
||||
if latest:
|
||||
data = self.get_fresh_data()
|
||||
else:
|
||||
data = self.data
|
||||
return data["status"] in ("ready", "error")
|
||||
|
||||
@property
|
||||
def progress(self):
|
||||
data = self.get_fresh_data()
|
||||
return data["progress"]
|
||||
|
||||
def get_attribute_default_url(self, attributes_type):
|
||||
url_path, default_url_path = self.attributes_urls[attributes_type]
|
||||
return "nodes/{0}/{1}/{2}".format(self.id, url_path, default_url_path)
|
||||
|
||||
def get_attribute_url(self, attributes_type):
|
||||
url_path, _ = self.attributes_urls[attributes_type]
|
||||
return "nodes/{0}/{1}/".format(self.id, url_path)
|
||||
|
||||
def get_default_attribute(self, attributes_type):
|
||||
return self.connection.get_request(
|
||||
self.get_attribute_default_url(attributes_type)
|
||||
)
|
||||
|
||||
def get_attribute(self, attributes_type):
|
||||
return self.connection.get_request(
|
||||
self.get_attribute_url(attributes_type)
|
||||
)
|
||||
|
||||
def upload_node_attribute(self, attributes_type, attributes):
|
||||
url = self.get_attribute_url(attributes_type)
|
||||
return self.connection.put_request(
|
||||
url,
|
||||
attributes
|
||||
)
|
||||
|
||||
def write_attribute(self, attribute_type, attributes,
|
||||
directory, serializer=None):
|
||||
attributes_directory = self.get_attributes_path(directory)
|
||||
if not os.path.exists(attributes_directory):
|
||||
os.mkdir(attributes_directory)
|
||||
attribute_path = os.path.join(
|
||||
attributes_directory,
|
||||
attribute_type
|
||||
)
|
||||
if os.path.exists(attribute_path):
|
||||
os.remove(attribute_path)
|
||||
return (serializer or self.serializer).write_to_file(
|
||||
attribute_path,
|
||||
attributes
|
||||
)
|
||||
|
||||
def read_attribute(self, attributes_type, directory, serializer=None):
|
||||
attributes_directory = self.get_attributes_path(directory)
|
||||
if not os.path.exists(attributes_directory):
|
||||
exit_with_error(
|
||||
"Folder {0} doesn't contain node folder '{1}'"
|
||||
.format(directory, "node_{0}".format(self.id))
|
||||
)
|
||||
return (serializer or self.serializer).read_from_file(
|
||||
os.path.join(
|
||||
attributes_directory,
|
||||
attributes_type
|
||||
)
|
||||
)
|
||||
|
||||
def deploy(self):
|
||||
self.env.install_selected_nodes("deploy", (self,))
|
||||
|
||||
def provision(self):
|
||||
self.env.install_selected_nodes("provision", (self,))
|
||||
|
||||
def delete(self):
|
||||
self.connection.delete_request(self.instance_api_path.format(self.id))
|
||||
|
||||
|
||||
class NodeCollection(object):
|
||||
|
||||
def __init__(self, nodes):
|
||||
self.collection = nodes
|
||||
|
||||
@classmethod
|
||||
def init_with_ids(cls, ids):
|
||||
return cls(map(Node, ids))
|
||||
|
||||
@classmethod
|
||||
def init_with_data(cls, data):
|
||||
return cls(map(Node.init_with_data, data))
|
||||
|
||||
def __str__(self):
|
||||
return "nodes [{0}]".format(
|
||||
", ".join(map(lambda n: str(n.id), self.collection))
|
||||
)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.collection)
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return map(attrgetter("data"), self.collection)
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
return cls(Node.get_all())
|
||||
|
||||
def filter_by_env_id(self, env_id):
|
||||
predicate = lambda node: node.data['cluster'] == env_id
|
||||
self.collection = filter(predicate, self.collection)
|
|
@ -1,85 +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 operator import attrgetter
|
||||
|
||||
from fuelclient.objects.base import BaseObject
|
||||
|
||||
|
||||
class NodeGroup(BaseObject):
|
||||
|
||||
class_api_path = "nodegroups/"
|
||||
instance_api_path = "nodegroups/{0}/"
|
||||
|
||||
@property
|
||||
def env_id(self):
|
||||
return self.get_fresh_data()["cluster"]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.get_fresh_data()["name"]
|
||||
|
||||
@classmethod
|
||||
def create(cls, name, cluster_id):
|
||||
return cls.connection.post_request(
|
||||
cls.class_api_path,
|
||||
{'cluster_id': cluster_id, 'name': name},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, group_id):
|
||||
return cls.connection.delete_request(
|
||||
cls.instance_api_path.format(group_id)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def assign(cls, group_id, nodes):
|
||||
return cls.connection.post_request(
|
||||
cls.instance_api_path.format(group_id),
|
||||
nodes
|
||||
)
|
||||
|
||||
|
||||
class NodeGroupCollection(object):
|
||||
|
||||
def __init__(self, groups):
|
||||
self.collection = groups
|
||||
|
||||
@classmethod
|
||||
def init_with_ids(cls, ids):
|
||||
return cls(map(NodeGroup, ids))
|
||||
|
||||
@classmethod
|
||||
def init_with_data(cls, data):
|
||||
return cls(map(NodeGroup.init_with_data, data))
|
||||
|
||||
def __str__(self):
|
||||
return "node groups [{0}]".format(
|
||||
", ".join(map(lambda n: str(n.id), self.collection))
|
||||
)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.collection)
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return map(attrgetter("data"), self.collection)
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
return cls(NodeGroup.get_all())
|
||||
|
||||
def filter_by_env_id(self, env_id):
|
||||
predicate = lambda group: group.data['cluster'] == env_id
|
||||
self.collection = filter(predicate, self.collection)
|
|
@ -1,70 +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 tarfile
|
||||
|
||||
import yaml
|
||||
|
||||
from fuelclient.cli import error
|
||||
from fuelclient.objects import base
|
||||
|
||||
|
||||
EXTRACT_PATH = "/var/www/nailgun/plugins/"
|
||||
VERSIONS_PATH = '/etc/fuel/version.yaml'
|
||||
|
||||
|
||||
class Plugins(base.BaseObject):
|
||||
|
||||
class_api_path = "plugins/"
|
||||
class_instance_path = "plugins/{id}"
|
||||
|
||||
metadata_config = 'metadata.yaml'
|
||||
|
||||
@classmethod
|
||||
def validate_environment(cls):
|
||||
return os.path.exists(VERSIONS_PATH)
|
||||
|
||||
@classmethod
|
||||
def get_metadata(cls, plugin_tar):
|
||||
for member_name in plugin_tar.getnames():
|
||||
if cls.metadata_config in member_name:
|
||||
return yaml.load(plugin_tar.extractfile(member_name).read())
|
||||
raise error.BadDataException("Tarfile {0} doesn't have {1}".format(
|
||||
plugin_tar.name, cls.metadata_config))
|
||||
|
||||
@classmethod
|
||||
def add_plugin(cls, plugin_meta, plugin_tar):
|
||||
return plugin_tar.extractall(EXTRACT_PATH)
|
||||
|
||||
@classmethod
|
||||
def install_plugin(cls, plugin_path, force=False):
|
||||
if not cls.validate_environment():
|
||||
raise error.WrongEnvironmentError(
|
||||
'Plugin can be installed only from master node.')
|
||||
plugin_tar = tarfile.open(plugin_path, 'r')
|
||||
try:
|
||||
metadata = cls.get_metadata(plugin_tar)
|
||||
resp = cls.connection.post_request_raw(
|
||||
cls.class_api_path, metadata)
|
||||
if resp.status_code == 409 and force:
|
||||
url = cls.class_instance_path.format(id=resp.json()['id'])
|
||||
resp = cls.connection.put_request(
|
||||
url, metadata)
|
||||
else:
|
||||
resp.raise_for_status()
|
||||
cls.add_plugin(metadata, plugin_tar)
|
||||
finally:
|
||||
plugin_tar.close()
|
||||
return resp
|
|
@ -1,43 +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 fuelclient.objects.base import BaseObject
|
||||
|
||||
|
||||
class Release(BaseObject):
|
||||
|
||||
class_api_path = "releases/"
|
||||
instance_api_path = "releases/{0}/"
|
||||
networks_path = 'releases/{0}/networks'
|
||||
deployment_tasks_path = 'releases/{0}/deployment_tasks'
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
map(cls.init_with_data, cls.get_all_data())
|
||||
|
||||
def get_networks(self):
|
||||
url = self.networks_path.format(self.id)
|
||||
return self.connection.get_request(url)
|
||||
|
||||
def update_networks(self, data):
|
||||
url = self.networks_path.format(self.id)
|
||||
return self.connection.put_request(url, data)
|
||||
|
||||
def get_deployment_tasks(self):
|
||||
url = self.deployment_tasks_path.format(self.id)
|
||||
return self.connection.get_request(url)
|
||||
|
||||
def update_deployment_tasks(self, data):
|
||||
url = self.deployment_tasks_path.format(self.id)
|
||||
return self.connection.put_request(url, data)
|
|
@ -1,100 +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 operator import methodcaller
|
||||
from time import sleep
|
||||
|
||||
from fuelclient.cli.error import DeployProgressError
|
||||
from fuelclient.objects.base import BaseObject
|
||||
|
||||
|
||||
class Task(BaseObject):
|
||||
|
||||
class_api_path = "tasks/"
|
||||
instance_api_path = "tasks/{0}/"
|
||||
|
||||
def delete(self, force=False):
|
||||
return self.connection.delete_request(
|
||||
"tasks/{0}/?force={1}".format(
|
||||
self.id,
|
||||
int(force),
|
||||
))
|
||||
|
||||
@property
|
||||
def progress(self):
|
||||
return self.get_fresh_data()["progress"]
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self.get_fresh_data()["status"]
|
||||
|
||||
@property
|
||||
def is_finished(self):
|
||||
return self.status in ("ready", "error")
|
||||
|
||||
def wait(self):
|
||||
while not self.is_finished:
|
||||
sleep(0.5)
|
||||
|
||||
|
||||
class DeployTask(Task):
|
||||
|
||||
def __init__(self, obj_id, env_id):
|
||||
from fuelclient.objects.environment import Environment
|
||||
super(DeployTask, self).__init__(obj_id)
|
||||
self.env = Environment(env_id)
|
||||
self.nodes = self.env.get_all_nodes()
|
||||
|
||||
@classmethod
|
||||
def init_with_data(cls, data):
|
||||
return cls(data["id"], data["cluster"])
|
||||
|
||||
@property
|
||||
def not_finished_nodes(self):
|
||||
return filter(
|
||||
lambda n: not n.is_finished(latest=False),
|
||||
self.nodes
|
||||
)
|
||||
|
||||
@property
|
||||
def is_finished(self):
|
||||
return super(DeployTask, self).is_finished and all(
|
||||
map(
|
||||
methodcaller("is_finished"),
|
||||
self.not_finished_nodes
|
||||
)
|
||||
)
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
if not self.is_finished:
|
||||
sleep(1)
|
||||
deploy_task_data = self.get_fresh_data()
|
||||
if deploy_task_data["status"] == "error":
|
||||
raise DeployProgressError(deploy_task_data["message"])
|
||||
for node in self.not_finished_nodes:
|
||||
node.update()
|
||||
return self.progress, self.nodes
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
|
||||
class SnapshotTask(Task):
|
||||
|
||||
@classmethod
|
||||
def start_snapshot_task(cls):
|
||||
dump_task = cls.connection.put_request("logs/package", {})
|
||||
return cls(dump_task["id"])
|
|
@ -1,98 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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 cProfile
|
||||
import os
|
||||
from pstats import Stats
|
||||
import time
|
||||
|
||||
from fuelclient.cli import error
|
||||
from fuelclient import fuelclient_settings
|
||||
|
||||
|
||||
def profiling_enabled():
|
||||
settings = fuelclient_settings.get_settings()
|
||||
return bool(settings.PERFORMANCE_PROFILING_TESTS)
|
||||
|
||||
|
||||
class Profiler(object):
|
||||
"""Runs profiler and saves results."""
|
||||
|
||||
def __init__(self, method='', handler_name=''):
|
||||
self.method = method
|
||||
self.handler_name = handler_name
|
||||
settings = fuelclient_settings.get_settings()
|
||||
self.paths = settings.PERF_TESTS_PATHS
|
||||
|
||||
if not os.path.exists(self.paths['last_performance_test']):
|
||||
os.makedirs(self.paths['last_performance_test'])
|
||||
|
||||
self.profiler = cProfile.Profile()
|
||||
self.profiler.enable()
|
||||
self.start = time.time()
|
||||
|
||||
def save_data(self):
|
||||
try:
|
||||
import gprof2dot
|
||||
import pyprof2calltree
|
||||
except ImportError:
|
||||
msg = ('Unable to start profiling.\n Please either '
|
||||
'disable performance profiling in settings.yaml or '
|
||||
'install all modules listed in test-requirements.txt.')
|
||||
raise error.ProfilingError(msg)
|
||||
|
||||
self.profiler.disable()
|
||||
elapsed = time.time() - self.start
|
||||
pref_filename = os.path.join(
|
||||
self.paths['last_performance_test'],
|
||||
'{method:s}.{handler_name:s}.{elapsed_time:.0f}ms.{t_time}.'.
|
||||
format(
|
||||
method=self.method,
|
||||
handler_name=self.handler_name or 'root',
|
||||
elapsed_time=elapsed * 1000.0,
|
||||
t_time=time.time()))
|
||||
tree_file = pref_filename + 'prof'
|
||||
stats_file = pref_filename + 'txt'
|
||||
callgraph_file = pref_filename + 'dot'
|
||||
|
||||
# write pstats
|
||||
with file(stats_file, 'w') as file_o:
|
||||
stats = Stats(self.profiler, stream=file_o)
|
||||
stats.sort_stats('time', 'cumulative').print_stats()
|
||||
|
||||
# write callgraph in dot format
|
||||
parser = gprof2dot.PstatsParser(self.profiler)
|
||||
|
||||
def get_function_name((filename, line, name)):
|
||||
module = os.path.splitext(filename)[0]
|
||||
module_pieces = module.split(os.path.sep)
|
||||
return "{module:s}:{line:d}:{name:s}".format(
|
||||
module="/".join(module_pieces[-4:]),
|
||||
line=line,
|
||||
name=name)
|
||||
|
||||
parser.get_function_name = get_function_name
|
||||
gprof = parser.parse()
|
||||
|
||||
with open(callgraph_file, 'w') as file_o:
|
||||
dot = gprof2dot.DotWriter(file_o)
|
||||
theme = gprof2dot.TEMPERATURE_COLORMAP
|
||||
dot.graph(gprof, theme)
|
||||
|
||||
# write calltree
|
||||
call_tree = pyprof2calltree.CalltreeConverter(stats)
|
||||
with file(tree_file, 'wb') as file_o:
|
||||
call_tree.output(file_o)
|
|
@ -1,13 +0,0 @@
|
|||
# Copyright 2013 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.
|
|
@ -1,165 +0,0 @@
|
|||
# Copyright 2013-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.
|
||||
|
||||
try:
|
||||
from unittest.case import TestCase
|
||||
except ImportError:
|
||||
# Runing unit-tests in production environment
|
||||
from unittest2.case import TestCase
|
||||
|
||||
from mock import patch
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from fuelclient.cli.parser import main
|
||||
|
||||
|
||||
logging.basicConfig(stream=sys.stderr)
|
||||
log = logging.getLogger("CliTest.ExecutionLog")
|
||||
log.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
class CliExectutionResult:
|
||||
def __init__(self, process_handle, out, err):
|
||||
self.return_code = process_handle.returncode
|
||||
self.stdout = out
|
||||
self.stderr = err
|
||||
|
||||
@property
|
||||
def has_errors(self):
|
||||
return bool(len(self.stderr))
|
||||
|
||||
@property
|
||||
def is_return_code_zero(self):
|
||||
return self.return_code == 0
|
||||
|
||||
|
||||
class UnitTestCase(TestCase):
|
||||
"""Base test class which does not require nailgun server to run."""
|
||||
|
||||
def execute(self, command):
|
||||
return main(command)
|
||||
|
||||
def execute_wo_auth(self, command):
|
||||
with patch('fuelclient.client.Client.auth_required') as auth:
|
||||
auth.return_value = False
|
||||
return self.execute(command)
|
||||
|
||||
|
||||
class BaseTestCase(UnitTestCase):
|
||||
root_path = os.path.abspath(
|
||||
os.path.join(
|
||||
os.curdir,
|
||||
os.path.pardir
|
||||
)
|
||||
)
|
||||
manage_path = os.path.join(
|
||||
root_path,
|
||||
"nailgun/manage.py"
|
||||
)
|
||||
fuel_path = os.path.join(
|
||||
root_path,
|
||||
"fuelclient/fuel"
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
self.reload_nailgun_server()
|
||||
self.temp_directory = tempfile.mkdtemp()
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.temp_directory)
|
||||
|
||||
@staticmethod
|
||||
def run_command(*args):
|
||||
handle = subprocess.Popen(
|
||||
[" ".join(args)],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
shell=True
|
||||
)
|
||||
log.debug("Running " + " ".join(args))
|
||||
out, err = handle.communicate()
|
||||
log.debug("Finished command with {0} - {1}".format(out, err))
|
||||
|
||||
def upload_command(self, cmd):
|
||||
return "{0} --upload --dir {1}".format(cmd, self.temp_directory)
|
||||
|
||||
def download_command(self, cmd):
|
||||
return "{0} --download --dir {1}".format(cmd, self.temp_directory)
|
||||
|
||||
@classmethod
|
||||
def reload_nailgun_server(cls):
|
||||
for action in ("dropdb", "syncdb", "loaddefault"):
|
||||
cls.run_command(cls.manage_path, action)
|
||||
|
||||
@classmethod
|
||||
def load_data_to_nailgun_server(cls):
|
||||
cls.run_command(cls.manage_path, "loaddata {0}".format(
|
||||
os.path.join(
|
||||
cls.root_path,
|
||||
"nailgun/nailgun/fixtures/sample_environment.json"
|
||||
)
|
||||
))
|
||||
|
||||
def run_cli_command(self, command_line, check_errors=False):
|
||||
modified_env = os.environ.copy()
|
||||
command_args = [" ".join((self.fuel_path, command_line))]
|
||||
process_handle = subprocess.Popen(
|
||||
command_args,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
shell=True,
|
||||
env=modified_env
|
||||
)
|
||||
out, err = process_handle.communicate()
|
||||
result = CliExectutionResult(process_handle, out, err)
|
||||
log.debug("command_args: '%s',stdout: '%s', stderr: '%s'",
|
||||
command_args[0], out, err)
|
||||
if not check_errors:
|
||||
if not result.is_return_code_zero or result.has_errors:
|
||||
self.fail(err)
|
||||
return result
|
||||
|
||||
def run_cli_commands(self, command_lines, **kwargs):
|
||||
for command in command_lines:
|
||||
self.run_cli_command(command, **kwargs)
|
||||
|
||||
def check_if_required(self, command):
|
||||
call = self.run_cli_command(command, check_errors=True)
|
||||
#should not work without env id
|
||||
self.assertIn("required", call.stderr)
|
||||
|
||||
def check_for_stdout(self, command, msg):
|
||||
call = self.run_cli_command(command)
|
||||
self.assertEqual(call.stdout, msg)
|
||||
|
||||
def check_all_in_msg(self, command, substrings, **kwargs):
|
||||
output = self.run_cli_command(command, **kwargs)
|
||||
for substring in substrings:
|
||||
self.assertIn(substring, output.stdout)
|
||||
|
||||
def check_for_rows_in_table(self, command):
|
||||
output = self.run_cli_command(command)
|
||||
message = output.stdout.split("\n")
|
||||
#no env
|
||||
self.assertEqual(message[2], '')
|
||||
|
||||
def check_number_of_rows_in_table(self, command, number_of_rows):
|
||||
output = self.run_cli_command(command)
|
||||
self.assertEqual(len(output.stdout.split("\n")), number_of_rows + 3)
|
|
@ -1,322 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013-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 mock import Mock
|
||||
from mock import patch
|
||||
|
||||
from fuelclient.tests import base
|
||||
|
||||
|
||||
class TestHandlers(base.BaseTestCase):
|
||||
|
||||
def test_env_action(self):
|
||||
#check env help
|
||||
help_msgs = ["usage: fuel environment [-h]",
|
||||
"[--list | --set | --delete | --create | --update]",
|
||||
"optional arguments:", "--help", "--list", "--set",
|
||||
"--delete", "--rel", "--env-create",
|
||||
"--create", "--name", "--env-name", "--mode", "--net",
|
||||
"--network-mode", "--nst", "--net-segment-type",
|
||||
"--deployment-mode", "--update", "--env-update"]
|
||||
self.check_all_in_msg("env --help", help_msgs)
|
||||
#no clusters
|
||||
self.check_for_rows_in_table("env")
|
||||
|
||||
for action in ("set", "create", "delete"):
|
||||
self.check_if_required("env {0}".format(action))
|
||||
|
||||
#list of tuples (<fuel CLI command>, <expected output of a command>)
|
||||
expected_stdout = \
|
||||
[(
|
||||
"env --create --name=TestEnv --release=1",
|
||||
"Environment 'TestEnv' with id=1, mode=ha_compact and "
|
||||
"network-mode=nova_network was created!\n"
|
||||
), (
|
||||
"--env-id=1 env set --name=NewEnv",
|
||||
("Following attributes are changed for "
|
||||
"the environment: name=NewEnv\n")
|
||||
), (
|
||||
"--env-id=1 env set --mode=multinode",
|
||||
("Following attributes are changed for "
|
||||
"the environment: mode=multinode\n")
|
||||
)]
|
||||
|
||||
for cmd, msg in expected_stdout:
|
||||
self.check_for_stdout(cmd, msg)
|
||||
|
||||
def test_node_action(self):
|
||||
help_msg = ["fuel node [-h] [--env ENV]",
|
||||
"[--list | --set | --delete | --network | --disk |"
|
||||
" --deploy | --delete-from-db | --provision]", "-h",
|
||||
"--help", " -s", "--default", " -d", "--download", " -u",
|
||||
"--upload", "--dir", "--node", "--node-id", " -r",
|
||||
"--role", "--net"]
|
||||
self.check_all_in_msg("node --help", help_msg)
|
||||
|
||||
self.check_for_rows_in_table("node")
|
||||
|
||||
for action in ("set", "remove", "--network", "--disk"):
|
||||
self.check_if_required("node {0}".format(action))
|
||||
|
||||
self.load_data_to_nailgun_server()
|
||||
self.check_number_of_rows_in_table("node --node 9f:b7,9d:24,ab:aa", 3)
|
||||
|
||||
def test_selected_node_deploy_or_provision(self):
|
||||
self.load_data_to_nailgun_server()
|
||||
self.run_cli_commands((
|
||||
"env create --name=NewEnv --release=1",
|
||||
"--env-id=1 node set --node 1 --role=controller"
|
||||
))
|
||||
commands = ("--provision", "--deploy")
|
||||
for action in commands:
|
||||
self.check_if_required("--env-id=1 node {0}".format(action))
|
||||
messages = (
|
||||
"Started provisioning nodes [1].\n",
|
||||
"Started deploying nodes [1].\n"
|
||||
)
|
||||
for cmd, msg in zip(commands, messages):
|
||||
self.check_for_stdout(
|
||||
"--env-id=1 node {0} --node=1".format(cmd),
|
||||
msg
|
||||
)
|
||||
|
||||
def test_check_wrong_server(self):
|
||||
os.environ["SERVER_ADDRESS"] = "0"
|
||||
result = self.run_cli_command("-h", check_errors=True)
|
||||
self.assertEqual(result.stderr, '')
|
||||
del os.environ["SERVER_ADDRESS"]
|
||||
|
||||
def test_destroy_node(self):
|
||||
self.load_data_to_nailgun_server()
|
||||
self.run_cli_commands((
|
||||
"env create --name=NewEnv --release=1",
|
||||
"--env-id=1 node set --node 1 --role=controller"
|
||||
))
|
||||
msg = ("Nodes with id [1] has been deleted from fuel db.\n"
|
||||
"You should still delete node from cobbler\n")
|
||||
self.check_for_stdout(
|
||||
"node --node 1 --delete-from-db",
|
||||
msg
|
||||
)
|
||||
|
||||
def test_for_examples_in_action_help(self):
|
||||
actions = (
|
||||
"node", "stop", "deployment", "reset", "task", "network",
|
||||
"settings", "provisioning", "environment", "deploy-changes",
|
||||
"role", "release", "snapshot", "health"
|
||||
)
|
||||
for action in actions:
|
||||
self.check_all_in_msg("{0} -h".format(action), ("Examples",))
|
||||
|
||||
def test_task_action_urls(self):
|
||||
self.check_all_in_msg(
|
||||
"task --task-id 1 --debug",
|
||||
[
|
||||
"GET http://127.0.0.1",
|
||||
"/api/v1/tasks/1/"
|
||||
],
|
||||
check_errors=True
|
||||
)
|
||||
self.check_all_in_msg(
|
||||
"task --task-id 1 --delete --debug",
|
||||
[
|
||||
"DELETE http://127.0.0.1",
|
||||
"/api/v1/tasks/1/?force=0"
|
||||
],
|
||||
check_errors=True
|
||||
)
|
||||
self.check_all_in_msg(
|
||||
"task --task-id 1 --delete --force --debug",
|
||||
[
|
||||
"DELETE http://127.0.0.1",
|
||||
"/api/v1/tasks/1/?force=1"
|
||||
],
|
||||
check_errors=True
|
||||
)
|
||||
self.check_all_in_msg(
|
||||
"task --tid 1 --delete --debug",
|
||||
[
|
||||
"DELETE http://127.0.0.1",
|
||||
"/api/v1/tasks/1/?force=0"
|
||||
],
|
||||
check_errors=True
|
||||
)
|
||||
|
||||
def test_get_release_list_without_errors(self):
|
||||
cmd = 'release --list'
|
||||
self.run_cli_command(cmd)
|
||||
|
||||
|
||||
class TestUserActions(base.BaseTestCase):
|
||||
|
||||
def test_change_password_params(self):
|
||||
cmd = "user change-password"
|
||||
msg = "Expect password [--newpass NEWPASS]"
|
||||
result = self.run_cli_command(cmd, check_errors=True)
|
||||
self.assertTrue(msg, result)
|
||||
|
||||
|
||||
class TestCharset(base.BaseTestCase):
|
||||
|
||||
def test_charset_problem(self):
|
||||
self.load_data_to_nailgun_server()
|
||||
self.run_cli_commands((
|
||||
"env create --name=привет --release=1",
|
||||
"--env-id=1 node set --node 1 --role=controller",
|
||||
"env"
|
||||
))
|
||||
|
||||
|
||||
class TestFiles(base.BaseTestCase):
|
||||
|
||||
def test_file_creation(self):
|
||||
self.load_data_to_nailgun_server()
|
||||
self.run_cli_commands((
|
||||
"env create --name=NewEnv --release=1",
|
||||
"--env-id=1 node set --node 1 --role=controller",
|
||||
"--env-id=1 node set --node 2,3 --role=compute"
|
||||
))
|
||||
for action in ("network", "settings"):
|
||||
for format_ in ("yaml", "json"):
|
||||
self.check_if_files_created(
|
||||
"--env 1 {0} --download --{1}".format(action, format_),
|
||||
("{0}_1.{1}".format(action, format_),)
|
||||
)
|
||||
command_to_files_map = (
|
||||
(
|
||||
"--env 1 deployment --default",
|
||||
(
|
||||
"deployment_1",
|
||||
"deployment_1/primary-controller_1.yaml",
|
||||
"deployment_1/compute_2.yaml",
|
||||
"deployment_1/compute_3.yaml"
|
||||
)
|
||||
),
|
||||
(
|
||||
"--env 1 provisioning --default",
|
||||
(
|
||||
"provisioning_1",
|
||||
"provisioning_1/engine.yaml",
|
||||
"provisioning_1/node-1.yaml",
|
||||
"provisioning_1/node-2.yaml",
|
||||
"provisioning_1/node-3.yaml"
|
||||
)
|
||||
),
|
||||
(
|
||||
"--env 1 deployment --default --json",
|
||||
(
|
||||
"deployment_1/primary-controller_1.json",
|
||||
"deployment_1/compute_2.json",
|
||||
"deployment_1/compute_3.json"
|
||||
)
|
||||
),
|
||||
(
|
||||
"--env 1 provisioning --default --json",
|
||||
(
|
||||
"provisioning_1/engine.json",
|
||||
"provisioning_1/node-1.json",
|
||||
"provisioning_1/node-2.json",
|
||||
"provisioning_1/node-3.json"
|
||||
)
|
||||
),
|
||||
(
|
||||
"node --node 1 --disk --default",
|
||||
(
|
||||
"node_1",
|
||||
"node_1/disks.yaml"
|
||||
)
|
||||
),
|
||||
(
|
||||
"node --node 1 --network --default",
|
||||
(
|
||||
"node_1",
|
||||
"node_1/interfaces.yaml"
|
||||
)
|
||||
),
|
||||
(
|
||||
"node --node 1 --disk --default --json",
|
||||
(
|
||||
"node_1/disks.json",
|
||||
)
|
||||
),
|
||||
(
|
||||
"node --node 1 --network --default --json",
|
||||
(
|
||||
"node_1/interfaces.json",
|
||||
)
|
||||
)
|
||||
)
|
||||
for command, files in command_to_files_map:
|
||||
self.check_if_files_created(command, files)
|
||||
|
||||
def check_if_files_created(self, command, paths):
|
||||
command_in_dir = "{0} --dir={1}".format(command, self.temp_directory)
|
||||
self.run_cli_command(command_in_dir)
|
||||
for path in paths:
|
||||
self.assertTrue(os.path.exists(
|
||||
os.path.join(self.temp_directory, path)
|
||||
))
|
||||
|
||||
|
||||
class TestDownloadUploadNodeAttributes(base.BaseTestCase):
|
||||
|
||||
def test_upload_download_interfaces(self):
|
||||
self.load_data_to_nailgun_server()
|
||||
cmd = "node --node-id 1 --network"
|
||||
self.run_cli_commands((self.download_command(cmd),
|
||||
self.upload_command(cmd)))
|
||||
|
||||
def test_upload_download_disks(self):
|
||||
self.load_data_to_nailgun_server()
|
||||
cmd = "node --node-id 1 --disk"
|
||||
self.run_cli_commands((self.download_command(cmd),
|
||||
self.upload_command(cmd)))
|
||||
|
||||
|
||||
class TestDeployChanges(base.BaseTestCase):
|
||||
|
||||
def test_deploy_changes_no_failure(self):
|
||||
self.load_data_to_nailgun_server()
|
||||
env_create = "env create --name=test --release=1"
|
||||
add_node = "--env-id=1 node set --node 1 --role=controller"
|
||||
deploy_changes = "deploy-changes --env 1"
|
||||
self.run_cli_commands((env_create, add_node, deploy_changes))
|
||||
|
||||
|
||||
class TestAuthentication(base.UnitTestCase):
|
||||
|
||||
@patch('fuelclient.client.requests')
|
||||
@patch('fuelclient.client.auth_client')
|
||||
def test_wrong_credentials(self, mkeystone_cli, mrequests):
|
||||
mkeystone_cli.return_value = Mock(auth_token='')
|
||||
mrequests.get_request.return_value = Mock(status_code=200)
|
||||
self.execute(
|
||||
['fuel', '--user=a', '--password=a', 'node'])
|
||||
mkeystone_cli.Client.assert_called_with(
|
||||
username='a',
|
||||
tenant_name='admin',
|
||||
password='a',
|
||||
auth_url='http://127.0.0.1:8003/keystone/v2.0')
|
||||
self.execute(
|
||||
['fuel', '--user=a', '--password', 'a', 'node'])
|
||||
mkeystone_cli.Client.assert_called_with(
|
||||
username='a',
|
||||
tenant_name='admin',
|
||||
password='a',
|
||||
auth_url='http://127.0.0.1:8003/keystone/v2.0')
|
|
@ -1,132 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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 json
|
||||
import os
|
||||
|
||||
from mock import patch
|
||||
|
||||
from fuelclient.tests import base
|
||||
|
||||
|
||||
API_INPUT = [{'id': 'primary-controller'}]
|
||||
API_OUTPUT = '- id: primary-controller\n'
|
||||
RELEASE_OUTPUT = [{'id': 1, 'version': '2014.2-6.0', 'name': 'Something'}]
|
||||
MULTIPLE_RELEASES = [{'id': 1, 'version': '2014.2-6.0', 'name': 'Something'},
|
||||
{'id': 2, 'version': '2014.3-6.1', 'name': 'Something'}]
|
||||
|
||||
|
||||
@patch('fuelclient.client.requests')
|
||||
@patch('fuelclient.cli.serializers.open', create=True)
|
||||
@patch('fuelclient.cli.actions.base.os')
|
||||
class TestReleaseDeploymentTasksActions(base.UnitTestCase):
|
||||
|
||||
def test_release_tasks_download(self, mos, mopen, mrequests):
|
||||
mrequests.get().json.return_value = API_INPUT
|
||||
self.execute_wo_auth(
|
||||
['fuel', 'rel', '--rel', '1', '--deployment-tasks', '--download'])
|
||||
mopen().__enter__().write.assert_called_once_with(API_OUTPUT)
|
||||
|
||||
def test_release_tasks_upload(self, mos, mopen, mrequests):
|
||||
mopen().__enter__().read.return_value = API_OUTPUT
|
||||
self.execute_wo_auth(
|
||||
['fuel', 'rel', '--rel', '1', '--deployment-tasks', '--upload'])
|
||||
self.assertEqual(mrequests.put.call_count, 1)
|
||||
call_args = mrequests.put.call_args_list[0]
|
||||
url = call_args[0][0]
|
||||
kwargs = call_args[1]
|
||||
self.assertIn('releases/1/deployment_tasks', url)
|
||||
self.assertEqual(
|
||||
json.loads(kwargs['data']), API_INPUT)
|
||||
|
||||
|
||||
@patch('fuelclient.client.requests')
|
||||
@patch('fuelclient.cli.serializers.open', create=True)
|
||||
@patch('fuelclient.cli.actions.base.os')
|
||||
class TestClusterDeploymentTasksActions(base.UnitTestCase):
|
||||
|
||||
def test_cluster_tasks_download(self, mos, mopen, mrequests):
|
||||
mrequests.get().json.return_value = API_INPUT
|
||||
self.execute_wo_auth(
|
||||
['fuel', 'env', '--env', '1', '--deployment-tasks', '--download'])
|
||||
mopen().__enter__().write.assert_called_once_with(API_OUTPUT)
|
||||
|
||||
def test_cluster_tasks_upload(self, mos, mopen, mrequests):
|
||||
mopen().__enter__().read.return_value = API_OUTPUT
|
||||
self.execute_wo_auth(
|
||||
['fuel', 'env', '--env', '1', '--deployment-tasks', '--upload'])
|
||||
self.assertEqual(mrequests.put.call_count, 1)
|
||||
call_args = mrequests.put.call_args_list[0]
|
||||
url = call_args[0][0]
|
||||
kwargs = call_args[1]
|
||||
self.assertIn('clusters/1/deployment_tasks', url)
|
||||
self.assertEqual(
|
||||
json.loads(kwargs['data']), API_INPUT)
|
||||
|
||||
|
||||
@patch('fuelclient.client.requests')
|
||||
@patch('fuelclient.cli.serializers.open', create=True)
|
||||
@patch('fuelclient.cli.utils.iterfiles')
|
||||
class TestSyncDeploymentTasks(base.UnitTestCase):
|
||||
|
||||
def test_sync_deployment_scripts(self, mfiles, mopen, mrequests):
|
||||
mrequests.get().json.return_value = RELEASE_OUTPUT
|
||||
mfiles.return_value = ['/etc/puppet/2014.2-6.0/tasks.yaml']
|
||||
mopen().__enter__().read.return_value = API_OUTPUT
|
||||
|
||||
self.execute_wo_auth(
|
||||
['fuel', 'rel', '--sync-deployment-tasks'])
|
||||
|
||||
mfiles.assert_called_once_with(
|
||||
os.path.realpath(os.curdir), ('tasks.yaml',))
|
||||
|
||||
call_args = mrequests.put.call_args_list[0]
|
||||
url = call_args[0][0]
|
||||
kwargs = call_args[1]
|
||||
self.assertIn('releases/1/deployment_tasks', url)
|
||||
self.assertEqual(
|
||||
json.loads(kwargs['data']), API_INPUT)
|
||||
|
||||
def test_sync_with_directory_path(self, mfiles, mopen, mrequests):
|
||||
mrequests.get().json.return_value = RELEASE_OUTPUT
|
||||
mfiles.return_value = ['/etc/puppet/2014.2-6.0/tasks.yaml']
|
||||
mopen().__enter__().read.return_value = API_OUTPUT
|
||||
real_path = '/etc/puppet'
|
||||
self.execute_wo_auth(
|
||||
['fuel', 'rel', '--sync-deployment-tasks', '--dir', real_path])
|
||||
mfiles.assert_called_once_with(real_path, ('tasks.yaml',))
|
||||
|
||||
def test_multiple_tasks_but_one_release(self, mfiles, mopen, mrequests):
|
||||
mrequests.get().json.return_value = RELEASE_OUTPUT
|
||||
mfiles.return_value = ['/etc/puppet/2014.2-6.0/tasks.yaml',
|
||||
'/etc/puppet/2014.3-6.1/tasks.yaml']
|
||||
mopen().__enter__().read.return_value = API_OUTPUT
|
||||
|
||||
self.execute_wo_auth(
|
||||
['fuel', 'rel', '--sync-deployment-tasks'])
|
||||
|
||||
self.assertEqual(mrequests.put.call_count, 1)
|
||||
|
||||
def test_multiple_releases(self, mfiles, mopen, mrequests):
|
||||
mrequests.get().json.return_value = MULTIPLE_RELEASES
|
||||
mfiles.return_value = ['/etc/puppet/2014.2-6.0/tasks.yaml',
|
||||
'/etc/puppet/2014.3-6.1/tasks.yaml']
|
||||
mopen().__enter__().read.return_value = API_OUTPUT
|
||||
|
||||
self.execute_wo_auth(
|
||||
['fuel', 'rel', '--sync-deployment-tasks'])
|
||||
|
||||
self.assertEqual(mrequests.put.call_count, 2)
|
|
@ -1,56 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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 fuelclient.objects.environment import Environment
|
||||
from fuelclient.tests import base
|
||||
|
||||
|
||||
class TestEnvironmentOstf(base.UnitTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestEnvironmentOstf, self).setUp()
|
||||
|
||||
self.env = Environment(None)
|
||||
|
||||
@mock.patch.object(Environment.connection, 'post_request', mock.Mock(
|
||||
return_value=[
|
||||
{'id': 1},
|
||||
{'id': 2}, ]))
|
||||
def test_run_test_sets(self):
|
||||
self.assertEqual(self.env._testruns_ids, [])
|
||||
|
||||
testruns = self.env.run_test_sets(['sanity', 'ha'])
|
||||
|
||||
self.assertEqual(len(testruns), 2)
|
||||
self.assertIn(1, self.env._testruns_ids)
|
||||
self.assertIn(2, self.env._testruns_ids)
|
||||
|
||||
@mock.patch.object(Environment.connection, 'get_request', mock.Mock(
|
||||
side_effect=[
|
||||
{'id': 1, 'status': 'running'},
|
||||
{'id': 2, 'status': 'finished'}, ]))
|
||||
def test_get_state_of_tests(self):
|
||||
self.env._testruns_ids.extend([1, 2])
|
||||
tests = self.env.get_state_of_tests()
|
||||
|
||||
self.env.connection.get_request.assert_has_calls([
|
||||
mock.call('testruns/1', ostf=True),
|
||||
mock.call('testruns/2', ostf=True)])
|
||||
self.assertEqual(tests, [
|
||||
{'id': 1, 'status': 'running'},
|
||||
{'id': 2, 'status': 'finished'}])
|
|
@ -1,160 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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 json
|
||||
import os
|
||||
import shutil
|
||||
import tarfile
|
||||
import time
|
||||
|
||||
import mock
|
||||
import nose
|
||||
import requests
|
||||
from six import moves as six_moves
|
||||
|
||||
from fuelclient import client
|
||||
from fuelclient import fuelclient_settings
|
||||
from fuelclient.objects import node
|
||||
from fuelclient import profiler
|
||||
from fuelclient.tests import base
|
||||
from fuelclient.tests import utils
|
||||
|
||||
|
||||
class ClientPerfTest(base.UnitTestCase):
|
||||
|
||||
NUMBER_OF_NODES = 100
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(ClientPerfTest, cls).setUpClass()
|
||||
|
||||
if not profiler.profiling_enabled():
|
||||
raise nose.SkipTest('Performance profiling tests are not '
|
||||
'enabled in settings.yaml')
|
||||
|
||||
cls.nodes = cls.get_random_nodes(cls.NUMBER_OF_NODES)
|
||||
settings = fuelclient_settings.get_settings()
|
||||
test_base = settings.PERF_TESTS_PATHS['perf_tests_base']
|
||||
|
||||
if os.path.exists(test_base):
|
||||
shutil.rmtree(test_base)
|
||||
|
||||
os.makedirs(test_base)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""Packs all the files from the profiling."""
|
||||
|
||||
settings = fuelclient_settings.get_settings()
|
||||
test_base = settings.PERF_TESTS_PATHS['perf_tests_base']
|
||||
test_results = settings.PERF_TESTS_PATHS['perf_tests_results']
|
||||
|
||||
if not os.path.exists(test_results):
|
||||
os.makedirs(test_results)
|
||||
|
||||
if os.path.exists(test_base):
|
||||
test_result_name = os.path.join(
|
||||
test_results,
|
||||
'{name:s}_{timestamp}.tar.gz'.format(name=cls.__name__,
|
||||
timestamp=time.time()))
|
||||
tar = tarfile.open(test_result_name, "w:gz")
|
||||
tar.add(test_base)
|
||||
tar.close()
|
||||
|
||||
shutil.rmtree(test_base)
|
||||
|
||||
def setUp(self):
|
||||
super(ClientPerfTest, self).setUp()
|
||||
|
||||
req_patcher = mock.patch.object(requests.api, 'request')
|
||||
token_patcher = mock.patch.object(client.Client, 'auth_token',
|
||||
new_callable=mock.PropertyMock)
|
||||
|
||||
self.mock_request = req_patcher.start()
|
||||
self.mock_auth_token = token_patcher.start()
|
||||
|
||||
def tearDown(self):
|
||||
super(ClientPerfTest, self).tearDown()
|
||||
|
||||
self.mock_request.stop()
|
||||
self.mock_auth_token.stop()
|
||||
|
||||
@classmethod
|
||||
def get_random_nodes(cls, number):
|
||||
"""Returns specified number of random fake nodes."""
|
||||
|
||||
return [utils.get_fake_node() for i in six_moves.range(number)]
|
||||
|
||||
def _invoke_client(self, *args):
|
||||
"""Invokes Fuel Client with the specified arguments."""
|
||||
|
||||
args = ['fuelclient'] + list(args)
|
||||
self.execute(args)
|
||||
|
||||
def mock_nailgun_response(self, *responses):
|
||||
"""Mocks network requests in order to return specified content."""
|
||||
|
||||
m_responses = []
|
||||
|
||||
for resp in responses:
|
||||
m_resp = requests.models.Response()
|
||||
m_resp.encoding = 'utf8'
|
||||
m_resp._content = resp
|
||||
|
||||
m_responses.append(m_resp)
|
||||
|
||||
self.mock_request.side_effect = m_responses
|
||||
|
||||
def test_list_nodes(self):
|
||||
nodes_text = json.dumps(self.nodes)
|
||||
self.mock_nailgun_response(nodes_text)
|
||||
|
||||
self._invoke_client('node', 'list')
|
||||
|
||||
def test_assign_nodes(self):
|
||||
node_ids = ','.join([str(n['id']) for n in self.nodes])
|
||||
|
||||
self.mock_nailgun_response('{}')
|
||||
self._invoke_client('--env', '42', 'node', 'set', '--node',
|
||||
node_ids, '--role', 'compute')
|
||||
|
||||
def test_list_environment(self):
|
||||
# NOTE(romcheg): After 100 nodes were added to an environment
|
||||
# they are listed as pending changes so that may potentially
|
||||
# affect the performance.
|
||||
env = [utils.get_fake_env(changes_number=self.NUMBER_OF_NODES)]
|
||||
resp_text = json.dumps(env)
|
||||
|
||||
self.mock_nailgun_response(resp_text)
|
||||
|
||||
self._invoke_client('env', '--list')
|
||||
|
||||
@mock.patch.object(node, 'exit_with_error', new_callable=mock.MagicMock)
|
||||
@mock.patch('__builtin__.open', create=True)
|
||||
def test_upload_node_settings(self, m_open, m_exit):
|
||||
node_configs = [json.dumps(utils.get_fake_network_config(3))
|
||||
for i in six_moves.range(self.NUMBER_OF_NODES)]
|
||||
|
||||
node_ids = ','.join([str(n['id']) for n in self.nodes])
|
||||
|
||||
m_open.return_value = mock.MagicMock(spec=file)
|
||||
m_file = m_open.return_value.__enter__.return_value
|
||||
m_file.read.side_effect = node_configs
|
||||
|
||||
self.mock_nailgun_response(*node_configs)
|
||||
|
||||
self._invoke_client('--json', 'node', '--node-id', node_ids,
|
||||
'--network', '--upload', '--dir', '/fake/dir')
|
|
@ -1,62 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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 mock import Mock
|
||||
from mock import patch
|
||||
|
||||
from fuelclient.tests import base
|
||||
|
||||
DATA = """
|
||||
name: sample
|
||||
version: 0.1.0
|
||||
"""
|
||||
|
||||
|
||||
@patch('fuelclient.client.requests')
|
||||
class TestPluginsActions(base.UnitTestCase):
|
||||
|
||||
def test_001_plugins_action(self, mrequests):
|
||||
self.execute(['fuel', 'plugins'])
|
||||
plugins_call = mrequests.get.call_args_list[-1]
|
||||
url = plugins_call[0][0]
|
||||
self.assertIn('api/v1/plugins', url)
|
||||
|
||||
@patch('fuelclient.objects.plugins.tarfile')
|
||||
@patch('fuelclient.objects.plugins.os')
|
||||
def test_install_plugin(self, mos, mtar, mrequests):
|
||||
mos.path.exists.return_value = True
|
||||
mtar.open().getnames.return_value = ['metadata.yaml']
|
||||
mtar.open().extractfile().read.return_value = DATA
|
||||
response_mock = Mock(status_code=201)
|
||||
mrequests.post.return_value = response_mock
|
||||
self.execute(
|
||||
['fuel', 'plugins', '--install', '/tmp/sample.fp'])
|
||||
self.assertEqual(mrequests.post.call_count, 1)
|
||||
self.assertEqual(mrequests.put.call_count, 0)
|
||||
|
||||
@patch('fuelclient.objects.plugins.tarfile')
|
||||
@patch('fuelclient.objects.plugins.os')
|
||||
def test_install_plugin_with_force(self, mos, mtar, mrequests):
|
||||
mos.path.exists.return_value = True
|
||||
mtar.open().getnames.return_value = ['metadata.yaml']
|
||||
mtar.open().extractfile().read.return_value = DATA
|
||||
response_mock = Mock(status_code=409)
|
||||
response_mock.json.return_value = {'id': '12'}
|
||||
mrequests.post.return_value = response_mock
|
||||
self.execute(
|
||||
['fuel', 'plugins', '--install', '/tmp/sample.fp', '--force'])
|
||||
self.assertEqual(mrequests.post.call_count, 1)
|
||||
self.assertEqual(mrequests.put.call_count, 1)
|
|
@ -1,47 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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 json
|
||||
|
||||
from mock import patch
|
||||
|
||||
from fuelclient.tests import base
|
||||
|
||||
|
||||
API_INPUT = {'config': 'nova_network'}
|
||||
API_OUTPUT = 'config: nova_network\n'
|
||||
|
||||
|
||||
@patch('fuelclient.client.requests')
|
||||
@patch('fuelclient.cli.serializers.open', create=True)
|
||||
@patch('fuelclient.cli.actions.base.os')
|
||||
class TestReleaseNetworkActions(base.UnitTestCase):
|
||||
|
||||
def test_release_network_download(self, mos, mopen, mrequests):
|
||||
mrequests.get().json.return_value = API_INPUT
|
||||
self.execute(['fuel', 'rel', '--rel', '1', '--network', '--download'])
|
||||
mopen().__enter__().write.assert_called_once_with(API_OUTPUT)
|
||||
|
||||
def test_release_network_upload(self, mos, mopen, mrequests):
|
||||
mopen().__enter__().read.return_value = API_OUTPUT
|
||||
self.execute(['fuel', 'rel', '--rel', '1', '--network', '--upload'])
|
||||
self.assertEqual(mrequests.put.call_count, 1)
|
||||
call_args = mrequests.put.call_args_list[0]
|
||||
url = call_args[0][0]
|
||||
kwargs = call_args[1]
|
||||
self.assertIn('releases/1/networks', url)
|
||||
self.assertEqual(
|
||||
json.loads(kwargs['data']), API_INPUT)
|
|
@ -1,39 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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
|
||||
|
||||
from fuelclient.cli import utils
|
||||
from fuelclient.tests import base
|
||||
|
||||
|
||||
class TestUtils(base.UnitTestCase):
|
||||
|
||||
@mock.patch('fuelclient.cli.utils.os.walk')
|
||||
def test_iterfiles(self, mwalk):
|
||||
mwalk.return_value = [
|
||||
('/some_directory/', [], ['valid.yaml', 'invalid.yaml'])]
|
||||
|
||||
pattern = ('valid.yaml',)
|
||||
directory = '/some_directory'
|
||||
|
||||
expected_result = [os.path.join(directory, 'valid.yaml')]
|
||||
files = list(utils.iterfiles(directory, pattern))
|
||||
|
||||
mwalk.assert_called_once_with(directory)
|
||||
self.assertEqual(expected_result, files)
|
|
@ -1,28 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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 fuelclient.tests.utils.random_data import random_string
|
||||
from fuelclient.tests.utils.fake_net_conf import get_fake_interface_config
|
||||
from fuelclient.tests.utils.fake_net_conf import get_fake_network_config
|
||||
from fuelclient.tests.utils.fake_node import get_fake_node
|
||||
from fuelclient.tests.utils.fake_env import get_fake_env
|
||||
|
||||
|
||||
__all__ = (get_fake_env,
|
||||
get_fake_node,
|
||||
random_string,
|
||||
get_fake_interface_config,
|
||||
get_fake_network_config)
|
|
@ -1,60 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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 random
|
||||
|
||||
|
||||
def _generate_changes(number=None, node_ids=None):
|
||||
"""Generates specified number of changes
|
||||
|
||||
:param number: Number of changes to create.
|
||||
:param node_ids: IDs of the nodes which are changed. If None, a linear
|
||||
sequense [0, <number>) will be used. Number of IDs must
|
||||
match the number of changes.
|
||||
:return: list of dict
|
||||
|
||||
"""
|
||||
if (number is None) and (node_ids is None):
|
||||
raise ValueError("Either number of changes or Nodes' IDs is requied.")
|
||||
|
||||
if node_ids is None:
|
||||
node_ids = range(number)
|
||||
|
||||
change_types = ["networks", "interfaces", "disks", "attributes"]
|
||||
|
||||
return [{"node_id": n_id, "name": random.choice(change_types)}
|
||||
for n_id in node_ids]
|
||||
|
||||
|
||||
def get_fake_env(name=None, status=None, release_id=None, fuel_version=None,
|
||||
pending_release=None, env_id=None, changes_number=None):
|
||||
"""Create a random fake environment
|
||||
|
||||
Returns the serialized and parametrized representation of a dumped Fuel
|
||||
environment. Represents the average amount of data.
|
||||
|
||||
"""
|
||||
return {"status": status or "new",
|
||||
"is_customized": False,
|
||||
"release_id": release_id or 1,
|
||||
"name": name or "fake_env",
|
||||
"grouping": "roles",
|
||||
"net_provider": "nova_network",
|
||||
"fuel_version": fuel_version or "5.1",
|
||||
"pending_release_id": pending_release,
|
||||
"id": env_id or 1,
|
||||
"mode": "multinode",
|
||||
"changes": _generate_changes(changes_number)}
|
|
@ -1,52 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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 random
|
||||
|
||||
from six import moves as six_moves
|
||||
|
||||
|
||||
def get_fake_interface_config(iface=None, iface_id=None, state=None, mac=None,
|
||||
iface_type=None, networks=None):
|
||||
"""Create a random fake interface configuration
|
||||
|
||||
Returns the serialized and parametrized representation of a node's
|
||||
interface configuration. Represents the average amount of data.
|
||||
|
||||
"""
|
||||
|
||||
return {"name": iface or "eth0",
|
||||
"id": iface_id or random.randint(0, 1000),
|
||||
"state": state or "unknown",
|
||||
"mac": mac or "08:00:27:a4:01:6b",
|
||||
"max_speed": 100,
|
||||
"type": iface_type or "ether",
|
||||
"current_speed": 100,
|
||||
"assigned_networks": networks or [{"id": 1,
|
||||
"name": "fuelweb_admin"},
|
||||
{"id": 3,
|
||||
"name": "management"},
|
||||
{"id": 4,
|
||||
"name": "storage"},
|
||||
{"id": 5,
|
||||
"name": "fixed"}]}
|
||||
|
||||
|
||||
def get_fake_network_config(iface_number):
|
||||
"""Creates a fake network configuration for a single node."""
|
||||
|
||||
return [get_fake_interface_config(iface='eth{0}'.format(i))
|
||||
for i in six_moves.range(iface_number)]
|
|
@ -1,131 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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 random
|
||||
|
||||
from fuelclient import tests
|
||||
|
||||
|
||||
def get_fake_node(cluster=None, hostname=None, node_id=None, cpu_model=None,
|
||||
roles=None, mac=None, memory_b=None, os_platform=None,
|
||||
status=None, node_name=None, group_id=None):
|
||||
"""Creates a fake random node
|
||||
|
||||
Returns the serialized and parametrized representation of a dumped Fuel
|
||||
environment. Represents the average amount of data.
|
||||
|
||||
"""
|
||||
host_name = hostname or tests.utils.random_string(15, prefix='fake-node-')
|
||||
|
||||
return {"name": node_name or host_name,
|
||||
"error_type": None,
|
||||
"cluster": cluster or 1,
|
||||
"id": node_id or random.randint(1, 10000),
|
||||
"ip": "10.20.0.4",
|
||||
"kernel_params": None,
|
||||
"group_id": group_id or 1,
|
||||
"mac": mac or "d6:11:3f:b0:f1:43",
|
||||
"manufacturer": "VirtualBox",
|
||||
"online": True,
|
||||
"os_platform": os_platform or "centos",
|
||||
"pending_addition": False,
|
||||
"pending_deletion": False,
|
||||
"pending_roles": [],
|
||||
"platform_name": None,
|
||||
"progress": 100,
|
||||
"roles": roles or ["compute"],
|
||||
"status": status or "ready",
|
||||
"fqdn": "{hostname}.example.com".format(hostname=host_name),
|
||||
|
||||
"meta": {"cpu": {"real": 0,
|
||||
"spec": [{"frequency": 2553,
|
||||
"model": cpu_model or "Random CPU"}],
|
||||
"total": 1},
|
||||
|
||||
"disks": [{"disk": "disk/by-path/pci:00:0d.0-scsi-2:0:0",
|
||||
"extra": ["disk/by-id/scsi-SATA_VBOX_aef0bb5c",
|
||||
"disk/by-id/ata-VBOX_HARDDISK_VB37"],
|
||||
"model": "VBOX HARDDISK",
|
||||
"name": "sdc",
|
||||
"removable": "0",
|
||||
"size": 68718428160},
|
||||
|
||||
{"disk": "disk/by-path/pci:0:0d.0-scsi-1:0:0:0",
|
||||
"extra": ["disk/by-id/scsi-SATA_VBOX_30fbc3bb",
|
||||
"disk/by-id/ata-VBOX_HARDD30fbc3bb"],
|
||||
"model": "VBOX HARDDISK",
|
||||
"name": "sdb",
|
||||
"removable": "0",
|
||||
"size": 68718428160},
|
||||
|
||||
{"disk": "disk/by-path/pci:00:d.0-scsi-0:0:0:0",
|
||||
"extra": ["disk/by-id/scsi-SATA_VBOX-17e33653",
|
||||
"disk/by-id/ata-VBOX_HARDD17e33653"],
|
||||
"model": "VBOX HARDDISK",
|
||||
"name": "sda",
|
||||
"removable": "0",
|
||||
"size": 68718428160}],
|
||||
|
||||
"interfaces": [{"name": "eth2",
|
||||
"current_speed": 100,
|
||||
"mac": "08:00:27:88:9C:46",
|
||||
"max_speed": 100,
|
||||
"state": "unknown"},
|
||||
|
||||
{"name": "eth1",
|
||||
"current_speed": 100,
|
||||
"mac": "08:00:27:24:BD:6D",
|
||||
"max_speed": 100,
|
||||
"state": "unknown"},
|
||||
|
||||
{"name": "eth0",
|
||||
"current_speed": 100,
|
||||
"mac": "08:00:27:C1:C5:72",
|
||||
"max_speed": 100,
|
||||
"state": "unknown"}],
|
||||
"memory": {"total": memory_b or 1968627712},
|
||||
|
||||
"system": {"family": "Virtual Machine",
|
||||
"fqdn": host_name,
|
||||
"manufacturer": "VirtualBox",
|
||||
"serial": "0",
|
||||
"version": "1.2"}},
|
||||
"network_data": [{"brd": "192.168.0.255",
|
||||
"dev": "eth0",
|
||||
"gateway": None,
|
||||
"ip": "192.168.0.2/24",
|
||||
"name": "management",
|
||||
"netmask": "255.255.255.0",
|
||||
"vlan": 101},
|
||||
|
||||
{"brd": "192.168.1.255",
|
||||
"dev": "eth0",
|
||||
"gateway": None,
|
||||
"ip": "192.168.1.2/24",
|
||||
"name": "storage",
|
||||
"netmask": "255.255.255.0",
|
||||
"vlan": 102},
|
||||
|
||||
{"brd": "172.16.0.255",
|
||||
"dev": "eth1",
|
||||
"gateway": "172.16.0.1",
|
||||
"ip": "172.16.0.3/24",
|
||||
"name": "public",
|
||||
"netmask": "255.255.255.0",
|
||||
"vlan": None},
|
||||
|
||||
{"dev": "eth0",
|
||||
"name": "admin"}]}
|
|
@ -1,42 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 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 random
|
||||
import string
|
||||
|
||||
|
||||
def random_string(lenght, prefix='', postfix='', charset=None):
|
||||
"""Returns a random string of the specified length
|
||||
|
||||
:param length: The length of the resulting string.
|
||||
:type lenght: int.
|
||||
:param prefix: Prefix for the random string.
|
||||
:type prefix: str, default: ''.
|
||||
:param postfix: Postfix for the random string.
|
||||
:type postfix: str, default ''.
|
||||
:param charset: A set of characters to use for building random strings.
|
||||
:type chartet: Iterable object. Default: ASCII letters and digits.
|
||||
:return: str
|
||||
|
||||
"""
|
||||
charset = charset or string.ascii_letters + string.digits
|
||||
base_length = lenght - (len(prefix) + len(postfix))
|
||||
|
||||
base = ''.join([str(random.choice(charset)) for i in xrange(base_length)])
|
||||
|
||||
return '{prefix}{base}{postfix}'.format(prefix=prefix,
|
||||
postfix=postfix,
|
||||
base=base)
|
|
@ -1,28 +0,0 @@
|
|||
Babel==1.3
|
||||
Jinja2==2.7
|
||||
Mako==0.9.1
|
||||
MarkupSafe==0.18
|
||||
Paste==1.7.5.1
|
||||
PyYAML==3.10
|
||||
requests>=1.2.3
|
||||
SQLAlchemy>=0.9.4
|
||||
Shotgun==0.1.0
|
||||
alembic==0.6.2
|
||||
amqplib==1.0.2
|
||||
anyjson==0.3.3
|
||||
argparse==1.2.1
|
||||
decorator==3.4.0
|
||||
fysom==1.0.11
|
||||
iso8601==0.1.9
|
||||
jsonschema==2.3.0
|
||||
kombu==3.0.16
|
||||
netaddr==0.7.10
|
||||
oslo.config==1.2.1
|
||||
psycopg2==2.5.1
|
||||
pycrypto==2.6.1
|
||||
simplejson==3.3.0
|
||||
six>=1.5.2
|
||||
web.py==0.37
|
||||
wsgilog==0.3
|
||||
wsgiref==0.1.2
|
||||
python-keystoneclient>=0.7.1,<=0.7.2
|
|
@ -1,34 +0,0 @@
|
|||
# Copyright 2013-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 setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='fuelclient',
|
||||
version='6.0.0',
|
||||
description='Command line interface for Nailgun',
|
||||
long_description="""Command line interface for Nailgun""",
|
||||
author='Mirantis Inc.',
|
||||
author_email='product@mirantis.com',
|
||||
url='http://mirantis.com',
|
||||
install_requires=['PyYAML==3.10', "argparse==1.2.1"],
|
||||
packages=find_packages(),
|
||||
package_data = {'': ['*.yaml']},
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'fuel = fuelclient.cli.parser:main',
|
||||
],
|
||||
}
|
||||
)
|
|
@ -1,17 +0,0 @@
|
|||
-r requirements.txt
|
||||
hacking==0.7
|
||||
mock==1.0
|
||||
nose==1.1.2
|
||||
nose2==0.4.1
|
||||
nose-timer==0.2.0
|
||||
sphinx==1.2
|
||||
rst2pdf==0.93
|
||||
sphinxcontrib-plantuml==0.3
|
||||
sphinxcontrib-blockdiag==1.3.0
|
||||
sphinxcontrib-actdiag==0.6.0
|
||||
sphinxcontrib-seqdiag==0.6.0
|
||||
sphinxcontrib-nwdiag==0.7.0
|
||||
webtest==2.0.14
|
||||
ply==3.4
|
||||
pyprof2calltree==1.3.2
|
||||
gprof2dot==2014.09.29
|
|
@ -1,41 +0,0 @@
|
|||
[tox]
|
||||
minversion = 1.6
|
||||
skipsdist = True
|
||||
envlist = py26,py27,pep8
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
install_command = pip install --allow-external -U {opts} {packages}
|
||||
setenv = VIRTUAL_ENV={envdir}
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
commands =
|
||||
nosetests {posargs:fuelclient}
|
||||
|
||||
[tox:jenkins]
|
||||
downloadcache = ~/cache/pip
|
||||
|
||||
[testenv:pep8]
|
||||
deps = hacking==0.7
|
||||
usedevelop = False
|
||||
commands =
|
||||
flake8 {posargs:fuelclient}
|
||||
|
||||
[testenv:cover]
|
||||
setenv = NOSE_WITH_COVERAGE=1
|
||||
|
||||
[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
|
71
run_tests.sh
71
run_tests.sh
|
@ -22,8 +22,6 @@ function usage {
|
|||
echo ""
|
||||
echo " -a, --agent Run FUEL_AGENT unit tests"
|
||||
echo " -A, --no-agent Don't run FUEL_AGENT unit tests"
|
||||
echo " -c, --cli Run FUELCLIENT tests"
|
||||
echo " -C, --no-cli Don't run FUELCLIENT tests"
|
||||
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"
|
||||
|
@ -61,8 +59,6 @@ function process_options {
|
|||
-K|--no-tasklib) no_tasklib_tests=1;;
|
||||
-w|--webui) webui_tests=1;;
|
||||
-W|--no-webui) no_webui_tests=1;;
|
||||
-c|--cli) cli_tests=1;;
|
||||
-C|--no-cli) no_cli_tests=1;;
|
||||
-u|--upgrade) upgrade_system=1;;
|
||||
-U|--no-upgrade) no_upgrade_system=1;;
|
||||
-s|--shotgun) shotgun_tests=1;;
|
||||
|
@ -92,14 +88,13 @@ testropts="--with-timer --timer-warning=10 --timer-ok=2 --timer-top-n=10"
|
|||
|
||||
# nosetest xunit options
|
||||
NAILGUN_XUNIT=${NAILGUN_XUNIT:-"$ROOT/nailgun.xml"}
|
||||
FUELCLIENT_XUNIT=${FUELCLIENT_XUNIT:-"$ROOT/fuelclient.xml"}
|
||||
FUELUPGRADE_XUNIT=${FUELUPGRADE_XUNIT:-"$ROOT/fuelupgrade.xml"}
|
||||
FUELUPGRADEDOWNLOADER_XUNIT=${FUELUPGRADEDOWNLOADER_XUNIT:-"$ROOT/fuelupgradedownloader.xml"}
|
||||
SHOTGUN_XUNIT=${SHOTGUN_XUNIT:-"$ROOT/shotgun.xml"}
|
||||
UI_SERVER_PORT=${UI_SERVER_PORT:-5544}
|
||||
FUELCLIENT_SERVER_PORT=${FUELCLIENT_SERVER_PORT:-8003}
|
||||
NAILGUN_PORT=${NAILGUN_PORT:-8003}
|
||||
TEST_NAILGUN_DB=${TEST_NAILGUN_DB:-nailgun}
|
||||
NAILGUN_CHECK_URL=${NAILGUN_CHECK_URL:-"http://0.0.0.0:$FUELCLIENT_SERVER_PORT/api/version"}
|
||||
NAILGUN_CHECK_URL=${NAILGUN_CHECK_URL:-"http://0.0.0.0:$NAILGUN_PORT/api/version"}
|
||||
NAILGUN_START_MAX_WAIT_TIME=${NAILGUN_START_MAX_WAIT_TIME:-5}
|
||||
ARTIFACTS=${ARTIFACTS:-`pwd`/test_run}
|
||||
TEST_WORKERS=${TEST_WORKERS:-0}
|
||||
|
@ -114,8 +109,6 @@ no_nailgun_tests=0
|
|||
performance_tests=0
|
||||
webui_tests=0
|
||||
no_webui_tests=0
|
||||
cli_tests=0
|
||||
no_cli_tests=0
|
||||
upgrade_system=0
|
||||
no_upgrade_system=0
|
||||
shotgun_tests=0
|
||||
|
@ -156,7 +149,6 @@ function run_tests {
|
|||
$performance_tests -eq 0 && \
|
||||
$tasklib_tests -eq 0 && \
|
||||
$webui_tests -eq 0 && \
|
||||
$cli_tests -eq 0 && \
|
||||
$upgrade_system -eq 0 && \
|
||||
$shotgun_tests -eq 0 && \
|
||||
$flake8_checks -eq 0 && \
|
||||
|
@ -166,7 +158,6 @@ function run_tests {
|
|||
if [ $no_nailgun_tests -ne 1 ]; then nailgun_tests=1; fi
|
||||
if [ $no_tasklib_tests -ne 1 ]; then tasklib_tests=1; fi
|
||||
if [ $no_webui_tests -ne 1 ]; then webui_tests=1; fi
|
||||
if [ $no_cli_tests -ne 1 ]; then cli_tests=1; fi
|
||||
if [ $no_upgrade_system -ne 1 ]; then upgrade_system=1; fi
|
||||
if [ $no_shotgun_tests -ne 1 ]; then shotgun_tests=1; fi
|
||||
if [ $no_flake8_checks -ne 1 ]; then flake8_checks=1; fi
|
||||
|
@ -199,11 +190,6 @@ function run_tests {
|
|||
run_webui_tests || errors+=" webui_tests"
|
||||
fi
|
||||
|
||||
if [ $cli_tests -eq 1 ]; then
|
||||
echo "Starting Fuel client tests..."
|
||||
run_cli_tests || errors+=" cli_tests"
|
||||
fi
|
||||
|
||||
if [ $upgrade_system -eq 1 ]; then
|
||||
echo "Starting upgrade system tests..."
|
||||
run_upgrade_system_tests || errors+=" upgrade_system_tests"
|
||||
|
@ -352,56 +338,6 @@ function run_webui_tests {
|
|||
}
|
||||
|
||||
|
||||
# Run fuelclient tests.
|
||||
#
|
||||
# Arguments:
|
||||
#
|
||||
# $@ -- tests to be run; with no arguments all tests will be run
|
||||
#
|
||||
# It is supposed that nailgun server is up and working.
|
||||
# We are going to pass nailgun url to test runner.
|
||||
function run_cli_tests {
|
||||
local SERVER_PORT=$FUELCLIENT_SERVER_PORT
|
||||
local TESTS=$ROOT/fuelclient/fuelclient/tests
|
||||
local artifacts=$ARTIFACTS/cli
|
||||
local config=$artifacts/test.yaml
|
||||
local pid
|
||||
|
||||
prepare_artifacts $artifacts $config
|
||||
|
||||
if [ $# -ne 0 ]; then
|
||||
TESTS=$@
|
||||
fi
|
||||
|
||||
local server_log=`mktemp /tmp/test_nailgun_cli_server.XXXX`
|
||||
local result=0
|
||||
|
||||
dropdb $config
|
||||
syncdb $config true
|
||||
|
||||
pid=`run_server $SERVER_PORT $server_log $config` || \
|
||||
{ echo 'Failed to start Nailgun'; return 1; }
|
||||
|
||||
if [ "$pid" -ne "0" ]; then
|
||||
|
||||
pushd $ROOT/fuelclient >> /dev/null
|
||||
# run tests
|
||||
NAILGUN_CONFIG=$config LISTEN_PORT=$SERVER_PORT \
|
||||
tox -epy26 -- -vv $testropts $TESTS --xunit-file $FUELCLIENT_XUNIT || result=1
|
||||
popd >> /dev/null
|
||||
|
||||
kill $pid
|
||||
wait $pid 2> /dev/null
|
||||
else
|
||||
cat $server_log
|
||||
result=1
|
||||
fi
|
||||
|
||||
rm $server_log
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
# Run tests for fuel upgrade system
|
||||
#
|
||||
# Arguments:
|
||||
|
@ -486,7 +422,6 @@ function run_flake8 {
|
|||
run_flake8_subproject fuel_agent && \
|
||||
run_flake8_subproject nailgun && \
|
||||
run_flake8_subproject tasklib && \
|
||||
run_flake8_subproject fuelclient && \
|
||||
run_flake8_subproject fuelmenu && \
|
||||
run_flake8_subproject network_checker && \
|
||||
run_flake8_subproject fuel_upgrade_system/fuel_update_downloader && \
|
||||
|
@ -636,8 +571,6 @@ EOL
|
|||
function guess_test_run {
|
||||
if [[ $1 == *ui_tests* && $1 == *.js ]]; then
|
||||
run_webui_tests $1 || echo "ERROR: $1"
|
||||
elif [[ $1 == *fuelclient* ]]; then
|
||||
run_cli_tests $1 || echo "ERROR: $1"
|
||||
elif [[ $1 == *fuel_upgrade_system* ]]; then
|
||||
run_upgrade_system_tests $1 || echo "ERROR: $1"
|
||||
elif [[ $1 == *shotgun* ]]; then
|
||||
|
|
Loading…
Reference in New Issue