Merge tag '9.0' into debian/mitaka
python-fuelclient 9.0 release
This commit is contained in:
@@ -48,9 +48,13 @@ def get_client(resource, version='v1'):
|
||||
|
||||
version_map = {
|
||||
'v1': {
|
||||
'cluster-settings': v1.cluster_settings,
|
||||
'deployment_history': v1.deployment_history,
|
||||
'deployment-info': v1.deployment_info,
|
||||
'environment': v1.environment,
|
||||
'fuel-version': v1.fuelversion,
|
||||
'graph': v1.graph,
|
||||
'network-configuration': v1.network_configuration,
|
||||
'network-group': v1.network_group,
|
||||
'node': v1.node,
|
||||
'openstack-config': v1.openstack_config,
|
||||
|
||||
@@ -32,7 +32,7 @@ class OpenstackConfigAction(Action):
|
||||
self.args = (
|
||||
Args.get_env_arg(),
|
||||
Args.get_file_arg("Openstack configuration file"),
|
||||
Args.get_single_node_arg("Node ID"),
|
||||
Args.get_node_arg("Node IDs list"),
|
||||
Args.get_single_role_arg("Node role"),
|
||||
Args.get_config_id_arg("Openstack config ID"),
|
||||
Args.get_deleted_arg("Get deleted configurations"),
|
||||
@@ -60,7 +60,7 @@ class OpenstackConfigAction(Action):
|
||||
def list(self, params):
|
||||
"""List all available configurations:
|
||||
fuel openstack-config --list --env 1
|
||||
fuel openstack-config --list --env 1 --node 1
|
||||
fuel openstack-config --list --env 1 --node 1[,2,3,...]
|
||||
fuel openstack-config --list --env 1 --deleted
|
||||
"""
|
||||
filters = {'cluster_id': params.env}
|
||||
@@ -69,7 +69,7 @@ class OpenstackConfigAction(Action):
|
||||
filters['is_active'] = int(not params.deleted)
|
||||
|
||||
if 'node' in params:
|
||||
filters['node_id'] = params.node
|
||||
filters['node_ids'] = params.node
|
||||
|
||||
if 'role' in params:
|
||||
filters['node_role'] = params.role
|
||||
@@ -99,21 +99,27 @@ class OpenstackConfigAction(Action):
|
||||
def upload(self, params):
|
||||
"""Upload new configuration from file:
|
||||
fuel openstack-config --upload --env 1 --file config.yaml
|
||||
fuel openstack-config --upload --env 1 --node 1 --file config.yaml
|
||||
fuel openstack-config --upload --env 1 --node 1[,2,3,...]
|
||||
--file config.yaml
|
||||
fuel openstack-config --upload --env 1
|
||||
--role controller --file config.yaml
|
||||
"""
|
||||
node_id = getattr(params, 'node', None)
|
||||
node_ids = getattr(params, 'node', None)
|
||||
node_role = getattr(params, 'role', None)
|
||||
data = OpenstackConfig.read_file(params.file)
|
||||
|
||||
config = OpenstackConfig.create(
|
||||
configs = OpenstackConfig.create(
|
||||
cluster_id=params.env,
|
||||
configuration=data['configuration'],
|
||||
node_id=node_id, node_role=node_role)
|
||||
print("Openstack configuration with id {0} "
|
||||
"has been uploaded from file '{1}'"
|
||||
"".format(config.id, params.file))
|
||||
node_ids=node_ids, node_role=node_role)
|
||||
configs = [c.data for c in configs]
|
||||
self.serializer.print_to_output(
|
||||
configs,
|
||||
format_table(
|
||||
configs,
|
||||
acceptable_keys=self.acceptable_keys
|
||||
)
|
||||
)
|
||||
|
||||
@check_all('config-id')
|
||||
def delete(self, params):
|
||||
@@ -130,15 +136,15 @@ class OpenstackConfigAction(Action):
|
||||
def execute(self, params):
|
||||
"""Deploy configuration:
|
||||
fuel openstack-config --execute --env 1
|
||||
fuel openstack-config --execute --env 1 --node 1
|
||||
fuel openstack-config --execute --env 1 --node 1[,2,3,...]
|
||||
fuel openstack-config --execute --env 1 --role controller
|
||||
fuel openstack-config --execute --env 1 --force
|
||||
"""
|
||||
node_id = getattr(params, 'node', None)
|
||||
node_ids = getattr(params, 'node', None)
|
||||
node_role = getattr(params, 'role', None)
|
||||
force = getattr(params, 'force', False)
|
||||
task_result = OpenstackConfig.execute(
|
||||
cluster_id=params.env, node_id=node_id,
|
||||
cluster_id=params.env, node_ids=node_ids,
|
||||
node_role=node_role, force=force)
|
||||
if task_result['status'] == 'error':
|
||||
print(
|
||||
|
||||
@@ -18,6 +18,7 @@ from fuelclient.cli.actions.base import Action
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.error import ArgumentException
|
||||
from fuelclient.client import APIClient
|
||||
from fuelclient import fuelclient_settings
|
||||
|
||||
|
||||
class UserAction(Action):
|
||||
@@ -56,3 +57,9 @@ class UserAction(Action):
|
||||
password = self._get_password_from_prompt()
|
||||
|
||||
APIClient.update_own_password(password)
|
||||
settings = fuelclient_settings.get_settings()
|
||||
self.serializer.print_to_output(
|
||||
None, "\nPassword changed.\nPlease note that configuration "
|
||||
"is not automatically updated.\nYou may want to update "
|
||||
"{0}.".format(
|
||||
settings.user_settings))
|
||||
|
||||
@@ -201,14 +201,35 @@ class EnvDeploy(EnvMixIn, base.BaseCommand):
|
||||
|
||||
parser.add_argument('id',
|
||||
type=int,
|
||||
help='Id of the nailgun entity to be processed.')
|
||||
help='Id of the environment to be deployed.')
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
task_id = self.client.deploy_changes(parsed_args.id)
|
||||
|
||||
msg = 'Deploy task with id {t} for the environment {e} '\
|
||||
msg = 'Deployment task with id {t} for the environment {e} '\
|
||||
'has been started.\n'.format(t=task_id, e=parsed_args.id)
|
||||
|
||||
self.app.stdout.write(msg)
|
||||
|
||||
|
||||
class EnvRedeploy(EnvMixIn, base.BaseCommand):
|
||||
"""Redeploys changes on the specified environment."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(EnvRedeploy, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument('id',
|
||||
type=int,
|
||||
help='Id of the environment to be redeployed.')
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
task_id = self.client.redeploy_changes(parsed_args.id)
|
||||
|
||||
msg = 'Deployment task with id {t} for the environment {e} '\
|
||||
'has been started.\n'.format(t=task_id, e=parsed_args.id)
|
||||
|
||||
self.app.stdout.write(msg)
|
||||
|
||||
282
fuelclient/commands/graph.py
Normal file
282
fuelclient/commands/graph.py
Normal file
@@ -0,0 +1,282 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2016 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
|
||||
from fuelclient.cli import error
|
||||
from fuelclient.cli.serializers import Serializer
|
||||
from fuelclient.commands import base
|
||||
from fuelclient.common import data_utils
|
||||
|
||||
|
||||
class FileMethodsMixin(object):
|
||||
@classmethod
|
||||
def check_file_path(cls, file_path):
|
||||
if not os.path.exists(file_path):
|
||||
raise error.InvalidFileException(
|
||||
"File '{0}' doesn't exist.".format(file_path))
|
||||
|
||||
@classmethod
|
||||
def check_dir(cls, directory):
|
||||
if not os.path.exists(directory):
|
||||
raise error.InvalidDirectoryException(
|
||||
"Directory '{0}' doesn't exist.".format(directory))
|
||||
if not os.path.isdir(directory):
|
||||
raise error.InvalidDirectoryException(
|
||||
"Error: '{0}' is not a directory.".format(directory))
|
||||
|
||||
|
||||
class GraphUpload(base.BaseCommand, FileMethodsMixin):
|
||||
"""Upload deployment graph configuration."""
|
||||
entity_name = 'graph'
|
||||
|
||||
@classmethod
|
||||
def read_tasks_data_from_file(cls, file_path=None, serializer=None):
|
||||
"""Read Tasks data from given path.
|
||||
|
||||
:param file_path: path
|
||||
:type file_path: str
|
||||
:param serializer: serializer object
|
||||
:type serializer: object
|
||||
:return: data
|
||||
:rtype: list|object
|
||||
"""
|
||||
cls.check_file_path(file_path)
|
||||
return (serializer or Serializer()).read_from_full_path(file_path)
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(GraphUpload, self).get_parser(prog_name)
|
||||
graph_class = parser.add_mutually_exclusive_group()
|
||||
|
||||
graph_class.add_argument('-e',
|
||||
'--env',
|
||||
type=int,
|
||||
required=False,
|
||||
help='Id of the environment')
|
||||
graph_class.add_argument('-r',
|
||||
'--release',
|
||||
type=int,
|
||||
required=False,
|
||||
help='Id of the release')
|
||||
graph_class.add_argument('-p',
|
||||
'--plugin',
|
||||
type=int,
|
||||
required=False,
|
||||
help='Id of the plugin')
|
||||
|
||||
parser.add_argument('-t',
|
||||
'--type',
|
||||
type=str,
|
||||
default=None,
|
||||
required=False,
|
||||
help='Type of the deployment graph')
|
||||
parser.add_argument('-f',
|
||||
'--file',
|
||||
type=str,
|
||||
required=True,
|
||||
default=None,
|
||||
help='YAML file that contains '
|
||||
'deployment graph data.')
|
||||
return parser
|
||||
|
||||
def take_action(self, args):
|
||||
parameters_to_graph_class = (
|
||||
('env', 'clusters'),
|
||||
('release', 'releases'),
|
||||
('plugin', 'plugins'),
|
||||
)
|
||||
|
||||
for parameter, graph_class in parameters_to_graph_class:
|
||||
model_id = getattr(args, parameter)
|
||||
if model_id:
|
||||
self.client.upload(
|
||||
data=self.read_tasks_data_from_file(args.file),
|
||||
related_model=graph_class,
|
||||
related_id=model_id,
|
||||
graph_type=args.type
|
||||
)
|
||||
break
|
||||
|
||||
self.app.stdout.write(
|
||||
"Deployment graph was uploaded from {0}\n".format(args.file)
|
||||
)
|
||||
|
||||
|
||||
class GraphExecute(base.BaseCommand):
|
||||
"""Start deployment with given graph type."""
|
||||
entity_name = 'graph'
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(GraphExecute, self).get_parser(prog_name)
|
||||
parser.add_argument('-e',
|
||||
'--env',
|
||||
type=int,
|
||||
required=True,
|
||||
help='Id of the environment')
|
||||
parser.add_argument('-t',
|
||||
'--type',
|
||||
type=str,
|
||||
default=None,
|
||||
required=False,
|
||||
help='Type of the deployment graph')
|
||||
parser.add_argument('-n',
|
||||
'--nodes',
|
||||
type=int,
|
||||
nargs='+',
|
||||
required=False,
|
||||
help='Ids of the nodes to use for deployment.')
|
||||
return parser
|
||||
|
||||
def take_action(self, args):
|
||||
self.client.execute(
|
||||
env_id=args.env,
|
||||
graph_type=args.type,
|
||||
nodes=args.nodes
|
||||
)
|
||||
self.app.stdout.write(
|
||||
"Deployment was executed\n"
|
||||
)
|
||||
|
||||
|
||||
class GraphDownload(base.BaseCommand):
|
||||
"""Download deployment graph configuration."""
|
||||
entity_name = 'graph'
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(GraphDownload, self).get_parser(prog_name)
|
||||
tasks_level = parser.add_mutually_exclusive_group()
|
||||
parser.add_argument('-e',
|
||||
'--env',
|
||||
type=int,
|
||||
required=True,
|
||||
help='Id of the environment')
|
||||
|
||||
tasks_level.add_argument('-a',
|
||||
'--all',
|
||||
action="store_true",
|
||||
required=False,
|
||||
default=False,
|
||||
help='Download merged graph for the '
|
||||
'environment')
|
||||
tasks_level.add_argument('-c',
|
||||
'--cluster',
|
||||
action="store_true",
|
||||
required=False,
|
||||
default=False,
|
||||
help='Download cluster-specific tasks')
|
||||
tasks_level.add_argument('-p',
|
||||
'--plugins',
|
||||
action="store_true",
|
||||
required=False,
|
||||
default=False,
|
||||
help='Download plugins-specific tasks')
|
||||
tasks_level.add_argument('-r',
|
||||
'--release',
|
||||
action="store_true",
|
||||
required=False,
|
||||
default=False,
|
||||
help='Download release-specific tasks')
|
||||
|
||||
parser.add_argument('-t',
|
||||
'--type',
|
||||
type=str,
|
||||
default=None,
|
||||
required=False,
|
||||
help='Graph type string')
|
||||
parser.add_argument('-f',
|
||||
'--file',
|
||||
type=str,
|
||||
required=False,
|
||||
default=None,
|
||||
help='YAML file that contains tasks data.')
|
||||
return parser
|
||||
|
||||
@classmethod
|
||||
def get_default_tasks_data_path(cls):
|
||||
return os.path.join(
|
||||
os.path.abspath(os.curdir),
|
||||
"cluster_graph"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def write_tasks_to_file(cls, tasks_data, serializer=None, file_path=None):
|
||||
serializer = serializer or Serializer()
|
||||
if file_path:
|
||||
return serializer.write_to_full_path(
|
||||
file_path,
|
||||
tasks_data
|
||||
)
|
||||
else:
|
||||
return serializer.write_to_path(
|
||||
cls.get_default_tasks_data_path(),
|
||||
tasks_data
|
||||
)
|
||||
|
||||
def take_action(self, args):
|
||||
tasks_data = []
|
||||
for tasks_level_name in ('all', 'cluster', 'release', 'plugins'):
|
||||
if getattr(args, tasks_level_name):
|
||||
tasks_data = self.client.download(
|
||||
env_id=args.env,
|
||||
level=tasks_level_name,
|
||||
graph_type=args.type
|
||||
)
|
||||
break
|
||||
|
||||
# write to file
|
||||
graph_data_file_path = self.write_tasks_to_file(
|
||||
tasks_data=tasks_data,
|
||||
serializer=Serializer(),
|
||||
file_path=args.file)
|
||||
|
||||
self.app.stdout.write(
|
||||
"Tasks were downloaded to {0}\n".format(graph_data_file_path)
|
||||
)
|
||||
|
||||
|
||||
class GraphList(base.BaseListCommand):
|
||||
"""Upload deployment graph configuration."""
|
||||
entity_name = 'graph'
|
||||
columns = ("id",
|
||||
"name",
|
||||
"tasks",
|
||||
"relations")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(GraphList, self).get_parser(prog_name)
|
||||
parser.add_argument('-e',
|
||||
'--env',
|
||||
type=int,
|
||||
required=True,
|
||||
help='Id of the environment')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
data = self.client.list(
|
||||
env_id=parsed_args.env
|
||||
)
|
||||
# format fields
|
||||
for d in data:
|
||||
d['relations'] = "\n".join(
|
||||
'as "{type}" to {model}(ID={model_id})'
|
||||
.format(**r) for r in d['relations']
|
||||
)
|
||||
d['tasks'] = ', '.join(sorted(t['id'] for t in d['tasks']))
|
||||
data = data_utils.get_display_data_multi(self.columns, data)
|
||||
scolumn_ids = [self.columns.index(col)
|
||||
for col in parsed_args.sort_columns]
|
||||
data.sort(key=lambda x: [x[scolumn_id] for scolumn_id in scolumn_ids])
|
||||
return self.columns, data
|
||||
@@ -20,6 +20,10 @@ class OpenstackConfigMixin(object):
|
||||
|
||||
entity_name = 'openstack-config'
|
||||
|
||||
columns = (
|
||||
'id', 'is_active', 'config_type',
|
||||
'cluster_id', 'node_id', 'node_role')
|
||||
|
||||
@staticmethod
|
||||
def add_env_arg(parser):
|
||||
parser.add_argument(
|
||||
@@ -30,7 +34,7 @@ class OpenstackConfigMixin(object):
|
||||
@staticmethod
|
||||
def add_file_arg(parser):
|
||||
parser.add_argument(
|
||||
'-f', '--file', required=True,
|
||||
'--file', required=True,
|
||||
type=str, help='YAML file that contains openstack configuration.')
|
||||
|
||||
@staticmethod
|
||||
@@ -41,10 +45,10 @@ class OpenstackConfigMixin(object):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def add_node_id_arg(parser):
|
||||
def add_node_ids_arg(parser):
|
||||
parser.add_argument(
|
||||
'-n', '--node',
|
||||
type=int, default=None, help='Node ID.'
|
||||
type=int, nargs='+', default=None, help='Node IDs.'
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -69,19 +73,15 @@ class OpenstackConfigMixin(object):
|
||||
)
|
||||
|
||||
|
||||
class OpenstackConfigList(OpenstackConfigMixin, base.BaseCommand):
|
||||
class OpenstackConfigList(OpenstackConfigMixin, base.BaseListCommand):
|
||||
"""List all openstack configurations.
|
||||
"""
|
||||
|
||||
columns = (
|
||||
'id', 'is_active', 'config_type',
|
||||
'cluster_id', 'node_id', 'node_role')
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(OpenstackConfigList, self).get_parser(prog_name)
|
||||
|
||||
self.add_env_arg(parser)
|
||||
self.add_node_id_arg(parser)
|
||||
self.add_node_ids_arg(parser)
|
||||
self.add_node_role_arg(parser)
|
||||
self.add_deleted_arg(parser)
|
||||
|
||||
@@ -89,7 +89,7 @@ class OpenstackConfigList(OpenstackConfigMixin, base.BaseCommand):
|
||||
|
||||
def take_action(self, args):
|
||||
data = self.client.get_filtered(
|
||||
cluster_id=args.env, node_id=args.node,
|
||||
cluster_id=args.env, node_ids=args.node,
|
||||
node_role=args.role, is_active=(not args.deleted))
|
||||
data = data_utils.get_display_data_multi(self.columns, data)
|
||||
|
||||
@@ -116,7 +116,7 @@ class OpenstackConfigDownload(OpenstackConfigMixin, base.BaseCommand):
|
||||
self.app.stdout.write(msg)
|
||||
|
||||
|
||||
class OpenstackConfigUpload(OpenstackConfigMixin, base.BaseCommand):
|
||||
class OpenstackConfigUpload(OpenstackConfigMixin, base.BaseListCommand):
|
||||
"""Upload new opesntack configuration from file.
|
||||
"""
|
||||
|
||||
@@ -124,20 +124,20 @@ class OpenstackConfigUpload(OpenstackConfigMixin, base.BaseCommand):
|
||||
parser = super(OpenstackConfigUpload, self).get_parser(prog_name)
|
||||
|
||||
self.add_env_arg(parser)
|
||||
self.add_node_id_arg(parser)
|
||||
self.add_node_ids_arg(parser)
|
||||
self.add_node_role_arg(parser)
|
||||
self.add_file_arg(parser)
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, args):
|
||||
config = self.client.upload(
|
||||
configs = self.client.upload(
|
||||
path=args.file, cluster_id=args.env,
|
||||
node_id=args.node, node_role=args.role)
|
||||
node_ids=args.node, node_role=args.role)
|
||||
|
||||
msg = "OpenStack configuration with id {0} " \
|
||||
"uploaded from file '{0}'\n".format(config.id, args.file)
|
||||
self.app.stdout.write(msg)
|
||||
data = [c.data for c in configs]
|
||||
data = data_utils.get_display_data_multi(self.columns, data)
|
||||
return self.columns, data
|
||||
|
||||
|
||||
class OpenstackConfigExecute(OpenstackConfigMixin, base.BaseCommand):
|
||||
@@ -148,7 +148,7 @@ class OpenstackConfigExecute(OpenstackConfigMixin, base.BaseCommand):
|
||||
parser = super(OpenstackConfigExecute, self).get_parser(prog_name)
|
||||
|
||||
self.add_env_arg(parser)
|
||||
self.add_node_id_arg(parser)
|
||||
self.add_node_ids_arg(parser)
|
||||
self.add_node_role_arg(parser)
|
||||
self.add_force_arg(parser)
|
||||
|
||||
@@ -156,7 +156,7 @@ class OpenstackConfigExecute(OpenstackConfigMixin, base.BaseCommand):
|
||||
|
||||
def take_action(self, args):
|
||||
self.client.execute(
|
||||
cluster_id=args.env, node_id=args.node, node_role=args.role,
|
||||
cluster_id=args.env, node_ids=args.node, node_role=args.role,
|
||||
force=args.force)
|
||||
|
||||
msg = "OpenStack configuration execution started.\n"
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
|
||||
from fuelclient.cli.serializers import Serializer
|
||||
from fuelclient.commands import base
|
||||
from fuelclient.common import data_utils
|
||||
|
||||
@@ -19,6 +22,97 @@ from fuelclient.common import data_utils
|
||||
class TaskMixIn(object):
|
||||
entity_name = 'task'
|
||||
|
||||
@staticmethod
|
||||
def add_file_arg(parser):
|
||||
parser.add_argument(
|
||||
'-f',
|
||||
'--file',
|
||||
required=False,
|
||||
type=str,
|
||||
help='YAML file that contains network configuration.'
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def write_info_to_file(cls, info_type, data, transaction_id,
|
||||
serializer=None, file_path=None):
|
||||
"""Write additional info to the given path.
|
||||
|
||||
:param info_type: deployment_info | cluster_settings |
|
||||
network_configuration
|
||||
:type info_type: str
|
||||
:param data: data
|
||||
:type data: list of dict
|
||||
:param serializer: serializer
|
||||
:param transaction_id: Transaction ID
|
||||
:type transaction_id: str or int
|
||||
:param file_path: path
|
||||
:type file_path: str
|
||||
:return: path to resulting file
|
||||
:rtype: str
|
||||
"""
|
||||
return (serializer or Serializer()).write_to_path(
|
||||
(file_path or cls.get_default_info_path(info_type,
|
||||
transaction_id)),
|
||||
data)
|
||||
|
||||
@staticmethod
|
||||
def get_default_info_path(info_type, transaction_id):
|
||||
"""Generate default path for task additional info e.g. deployment info
|
||||
|
||||
:param info_type: deployment_info | cluster_settings |
|
||||
network_configuration
|
||||
:type info_type: str
|
||||
:param transaction_id: Transaction ID
|
||||
:type transaction_id: str or int
|
||||
:return: path
|
||||
:rtype: str
|
||||
"""
|
||||
return os.path.join(
|
||||
os.path.abspath(os.curdir),
|
||||
"{info_type}_{transaction_id}".format(
|
||||
info_type=info_type,
|
||||
transaction_id=transaction_id)
|
||||
)
|
||||
|
||||
def download_info_to_file(self, transaction_id, info_type, file_path):
|
||||
"""Get and save to path for task additional info e.g. deployment info
|
||||
|
||||
:param transaction_id: Transaction ID
|
||||
:type transaction_id: str or int
|
||||
:param info_type: deployment_info | cluster_settings |
|
||||
network_configuration
|
||||
:type info_type: str
|
||||
:param file_path: path
|
||||
:type file_path: str
|
||||
:return: path
|
||||
:rtype: str
|
||||
"""
|
||||
data = self.client.download(transaction_id=transaction_id)
|
||||
data_file_path = TaskMixIn.write_info_to_file(
|
||||
info_type,
|
||||
data,
|
||||
transaction_id,
|
||||
file_path)
|
||||
return data_file_path
|
||||
|
||||
|
||||
class TaskInfoFileMixIn(TaskMixIn):
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(TaskInfoFileMixIn, self).get_parser(
|
||||
prog_name)
|
||||
parser.add_argument('id', type=int, help='Id of the Task.')
|
||||
self.add_file_arg(parser)
|
||||
return parser
|
||||
|
||||
def download_info(self, parsed_args):
|
||||
data_file_path = self.download_info_to_file(
|
||||
transaction_id=parsed_args.id,
|
||||
info_type=self.info_type,
|
||||
file_path=parsed_args.file)
|
||||
|
||||
return data_file_path
|
||||
|
||||
|
||||
class TaskList(TaskMixIn, base.BaseListCommand):
|
||||
"""Show list of all available tasks."""
|
||||
@@ -85,3 +179,42 @@ class TaskHistoryShow(TaskMixIn, base.BaseListCommand):
|
||||
data = data_utils.get_display_data_multi(self.columns, data)
|
||||
|
||||
return (self.columns, data)
|
||||
|
||||
|
||||
class TaskNetworkConfigurationDownload(TaskInfoFileMixIn, base.BaseCommand):
|
||||
|
||||
entity_name = 'network-configuration'
|
||||
info_type = 'network_configuration'
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.stdout.write(
|
||||
"Network configuration for task with id={0}"
|
||||
" downloaded to {1}\n".format(parsed_args.id,
|
||||
self.download_info(parsed_args))
|
||||
)
|
||||
|
||||
|
||||
class TaskDeploymentInfoDownload(TaskInfoFileMixIn, base.BaseCommand):
|
||||
|
||||
entity_name = 'deployment-info'
|
||||
info_type = 'deployment_info'
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.stdout.write(
|
||||
"Deployment info for task with id={0}"
|
||||
" downloaded to {1}\n".format(parsed_args.id,
|
||||
self.download_info(parsed_args))
|
||||
)
|
||||
|
||||
|
||||
class TaskClusterSettingsDownload(TaskInfoFileMixIn, base.BaseCommand):
|
||||
|
||||
entity_name = 'cluster-settings'
|
||||
info_type = 'cluster_settings'
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.stdout.write(
|
||||
"Cluster settings for task with id={0}"
|
||||
" downloaded to {1}\n".format(parsed_args.id,
|
||||
self.download_info(parsed_args))
|
||||
)
|
||||
|
||||
@@ -14,4 +14,21 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
def Enum(*values, **kwargs):
|
||||
names = kwargs.get('names')
|
||||
if names:
|
||||
return namedtuple('Enum', names)(*values)
|
||||
return namedtuple('Enum', values)(*values)
|
||||
|
||||
|
||||
SERIALIZATION_FORMAT_FLAG = 'serialization_format'
|
||||
|
||||
TASK_STATUSES = Enum(
|
||||
'error',
|
||||
'pending',
|
||||
'ready',
|
||||
'running'
|
||||
)
|
||||
|
||||
@@ -61,17 +61,19 @@ class FuelClientSettings(object):
|
||||
default_settings = pkg_resources.resource_filename('fuelclient',
|
||||
'fuel_client.yaml')
|
||||
|
||||
user_settings = os.path.join(user_conf_dir, 'fuel', 'fuel_client.yaml')
|
||||
self.user_settings = os.path.join(user_conf_dir, 'fuel',
|
||||
'fuel_client.yaml')
|
||||
custom_settings = os.getenv('FUELCLIENT_CUSTOM_SETTINGS')
|
||||
|
||||
if not os.path.exists(user_settings) and not custom_settings:
|
||||
self.populate_default_settings(default_settings, user_settings)
|
||||
if not os.path.exists(self.user_settings) and not custom_settings:
|
||||
self.populate_default_settings(default_settings,
|
||||
self.user_settings)
|
||||
six.print_('Settings for Fuel Client have been saved to {0}.\n'
|
||||
'Consider changing default values to the ones which '
|
||||
'are appropriate for you.'.format(user_settings))
|
||||
'are appropriate for you.'.format(self.user_settings))
|
||||
|
||||
self._add_file_if_exists(default_settings, settings_files)
|
||||
self._add_file_if_exists(user_settings, settings_files)
|
||||
self._add_file_if_exists(self.user_settings, settings_files)
|
||||
|
||||
# Add a custom settings file specified by user
|
||||
self._add_file_if_exists(custom_settings, settings_files)
|
||||
|
||||
@@ -356,13 +356,20 @@ class Environment(BaseObject):
|
||||
(serializer or self.serializer).write_to_path(
|
||||
engine_file_path, facts["engine"])
|
||||
facts = facts["nodes"]
|
||||
name_template = u"{name}"
|
||||
|
||||
def name_builder(fact):
|
||||
return fact['name']
|
||||
else:
|
||||
name_template = "{role}_{uid}"
|
||||
def name_builder(fact):
|
||||
if 'role' in fact:
|
||||
# from 9.0 the deployment info is serialized only per node
|
||||
return "{role}_{uid}".format(**fact)
|
||||
return fact['uid']
|
||||
|
||||
for _fact in facts:
|
||||
fact_path = os.path.join(
|
||||
dir_name,
|
||||
name_template.format(**_fact)
|
||||
name_builder(_fact)
|
||||
)
|
||||
(serializer or self.serializer).write_to_path(fact_path, _fact)
|
||||
return dir_name
|
||||
|
||||
@@ -35,7 +35,7 @@ class OpenstackConfig(BaseObject):
|
||||
def create(cls, **kwargs):
|
||||
params = cls._prepare_params(kwargs)
|
||||
data = cls.connection.post_request(cls.class_api_path, params)
|
||||
return cls.init_with_data(data)
|
||||
return [cls.init_with_data(item) for item in data]
|
||||
|
||||
def delete(self):
|
||||
return self.connection.delete_request(
|
||||
@@ -50,6 +50,11 @@ class OpenstackConfig(BaseObject):
|
||||
def get_filtered_data(cls, **kwargs):
|
||||
url = cls.class_api_path
|
||||
params = cls._prepare_params(kwargs)
|
||||
|
||||
node_ids = params.get('node_ids')
|
||||
if node_ids is not None:
|
||||
params['node_ids'] = ','.join([str(n) for n in node_ids])
|
||||
|
||||
return cls.connection.get_request(url, params=params)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -23,6 +23,10 @@ class Task(BaseObject):
|
||||
|
||||
class_api_path = "transactions/"
|
||||
instance_api_path = "transactions/{0}/"
|
||||
info_types_url_map = {
|
||||
'deployment_info': 'deployment_info',
|
||||
'cluster_settings': 'settings',
|
||||
'network_configuration': 'network_configuration'}
|
||||
|
||||
def delete(self, force=False):
|
||||
return self.connection.delete_request(
|
||||
@@ -47,6 +51,31 @@ class Task(BaseObject):
|
||||
while not self.is_finished:
|
||||
sleep(0.5)
|
||||
|
||||
def deployment_info(self):
|
||||
return self.connection.get_request(
|
||||
self._get_additional_info_url('deployment_info'))
|
||||
|
||||
def network_configuration(self):
|
||||
return self.connection.get_request(
|
||||
self._get_additional_info_url('network_configuration'))
|
||||
|
||||
def cluster_settings(self):
|
||||
return self.connection.get_request(
|
||||
self._get_additional_info_url('cluster_settings'))
|
||||
|
||||
def _get_additional_info_url(self, info_type):
|
||||
"""Generate additional info url.
|
||||
|
||||
:param info_type: one of deployment_info, cluster_settings,
|
||||
network_configuration
|
||||
:type info_type: str
|
||||
:return: url
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
return self.instance_api_path.format(self.id) +\
|
||||
self.info_types_url_map[info_type]
|
||||
|
||||
|
||||
class DeployTask(Task):
|
||||
|
||||
|
||||
@@ -12,24 +12,27 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from oslotest import base as oslo_base
|
||||
|
||||
from fuelclient import consts
|
||||
from fuelclient.objects import Release
|
||||
|
||||
from oslotest import base as oslo_base
|
||||
|
||||
logging.basicConfig(stream=sys.stderr)
|
||||
log = logging.getLogger("CliTest.ExecutionLog")
|
||||
log.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
class CliExectutionResult(object):
|
||||
class CliExecutionResult(object):
|
||||
def __init__(self, process_handle, out, err):
|
||||
self.return_code = process_handle.returncode
|
||||
self.stdout = out
|
||||
@@ -45,6 +48,8 @@ class CliExectutionResult(object):
|
||||
|
||||
|
||||
class BaseTestCase(oslo_base.BaseTestCase):
|
||||
|
||||
handler = ''
|
||||
nailgun_root = os.environ.get('NAILGUN_ROOT', '/tmp/fuel_web/nailgun')
|
||||
|
||||
def setUp(self):
|
||||
@@ -91,7 +96,7 @@ class BaseTestCase(oslo_base.BaseTestCase):
|
||||
def run_cli_command(self, command_line,
|
||||
check_errors=True, env=os.environ.copy()):
|
||||
|
||||
command_args = [" ".join(('fuel', command_line))]
|
||||
command_args = [" ".join((self.handler, command_line))]
|
||||
process_handle = subprocess.Popen(
|
||||
command_args,
|
||||
stdout=subprocess.PIPE,
|
||||
@@ -100,7 +105,7 @@ class BaseTestCase(oslo_base.BaseTestCase):
|
||||
env=env
|
||||
)
|
||||
out, err = process_handle.communicate()
|
||||
result = CliExectutionResult(process_handle, out, err)
|
||||
result = CliExecutionResult(process_handle, out, err)
|
||||
log.debug("command_args: '%s',stdout: '%s', stderr: '%s'",
|
||||
command_args[0], out, err)
|
||||
if check_errors:
|
||||
@@ -129,6 +134,12 @@ class BaseTestCase(oslo_base.BaseTestCase):
|
||||
call = self.run_cli_command(command, check_errors=check_errors)
|
||||
self.assertEqual(call.stdout, msg)
|
||||
|
||||
def check_for_stdout_by_regexp(self, command, pattern, check_errors=True):
|
||||
call = self.run_cli_command(command, check_errors=check_errors)
|
||||
result = re.search(pattern, call.stdout)
|
||||
self.assertIsNotNone(result)
|
||||
return result
|
||||
|
||||
def check_for_stderr(self, command, msg, check_errors=True):
|
||||
call = self.run_cli_command(command, check_errors=check_errors)
|
||||
self.assertIn(msg, call.stderr)
|
||||
@@ -147,3 +158,59 @@ class BaseTestCase(oslo_base.BaseTestCase):
|
||||
def check_number_of_rows_in_table(self, command, number_of_rows):
|
||||
output = self.run_cli_command(command)
|
||||
self.assertEqual(len(output.stdout.split("\n")), number_of_rows + 3)
|
||||
|
||||
def _get_task_info(self, task_id):
|
||||
"""Get info about task with given ID.
|
||||
|
||||
:param task_id: Task ID
|
||||
:type task_id: str or int
|
||||
:return: Task info
|
||||
:rtype: dict
|
||||
"""
|
||||
return {}
|
||||
|
||||
def wait_task_ready(self, task_id, timeout=60, interval=3):
|
||||
"""Wait for changing task status to 'ready'.
|
||||
|
||||
:param task_id: Task ID
|
||||
:type task_id: str or int
|
||||
:param timeout: Max time of waiting, in seconds
|
||||
:type timeout: int
|
||||
:param interval: Interval of getting task info, in seconds
|
||||
:type interval: int
|
||||
"""
|
||||
wait_until_in_statuses = (consts.TASK_STATUSES.running,
|
||||
consts.TASK_STATUSES.pending)
|
||||
timer = time.time()
|
||||
while True:
|
||||
task = self._get_task_info(task_id)
|
||||
status = task.get('status', '')
|
||||
if status not in wait_until_in_statuses:
|
||||
self.assertEqual(status, consts.TASK_STATUSES.ready)
|
||||
break
|
||||
|
||||
if time.time() - timer > timeout:
|
||||
raise Exception(
|
||||
"Task '{0}' seems to be hanged".format(task['name'])
|
||||
)
|
||||
time.sleep(interval)
|
||||
|
||||
|
||||
class CLIv1TestCase(BaseTestCase):
|
||||
|
||||
handler = 'fuel'
|
||||
|
||||
def _get_task_info(self, task_id):
|
||||
command = "task --task {0} --json".format(str(task_id))
|
||||
call = self.run_cli_command(command)
|
||||
return json.loads(call.stdout)[0]
|
||||
|
||||
|
||||
class CLIv2TestCase(BaseTestCase):
|
||||
|
||||
handler = 'fuel2'
|
||||
|
||||
def _get_task_info(self, task_id):
|
||||
command = "task show -f json {0}".format(str(task_id))
|
||||
call = self.run_cli_command(command)
|
||||
return json.loads(call.stdout)
|
||||
|
||||
@@ -20,7 +20,7 @@ import tempfile
|
||||
from fuelclient.tests.functional import base
|
||||
|
||||
|
||||
class TestHandlers(base.BaseTestCase):
|
||||
class TestHandlers(base.CLIv1TestCase):
|
||||
|
||||
def test_env_action(self):
|
||||
# check env help
|
||||
@@ -289,7 +289,7 @@ class TestHandlers(base.BaseTestCase):
|
||||
)
|
||||
|
||||
|
||||
class TestCharset(base.BaseTestCase):
|
||||
class TestCharset(base.CLIv1TestCase):
|
||||
|
||||
def test_charset_problem(self):
|
||||
self.load_data_to_nailgun_server()
|
||||
@@ -301,7 +301,7 @@ class TestCharset(base.BaseTestCase):
|
||||
))
|
||||
|
||||
|
||||
class TestFiles(base.BaseTestCase):
|
||||
class TestFiles(base.CLIv1TestCase):
|
||||
|
||||
def test_file_creation(self):
|
||||
self.load_data_to_nailgun_server()
|
||||
@@ -322,9 +322,9 @@ class TestFiles(base.BaseTestCase):
|
||||
"--env 1 deployment --default",
|
||||
(
|
||||
"deployment_1",
|
||||
"deployment_1/primary-controller_1.yaml",
|
||||
"deployment_1/compute_2.yaml",
|
||||
"deployment_1/compute_3.yaml"
|
||||
"deployment_1/1.yaml",
|
||||
"deployment_1/2.yaml",
|
||||
"deployment_1/3.yaml"
|
||||
)
|
||||
),
|
||||
(
|
||||
@@ -340,9 +340,9 @@ class TestFiles(base.BaseTestCase):
|
||||
(
|
||||
"--env 1 deployment --default --json",
|
||||
(
|
||||
"deployment_1/primary-controller_1.json",
|
||||
"deployment_1/compute_2.json",
|
||||
"deployment_1/compute_3.json"
|
||||
"deployment_1/1.json",
|
||||
"deployment_1/2.json",
|
||||
"deployment_1/3.json"
|
||||
)
|
||||
),
|
||||
(
|
||||
@@ -393,7 +393,7 @@ class TestFiles(base.BaseTestCase):
|
||||
))
|
||||
|
||||
|
||||
class TestDownloadUploadNodeAttributes(base.BaseTestCase):
|
||||
class TestDownloadUploadNodeAttributes(base.CLIv1TestCase):
|
||||
|
||||
def test_upload_download_interfaces(self):
|
||||
self.load_data_to_nailgun_server()
|
||||
@@ -415,35 +415,39 @@ class TestDownloadUploadNodeAttributes(base.BaseTestCase):
|
||||
self.upload_command(cmd)))
|
||||
|
||||
|
||||
class TestDeployChanges(base.BaseTestCase):
|
||||
class TestDeployChanges(base.CLIv1TestCase):
|
||||
|
||||
create_env = "env create --name=test --release={0}"
|
||||
add_node = "--env-id=1 node set --node 1 --role=controller"
|
||||
deploy_changes = "deploy-changes --env 1"
|
||||
redeploy_changes = "redeploy-changes --env 1"
|
||||
cmd_create_env = "env create --name=test --release={0}"
|
||||
cmd_add_node = "--env-id=1 node set --node 1 --role=controller"
|
||||
cmd_deploy_changes = "deploy-changes --env 1"
|
||||
cmd_redeploy_changes = "redeploy-changes --env 1"
|
||||
|
||||
messages_success = [
|
||||
"Deploying changes to environment with id=1\n",
|
||||
"Finished deployment!\n"
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(TestDeployChanges, self).setUp()
|
||||
self.load_data_to_nailgun_server()
|
||||
release_id = self.get_first_deployable_release_id()
|
||||
self.create_env = self.create_env.format(release_id)
|
||||
self.run_cli_commands((self.create_env, self.add_node))
|
||||
self.cmd_create_env = self.cmd_create_env.format(release_id)
|
||||
self.run_cli_commands((
|
||||
self.cmd_create_env,
|
||||
self.cmd_add_node
|
||||
))
|
||||
|
||||
def test_deploy_changes(self):
|
||||
self.run_cli_commands((self.deploy_changes,))
|
||||
|
||||
def test_no_changes_to_deploy(self):
|
||||
self.run_cli_commands((self.deploy_changes,))
|
||||
self.check_for_stderr(self.deploy_changes,
|
||||
"(No changes to deploy)\n",
|
||||
check_errors=False)
|
||||
self.check_all_in_msg(self.cmd_deploy_changes,
|
||||
self.messages_success)
|
||||
|
||||
def test_redeploy_changes(self):
|
||||
self.run_cli_commands((self.deploy_changes,
|
||||
self.redeploy_changes))
|
||||
self.run_cli_command(self.cmd_deploy_changes)
|
||||
self.check_all_in_msg(self.cmd_redeploy_changes,
|
||||
self.messages_success)
|
||||
|
||||
|
||||
class TestDirectoryDoesntExistErrorMessages(base.BaseTestCase):
|
||||
class TestDirectoryDoesntExistErrorMessages(base.CLIv1TestCase):
|
||||
|
||||
def test_settings_upload(self):
|
||||
self.check_for_stderr(
|
||||
@@ -520,7 +524,7 @@ class TestDirectoryDoesntExistErrorMessages(base.BaseTestCase):
|
||||
)
|
||||
|
||||
|
||||
class TestUploadSettings(base.BaseTestCase):
|
||||
class TestUploadSettings(base.CLIv1TestCase):
|
||||
|
||||
create_env = "env create --name=test --release={0}"
|
||||
add_node = "--env-id=1 node set --node 1 --role=controller"
|
||||
|
||||
51
fuelclient/tests/functional/v2/test_client.py
Normal file
51
fuelclient/tests/functional/v2/test_client.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013-2014 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from fuelclient.tests.functional import base
|
||||
|
||||
|
||||
class TestDeployChanges(base.CLIv2TestCase):
|
||||
|
||||
cmd_create_env = "env create -r {0} cluster-test"
|
||||
cmd_add_node = "env add nodes -e 1 -n 1 -r controller"
|
||||
cmd_deploy_changes = "env deploy 1"
|
||||
cmd_redeploy_changes = "env redeploy 1"
|
||||
|
||||
pattern_success = (r"^Deployment task with id (\d{1,}) "
|
||||
r"for the environment 1 has been started.\n$")
|
||||
|
||||
def setUp(self):
|
||||
super(TestDeployChanges, self).setUp()
|
||||
self.load_data_to_nailgun_server()
|
||||
release_id = self.get_first_deployable_release_id()
|
||||
self.cmd_create_env = self.cmd_create_env.format(release_id)
|
||||
self.run_cli_commands((
|
||||
self.cmd_create_env,
|
||||
self.cmd_add_node
|
||||
))
|
||||
|
||||
def test_deploy_changes(self):
|
||||
self.check_for_stdout_by_regexp(self.cmd_deploy_changes,
|
||||
self.pattern_success)
|
||||
|
||||
def test_redeploy_changes(self):
|
||||
result = self.check_for_stdout_by_regexp(self.cmd_deploy_changes,
|
||||
self.pattern_success)
|
||||
task_id = result.group(1)
|
||||
self.wait_task_ready(task_id)
|
||||
|
||||
self.check_for_stdout_by_regexp(self.cmd_redeploy_changes,
|
||||
self.pattern_success)
|
||||
127
fuelclient/tests/unit/common/test_environments.py
Normal file
127
fuelclient/tests/unit/common/test_environments.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2016 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
import os
|
||||
|
||||
from fuelclient import objects
|
||||
|
||||
from fuelclient.tests.unit.v1 import base
|
||||
|
||||
|
||||
class TestEnvironmentObject(base.UnitTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestEnvironmentObject, self).setUp()
|
||||
self.env_object = objects.Environment(1)
|
||||
|
||||
def _setup_os_mock(self, os_mock):
|
||||
os_mock.path.exists.return_value = False
|
||||
os_mock.path.join = os.path.join
|
||||
os_mock.path.abspath = lambda x: x
|
||||
|
||||
@mock.patch("fuelclient.objects.environment.os")
|
||||
def test_write_facts_to_dir_for_legacy_envs(self, os_mock):
|
||||
facts = [
|
||||
{
|
||||
"uid": "1",
|
||||
"role": "controller",
|
||||
"data": "data1"
|
||||
},
|
||||
{
|
||||
"uid": "2",
|
||||
"role": "compute",
|
||||
"data": "data2"
|
||||
},
|
||||
]
|
||||
|
||||
self._setup_os_mock(os_mock)
|
||||
serializer = mock.MagicMock()
|
||||
|
||||
self.env_object.write_facts_to_dir(
|
||||
"deployment", facts, serializer=serializer
|
||||
)
|
||||
|
||||
serializer.write_to_path.assert_has_calls(
|
||||
[
|
||||
mock.call("./deployment_1/controller_1", facts[0]),
|
||||
mock.call("./deployment_1/compute_2", facts[1])
|
||||
]
|
||||
)
|
||||
|
||||
@mock.patch("fuelclient.objects.environment.os")
|
||||
def test_write_facts_to_dir_for_new_envs(self, os_mock):
|
||||
facts = [
|
||||
{
|
||||
"uid": "1",
|
||||
"roles": ["controller"],
|
||||
"data": "data1"
|
||||
},
|
||||
{
|
||||
"uid": "2",
|
||||
"roles": ["compute"],
|
||||
"data": "data2"
|
||||
},
|
||||
]
|
||||
|
||||
self._setup_os_mock(os_mock)
|
||||
serializer = mock.MagicMock()
|
||||
|
||||
self.env_object.write_facts_to_dir(
|
||||
"deployment", facts, serializer=serializer
|
||||
)
|
||||
|
||||
serializer.write_to_path.assert_has_calls(
|
||||
[
|
||||
mock.call("./deployment_1/1", facts[0]),
|
||||
mock.call("./deployment_1/2", facts[1])
|
||||
]
|
||||
)
|
||||
|
||||
@mock.patch("fuelclient.objects.environment.os")
|
||||
def test_write_facts_to_dir_if_facts_is_dict(self, os_mock):
|
||||
facts = {
|
||||
"engine": "test_engine",
|
||||
"nodes": [
|
||||
{
|
||||
"uid": "1",
|
||||
"name": "node-1",
|
||||
"roles": ["controller"],
|
||||
"data": "data1"
|
||||
},
|
||||
{
|
||||
"uid": "2",
|
||||
"name": "node-2",
|
||||
"roles": ["compute"],
|
||||
"data": "data2"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
self._setup_os_mock(os_mock)
|
||||
serializer = mock.MagicMock()
|
||||
|
||||
self.env_object.write_facts_to_dir(
|
||||
"deployment", facts, serializer=serializer
|
||||
)
|
||||
|
||||
serializer.write_to_path.assert_has_calls(
|
||||
[
|
||||
mock.call("./deployment_1/engine", facts['engine']),
|
||||
mock.call("./deployment_1/node-1", facts['nodes'][0]),
|
||||
mock.call("./deployment_1/node-2", facts['nodes'][1])
|
||||
]
|
||||
)
|
||||
@@ -62,7 +62,7 @@ class TestOpenstackConfigActions(base.UnitTestCase):
|
||||
|
||||
def test_config_upload(self):
|
||||
m_post = self.m_request.post(
|
||||
'/api/v1/openstack-config/', json=self.config)
|
||||
'/api/v1/openstack-config/', json=[self.config])
|
||||
m_open = mock.mock_open(read_data=yaml.safe_dump(
|
||||
{'configuration': self.config['configuration']}))
|
||||
with mock.patch('fuelclient.cli.serializers.open',
|
||||
@@ -72,6 +72,30 @@ class TestOpenstackConfigActions(base.UnitTestCase):
|
||||
'--upload', '--file', 'config.yaml'])
|
||||
self.assertTrue(m_post.called)
|
||||
|
||||
req = json.loads(m_post.last_request.text)
|
||||
self.assertEqual(req['cluster_id'], 1)
|
||||
|
||||
def test_config_upload_multinode(self):
|
||||
configs = [utils.get_fake_openstack_config(node_id=node_id)
|
||||
for node_id in [1, 2, 3]]
|
||||
|
||||
m_post = self.m_request.post(
|
||||
'/api/v1/openstack-config/', json=configs)
|
||||
|
||||
m_open = mock.mock_open(read_data=yaml.safe_dump(
|
||||
{'configuration': self.config['configuration']}))
|
||||
with mock.patch('fuelclient.cli.serializers.open',
|
||||
m_open, create=True):
|
||||
with mock.patch('fuelclient.objects.openstack_config.os'):
|
||||
self.execute(['fuel', 'openstack-config', '--env', '1',
|
||||
'--node', '1,2,3',
|
||||
'--upload', '--file', 'config.yaml'])
|
||||
self.assertTrue(m_post.called)
|
||||
|
||||
req = json.loads(m_post.last_request.text)
|
||||
self.assertEqual(req['node_ids'], [1, 2, 3])
|
||||
self.assertEqual(req['cluster_id'], 1)
|
||||
|
||||
@mock.patch('sys.stderr')
|
||||
def test_config_upload_fail(self, mocked_stderr):
|
||||
self.assertRaises(
|
||||
@@ -107,13 +131,23 @@ class TestOpenstackConfigActions(base.UnitTestCase):
|
||||
self.assertTrue(m_get.called)
|
||||
|
||||
m_get = self.m_request.get(
|
||||
'/api/v1/openstack-config/?cluster_id=84&node_id=42', json=[
|
||||
'/api/v1/openstack-config/?cluster_id=84&node_ids=42', json=[
|
||||
utils.get_fake_openstack_config(id=1, cluster_id=32),
|
||||
])
|
||||
self.execute(['fuel', 'openstack-config', '--env', '84',
|
||||
'--node', '42', '--list'])
|
||||
self.assertTrue(m_get.called)
|
||||
|
||||
def test_config_list_multinode(self):
|
||||
m_get = self.m_request.get(
|
||||
'/api/v1/openstack-config/?cluster_id=84&node_ids=1,2,3',
|
||||
json=[utils.get_fake_openstack_config(
|
||||
id=1, cluster_id=32, node_id=1)])
|
||||
|
||||
self.execute(['fuel', 'openstack-config', '--env', '84',
|
||||
'--node', '1,2,3', '--list'])
|
||||
self.assertTrue(m_get.called)
|
||||
|
||||
@mock.patch('sys.stderr')
|
||||
def test_config_list_fail(self, m_stderr):
|
||||
self.assertRaises(
|
||||
@@ -137,6 +171,17 @@ class TestOpenstackConfigActions(base.UnitTestCase):
|
||||
self.assertEqual({"cluster_id": 42, "force": False},
|
||||
json.loads(m_put.last_request.text))
|
||||
|
||||
def test_config_execute_multinode(self):
|
||||
m_put = self.m_request.put('/api/v1/openstack-config/execute/',
|
||||
json={'status': 'ready'})
|
||||
|
||||
self.execute(['fuel', 'openstack-config', '--env', '42',
|
||||
'--node', '1,2,3', '--execute'])
|
||||
self.assertTrue(m_put.called)
|
||||
self.assertEqual(
|
||||
{"cluster_id": 42, "force": False, "node_ids": [1, 2, 3]},
|
||||
json.loads(m_put.last_request.text))
|
||||
|
||||
def test_config_force_execute(self):
|
||||
m_put = self.m_request.put('/api/v1/openstack-config/execute/',
|
||||
json={'status': 'ready'})
|
||||
|
||||
@@ -21,6 +21,9 @@ from fuelclient.tests.unit.v1 import base
|
||||
|
||||
class TestChangePassword(base.UnitTestCase):
|
||||
|
||||
def assert_print(self, print_mock, result, msg):
|
||||
print_mock.assert_called_once_with(result, msg)
|
||||
|
||||
def test_get_password_from_prompt(self):
|
||||
user_action = UserAction()
|
||||
passwd = 'secret!'
|
||||
@@ -38,12 +41,17 @@ class TestChangePassword(base.UnitTestCase):
|
||||
ArgumentException, 'Passwords are not the same'):
|
||||
user_action._get_password_from_prompt()
|
||||
|
||||
@mock.patch('fuelclient.cli.serializers.Serializer.print_to_output')
|
||||
@mock.patch('fuelclient.cli.actions.user.fuelclient_settings')
|
||||
@mock.patch('fuelclient.cli.actions.user.APIClient')
|
||||
def test_change_password(self, mapiclient):
|
||||
def test_change_password(self, mapiclient, settings_mock, print_mock):
|
||||
user_action = UserAction()
|
||||
params = mock.Mock()
|
||||
params.newpass = None
|
||||
password = 'secret'
|
||||
conf_file = '/tmp/fuel_client.yaml'
|
||||
settings_mock.get_settings.return_value = mock.Mock(
|
||||
user_settings=conf_file)
|
||||
|
||||
with mock.patch('fuelclient.cli.actions.user.getpass',
|
||||
return_value=password) as mgetpass:
|
||||
@@ -57,6 +65,15 @@ class TestChangePassword(base.UnitTestCase):
|
||||
mgetpass.assert_has_calls(calls)
|
||||
mapiclient.update_own_password.assert_called_once_with(password)
|
||||
|
||||
msg = "\nPassword changed.\nPlease note that configuration " \
|
||||
"is not automatically updated.\nYou may want to update " \
|
||||
"{0}.".format(conf_file)
|
||||
|
||||
self.assert_print(
|
||||
print_mock,
|
||||
None,
|
||||
msg)
|
||||
|
||||
@mock.patch('fuelclient.cli.actions.user.APIClient')
|
||||
def test_change_password_w_newpass(self, mapiclient):
|
||||
user_action = UserAction()
|
||||
|
||||
130
fuelclient/tests/unit/v2/cli/test_deployment_graph.py
Normal file
130
fuelclient/tests/unit/v2/cli/test_deployment_graph.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2016 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
import six
|
||||
import yaml
|
||||
|
||||
from fuelclient.tests.unit.v2.cli import test_engine
|
||||
|
||||
|
||||
TASKS_YAML = '''- id: custom-task-1
|
||||
type: puppet
|
||||
parameters:
|
||||
param: value
|
||||
- id: custom-task-2
|
||||
type: puppet
|
||||
parameters:
|
||||
param: value
|
||||
'''
|
||||
|
||||
|
||||
class TestGraphActions(test_engine.BaseCLITest):
|
||||
|
||||
@mock.patch('fuelclient.commands.graph.os')
|
||||
def _test_cmd(self, method, cmd_line, expected_kwargs, os_m):
|
||||
os_m.exists.return_value = True
|
||||
self.m_get_client.reset_mock()
|
||||
self.m_client.get_filtered.reset_mock()
|
||||
m_open = mock.mock_open(read_data=TASKS_YAML)
|
||||
with mock.patch(
|
||||
'fuelclient.cli.serializers.open', m_open, create=True):
|
||||
self.exec_command('graph {0} {1}'.format(method, cmd_line))
|
||||
self.m_get_client.assert_called_once_with('graph', mock.ANY)
|
||||
self.m_client.__getattr__(method).assert_called_once_with(
|
||||
**expected_kwargs)
|
||||
|
||||
def test_upload(self):
|
||||
self._test_cmd('upload', '--env 1 --file new_graph.yaml', dict(
|
||||
data=yaml.load(TASKS_YAML),
|
||||
related_model='clusters',
|
||||
related_id=1,
|
||||
graph_type=None
|
||||
))
|
||||
self._test_cmd('upload', '--release 1 --file new_graph.yaml', dict(
|
||||
data=yaml.load(TASKS_YAML),
|
||||
related_model='releases',
|
||||
related_id=1,
|
||||
graph_type=None
|
||||
))
|
||||
self._test_cmd('upload', '--plugin 1 --file new_graph.yaml', dict(
|
||||
data=yaml.load(TASKS_YAML),
|
||||
related_model='plugins',
|
||||
related_id=1,
|
||||
graph_type=None
|
||||
))
|
||||
self._test_cmd(
|
||||
'upload',
|
||||
'--plugin 1 --file new_graph.yaml --type custom_type',
|
||||
dict(
|
||||
data=yaml.load(TASKS_YAML),
|
||||
related_model='plugins',
|
||||
related_id=1,
|
||||
graph_type='custom_type'
|
||||
)
|
||||
)
|
||||
|
||||
def test_execute(self):
|
||||
self._test_cmd(
|
||||
'execute',
|
||||
'--env 1 --type custom_graph --nodes 1 2 3',
|
||||
dict(
|
||||
env_id=1,
|
||||
graph_type='custom_graph',
|
||||
nodes=[1, 2, 3]
|
||||
)
|
||||
)
|
||||
|
||||
def test_download(self):
|
||||
self._test_cmd(
|
||||
'download',
|
||||
'--env 1 --all --file existing_graph.yaml --type custom_graph',
|
||||
dict(
|
||||
env_id=1,
|
||||
level='all',
|
||||
graph_type='custom_graph'
|
||||
)
|
||||
)
|
||||
|
||||
def test_list(self):
|
||||
with mock.patch('sys.stdout', new=six.moves.cStringIO()) as m_stdout:
|
||||
self.m_get_client.reset_mock()
|
||||
self.m_client.get_filtered.reset_mock()
|
||||
self.m_client.list.return_value = [
|
||||
{
|
||||
'name': 'updated-graph-name',
|
||||
'tasks': [{
|
||||
'id': 'test-task2',
|
||||
'type': 'puppet',
|
||||
'task_name': 'test-task2',
|
||||
'version': '2.0.0'
|
||||
}],
|
||||
'relations': [{
|
||||
'model': 'cluster',
|
||||
'model_id': 370,
|
||||
'type': 'custom-graph'
|
||||
}],
|
||||
'id': 1
|
||||
}
|
||||
]
|
||||
self.exec_command('graph list --env 1')
|
||||
self.m_get_client.assert_called_once_with('graph', mock.ANY)
|
||||
self.m_client.list.assert_called_once_with(env_id=1)
|
||||
|
||||
self.assertIn('1', m_stdout.getvalue())
|
||||
self.assertIn('updated-graph-name', m_stdout.getvalue())
|
||||
self.assertIn('custom-graph', m_stdout.getvalue())
|
||||
self.assertIn('test-task2', m_stdout.getvalue())
|
||||
@@ -95,6 +95,13 @@ class TestEnvCommand(test_engine.BaseCLITest):
|
||||
self.m_get_client.assert_called_once_with('environment', mock.ANY)
|
||||
self.m_client.deploy_changes.assert_called_once_with(42)
|
||||
|
||||
def test_env_redeploy(self):
|
||||
args = 'env redeploy 42'
|
||||
self.exec_command(args)
|
||||
|
||||
self.m_get_client.assert_called_once_with('environment', mock.ANY)
|
||||
self.m_client.redeploy_changes.assert_called_once_with(42)
|
||||
|
||||
def test_env_add_nodes(self):
|
||||
args = 'env add nodes -e 42 -n 24 25 -r compute cinder'
|
||||
self.exec_command(args)
|
||||
|
||||
@@ -36,21 +36,21 @@ class TestOpenstackConfig(test_engine.BaseCLITest):
|
||||
cmd_line='--env {0} --node {1}'.format(self.CLUSTER_ID,
|
||||
self.NODE_ID),
|
||||
expected_kwargs={'cluster_id': self.CLUSTER_ID,
|
||||
'node_id': self.NODE_ID, 'node_role': None,
|
||||
'node_ids': [self.NODE_ID], 'node_role': None,
|
||||
'is_active': True}
|
||||
)
|
||||
|
||||
def test_config_list_for_role(self):
|
||||
self._test_config_list(
|
||||
cmd_line='--env {0} --role compute'.format(self.CLUSTER_ID),
|
||||
expected_kwargs={'cluster_id': self.CLUSTER_ID, 'node_id': None,
|
||||
expected_kwargs={'cluster_id': self.CLUSTER_ID, 'node_ids': None,
|
||||
'node_role': 'compute', 'is_active': True}
|
||||
)
|
||||
|
||||
def test_config_list_for_cluster(self):
|
||||
self._test_config_list(
|
||||
cmd_line='--env {0}'.format(self.CLUSTER_ID),
|
||||
expected_kwargs={'cluster_id': self.CLUSTER_ID, 'node_id': None,
|
||||
expected_kwargs={'cluster_id': self.CLUSTER_ID, 'node_ids': None,
|
||||
'node_role': None, 'is_active': True}
|
||||
)
|
||||
|
||||
@@ -71,14 +71,14 @@ class TestOpenstackConfig(test_engine.BaseCLITest):
|
||||
self.m_get_client.assert_called_once_with('openstack-config', mock.ANY)
|
||||
self.m_client.upload.assert_called_once_with(
|
||||
path='config.yaml', cluster_id=self.CLUSTER_ID,
|
||||
node_id=self.NODE_ID, node_role=None)
|
||||
node_ids=[self.NODE_ID], node_role=None)
|
||||
|
||||
@mock.patch('sys.stderr')
|
||||
def test_config_upload_fail(self, mocked_stderr):
|
||||
cmd = 'openstack-config upload --env {0} ' \
|
||||
'--node {1}'.format(self.CLUSTER_ID, self.NODE_ID)
|
||||
self.assertRaises(SystemExit, self.exec_command, cmd)
|
||||
self.assertIn('-f/--file',
|
||||
self.assertIn('--file',
|
||||
mocked_stderr.write.call_args_list[-1][0][0])
|
||||
mocked_stderr.reset_mock()
|
||||
|
||||
@@ -101,7 +101,7 @@ class TestOpenstackConfig(test_engine.BaseCLITest):
|
||||
def test_config_download_fail(self, mocked_stderr):
|
||||
cmd = 'openstack-config download 1'
|
||||
self.assertRaises(SystemExit, self.exec_command, cmd)
|
||||
self.assertIn('-f/--file',
|
||||
self.assertIn('--file',
|
||||
mocked_stderr.write.call_args_list[-1][0][0])
|
||||
|
||||
def test_config_execute(self):
|
||||
@@ -111,8 +111,8 @@ class TestOpenstackConfig(test_engine.BaseCLITest):
|
||||
|
||||
self.m_get_client.assert_called_once_with('openstack-config', mock.ANY)
|
||||
self.m_client.execute.assert_called_once_with(
|
||||
cluster_id=self.CLUSTER_ID, node_id=self.NODE_ID, node_role=None,
|
||||
force=False)
|
||||
cluster_id=self.CLUSTER_ID, node_ids=[self.NODE_ID],
|
||||
node_role=None, force=False)
|
||||
|
||||
def test_config_force_execute(self):
|
||||
cmd = 'openstack-config execute --env {0} --node {1} --force' \
|
||||
@@ -121,5 +121,5 @@ class TestOpenstackConfig(test_engine.BaseCLITest):
|
||||
|
||||
self.m_get_client.assert_called_once_with('openstack-config', mock.ANY)
|
||||
self.m_client.execute.assert_called_once_with(
|
||||
cluster_id=self.CLUSTER_ID, node_id=self.NODE_ID, node_role=None,
|
||||
force=True)
|
||||
cluster_id=self.CLUSTER_ID, node_ids=[self.NODE_ID],
|
||||
node_role=None, force=True)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
import yaml
|
||||
|
||||
from fuelclient.tests.unit.v2.cli import test_engine
|
||||
from fuelclient.tests import utils
|
||||
@@ -58,3 +59,41 @@ class TestTaskCommand(test_engine.BaseCLITest):
|
||||
self.m_client.get_all.assert_called_once_with(transaction_id=task_id,
|
||||
nodes=None,
|
||||
statuses=None)
|
||||
|
||||
def _test_cmd(self, cmd, method, cmd_line, client,
|
||||
return_data, expected_kwargs):
|
||||
self.m_get_client.reset_mock()
|
||||
self.m_client.get_filtered.reset_mock()
|
||||
self.m_client.__getattr__(method).return_value =\
|
||||
yaml.safe_load(return_data)
|
||||
m_open = mock.mock_open()
|
||||
with mock.patch('fuelclient.cli.serializers.open',
|
||||
m_open, create=True):
|
||||
self.exec_command('task {0} {1} {2}'.format(cmd, method,
|
||||
cmd_line))
|
||||
|
||||
written_yaml = yaml.safe_load(m_open().write.mock_calls[0][1][0])
|
||||
expected_yaml = yaml.safe_load(return_data)
|
||||
self.assertEqual(written_yaml, expected_yaml)
|
||||
|
||||
self.m_get_client.assert_called_once_with(client, mock.ANY)
|
||||
self.m_client.__getattr__(method).assert_called_once_with(
|
||||
**expected_kwargs)
|
||||
|
||||
def test_task_deployment_info_download(self):
|
||||
self._test_cmd('deployment-info', 'download', '1',
|
||||
'deployment-info',
|
||||
utils.get_fake_yaml_deployment_info(),
|
||||
dict(transaction_id=1))
|
||||
|
||||
def test_task_cluster_settings_download(self):
|
||||
self._test_cmd('settings', 'download', '1',
|
||||
'cluster-settings',
|
||||
utils.get_fake_yaml_cluster_settings(),
|
||||
dict(transaction_id=1))
|
||||
|
||||
def test_task_network_configuration_download(self):
|
||||
self._test_cmd('network-configuration', 'download', '1',
|
||||
'network-configuration',
|
||||
utils.get_fake_yaml_network_conf(),
|
||||
dict(transaction_id=1))
|
||||
|
||||
133
fuelclient/tests/unit/v2/lib/test_deployment_graph.py
Normal file
133
fuelclient/tests/unit/v2/lib/test_deployment_graph.py
Normal file
@@ -0,0 +1,133 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2016 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
import yaml
|
||||
|
||||
import fuelclient
|
||||
from fuelclient.tests.unit.v2.lib import test_api
|
||||
from fuelclient.tests.utils import fake_task
|
||||
|
||||
TASKS_YAML = '''- id: custom-task-1
|
||||
type: puppet
|
||||
parameters:
|
||||
param: value
|
||||
- id: custom-task-2
|
||||
type: puppet
|
||||
parameters:
|
||||
param: value
|
||||
'''
|
||||
|
||||
|
||||
class TestDeploymentGraphFacade(test_api.BaseLibTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDeploymentGraphFacade, self).setUp()
|
||||
self.version = 'v1'
|
||||
self.client = fuelclient.get_client('graph', self.version)
|
||||
self.env_id = 1
|
||||
|
||||
def test_existing_graph_upload(self):
|
||||
expected_body = {
|
||||
'tasks': yaml.load(TASKS_YAML)}
|
||||
|
||||
matcher_post = self.m_request.post(
|
||||
'/api/v1/clusters/1/deployment_graphs/custom_graph',
|
||||
json=expected_body)
|
||||
|
||||
matcher_get = self.m_request.get(
|
||||
'/api/v1/clusters/1/deployment_graphs/custom_graph',
|
||||
status_code=404,
|
||||
json={'status': 'error', 'message': 'Does not exist'})
|
||||
|
||||
m_open = mock.mock_open(read_data=TASKS_YAML)
|
||||
with mock.patch(
|
||||
'fuelclient.cli.serializers.open', m_open, create=True):
|
||||
self.client.upload(
|
||||
data=expected_body,
|
||||
related_model='clusters',
|
||||
related_id=1,
|
||||
graph_type='custom_graph'
|
||||
)
|
||||
|
||||
self.assertTrue(matcher_get.called)
|
||||
self.assertTrue(matcher_post.called)
|
||||
self.assertItemsEqual(
|
||||
expected_body,
|
||||
matcher_post.last_request.json()
|
||||
)
|
||||
|
||||
def test_new_graph_upload(self):
|
||||
expected_body = {
|
||||
'tasks': yaml.load(TASKS_YAML)}
|
||||
|
||||
matcher_put = self.m_request.put(
|
||||
'/api/v1/clusters/1/deployment_graphs/custom_graph',
|
||||
json=expected_body)
|
||||
|
||||
matcher_get = self.m_request.get(
|
||||
'/api/v1/clusters/1/deployment_graphs/custom_graph',
|
||||
status_code=200,
|
||||
json={
|
||||
'tasks': [{'id': 'imatask', 'type': 'puppet'}]
|
||||
})
|
||||
|
||||
m_open = mock.mock_open(read_data=TASKS_YAML)
|
||||
with mock.patch(
|
||||
'fuelclient.cli.serializers.open', m_open, create=True):
|
||||
self.client.upload(
|
||||
data=expected_body,
|
||||
related_model='clusters',
|
||||
related_id=1,
|
||||
graph_type='custom_graph')
|
||||
|
||||
self.assertTrue(matcher_get.called)
|
||||
self.assertTrue(matcher_put.called)
|
||||
self.assertItemsEqual(
|
||||
expected_body,
|
||||
matcher_put.last_request.json()
|
||||
)
|
||||
|
||||
def test_new_graph_run(self):
|
||||
matcher_put = self.m_request.put(
|
||||
'/api/v1/clusters/1/deploy/?nodes=1,2,3&graph_type=custom_graph',
|
||||
json=fake_task.get_fake_task(cluster=370))
|
||||
# this is required to form running task info
|
||||
self.m_request.get(
|
||||
'/api/v1/nodes/?cluster_id=370',
|
||||
json={}
|
||||
)
|
||||
self.client.execute(
|
||||
env_id=1,
|
||||
nodes=[1, 2, 3],
|
||||
graph_type="custom_graph")
|
||||
self.assertTrue(matcher_put.called)
|
||||
|
||||
def test_graphs_list(self):
|
||||
matcher_get = self.m_request.get(
|
||||
'/api/v1/clusters/1/deployment_graphs/',
|
||||
json=[]
|
||||
)
|
||||
self.client.list(1)
|
||||
self.assertTrue(matcher_get.called)
|
||||
|
||||
def test_graphs_download(self):
|
||||
matcher_get = self.m_request.get(
|
||||
'/api/v1/clusters/1/deployment_tasks/?graph_type=custom_graph',
|
||||
json=[]
|
||||
)
|
||||
self.client.download(env_id=1, level='all', graph_type='custom_graph')
|
||||
self.assertTrue(matcher_get.called)
|
||||
141
fuelclient/tests/unit/v2/lib/test_openstack_config.py
Normal file
141
fuelclient/tests/unit/v2/lib/test_openstack_config.py
Normal file
@@ -0,0 +1,141 @@
|
||||
# Copyright 2016 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
import yaml
|
||||
|
||||
import fuelclient
|
||||
from fuelclient.tests.unit.v2.lib import test_api
|
||||
from fuelclient.tests import utils
|
||||
|
||||
|
||||
class TestOpenstackConfigClient(test_api.BaseLibTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOpenstackConfigClient, self).setUp()
|
||||
|
||||
self.version = 'v1'
|
||||
self.uri = '/api/{version}/openstack-config/'.format(
|
||||
version=self.version)
|
||||
|
||||
self.client = fuelclient.get_client('openstack-config', self.version)
|
||||
|
||||
def test_config_list_for_cluster(self):
|
||||
cluster_id = 1
|
||||
fake_configs = [
|
||||
utils.get_fake_openstack_config(cluster_id=cluster_id)
|
||||
]
|
||||
|
||||
uri = self.uri + '?cluster_id={0}&is_active=True'.format(cluster_id)
|
||||
m_get = self.m_request.get(uri, complete_qs=True, json=fake_configs)
|
||||
data = self.client.get_filtered(cluster_id=1)
|
||||
|
||||
self.assertTrue(m_get.called)
|
||||
self.assertEqual(data[0]['cluster_id'], cluster_id)
|
||||
|
||||
def test_config_list_for_node(self):
|
||||
cluster_id = 1
|
||||
fake_configs = [
|
||||
utils.get_fake_openstack_config(
|
||||
cluster_id=cluster_id, node_id=22),
|
||||
]
|
||||
|
||||
uri = self.uri + '?cluster_id={0}&node_ids=22' \
|
||||
'&is_active=True'.format(cluster_id)
|
||||
m_get = self.m_request.get(uri, complete_qs=True, json=fake_configs)
|
||||
data = self.client.get_filtered(cluster_id=1, node_ids=[22])
|
||||
|
||||
self.assertTrue(m_get.called)
|
||||
self.assertEqual(data[0]['cluster_id'], cluster_id)
|
||||
|
||||
def test_config_list_for_multinode(self):
|
||||
cluster_id = 1
|
||||
fake_configs = [
|
||||
utils.get_fake_openstack_config(
|
||||
cluster_id=cluster_id, node_id=22),
|
||||
utils.get_fake_openstack_config(
|
||||
cluster_id=cluster_id, node_id=44),
|
||||
]
|
||||
|
||||
uri = self.uri + '?cluster_id={0}&node_ids=22,44' \
|
||||
'&is_active=True'.format(cluster_id)
|
||||
m_get = self.m_request.get(uri, complete_qs=True, json=fake_configs)
|
||||
data = self.client.get_filtered(cluster_id=1, node_ids=[22, 44])
|
||||
|
||||
self.assertTrue(m_get.called)
|
||||
self.assertEqual(data[0]['cluster_id'], cluster_id)
|
||||
|
||||
def test_config_download(self):
|
||||
config_id = 42
|
||||
uri = self.uri + '{0}/'.format(42)
|
||||
fake_config = utils.get_fake_openstack_config(id=config_id)
|
||||
|
||||
m_get = self.m_request.get(uri, json=fake_config)
|
||||
|
||||
m_open = mock.mock_open()
|
||||
with mock.patch('fuelclient.cli.serializers.open',
|
||||
m_open, create=True):
|
||||
self.client.download(config_id, '/path/to/config')
|
||||
|
||||
self.assertTrue(m_get.called)
|
||||
written_yaml = yaml.safe_load(m_open().write.mock_calls[0][1][0])
|
||||
self.assertEqual(written_yaml,
|
||||
{'configuration': fake_config['configuration']})
|
||||
|
||||
def test_config_upload(self):
|
||||
cluster_id = 1
|
||||
fake_config = utils.get_fake_openstack_config(
|
||||
cluster_id=cluster_id)
|
||||
|
||||
m_post = self.m_request.post(self.uri, json=[fake_config])
|
||||
|
||||
m_open = mock.mock_open(read_data=yaml.safe_dump({
|
||||
'configuration': fake_config['configuration']
|
||||
}))
|
||||
with mock.patch(
|
||||
'fuelclient.cli.serializers.open', m_open, create=True), \
|
||||
mock.patch('os.path.exists', mock.Mock(return_value=True)):
|
||||
self.client.upload('/path/to/config', cluster_id)
|
||||
|
||||
self.assertTrue(m_post.called)
|
||||
body = m_post.last_request.json()
|
||||
self.assertEqual(body['cluster_id'], cluster_id)
|
||||
self.assertNotIn('node_ids', body)
|
||||
self.assertNotIn('node_role', body)
|
||||
|
||||
def test_config_upload_multinode(self):
|
||||
cluster_id = 1
|
||||
fake_configs = [
|
||||
utils.get_fake_openstack_config(
|
||||
cluster_id=cluster_id, node_id=42),
|
||||
utils.get_fake_openstack_config(
|
||||
cluster_id=cluster_id, node_id=44)
|
||||
]
|
||||
|
||||
m_post = self.m_request.post(self.uri, json=fake_configs)
|
||||
|
||||
m_open = mock.mock_open(read_data=yaml.safe_dump({
|
||||
'configuration': fake_configs[0]['configuration']
|
||||
}))
|
||||
with mock.patch(
|
||||
'fuelclient.cli.serializers.open', m_open, create=True), \
|
||||
mock.patch('os.path.exists', mock.Mock(return_value=True)):
|
||||
self.client.upload(
|
||||
'/path/to/config', cluster_id, node_ids=[42, 44])
|
||||
|
||||
self.assertTrue(m_post.called)
|
||||
body = m_post.last_request.json()
|
||||
self.assertEqual(body['cluster_id'], cluster_id)
|
||||
self.assertEqual(body['node_ids'], [42, 44])
|
||||
self.assertNotIn('node_role', body)
|
||||
58
fuelclient/tests/unit/v2/lib/test_task_additional_info.py
Normal file
58
fuelclient/tests/unit/v2/lib/test_task_additional_info.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2016 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 yaml
|
||||
|
||||
import fuelclient
|
||||
from fuelclient.tests.unit.v2.lib import test_api
|
||||
from fuelclient.tests import utils
|
||||
|
||||
|
||||
class TestTaskAdditionalInfoFacade(test_api.BaseLibTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestTaskAdditionalInfoFacade, self).setUp()
|
||||
|
||||
self.version = 'v1'
|
||||
self.task_id = 42
|
||||
self.res_uri = (
|
||||
'/api/{version}/transactions/{task_id}/'.format(
|
||||
version=self.version, task_id=self.task_id))
|
||||
|
||||
def _test_info_download(self, client_name, yaml_data, uri):
|
||||
client = fuelclient.get_client(client_name, self.version)
|
||||
expected_body = yaml.load(yaml_data)
|
||||
matcher = self.m_request.get("{0}{1}".format(self.res_uri, uri),
|
||||
json=expected_body)
|
||||
result = client.download(self.task_id)
|
||||
|
||||
self.assertTrue(matcher.called)
|
||||
self.assertEqual(expected_body, result)
|
||||
|
||||
def test_network_configuration_download(self):
|
||||
self._test_info_download('network-configuration',
|
||||
utils.get_fake_yaml_network_conf(),
|
||||
'network_configuration')
|
||||
|
||||
def test_cluster_settings_download(self):
|
||||
self._test_info_download('cluster-settings',
|
||||
utils.get_fake_yaml_cluster_settings(),
|
||||
'settings')
|
||||
|
||||
def test_deployment_info_download(self):
|
||||
self._test_info_download('deployment-info',
|
||||
utils.get_fake_yaml_deployment_info(),
|
||||
'deployment_info')
|
||||
@@ -15,6 +15,12 @@
|
||||
# under the License.
|
||||
|
||||
from fuelclient.tests.utils.random_data import random_string
|
||||
from fuelclient.tests.utils.fake_additional_info \
|
||||
import get_fake_yaml_cluster_settings
|
||||
from fuelclient.tests.utils.fake_additional_info \
|
||||
import get_fake_yaml_deployment_info
|
||||
from fuelclient.tests.utils.fake_additional_info \
|
||||
import get_fake_yaml_network_conf
|
||||
from fuelclient.tests.utils.fake_deployment_history \
|
||||
import get_fake_deployment_history
|
||||
from fuelclient.tests.utils.fake_net_conf import get_fake_interface_config
|
||||
@@ -31,6 +37,9 @@ from fuelclient.tests.utils.fake_openstack_config \
|
||||
|
||||
|
||||
__all__ = (get_fake_deployment_history,
|
||||
get_fake_yaml_cluster_settings,
|
||||
get_fake_yaml_deployment_info,
|
||||
get_fake_yaml_network_conf,
|
||||
get_fake_env,
|
||||
get_fake_fuel_version,
|
||||
get_fake_interface_config,
|
||||
|
||||
90
fuelclient/tests/utils/fake_additional_info.py
Normal file
90
fuelclient/tests/utils/fake_additional_info.py
Normal file
@@ -0,0 +1,90 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2016 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.
|
||||
|
||||
CLUSTER_SETTINGS = '''---
|
||||
editable:
|
||||
service_user:
|
||||
name:
|
||||
type: "hidden"
|
||||
value: "fuel"
|
||||
sudo:
|
||||
type: "hidden"
|
||||
value: "ALL=(ALL) NOPASSWD: ALL"
|
||||
homedir:
|
||||
type: "hidden"
|
||||
value: "/var/lib/fuel"
|
||||
'''
|
||||
|
||||
DEPLOYMENT_INFO = '''---
|
||||
glance_glare:
|
||||
user_password: yBw0bY60owLC1C0AplHpEiEX
|
||||
user_node_name: Untitled (5e:89)
|
||||
uid: '5'
|
||||
aodh:
|
||||
db_password: JnEjYacrjxU2TLdTUQE9LdKq
|
||||
user_password: 8MhyQgtWjWkl0Dv1r1worTjK
|
||||
mysql:
|
||||
root_password: bQhzpWjWIOTHOwEA4qNI8X4K
|
||||
wsrep_password: 01QSoq3bYHgA7oS0OPYQurgX
|
||||
murano-cfapi:
|
||||
db_password: hGrAhxUjv3kAPEjiV7uYNwgZ
|
||||
user_password: 43x0pvQMXugwd8JBaRSQXX4l
|
||||
enabled: false
|
||||
rabbit_password: ZqTnnw7lsGQNOFJRN6pTaI8t
|
||||
'''
|
||||
|
||||
NETWORK_CONF = '''---
|
||||
vips:
|
||||
vrouter_pub:
|
||||
network_role: "public/vip"
|
||||
ipaddr: "10.109.3.2"
|
||||
namespace: "vrouter"
|
||||
is_user_defined: false
|
||||
vendor_specific:
|
||||
iptables_rules:
|
||||
ns_start:
|
||||
- "iptables -t nat -A POSTROUTING -o <%INT%> -j MASQUERADE"
|
||||
'''
|
||||
|
||||
|
||||
def get_fake_yaml_cluster_settings():
|
||||
"""Create a fake cluster settings
|
||||
|
||||
Returns the serialized and parametrized representation of a dumped Fuel
|
||||
Cluster Settings. Represents the average amount of data.
|
||||
|
||||
"""
|
||||
return CLUSTER_SETTINGS
|
||||
|
||||
|
||||
def get_fake_yaml_deployment_info():
|
||||
"""Create a fake cluster settings
|
||||
|
||||
Returns the serialized and parametrized representation of a dumped Fuel
|
||||
Deployment Info. Represents the average amount of data.
|
||||
|
||||
"""
|
||||
return DEPLOYMENT_INFO
|
||||
|
||||
|
||||
def get_fake_yaml_network_conf():
|
||||
"""Create a fake cluster settings
|
||||
|
||||
Returns the serialized and parametrized representation of a dumped Fuel
|
||||
Network Conf. Represents the average amount of data.
|
||||
|
||||
"""
|
||||
return NETWORK_CONF
|
||||
@@ -25,6 +25,7 @@ def get_fake_task(task_id=None, status=None, name=None,
|
||||
"""
|
||||
return {'status': status or 'running',
|
||||
'name': name or 'deploy',
|
||||
'id': task_id or 42,
|
||||
'task_id': task_id or 42,
|
||||
'cluster': cluster or 34,
|
||||
'result': result or '',
|
||||
|
||||
@@ -12,9 +12,13 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from fuelclient.v1 import cluster_settings
|
||||
from fuelclient.v1 import deployment_history
|
||||
from fuelclient.v1 import deployment_info
|
||||
from fuelclient.v1 import environment
|
||||
from fuelclient.v1 import fuelversion
|
||||
from fuelclient.v1 import graph
|
||||
from fuelclient.v1 import network_configuration
|
||||
from fuelclient.v1 import network_group
|
||||
from fuelclient.v1 import node
|
||||
from fuelclient.v1 import openstack_config
|
||||
@@ -23,9 +27,13 @@ from fuelclient.v1 import task
|
||||
from fuelclient.v1 import vip
|
||||
|
||||
# Please keeps the list in alphabetical order
|
||||
__all__ = ('deployment_history',
|
||||
__all__ = ('cluster_settings',
|
||||
'deployment_history',
|
||||
'deployment_info',
|
||||
'environment',
|
||||
'fuelversion',
|
||||
'graph',
|
||||
'network_configuration',
|
||||
'network_group',
|
||||
'node',
|
||||
'openstack_config',
|
||||
|
||||
31
fuelclient/v1/cluster_settings.py
Normal file
31
fuelclient/v1/cluster_settings.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2016 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 import objects
|
||||
from fuelclient.v1 import base_v1
|
||||
|
||||
|
||||
class ClusterSettingsClient(base_v1.BaseV1Client):
|
||||
|
||||
_entity_wrapper = objects.Task
|
||||
|
||||
def download(self, transaction_id):
|
||||
task = self._entity_wrapper(transaction_id)
|
||||
return task.cluster_settings()
|
||||
|
||||
|
||||
def get_client():
|
||||
return ClusterSettingsClient()
|
||||
31
fuelclient/v1/deployment_info.py
Normal file
31
fuelclient/v1/deployment_info.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2016 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 import objects
|
||||
from fuelclient.v1 import base_v1
|
||||
|
||||
|
||||
class DeploymentInfoClient(base_v1.BaseV1Client):
|
||||
|
||||
_entity_wrapper = objects.Task
|
||||
|
||||
def download(self, transaction_id):
|
||||
task = self._entity_wrapper(transaction_id)
|
||||
return task.deployment_info()
|
||||
|
||||
|
||||
def get_client():
|
||||
return DeploymentInfoClient()
|
||||
@@ -71,6 +71,12 @@ class EnvironmentClient(base_v1.BaseV1Client):
|
||||
|
||||
return deploy_task.id
|
||||
|
||||
def redeploy_changes(self, environment_id):
|
||||
env = self._entity_wrapper(obj_id=environment_id)
|
||||
redeploy_task = env.redeploy_changes()
|
||||
|
||||
return redeploy_task.id
|
||||
|
||||
def spawn_vms(self, environment_id):
|
||||
env = self._entity_wrapper(obj_id=environment_id)
|
||||
return env.spawn_vms()
|
||||
|
||||
157
fuelclient/v1/graph.py
Normal file
157
fuelclient/v1/graph.py
Normal file
@@ -0,0 +1,157 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2016 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 import error
|
||||
from fuelclient.client import APIClient
|
||||
from fuelclient import objects
|
||||
from fuelclient.v1 import base_v1
|
||||
|
||||
|
||||
class GraphClient(base_v1.BaseV1Client):
|
||||
_entity_wrapper = objects.Environment
|
||||
|
||||
related_graphs_list_api_path = "{related_model}/{related_model_id}" \
|
||||
"/deployment_graphs/"
|
||||
|
||||
related_graph_api_path = "{related_model}/{related_model_id}" \
|
||||
"/deployment_graphs/{graph_type}"
|
||||
|
||||
cluster_deploy_api_path = "clusters/{env_id}/deploy/"
|
||||
|
||||
merged_cluster_tasks_api_path = "clusters/{env_id}/deployment_tasks" \
|
||||
"/?graph_type={graph_type}"
|
||||
|
||||
merged_plugins_tasks_api_path = "clusters/{env_id}/deployment_tasks" \
|
||||
"/plugins/?graph_type={graph_type}"
|
||||
|
||||
cluster_release_tasks_api_path = "clusters/{env_id}/deployment_tasks" \
|
||||
"/release/?graph_type={graph_type}"
|
||||
|
||||
@classmethod
|
||||
def update_graph_for_model(
|
||||
cls, data, related_model, related_model_id, graph_type=None):
|
||||
return APIClient.put_request(
|
||||
cls.related_graph_api_path.format(
|
||||
related_model=related_model,
|
||||
related_model_id=related_model_id,
|
||||
graph_type=graph_type or ""),
|
||||
data
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create_graph_for_model(
|
||||
cls, data, related_model, related_model_id, graph_type=None):
|
||||
return APIClient.post_request(
|
||||
cls.related_graph_api_path.format(
|
||||
related_model=related_model,
|
||||
related_model_id=related_model_id,
|
||||
graph_type=graph_type or ""),
|
||||
data
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_graph_for_model(
|
||||
cls, related_model, related_model_id, graph_type=None):
|
||||
return APIClient.get_request(
|
||||
cls.related_graph_api_path.format(
|
||||
related_model=related_model,
|
||||
related_model_id=related_model_id,
|
||||
graph_type=graph_type or ""))
|
||||
|
||||
def upload(self, data, related_model, related_id, graph_type):
|
||||
# create or update
|
||||
try:
|
||||
self.get_graph_for_model(
|
||||
related_model, related_id, graph_type)
|
||||
self.update_graph_for_model(
|
||||
{'tasks': data}, related_model, related_id, graph_type)
|
||||
except error.HTTPError as exc:
|
||||
if '404' in exc.message:
|
||||
self.create_graph_for_model(
|
||||
{'tasks': data}, related_model, related_id, graph_type)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, env_id, nodes, graph_type=None):
|
||||
put_args = []
|
||||
|
||||
if nodes:
|
||||
put_args.append("nodes={0}".format(",".join(map(str, nodes))))
|
||||
|
||||
if graph_type:
|
||||
put_args.append(("graph_type=" + graph_type))
|
||||
|
||||
url = "".join([
|
||||
cls.cluster_deploy_api_path.format(env_id=env_id),
|
||||
'?',
|
||||
'&'.join(put_args)])
|
||||
|
||||
deploy_data = APIClient.put_request(url, {})
|
||||
return objects.DeployTask.init_with_data(deploy_data)
|
||||
|
||||
# download
|
||||
@classmethod
|
||||
def get_merged_cluster_tasks(cls, env_id, graph_type=None):
|
||||
return APIClient.get_request(
|
||||
cls.merged_cluster_tasks_api_path.format(
|
||||
env_id=env_id,
|
||||
graph_type=graph_type or ""))
|
||||
|
||||
@classmethod
|
||||
def get_merged_plugins_tasks(cls, env_id, graph_type=None):
|
||||
return APIClient.get_request(
|
||||
cls.merged_plugins_tasks_api_path.format(
|
||||
env_id=env_id,
|
||||
graph_type=graph_type or ""))
|
||||
|
||||
@classmethod
|
||||
def get_release_tasks_for_cluster(cls, env_id, graph_type=None):
|
||||
return APIClient.get_request(
|
||||
cls.merged_plugins_tasks_api_path.format(
|
||||
env_id=env_id,
|
||||
graph_type=graph_type or ""))
|
||||
|
||||
def download(self, env_id, level, graph_type):
|
||||
tasks_levels = {
|
||||
'all': lambda: self.get_merged_cluster_tasks(
|
||||
env_id=env_id, graph_type=graph_type),
|
||||
|
||||
'cluster': lambda: self.get_graph_for_model(
|
||||
related_model='clusters',
|
||||
related_model_id=env_id,
|
||||
graph_type=graph_type).get('tasks', []),
|
||||
|
||||
'plugins': lambda: self.get_merged_plugins_tasks(
|
||||
env_id=env_id,
|
||||
graph_type=graph_type),
|
||||
|
||||
'release': lambda: self.get_release_tasks_for_cluster(
|
||||
env_id=env_id,
|
||||
graph_type=graph_type)
|
||||
}
|
||||
return tasks_levels[level]()
|
||||
|
||||
# list
|
||||
@classmethod
|
||||
def list(cls, env_id):
|
||||
# todo(ikutukov): extend lists to support all models
|
||||
return APIClient.get_request(
|
||||
cls.related_graphs_list_api_path.format(
|
||||
related_model='clusters',
|
||||
related_model_id=env_id))
|
||||
|
||||
|
||||
def get_client():
|
||||
return GraphClient()
|
||||
31
fuelclient/v1/network_configuration.py
Normal file
31
fuelclient/v1/network_configuration.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2016 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 import objects
|
||||
from fuelclient.v1 import base_v1
|
||||
|
||||
|
||||
class NetworkConfigurationClient(base_v1.BaseV1Client):
|
||||
|
||||
_entity_wrapper = objects.Task
|
||||
|
||||
def download(self, transaction_id):
|
||||
task = self._entity_wrapper(transaction_id)
|
||||
return task.network_configuration()
|
||||
|
||||
|
||||
def get_client():
|
||||
return NetworkConfigurationClient()
|
||||
@@ -20,11 +20,11 @@ class OpenstackConfigClient(base_v1.BaseV1Client):
|
||||
|
||||
_entity_wrapper = objects.OpenstackConfig
|
||||
|
||||
def upload(self, path, cluster_id, node_id=None, node_role=None):
|
||||
def upload(self, path, cluster_id, node_ids=None, node_role=None):
|
||||
data = self._entity_wrapper.read_file(path)
|
||||
return self._entity_wrapper.create(
|
||||
cluster_id=cluster_id, configuration=data['configuration'],
|
||||
node_id=node_id, node_role=node_role)
|
||||
node_ids=node_ids, node_role=node_role)
|
||||
|
||||
def download(self, config_id, path):
|
||||
config = self._entity_wrapper(config_id)
|
||||
@@ -33,15 +33,17 @@ class OpenstackConfigClient(base_v1.BaseV1Client):
|
||||
|
||||
return path
|
||||
|
||||
def execute(self, config_id, cluster_id, node_id, node_role, force=False):
|
||||
def execute(self, cluster_id, config_id=None, node_ids=None,
|
||||
node_role=None, force=False):
|
||||
return self._entity_wrapper.execute(
|
||||
config_id=config_id, cluster_id=cluster_id, node_id=node_id,
|
||||
cluster_id=cluster_id, config_id=config_id, node_ids=node_ids,
|
||||
node_role=node_role, force=force)
|
||||
|
||||
def get_filtered(self, cluster_id, node_id, node_role, is_active=True):
|
||||
def get_filtered(self, cluster_id, node_ids=None,
|
||||
node_role=None, is_active=True):
|
||||
return self._entity_wrapper.get_filtered_data(
|
||||
cluster_id=cluster_id, node_id=node_id, node_role=node_role,
|
||||
is_active=is_active)
|
||||
cluster_id=cluster_id, node_ids=node_ids,
|
||||
node_role=node_role, is_active=is_active)
|
||||
|
||||
|
||||
def get_client():
|
||||
|
||||
@@ -34,10 +34,15 @@ fuelclient =
|
||||
env_delete=fuelclient.commands.environment:EnvDelete
|
||||
env_deploy=fuelclient.commands.environment:EnvDeploy
|
||||
env_list=fuelclient.commands.environment:EnvList
|
||||
env_redeploy=fuelclient.commands.environment:EnvRedeploy
|
||||
env_show=fuelclient.commands.environment:EnvShow
|
||||
env_spawn-vms=fuelclient.commands.environment:EnvSpawnVms
|
||||
env_update=fuelclient.commands.environment:EnvUpdate
|
||||
fuel-version=fuelclient.commands.fuelversion:FuelVersion
|
||||
graph_download=fuelclient.commands.graph:GraphDownload
|
||||
graph_execute=fuelclient.commands.graph:GraphExecute
|
||||
graph_list=fuelclient.commands.graph:GraphList
|
||||
graph_upload=fuelclient.commands.graph:GraphUpload
|
||||
network-group_create=fuelclient.commands.network_group:NetworkGroupCreate
|
||||
network-group_delete=fuelclient.commands.network_group:NetworkGroupDelete
|
||||
network-group_list=fuelclient.commands.network_group:NetworkGroupList
|
||||
@@ -61,6 +66,9 @@ fuelclient =
|
||||
task_list=fuelclient.commands.task:TaskList
|
||||
task_show=fuelclient.commands.task:TaskShow
|
||||
task_history_show=fuelclient.commands.task:TaskHistoryShow
|
||||
task_settings_download=fuelclient.commands.task:TaskClusterSettingsDownload
|
||||
task_deployment-info_download=fuelclient.commands.task:TaskDeploymentInfoDownload
|
||||
task_network-configuration_download=fuelclient.commands.task:TaskNetworkConfigurationDownload
|
||||
openstack-config_list=fuelclient.commands.openstack_config:OpenstackConfigList
|
||||
openstack-config_upload=fuelclient.commands.openstack_config:OpenstackConfigUpload
|
||||
openstack-config_download=fuelclient.commands.openstack_config:OpenstackConfigDownload
|
||||
|
||||
@@ -122,7 +122,7 @@ obtain_nailgun() {
|
||||
err "Obtaining Nailgun with the revision $FUEL_COMMIT"
|
||||
|
||||
if [[ "$FUEL_WEB_CLONE" == "yes" ]]; then
|
||||
git clone --depth 1 $FUEL_WEB_REPO $FUEL_WEB_ROOT || \
|
||||
git clone "${FUEL_WEB_REPO}" "${FUEL_WEB_ROOT}" || \
|
||||
{ err "Failed to clone Nailgun"; exit 1; }
|
||||
fi
|
||||
|
||||
|
||||
4
tox.ini
4
tox.ini
@@ -14,11 +14,11 @@ setenv = VIRTUAL_ENV={envdir}
|
||||
|
||||
# Functional env settings
|
||||
FUEL_WEB_CLONE={env:FUEL_WEB_CLONE:yes}
|
||||
FUEL_WEB_REPO={env:FUEL_WEB_REPO:https://github.com/stackforge/fuel-web.git}
|
||||
FUEL_WEB_REPO={env:FUEL_WEB_REPO:https://github.com/openstack/fuel-web.git}
|
||||
FUEL_WEB_ROOT={env:FUEL_WEB_ROOT:/tmp/fuel_web}
|
||||
FETCH_REPO={env:FETCH_REPO:}
|
||||
FETCH_REFSPEC={env:FETCH_REFSPEC:}
|
||||
FUEL_COMMIT={env:FUEL_COMMIT:master}
|
||||
FUEL_COMMIT={env:FUEL_COMMIT:stable/mitaka}
|
||||
NAILGUN_ROOT={env:FUEL_WEB_ROOT:/tmp/fuel_web}/nailgun
|
||||
|
||||
# Nailgun server parameters
|
||||
|
||||
Reference in New Issue
Block a user