Fuelclient major refactoring
* package structure * some implementation ideas * a lot of working functions * fixed Fuelclient tests * fixed action validation * fixed formatting and pep8 * fixed copyright * fixed unnecessary request during deployment Implements: blueprint fuelclient-refactoring Change-Id: Ice7f96273afb5d96e7970acfc45c61bab620aaed
This commit is contained in:
parent
3bffdedf04
commit
d125ae1384
2065
fuelclient/fuel
2065
fuelclient/fuel
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,22 @@
|
|||
# Copyright 2014 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
try:
|
||||
import pkg_resources
|
||||
try:
|
||||
__version__ = pkg_resources.get_distribution(
|
||||
"fuelclient").version
|
||||
except pkg_resources.DistributionNotFound:
|
||||
__version__ = ""
|
||||
except ImportError:
|
||||
__version__ = ""
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2013 Mirantis, Inc.
|
||||
# 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
|
|
@ -0,0 +1,50 @@
|
|||
# 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.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.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
|
||||
|
||||
actions_tuple = (
|
||||
ReleaseAction,
|
||||
RoleAction,
|
||||
EnvironmentAction,
|
||||
DeployChangesAction,
|
||||
NodeAction,
|
||||
DeploymentAction,
|
||||
ProvisioningAction,
|
||||
StopAction,
|
||||
ResetAction,
|
||||
SettingsAction,
|
||||
NetworkAction,
|
||||
TaskAction,
|
||||
SnapshotAction,
|
||||
HealthCheckAction
|
||||
)
|
||||
|
||||
actions = dict(
|
||||
(action.action_name, action())
|
||||
for action in actions_tuple
|
||||
)
|
|
@ -0,0 +1,86 @@
|
|||
# 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
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
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)
|
||||
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):
|
||||
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 wrap(method, args, f):
|
||||
@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):
|
||||
return partial(wrap, all, args)
|
||||
|
||||
|
||||
def check_any(*args):
|
||||
return partial(wrap, any, args)
|
|
@ -0,0 +1,46 @@
|
|||
# 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)
|
|
@ -0,0 +1,151 @@
|
|||
# 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_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"
|
||||
)
|
||||
]
|
||||
self.flag_func_map = (
|
||||
("create", self.create),
|
||||
("set", self.set),
|
||||
("delete", self.delete),
|
||||
(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,
|
||||
"Environment '{name}' with id={id}, mode={mode}"
|
||||
" and network-mode={net_provider} was created!"
|
||||
.format(**data)
|
||||
)
|
||||
|
||||
@check_all("env")
|
||||
@check_any("name", "mode")
|
||||
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
|
||||
"""
|
||||
env = Environment(params.env, params=params)
|
||||
data = env.set(name=params.name, mode=params.mode)
|
||||
msg_templates = []
|
||||
if params.name:
|
||||
msg_templates.append(
|
||||
"Environment with id={id} was renamed to '{name}'.")
|
||||
if params.mode:
|
||||
msg_templates.append(
|
||||
"Mode of environment with id={id} was set to '{mode}'.")
|
||||
self.serializer.print_to_output(
|
||||
data,
|
||||
"\n".join(msg_templates).format(**data)
|
||||
)
|
||||
|
||||
@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")
|
||||
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,
|
||||
subdict_keys=[("release_id", u"id")]
|
||||
)
|
||||
)
|
|
@ -0,0 +1,123 @@
|
|||
# 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):
|
||||
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)
|
||||
env.write_facts_to_dir(
|
||||
self.action_name,
|
||||
env.get_default_facts(self.action_name, nodes=params.node),
|
||||
directory=params.dir
|
||||
)
|
||||
|
||||
def upload(self, params):
|
||||
"""To upload {action_name} information for some environment:
|
||||
fuel --env 1 {action_name} --upload
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
facts = getattr(env, self.read_method_name)(
|
||||
self.action_name,
|
||||
directory=params.dir
|
||||
)
|
||||
env.upload_facts(self.action_name, facts)
|
||||
self.serializer.print_to_output(
|
||||
facts,
|
||||
"{0} facts 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)
|
||||
self.serializer.print_to_output(
|
||||
{},
|
||||
"{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)
|
||||
env.write_facts_to_dir(
|
||||
self.action_name,
|
||||
env.get_facts(self.action_name, nodes=params.node),
|
||||
directory=params.dir
|
||||
)
|
||||
|
||||
@property
|
||||
def read_method_name(self):
|
||||
return "read_{0}_info".format(self.action_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"
|
|
@ -0,0 +1,75 @@
|
|||
# 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"
|
||||
|
||||
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.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)
|
||||
)
|
|
@ -0,0 +1,58 @@
|
|||
# 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"
|
|
@ -0,0 +1,88 @@
|
|||
# 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/derectory
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
network_data = env.read_network_data(directory=params.dir)
|
||||
response = env.set_network_data(network_data)
|
||||
self.serializer.print_to_output(
|
||||
response,
|
||||
"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/derectory
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
response = env.verify_network()
|
||||
self.serializer.print_to_output(
|
||||
response,
|
||||
"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)
|
||||
self.serializer.print_to_output(
|
||||
network_data,
|
||||
"Network configuration for environment with id={0}"
|
||||
" downloaded to {1}"
|
||||
.format(env.id, network_file_path)
|
||||
)
|
|
@ -0,0 +1,239 @@
|
|||
# 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.cli.formatting import quote_and_join
|
||||
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")
|
||||
|
||||
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_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),
|
||||
(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 = list(_nodes)
|
||||
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)
|
||||
)
|
||||
|
||||
@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
|
||||
)
|
||||
files.append(file_path)
|
||||
attributes.append(default_attribute)
|
||||
message = "Default node attributes for {0} were written" \
|
||||
" to {1}".format(attribute_type, quote_and_join(files))
|
||||
elif params.upload:
|
||||
for node in nodes:
|
||||
attribute = node.read_attribute(attribute_type, params.dir)
|
||||
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
|
||||
)
|
||||
attributes.append(downloaded_attribute)
|
||||
files.append(file_path)
|
||||
message = "Node attributes for {0} were written" \
|
||||
" to {1}".format(attribute_type, quote_and_join(files))
|
||||
self.serializer.print_to_output(
|
||||
attributes,
|
||||
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
|
||||
)
|
||||
)
|
|
@ -0,0 +1,96 @@
|
|||
# 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.release import Release
|
||||
|
||||
|
||||
class ReleaseAction(Action):
|
||||
"""List and modify currently available releases
|
||||
"""
|
||||
action_name = "release"
|
||||
|
||||
def __init__(self):
|
||||
super(ReleaseAction, self).__init__()
|
||||
self.args = [
|
||||
group(
|
||||
Args.get_list_arg("List all available releases."),
|
||||
Args.get_config_arg("Configure release with --release"),
|
||||
),
|
||||
Args.get_release_arg("Specify release id to configure"),
|
||||
Args.get_username_arg("Username for release credentials"),
|
||||
Args.get_password_arg("Password for release credentials"),
|
||||
Args.get_satellite_arg("Satellite server hostname"),
|
||||
Args.get_activation_key_arg("activation key")
|
||||
]
|
||||
self.flag_func_map = (
|
||||
("config", self.configure_release),
|
||||
(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", "username", "password")
|
||||
def configure_release(self, params):
|
||||
"""To configure RedHat release:
|
||||
fuel rel --rel <id of RedHat release> \\
|
||||
-c -U <username> -P <password>
|
||||
|
||||
To configure RedHat release with satellite server:
|
||||
fuel rel --rel <...> -c -U <...> -P <...> \\
|
||||
--satellite-server-hostname <hostname> --activation-key <key>
|
||||
"""
|
||||
release = Release(params.release)
|
||||
release_response = release.configure(
|
||||
params.username,
|
||||
params.password,
|
||||
satellite_server_hostname=None,
|
||||
activation_key=None
|
||||
)
|
||||
|
||||
self.serializer.print_to_output(
|
||||
release_response,
|
||||
"Credentials for release with id={0}"
|
||||
" were modified."
|
||||
.format(release.id)
|
||||
)
|
|
@ -0,0 +1,58 @@
|
|||
# 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
|
||||
)
|
||||
)
|
|
@ -0,0 +1,84 @@
|
|||
# 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.")
|
||||
),
|
||||
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/derectory
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
network_data = env.read_settings_data(directory=params.dir)
|
||||
response = env.set_settings_data(network_data)
|
||||
self.serializer.print_to_output(
|
||||
response,
|
||||
"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/derectory
|
||||
"""
|
||||
env = Environment(params.env)
|
||||
default_data = env.get_default_settings_data()
|
||||
settings_file_path = env.write_settings_data(
|
||||
default_data,
|
||||
directory=params.dir)
|
||||
self.serializer.print_to_output(
|
||||
default_data,
|
||||
"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)
|
||||
self.serializer.print_to_output(
|
||||
settings_data,
|
||||
"Settings configuration for environment with id={0}"
|
||||
" downloaded to {1}"
|
||||
.format(env.id, settings_file_path)
|
||||
)
|
|
@ -0,0 +1,51 @@
|
|||
# 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.api_root + snapshot_task.data["message"],
|
||||
directory=params.dir
|
||||
)
|
|
@ -0,0 +1,81 @@
|
|||
# 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 -t 1,2,3
|
||||
|
||||
To delete some tasks forcefully (without considering their state):
|
||||
fuel task delete -f -t 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 -t 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)
|
||||
)
|
|
@ -0,0 +1,388 @@
|
|||
# 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"
|
||||
}
|
||||
|
||||
|
||||
def group(*args, **kwargs):
|
||||
required = kwargs.get("required", False)
|
||||
return (required, ) + args
|
||||
|
||||
|
||||
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 parse_ids(x):
|
||||
"""Parse arguments with commas and spaces
|
||||
|
||||
:returns: list of lists with numbers
|
||||
"""
|
||||
filtered = [y for y in x.split(",") if y.strip() != '']
|
||||
if len(filtered) > 1:
|
||||
return map(int, filtered)
|
||||
elif len(filtered) == 1:
|
||||
return [int(filtered[0])]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_serializer_arg(serialization_method):
|
||||
return {
|
||||
"args": ["--{0}".format(serialization_method)],
|
||||
"params": {
|
||||
"dest": serialization_method,
|
||||
"action": "store_true",
|
||||
"help": "prints only {0} to stdout".format(serialization_method),
|
||||
"default": False
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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_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_multinum_arg(name, **kwargs):
|
||||
default_kwargs = {
|
||||
"action": "store",
|
||||
"type": parse_ids,
|
||||
"nargs": '+',
|
||||
"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_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_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=False)
|
||||
|
||||
|
||||
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_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_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):
|
||||
return get_multinum_arg(
|
||||
"task",
|
||||
flags=("--tid", "--task-id"),
|
||||
help=help_msg)
|
||||
|
||||
|
||||
def get_config_arg(help_msg):
|
||||
return get_boolean_arg("config", flags=("-c",), help=help_msg)
|
||||
|
||||
|
||||
def get_username_arg(help_msg):
|
||||
return get_str_arg("username", flags=("-U", "--user"), help=help_msg)
|
||||
|
||||
|
||||
def get_password_arg(help_msg):
|
||||
return get_str_arg("password", flags=("-P", "--pass"), help=help_msg)
|
||||
|
||||
|
||||
def get_satellite_arg(help_msg):
|
||||
return get_str_arg("satellite_server_hostname", help=help_msg)
|
||||
|
||||
|
||||
def get_activation_key_arg(help_msg):
|
||||
return get_str_arg("activation_key", help=help_msg)
|
|
@ -0,0 +1,67 @@
|
|||
# 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
|
||||
import sys
|
||||
import urllib2
|
||||
|
||||
|
||||
def exit_with_error(message):
|
||||
sys.stderr.write(message + "\n")
|
||||
exit(1)
|
||||
|
||||
|
||||
class FuelClientException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DeployProgressError(FuelClientException):
|
||||
pass
|
||||
|
||||
|
||||
class ArgumentException(FuelClientException):
|
||||
pass
|
||||
|
||||
|
||||
class ActionException(FuelClientException):
|
||||
pass
|
||||
|
||||
|
||||
class ParserException(FuelClientException):
|
||||
pass
|
||||
|
||||
|
||||
def handle_exceptions(exc):
|
||||
if isinstance(exc, urllib2.HTTPError):
|
||||
error_body = exc.read()
|
||||
exit_with_error("{0} {1}".format(
|
||||
exc,
|
||||
"({0})".format(error_body or "")
|
||||
))
|
||||
elif isinstance(exc, urllib2.URLError):
|
||||
exit_with_error("Can't connect to Nailgun server!")
|
||||
elif isinstance(exc, FuelClientException):
|
||||
exit_with_error(exc.message)
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
def exceptions_decorator(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception as exc:
|
||||
handle_exceptions(exc)
|
||||
return wrapper
|
|
@ -0,0 +1,256 @@
|
|||
# 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 recur_get(multi_level_dict, key_chain):
|
||||
"""Method accesses some field in nested dictionaries
|
||||
|
||||
:returns: value for last key in key_chain in last dictionary
|
||||
"""
|
||||
if not isinstance(multi_level_dict[key_chain[0]], dict):
|
||||
return multi_level_dict[key_chain[0]]
|
||||
else:
|
||||
return recur_get(multi_level_dict[key_chain[0]], key_chain[1:])
|
||||
|
||||
|
||||
def format_table(data, acceptable_keys=None, subdict_keys=None):
|
||||
"""Format list of dicts to ascii table
|
||||
|
||||
:acceptable_keys list(str): list of keys for which to create table
|
||||
also specifies their order
|
||||
:subdict_keys list(tuple(str)): list of key chains (tuples of key strings)
|
||||
which are applied to dictionaries
|
||||
to extract values
|
||||
"""
|
||||
if subdict_keys:
|
||||
for key_chain in subdict_keys:
|
||||
for data_dict in data:
|
||||
data_dict[key_chain[0]] = recur_get(data_dict, key_chain)
|
||||
if acceptable_keys:
|
||||
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(str(element))))
|
||||
for index, element in enumerate(row)
|
||||
)
|
||||
row_template = ' | '.join(
|
||||
"{{{0}:{1}}}".format(idx, width)
|
||||
for idx, width in column_widths.iteritems()
|
||||
)
|
||||
return '\n'.join(
|
||||
(row_template.format(*header),
|
||||
'-|-'.join(column_widths[column_index] * '-'
|
||||
for column_index in range(number_of_columns)),
|
||||
'\n'.join(row_template.format(*x) for x in rows))
|
||||
)
|
||||
|
||||
|
||||
def quote_and_join(words):
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
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(
|
||||
"[{0:2} of {1}] [{status}] '{name}' "
|
||||
"({taken:.4} s) {message}".format(
|
||||
test_counter,
|
||||
total_tests_count,
|
||||
**test
|
||||
)
|
||||
)
|
||||
test_counter += 1
|
||||
sleep(1)
|
|
@ -0,0 +1,144 @@
|
|||
# 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
|
||||
|
||||
|
||||
class Parser:
|
||||
def __init__(self):
|
||||
self.args = sys.argv
|
||||
self.parser = argparse.ArgumentParser(
|
||||
usage="fuel [optional args] <namespace> [action] [flags]"
|
||||
)
|
||||
self.universal_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_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()
|
||||
parsed_params, _ = self.parser.parse_known_args(self.args[1:])
|
||||
if parsed_params.action not in actions:
|
||||
self.parser.print_help()
|
||||
sys.exit(0)
|
||||
actions[parsed_params.action].action_func(parsed_params)
|
||||
|
||||
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_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 --json and --debug flags before any action
|
||||
for flag in self.universal_flags:
|
||||
if flag in self.args:
|
||||
self.args.remove(flag)
|
||||
self.args.insert(1, flag)
|
||||
|
||||
self.move_argument_before_action("--env", )
|
||||
|
||||
def move_argument_before_action(self, argument):
|
||||
for arg in self.args:
|
||||
if argument in arg:
|
||||
# if declaration with '=' sign (e.g. --env-id=1)
|
||||
if "=" in arg:
|
||||
index_of_env = self.args.index(arg)
|
||||
env = self.args.pop(index_of_env)
|
||||
self.args.append(env)
|
||||
else:
|
||||
try:
|
||||
index_of_env = self.args.index(arg)
|
||||
self.args.pop(index_of_env)
|
||||
env = self.args.pop(index_of_env)
|
||||
self.args.append(arg)
|
||||
self.args.append(env)
|
||||
except IndexError:
|
||||
raise ParserException(
|
||||
'Corresponding value must follow "{0}" flag'
|
||||
.format(arg)
|
||||
)
|
||||
break
|
||||
|
||||
|
||||
@exceptions_decorator
|
||||
def main():
|
||||
parser = Parser()
|
||||
parser.parse()
|
|
@ -0,0 +1,90 @@
|
|||
# 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):
|
||||
|
||||
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:
|
||||
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):
|
||||
full_path = self.prepare_path(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)
|
||||
)
|
||||
)
|
|
@ -0,0 +1,127 @@
|
|||
# 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 urllib2
|
||||
|
||||
import yaml
|
||||
|
||||
from fuelclient.cli.error import exceptions_decorator
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""This class handles API requests
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.debug = False
|
||||
path_to_config = "/etc/fuel-client.yaml"
|
||||
defaults = {
|
||||
"LISTEN_ADDRESS": "127.0.0.1",
|
||||
"LISTEN_PORT": "8000"
|
||||
}
|
||||
if os.path.exists(path_to_config):
|
||||
with open(path_to_config, "r") as fh:
|
||||
config = yaml.load(fh.read())
|
||||
defaults.update(config)
|
||||
else:
|
||||
defaults.update(os.environ)
|
||||
self.root = "http://{LISTEN_ADDRESS}:{LISTEN_PORT}".format(**defaults)
|
||||
self.api_root = self.root + "/api/v1/"
|
||||
self.ostf_root = self.root + "/ostf/"
|
||||
|
||||
def debug_mode(self, debug=False):
|
||||
self.debug = debug
|
||||
return self
|
||||
|
||||
def print_debug(self, message):
|
||||
if self.debug:
|
||||
print(message)
|
||||
|
||||
def delete_request(self, api):
|
||||
"""Make DELETE request to specific API with some data
|
||||
"""
|
||||
self.print_debug(
|
||||
"DELETE {0}".format(self.api_root + api)
|
||||
)
|
||||
opener = urllib2.build_opener(urllib2.HTTPHandler)
|
||||
request = urllib2.Request(self.api_root + api)
|
||||
request.add_header('Content-Type', 'application/json')
|
||||
request.get_method = lambda: 'DELETE'
|
||||
opener.open(request)
|
||||
return {}
|
||||
|
||||
def put_request(self, api, data):
|
||||
"""Make PUT request to specific API with some data
|
||||
"""
|
||||
data_json = json.dumps(data)
|
||||
self.print_debug(
|
||||
"PUT {0} data={1}"
|
||||
.format(self.api_root + api, data_json)
|
||||
)
|
||||
opener = urllib2.build_opener(urllib2.HTTPHandler)
|
||||
request = urllib2.Request(self.api_root + api, data=data_json)
|
||||
request.add_header('Content-Type', 'application/json')
|
||||
request.get_method = lambda: 'PUT'
|
||||
return json.loads(
|
||||
opener.open(request).read()
|
||||
)
|
||||
|
||||
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)
|
||||
)
|
||||
request = urllib2.urlopen(url)
|
||||
return json.loads(
|
||||
request.read()
|
||||
)
|
||||
|
||||
def post_request(self, api, data, ostf=False):
|
||||
"""Make POST request to specific API with some data
|
||||
"""
|
||||
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)
|
||||
)
|
||||
request = urllib2.Request(
|
||||
url=url,
|
||||
data=data_json,
|
||||
headers={
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
)
|
||||
try:
|
||||
response = json.loads(
|
||||
urllib2.urlopen(request)
|
||||
.read()
|
||||
)
|
||||
except ValueError:
|
||||
response = {}
|
||||
return response
|
||||
|
||||
@exceptions_decorator
|
||||
def get_fuel_version(self):
|
||||
return yaml.safe_dump(
|
||||
self.get_request("version"),
|
||||
default_flow_style=False
|
||||
)
|
||||
|
||||
APIClient = Client()
|
|
@ -0,0 +1,23 @@
|
|||
# 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
|
||||
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
|
|
@ -0,0 +1,62 @@
|
|||
# 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):
|
||||
|
||||
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())
|
|
@ -0,0 +1,346 @@
|
|||
# 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.serializers import listdir_without_extensions
|
||||
from fuelclient.objects.base import BaseObject
|
||||
from fuelclient.objects.task import DeployTask
|
||||
from functools import wraps
|
||||
|
||||
|
||||
def memorize_one(func):
|
||||
func.cache = None
|
||||
|
||||
@wraps(func)
|
||||
def nested(*args, **kwargs):
|
||||
if func.cache is None:
|
||||
func.cache = func(*args, **kwargs)
|
||||
return func.cache
|
||||
return nested
|
||||
|
||||
|
||||
class Environment(BaseObject):
|
||||
|
||||
class_api_path = "clusters/"
|
||||
instance_api_path = "clusters/{0}/"
|
||||
|
||||
@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 not None:
|
||||
data["net_segment_type"] = net_segment_type
|
||||
else:
|
||||
raise ArgumentException(
|
||||
'"--net-segment-type" must be specified!')
|
||||
data = cls.connection.post_request("clusters/", data)
|
||||
return cls.init_with_data(data)
|
||||
|
||||
def set(self, name=None, mode=None):
|
||||
data = {}
|
||||
if mode:
|
||||
data["mode"] = "ha_compact" \
|
||||
if mode.lower() == "ha" else "multinode"
|
||||
if name:
|
||||
data["name"] = name
|
||||
return self.connection.put_request(
|
||||
"clusters/{0}/".format(self.id),
|
||||
data
|
||||
)
|
||||
|
||||
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):
|
||||
return self.serializer.write_to_file(
|
||||
self.get_network_data_path(directory),
|
||||
network_data
|
||||
)
|
||||
|
||||
def write_settings_data(self, settings_data, directory=os.curdir):
|
||||
return self.serializer.write_to_file(
|
||||
self.get_settings_data_path(directory),
|
||||
settings_data
|
||||
)
|
||||
|
||||
def read_network_data(self, directory=os.curdir):
|
||||
network_file_path = self.get_network_data_path(directory)
|
||||
return self.serializer.read_from_file(network_file_path)
|
||||
|
||||
def read_settings_data(self, directory=os.curdir):
|
||||
settings_file_path = self.get_settings_data_path(directory)
|
||||
return self.serializer.read_from_file(settings_file_path)
|
||||
|
||||
@property
|
||||
def settings_url(self):
|
||||
return "clusters/{0}/attributes".format(self.id)
|
||||
|
||||
@property
|
||||
def default_settings_url(self):
|
||||
return self.settings_url + "/defaults"
|
||||
|
||||
@property
|
||||
@memorize_one
|
||||
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):
|
||||
return self.connection.get_request(
|
||||
self._get_fact_default_url(fact_type, nodes=nodes))
|
||||
|
||||
def get_facts(self, fact_type, nodes=None):
|
||||
return self.connection.get_request(
|
||||
self._get_fact_url(fact_type, nodes=nodes))
|
||||
|
||||
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):
|
||||
if fact_type == "deployment":
|
||||
return self.read_deployment_info(fact_type)
|
||||
elif fact_type == "provisioning":
|
||||
return self.read_provisioning_info(fact_type)
|
||||
|
||||
def write_facts_to_dir(self, fact_type, facts, directory=os.path.curdir):
|
||||
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")
|
||||
self.serializer.write_to_file(engine_file_path, facts["engine"])
|
||||
facts = facts["nodes"]
|
||||
name_template = "{name}"
|
||||
else:
|
||||
name_template = "{role}_{uid}"
|
||||
for _fact in facts:
|
||||
fact_path = os.path.join(
|
||||
dir_name,
|
||||
name_template.format(**_fact)
|
||||
)
|
||||
self.serializer.write_to_file(fact_path, _fact)
|
||||
|
||||
def read_deployment_info(self, fact_type, directory=os.path.curdir):
|
||||
dir_name = self._get_fact_dir_name(fact_type, directory=directory)
|
||||
return map(
|
||||
lambda f: 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):
|
||||
dir_name = self._get_fact_dir_name(fact_type, directory=directory)
|
||||
node_facts = map(
|
||||
lambda f: 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 = 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
|
||||
)
|
||||
return self.connection.post_request(
|
||||
"testruns",
|
||||
tests_data,
|
||||
ostf=True
|
||||
)
|
||||
|
||||
def get_state_of_tests(self):
|
||||
return self.connection.get_request(
|
||||
"testruns/last/{0}".format(self.id),
|
||||
ostf=True
|
||||
)
|
||||
|
||||
def stop(self):
|
||||
from fuelclient.objects.task import Task
|
||||
return Task.init_with_data(
|
||||
self.connection.put_request(
|
||||
"clusters/{0}/stop_deployment/".format(self.id),
|
||||
{}
|
||||
)
|
||||
)
|
||||
|
||||
def reset(self):
|
||||
from fuelclient.objects.task import Task
|
||||
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):
|
||||
from fuelclient.objects.task import Task
|
||||
return Task.init_with_data(
|
||||
self.connection.put_request(
|
||||
self._get_method_url(method_type, nodes),
|
||||
{}
|
||||
)
|
||||
)
|
|
@ -0,0 +1,159 @@
|
|||
# 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)
|
||||
if attributes_type == "interfaces":
|
||||
attributes = [{
|
||||
"interfaces": attributes,
|
||||
"id": self.id
|
||||
}]
|
||||
return self.connection.put_request(
|
||||
url,
|
||||
attributes
|
||||
)
|
||||
|
||||
def write_attribute(self, attribute_type, attributes, directory):
|
||||
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)
|
||||
self.serializer.write_to_file(
|
||||
attribute_path,
|
||||
attributes
|
||||
)
|
||||
|
||||
def read_attribute(self, attributes_type, directory):
|
||||
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 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,))
|
||||
|
||||
|
||||
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):
|
||||
self.collection = filter(
|
||||
lambda node: node.env_id == env_id,
|
||||
self.collection
|
||||
)
|
|
@ -0,0 +1,60 @@
|
|||
# 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.error import ArgumentException
|
||||
from fuelclient.objects.base import BaseObject
|
||||
|
||||
|
||||
class Release(BaseObject):
|
||||
|
||||
class_api_path = "releases/"
|
||||
instance_api_path = "releases/{0}/"
|
||||
|
||||
def configure(self, username, password,
|
||||
satellite_server_hostname=None, activation_key=None):
|
||||
data = {
|
||||
"release_id": self.id,
|
||||
"username": username,
|
||||
"password": password
|
||||
}
|
||||
satellite_flags = [satellite_server_hostname,
|
||||
activation_key]
|
||||
|
||||
if not any(satellite_flags):
|
||||
data.update({
|
||||
"license_type": "rhsm",
|
||||
"satellite": "",
|
||||
"activation_key": ""
|
||||
})
|
||||
elif all(satellite_flags):
|
||||
data.update({
|
||||
"license_type": "rhn",
|
||||
"satellite": satellite_server_hostname,
|
||||
"activation_key": activation_key
|
||||
})
|
||||
else:
|
||||
raise ArgumentException(
|
||||
'RedHat satellite settings requires both a'
|
||||
' "--satellite-server-hostname" and '
|
||||
'a "--activation-key" flags.'
|
||||
)
|
||||
release_response = self.connection.post_request(
|
||||
"redhat/setup/",
|
||||
data
|
||||
)
|
||||
return release_response
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
map(cls.init_with_data, cls.get_all_data())
|
|
@ -0,0 +1,100 @@
|
|||
# Copyright 2014 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from operator import methodcaller
|
||||
from time import sleep
|
||||
|
||||
from fuelclient.cli.error import DeployProgressError
|
||||
from fuelclient.objects.base import BaseObject
|
||||
|
||||
|
||||
class Task(BaseObject):
|
||||
|
||||
class_api_path = "tasks/"
|
||||
instance_api_path = "tasks/{0}/"
|
||||
|
||||
def delete(self, force=False):
|
||||
return self.connection.delete_request(
|
||||
"tasks/{0}/?force={1}".format(
|
||||
self.id,
|
||||
int(force),
|
||||
))
|
||||
|
||||
@property
|
||||
def progress(self):
|
||||
return self.get_fresh_data()["progress"]
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self.get_fresh_data()["status"]
|
||||
|
||||
@property
|
||||
def is_finished(self):
|
||||
return self.status in ("ready", "error")
|
||||
|
||||
def wait(self):
|
||||
while not self.is_finished:
|
||||
sleep(0.5)
|
||||
|
||||
|
||||
class DeployTask(Task):
|
||||
|
||||
def __init__(self, obj_id, env_id):
|
||||
from fuelclient.objects.environment import Environment
|
||||
super(DeployTask, self).__init__(obj_id)
|
||||
self.env = Environment(env_id)
|
||||
self.nodes = self.env.get_all_nodes()
|
||||
|
||||
@classmethod
|
||||
def init_with_data(cls, data):
|
||||
return cls(data["id"], data["cluster"])
|
||||
|
||||
@property
|
||||
def not_finished_nodes(self):
|
||||
return filter(
|
||||
lambda n: not n.is_finished(latest=False),
|
||||
self.nodes
|
||||
)
|
||||
|
||||
@property
|
||||
def is_finished(self):
|
||||
return super(DeployTask, self).is_finished and all(
|
||||
map(
|
||||
methodcaller("is_finished"),
|
||||
self.not_finished_nodes
|
||||
)
|
||||
)
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
if not self.is_finished:
|
||||
sleep(1)
|
||||
deploy_task_data = self.get_fresh_data()
|
||||
if deploy_task_data["status"] == "error":
|
||||
raise DeployProgressError(deploy_task_data["message"])
|
||||
for node in self.not_finished_nodes:
|
||||
node.update()
|
||||
return self.progress, self.nodes
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
|
||||
class SnapshotTask(Task):
|
||||
|
||||
@classmethod
|
||||
def start_snapshot_task(cls):
|
||||
dump_task = cls.connection.put_request("logs/package", {})
|
||||
return cls(dump_task["id"])
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
# 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
|
||||
|
@ -13,16 +12,22 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='python-fuelclient',
|
||||
version='0.1',
|
||||
name='fuelclient',
|
||||
version='0.2',
|
||||
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'],
|
||||
scripts=['fuel']
|
||||
install_requires=['PyYAML==3.10', "argparse==1.2.1"],
|
||||
packages=find_packages(),
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'fuel = fuelclient.cli.parser:main',
|
||||
],
|
||||
}
|
||||
)
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2013 Mirantis, Inc.
|
||||
# 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
|
||||
|
@ -20,7 +18,7 @@ import os
|
|||
from shutil import rmtree
|
||||
from tempfile import mkdtemp
|
||||
|
||||
from fuelclient.tests.base import BaseTestCase
|
||||
from tests.base import BaseTestCase
|
||||
|
||||
|
||||
class TestHandlers(BaseTestCase):
|
||||
|
@ -28,9 +26,9 @@ class TestHandlers(BaseTestCase):
|
|||
def test_env_action(self):
|
||||
#check env help
|
||||
help_msgs = ["usage: fuel environment [-h]",
|
||||
"[-h] [--env ENV] [-l] [-s]",
|
||||
"[--list | --set | --delete | --create]",
|
||||
"optional arguments:", "--help", "--list", "--set",
|
||||
"--delete", "--rel", "--release", "--env-create,",
|
||||
"--delete", "--rel", "--env-create",
|
||||
"--create", "--name", "--env-name", "--mode", "--net",
|
||||
"--network-mode", "--nst", "--net-segment-type",
|
||||
"--deployment-mode"]
|
||||
|
@ -59,12 +57,11 @@ class TestHandlers(BaseTestCase):
|
|||
self.check_for_stdout(cmd, msg)
|
||||
|
||||
def test_node_action(self):
|
||||
help_msg = ["fuel node [-h] [--env ENV] [-l]",
|
||||
"[-l] [-s] [--delete] [--default]", "-h", "--help", "-l",
|
||||
"--list", "-s", "--set", "--delete", "--default", "-d",
|
||||
"--download", "-u", "--upload", "--dir", "--node",
|
||||
"--node-id", "-r", "--role", "--net", "--network",
|
||||
"--disk", "--deploy", "--provision"]
|
||||
help_msg = ["fuel node [-h] [--env ENV]",
|
||||
"[--list | --set | --delete | --network | --disk |"
|
||||
" --deploy | --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")
|
||||
|
|
Loading…
Reference in New Issue