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:
Roman Prykhodchenko 2015-01-24 01:58:53 +01:00
parent e75ac13a46
commit df66a77faa
59 changed files with 2 additions and 5767 deletions

View File

@ -1 +0,0 @@
include fuelclient/fuelclient_settings.yaml

View File

@ -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()

View File

@ -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__ = ""

View File

@ -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
"""

View File

@ -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
)

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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"

View File

@ -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)
)

View File

@ -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"

View File

@ -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)
)

View File

@ -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))

View File

@ -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,
)
)

View File

@ -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))

View File

@ -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']))

View File

@ -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
)
)

View File

@ -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)
)

View File

@ -1,51 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from 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
)

View File

@ -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)
)

View File

@ -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]")

View File

@ -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
)

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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)
)
)

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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/"

View File

@ -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

View File

@ -1,26 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""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

View File

@ -1,68 +0,0 @@
# Copyright 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
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())

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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"])

View File

@ -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)

View File

@ -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.

View File

@ -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)

View File

@ -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')

View File

@ -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)

View File

@ -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'}])

View File

@ -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')

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)}

View File

@ -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)]

View File

@ -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"}]}

View File

@ -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)

View File

@ -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

View File

@ -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',
],
}
)

View File

@ -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

View File

@ -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

View File

@ -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