Merge tag '9.0' into debian/mitaka

python-fuelclient 9.0 release
This commit is contained in:
Thomas Goirand
2016-05-23 10:14:06 +02:00
38 changed files with 1806 additions and 100 deletions

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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

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

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

View File

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

View 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

View File

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

View File

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

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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