Merge "Add tags support in CLI"
This commit is contained in:
commit
149be0d7c6
@ -78,6 +78,7 @@ def get_client(resource, version='v1', connection=None):
|
||||
'sequence': v1.sequence,
|
||||
'snapshot': v1.snapshot,
|
||||
'task': v1.task,
|
||||
'tag': v1.tag,
|
||||
'vip': v1.vip
|
||||
}
|
||||
}
|
||||
|
@ -168,16 +168,22 @@ class BaseDownloadCommand(NodeMixIn, base.BaseCommand):
|
||||
class NodeList(NodeMixIn, base.BaseListCommand):
|
||||
"""Show list of all available nodes."""
|
||||
|
||||
columns = ('id',
|
||||
'name',
|
||||
'status',
|
||||
'os_platform',
|
||||
'roles',
|
||||
'ip',
|
||||
'mac',
|
||||
'cluster',
|
||||
'platform_name',
|
||||
'online')
|
||||
_columns = (
|
||||
'id',
|
||||
'name',
|
||||
'status',
|
||||
'os_platform',
|
||||
'ip',
|
||||
'mac',
|
||||
'cluster',
|
||||
'platform_name',
|
||||
'online')
|
||||
|
||||
@property
|
||||
def columns(self):
|
||||
if self.app.is_advanced_mode:
|
||||
return self._columns + ('tags',)
|
||||
return self._columns + ('pending_roles', 'roles')
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(NodeList, self).get_parser(prog_name)
|
||||
@ -187,7 +193,6 @@ class NodeList(NodeMixIn, base.BaseListCommand):
|
||||
'--env',
|
||||
type=int,
|
||||
help='Show only nodes that are in the specified environment')
|
||||
|
||||
parser.add_argument(
|
||||
'-l',
|
||||
'--labels',
|
||||
@ -202,35 +207,40 @@ class NodeList(NodeMixIn, base.BaseListCommand):
|
||||
environment_id=parsed_args.env, labels=parsed_args.labels)
|
||||
data = data_utils.get_display_data_multi(self.columns, data)
|
||||
|
||||
return (self.columns, data)
|
||||
return self.columns, data
|
||||
|
||||
|
||||
class NodeShow(NodeMixIn, base.BaseShowCommand):
|
||||
"""Show info about node with given id."""
|
||||
|
||||
columns = ('id',
|
||||
'name',
|
||||
'status',
|
||||
'os_platform',
|
||||
'roles',
|
||||
'kernel_params',
|
||||
'pending_roles',
|
||||
'ip',
|
||||
'mac',
|
||||
'error_type',
|
||||
'pending_addition',
|
||||
'hostname',
|
||||
'fqdn',
|
||||
'platform_name',
|
||||
'cluster',
|
||||
'online',
|
||||
'progress',
|
||||
'pending_deletion',
|
||||
'group_id',
|
||||
# TODO(romcheg): network_data mostly never fits the screen
|
||||
# 'network_data',
|
||||
'manufacturer')
|
||||
columns += NodeMixIn.numa_fields
|
||||
_columns = (
|
||||
'id',
|
||||
'name',
|
||||
'status',
|
||||
'os_platform',
|
||||
'kernel_params',
|
||||
'ip',
|
||||
'mac',
|
||||
'error_type',
|
||||
'pending_addition',
|
||||
'hostname',
|
||||
'fqdn',
|
||||
'platform_name',
|
||||
'cluster',
|
||||
'online',
|
||||
'progress',
|
||||
'pending_deletion',
|
||||
'group_id',
|
||||
# TODO(romcheg): network_data mostly never fits the screen
|
||||
# 'network_data',
|
||||
'manufacturer')
|
||||
_columns += NodeMixIn.numa_fields
|
||||
|
||||
@property
|
||||
def columns(self):
|
||||
if self.app.is_advanced_mode:
|
||||
return self._columns + ('tags',)
|
||||
return self._columns + ('pending_roles', 'roles')
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
data = self.client.get_by_id(parsed_args.id)
|
||||
@ -240,9 +250,8 @@ class NodeShow(NodeMixIn, base.BaseShowCommand):
|
||||
return self.columns, data
|
||||
|
||||
|
||||
class NodeUpdate(NodeMixIn, base.BaseShowCommand):
|
||||
class NodeUpdate(NodeShow):
|
||||
"""Change given attributes for a node."""
|
||||
|
||||
columns = NodeShow.columns
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
|
296
fuelclient/commands/tag.py
Normal file
296
fuelclient/commands/tag.py
Normal file
@ -0,0 +1,296 @@
|
||||
# -*- 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 abc
|
||||
import argparse
|
||||
from cliff import show
|
||||
import os
|
||||
|
||||
from oslo_utils import fileutils
|
||||
import six
|
||||
|
||||
from fuelclient.cli import error
|
||||
from fuelclient.commands import base
|
||||
from fuelclient.common import data_utils
|
||||
from fuelclient import utils
|
||||
|
||||
|
||||
class TagMixIn(object):
|
||||
|
||||
entity_name = 'tag'
|
||||
supported_file_formats = ('json', 'yaml')
|
||||
fields_mapper = (
|
||||
('env', 'clusters'),
|
||||
('release', 'releases'),
|
||||
('plugin', 'plugins'),
|
||||
)
|
||||
|
||||
def parse_model(self, args):
|
||||
for param, tag_class in self.fields_mapper:
|
||||
model_id = getattr(args, param)
|
||||
if model_id:
|
||||
return tag_class, model_id
|
||||
|
||||
@staticmethod
|
||||
def check_file_path(file_path):
|
||||
if not utils.file_exists(file_path):
|
||||
raise argparse.ArgumentTypeError(
|
||||
'File "{0}" does not exist.'.format(file_path))
|
||||
return file_path
|
||||
|
||||
@staticmethod
|
||||
def get_file_path(directory, tag_id, file_format):
|
||||
return os.path.join(os.path.abspath(directory),
|
||||
'tag_{}.{}'.format(tag_id, file_format))
|
||||
|
||||
@staticmethod
|
||||
def read_tag_data(file_format, file_path):
|
||||
try:
|
||||
with open(file_path, 'r') as stream:
|
||||
data = data_utils.safe_load(file_format, stream)
|
||||
except (OSError, IOError):
|
||||
msg = "Could not read tag's description at {}.".format(file_path)
|
||||
raise error.InvalidFileException(msg)
|
||||
return data
|
||||
|
||||
|
||||
class TagShow(TagMixIn, base.BaseShowCommand):
|
||||
"""Show single tag by id."""
|
||||
columns = ("id", "tag", "has_primary")
|
||||
|
||||
|
||||
class TagList(TagMixIn, base.BaseListCommand):
|
||||
"""Show list of available tags for release, cluster or plugin."""
|
||||
columns = TagShow.columns
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(TagList, self).get_parser(prog_name)
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument(
|
||||
'-r',
|
||||
'--release',
|
||||
type=int,
|
||||
help='release id')
|
||||
group.add_argument(
|
||||
'-e',
|
||||
'--env',
|
||||
type=int,
|
||||
help='environment id')
|
||||
group.add_argument(
|
||||
'-p',
|
||||
'--plugin',
|
||||
type=int,
|
||||
help='plugin id')
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
model, model_id = self.parse_model(parsed_args)
|
||||
data = self.client.get_all(model, model_id)
|
||||
display_data = data_utils.get_display_data_multi(self.columns, data)
|
||||
return self.columns, display_data
|
||||
|
||||
|
||||
class TagDownload(TagMixIn, base.BaseCommand):
|
||||
"""Download full tag description to file."""
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(TagDownload, self).get_parser(prog_name)
|
||||
parser.add_argument('-t',
|
||||
'--tag_id',
|
||||
type=int,
|
||||
required=True,
|
||||
help='Id of the tag.')
|
||||
parser.add_argument('-f',
|
||||
'--format',
|
||||
required=True,
|
||||
choices=self.supported_file_formats,
|
||||
help='Format of serialized tag description.')
|
||||
parser.add_argument('-d',
|
||||
'--directory',
|
||||
required=False,
|
||||
default=os.path.curdir,
|
||||
help='Destination. Defaults to '
|
||||
'the current directory.')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
file_path = self.get_file_path(parsed_args.directory,
|
||||
parsed_args.tag_id,
|
||||
parsed_args.format)
|
||||
data = self.client.get_by_id(parsed_args.tag_id)
|
||||
try:
|
||||
fileutils.ensure_tree(os.path.dirname(file_path))
|
||||
fileutils.delete_if_exists(file_path)
|
||||
|
||||
with open(file_path, 'w') as stream:
|
||||
data_utils.safe_dump(parsed_args.format, stream, data)
|
||||
except (OSError, IOError):
|
||||
msg = ("Could not store description data "
|
||||
"for tag {} at {}".format(parsed_args.tag_id, file_path))
|
||||
raise error.InvalidFileException(msg)
|
||||
|
||||
msg = ("Description data of tag with id '{}'"
|
||||
" was stored in {}\n".format(parsed_args.tag_id,
|
||||
file_path))
|
||||
self.app.stdout.write(msg)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseTagUploader(TagMixIn, base.BaseShowCommand):
|
||||
"""Upload a tag data from file."""
|
||||
columns = TagShow.columns
|
||||
|
||||
@abc.abstractmethod
|
||||
def upload(self, parsed_args, data):
|
||||
"""String with the name of the action."""
|
||||
pass
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = show.ShowOne.get_parser(self, prog_name)
|
||||
parser.add_argument(
|
||||
'--file_path',
|
||||
required=True,
|
||||
type=self.check_file_path,
|
||||
help="Full path to the file in {} format that contains tag's "
|
||||
"data.".format("/".join(self.supported_file_formats)))
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
file_path = parsed_args.file_path
|
||||
file_format = os.path.splitext(file_path)[1].lstrip('.')
|
||||
|
||||
data = self.read_tag_data(file_format, file_path)
|
||||
display_data = data_utils.get_display_data_single(
|
||||
self.columns,
|
||||
self.upload(parsed_args, data))
|
||||
|
||||
return self.columns, display_data
|
||||
|
||||
|
||||
class TagUpdate(BaseTagUploader):
|
||||
"""Update a tag from file."""
|
||||
|
||||
def upload(self, parsed_args, data):
|
||||
return self.client.update(parsed_args.tag_id, data)
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(TagUpdate, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'-t',
|
||||
'--tag_id',
|
||||
type=int,
|
||||
required=True,
|
||||
help='Id of the tag.')
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
class TagCreate(BaseTagUploader):
|
||||
"""Create a tag from file."""
|
||||
|
||||
def upload(self, parsed_args, data):
|
||||
model, model_id = self.parse_model(parsed_args)
|
||||
return self.client.create(model, model_id, data)
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(TagCreate, self).get_parser(prog_name)
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument(
|
||||
'-r',
|
||||
'--release',
|
||||
type=int,
|
||||
help='release id')
|
||||
group.add_argument(
|
||||
'-e',
|
||||
'--env',
|
||||
type=int,
|
||||
help='environment id')
|
||||
group.add_argument(
|
||||
'-p',
|
||||
'--plugin',
|
||||
type=int,
|
||||
help='plugin id')
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
class TagDelete(base.BaseDeleteCommand):
|
||||
"""Delete a tag by id."""
|
||||
|
||||
entity_name = 'tag'
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseTagAssignee(TagMixIn, base.BaseCommand):
|
||||
"""Base class for tags assignment."""
|
||||
|
||||
@abc.abstractproperty
|
||||
def action(self):
|
||||
"""String with the name of the action."""
|
||||
pass
|
||||
|
||||
@abc.abstractproperty
|
||||
def assignment_method(self):
|
||||
"""Assignment method."""
|
||||
pass
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(BaseTagAssignee, self).get_parser(prog_name)
|
||||
parser.add_argument('-t',
|
||||
'--tags',
|
||||
type=str,
|
||||
nargs='+',
|
||||
required=True,
|
||||
help='List of tags to be {} '
|
||||
'node.'.format(self.action))
|
||||
parser.add_argument('-n',
|
||||
'--node',
|
||||
type=int,
|
||||
required=True,
|
||||
help='Id of the node.')
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.assignment_method(node=parsed_args.node,
|
||||
tag_ids=parsed_args.tags)
|
||||
|
||||
self.app.stdout.write('Tags {t} were {a} the node {n}.'
|
||||
'\n'.format(t=parsed_args.tags,
|
||||
a=self.action,
|
||||
n=parsed_args.node))
|
||||
|
||||
|
||||
class TagAssign(BaseTagAssignee):
|
||||
"""Assign tags to the node."""
|
||||
|
||||
action = 'assigned to'
|
||||
|
||||
@property
|
||||
def assignment_method(self):
|
||||
return self.client.assign
|
||||
|
||||
|
||||
class TagUnassign(BaseTagAssignee):
|
||||
"""Unassign tags from the node."""
|
||||
|
||||
action = 'unassigned from'
|
||||
|
||||
@property
|
||||
def assignment_method(self):
|
||||
return self.client.unassign
|
@ -32,3 +32,8 @@ TASK_STATUSES = Enum(
|
||||
'ready',
|
||||
'running'
|
||||
)
|
||||
|
||||
CLIENT_MODES = Enum(
|
||||
'advanced',
|
||||
'simple'
|
||||
)
|
||||
|
@ -9,6 +9,9 @@ OS_PASSWORD:
|
||||
OS_TENANT_NAME: "admin"
|
||||
HTTP_PROXY: null
|
||||
HTTP_TIMEOUT: 10
|
||||
# application mode
|
||||
# possible options: 'advanced', 'simple'
|
||||
MODE: 'simple'
|
||||
|
||||
# Performance tests settings
|
||||
PERFORMANCE_PROFILING_TESTS: 0
|
||||
|
@ -18,6 +18,8 @@ import sys
|
||||
from cliff import app
|
||||
from cliff.commandmanager import CommandManager
|
||||
|
||||
from fuelclient.cli import error
|
||||
from fuelclient import consts
|
||||
from fuelclient import fuelclient_settings
|
||||
from fuelclient import utils
|
||||
|
||||
@ -32,6 +34,19 @@ class FuelClient(app.App):
|
||||
configuration of basic engines.
|
||||
|
||||
"""
|
||||
_is_advanced_mode = None
|
||||
|
||||
@property
|
||||
def is_advanced_mode(self):
|
||||
if self._is_advanced_mode is None:
|
||||
self._is_advanced_mode = False
|
||||
settings = fuelclient_settings.get_settings()
|
||||
try:
|
||||
if settings.MODE == consts.CLIENT_MODES.advanced:
|
||||
self._is_advanced_mode = True
|
||||
except error.SettingsException:
|
||||
pass
|
||||
return self._is_advanced_mode
|
||||
|
||||
def build_option_parser(self, description, version, argparse_kwargs=None):
|
||||
"""Overrides default options for backwards compatibility."""
|
||||
|
@ -28,6 +28,7 @@ from fuelclient.objects.role import Role
|
||||
from fuelclient.objects.task import DeployTask
|
||||
from fuelclient.objects.task import SnapshotTask
|
||||
from fuelclient.objects.task import Task
|
||||
from fuelclient.objects.tag import Tag
|
||||
from fuelclient.objects.fuelversion import FuelVersion
|
||||
from fuelclient.objects.network_group import NetworkGroup
|
||||
from fuelclient.objects.plugins import Plugins
|
||||
|
59
fuelclient/objects/tag.py
Normal file
59
fuelclient/objects/tag.py
Normal file
@ -0,0 +1,59 @@
|
||||
# Copyright 2015 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
from fuelclient.objects.base import BaseObject
|
||||
|
||||
|
||||
class Tag(BaseObject):
|
||||
|
||||
class_api_path = "{}/{}/tags/"
|
||||
instance_api_path = "tags/{}/"
|
||||
assign_api_path = "nodes/{}/tags/"
|
||||
|
||||
def get_tag(self):
|
||||
return self.connection.get_request(
|
||||
self.instance_api_path.format(self.id))
|
||||
|
||||
def update_tag(self, data):
|
||||
return self.connection.put_request(
|
||||
self.instance_api_path.format(self.id), data)
|
||||
|
||||
def delete_tag(self):
|
||||
return self.connection.delete_request(
|
||||
self.instance_api_path.format(self.id))
|
||||
|
||||
@classmethod
|
||||
def get_all(cls, owner_type, owner_id):
|
||||
return cls.connection.get_request(
|
||||
cls.class_api_path.format(owner_type, owner_id))
|
||||
|
||||
@classmethod
|
||||
def create_tag(cls, owner_type, owner_id, data):
|
||||
return cls.connection.post_request(
|
||||
cls.class_api_path.format(owner_type, owner_id),
|
||||
data)
|
||||
|
||||
@classmethod
|
||||
def assign_tags(cls, tag_ids, node_id):
|
||||
return cls.connection.post_request(
|
||||
cls.assign_api_path.format(node_id), tag_ids)
|
||||
|
||||
@classmethod
|
||||
def unassign_tags(cls, tag_ids, node_id):
|
||||
url = '{0}?tags={1}'.format(
|
||||
cls.assign_api_path.format(node_id),
|
||||
','.join(map(str, tag_ids))
|
||||
)
|
||||
return cls.connection.delete_request(url)
|
@ -194,7 +194,9 @@ node-4 ansible_host=10.20.0.5
|
||||
node.NodeClient._updatable_attributes
|
||||
node_id = 42
|
||||
hostname = 'test-name'
|
||||
expected_field_data = cmd_node.NodeShow.columns
|
||||
|
||||
expected_field_data = cmd_node.NodeShow(
|
||||
mock.Mock(is_advanced_mode=False), []).columns
|
||||
|
||||
self.m_client.update.return_value = \
|
||||
fake_node.get_fake_node(node_id=node_id,
|
||||
@ -209,7 +211,7 @@ node-4 ansible_host=10.20.0.5
|
||||
mock.ANY,
|
||||
mock.ANY)
|
||||
|
||||
self.m_get_client.assert_called_once_with('node', mock.ANY)
|
||||
self.m_get_client.assert_called_with('node', mock.ANY)
|
||||
self.m_client.update.assert_called_once_with(
|
||||
node_id, hostname=hostname)
|
||||
|
||||
@ -218,7 +220,9 @@ node-4 ansible_host=10.20.0.5
|
||||
self.m_client._updatable_attributes = \
|
||||
node.NodeClient._updatable_attributes
|
||||
node_id = 37
|
||||
expected_field_data = cmd_node.NodeShow.columns
|
||||
|
||||
expected_field_data = cmd_node.NodeShow(
|
||||
mock.Mock(is_advanced_mode=False), []).columns
|
||||
|
||||
test_cases = ('new-name', 'New Name', 'śćż∑ Pó', '你一定是无聊')
|
||||
for name in test_cases:
|
||||
@ -238,7 +242,7 @@ node-4 ansible_host=10.20.0.5
|
||||
mock.ANY,
|
||||
mock.ANY,
|
||||
mock.ANY)
|
||||
self.m_get_client.assert_called_once_with('node', mock.ANY)
|
||||
self.m_get_client.assert_called_with('node', mock.ANY)
|
||||
self.m_client.update.assert_called_once_with(
|
||||
node_id, name=name)
|
||||
self.m_get_client.reset_mock()
|
||||
|
160
fuelclient/tests/unit/v2/cli/test_tag.py
Normal file
160
fuelclient/tests/unit/v2/cli/test_tag.py
Normal file
@ -0,0 +1,160 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2016 Vitalii Kulanov
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import mock
|
||||
import yaml
|
||||
|
||||
from fuelclient.tests.unit.v2.cli import test_engine
|
||||
from fuelclient.tests.utils import fake_tag
|
||||
|
||||
|
||||
class TestTagCommand(test_engine.BaseCLITest):
|
||||
"""Tests for fuel2 tag * commands."""
|
||||
|
||||
def test_tag_list(self):
|
||||
self.m_client.get_all.return_value = fake_tag.get_fake_tags(10)
|
||||
args = 'tag list -r 2'
|
||||
self.exec_command(args)
|
||||
self.m_client.get_all.assert_called_once_with('releases', 2)
|
||||
self.m_get_client.assert_called_once_with('tag', mock.ANY)
|
||||
|
||||
@mock.patch('json.dump')
|
||||
def test_tag_download_json(self, m_dump):
|
||||
tag_data = fake_tag.get_fake_tag()
|
||||
tag_id = tag_data['id']
|
||||
args = 'tag download -t {} -f json -d /tmp'.format(tag_id)
|
||||
expected_path = '/tmp/tag_{}.json'.format(tag_id)
|
||||
|
||||
self.m_client.get_by_id.return_value = tag_data
|
||||
|
||||
m_open = mock.mock_open()
|
||||
with mock.patch('fuelclient.commands.tag.open', m_open, create=True):
|
||||
self.exec_command(args)
|
||||
|
||||
m_open.assert_called_once_with(expected_path, 'w')
|
||||
m_dump.assert_called_once_with(tag_data, mock.ANY, indent=4)
|
||||
self.m_get_client.assert_called_once_with('tag', mock.ANY)
|
||||
self.m_client.get_by_id.assert_called_once_with(tag_id)
|
||||
|
||||
@mock.patch('yaml.safe_dump')
|
||||
def test_tag_download_yaml(self, m_safe_dump):
|
||||
tag_data = fake_tag.get_fake_tag()
|
||||
tag_id = tag_data['id']
|
||||
args = 'tag download -t {} -f yaml -d /tmp'.format(tag_id)
|
||||
expected_path = '/tmp/tag_{}.yaml'.format(tag_id)
|
||||
|
||||
self.m_client.get_by_id.return_value = tag_data
|
||||
|
||||
m_open = mock.mock_open()
|
||||
with mock.patch('fuelclient.commands.tag.open', m_open, create=True):
|
||||
self.exec_command(args)
|
||||
|
||||
m_open.assert_called_once_with(expected_path, 'w')
|
||||
m_safe_dump.assert_called_once_with(tag_data, mock.ANY,
|
||||
default_flow_style=False)
|
||||
self.m_get_client.assert_called_once_with('tag', mock.ANY)
|
||||
self.m_client.get_by_id.assert_called_once_with(tag_id)
|
||||
|
||||
@mock.patch('fuelclient.utils.file_exists', return_value=True)
|
||||
def test_tag_update_json(self, m_file_exists):
|
||||
tag_data = fake_tag.get_fake_tag()
|
||||
tag_id = tag_data['id']
|
||||
file_path = '/tmp/tag_{}.yaml'.format(tag_id)
|
||||
args = 'tag update -t {} --file_path {}'.format(tag_id, file_path)
|
||||
|
||||
m_open = mock.mock_open(read_data=json.dumps(tag_data))
|
||||
with mock.patch('fuelclient.commands.tag.open', m_open, create=True):
|
||||
self.exec_command(args)
|
||||
|
||||
m_open.assert_called_once_with(file_path, 'r')
|
||||
m_file_exists.assert_called_once_with(file_path)
|
||||
self.m_get_client.assert_called_once_with('tag', mock.ANY)
|
||||
self.m_client.update.assert_called_once_with(tag_id, tag_data)
|
||||
|
||||
@mock.patch('fuelclient.utils.file_exists', return_value=True)
|
||||
def test_tag_update_yaml(self, m_file_exists):
|
||||
tag_data = fake_tag.get_fake_tag()
|
||||
tag_id = tag_data['id']
|
||||
file_path = '/tmp/tag_{}.yaml'.format(tag_id)
|
||||
args = 'tag update -t {} --file_path {}'.format(tag_id, file_path)
|
||||
|
||||
m_open = mock.mock_open(read_data=yaml.safe_dump(tag_data))
|
||||
with mock.patch('fuelclient.commands.tag.open', m_open, create=True):
|
||||
self.exec_command(args)
|
||||
|
||||
m_open.assert_called_once_with(file_path, 'r')
|
||||
m_file_exists.assert_called_once_with(file_path)
|
||||
self.m_get_client.assert_called_once_with('tag', mock.ANY)
|
||||
self.m_client.update.assert_called_once_with(tag_id, tag_data)
|
||||
|
||||
@mock.patch('fuelclient.utils.file_exists', return_value=True)
|
||||
def test_tag_create_json(self, m_file_exists):
|
||||
tag_data = fake_tag.get_fake_tag()
|
||||
tag_path = "/tmp/tag22.json"
|
||||
args = 'tag create -r 2 --file_path {}'.format(tag_path)
|
||||
|
||||
m_open = mock.mock_open(read_data=json.dumps(tag_data))
|
||||
with mock.patch('fuelclient.commands.tag.open', m_open, create=True):
|
||||
self.exec_command(args)
|
||||
|
||||
m_open.assert_called_once_with(tag_path, 'r')
|
||||
m_file_exists.assert_called_once_with(tag_path)
|
||||
self.m_get_client.assert_called_once_with('tag', mock.ANY)
|
||||
self.m_client.create.assert_called_once_with('releases', 2, tag_data)
|
||||
|
||||
@mock.patch('fuelclient.utils.file_exists', return_value=True)
|
||||
def test_tag_create_yaml(self, m_file_exists):
|
||||
tag_data = fake_tag.get_fake_tag()
|
||||
tag_path = "/tmp/tag22.yaml"
|
||||
args = 'tag create -r 2 --file_path {}'.format(tag_path)
|
||||
|
||||
m_open = mock.mock_open(read_data=yaml.safe_dump(tag_data))
|
||||
with mock.patch('fuelclient.commands.tag.open', m_open, create=True):
|
||||
self.exec_command(args)
|
||||
|
||||
m_open.assert_called_once_with(tag_path, 'r')
|
||||
m_file_exists.assert_called_once_with(tag_path)
|
||||
self.m_get_client.assert_called_once_with('tag', mock.ANY)
|
||||
self.m_client.create.assert_called_once_with('releases', 2, tag_data)
|
||||
|
||||
def test_tag_delete(self):
|
||||
tag_id = 1
|
||||
args = 'tag delete {}'.format(tag_id)
|
||||
|
||||
self.exec_command(args)
|
||||
self.m_get_client.assert_called_once_with('tag', mock.ANY)
|
||||
self.m_client.delete_by_id.assert_called_once_with(tag_id)
|
||||
|
||||
def test_tag_assign(self):
|
||||
node_id = 1
|
||||
tag_ids = ['4', '5', '6']
|
||||
args = 'tag assign -n {} -t {}'.format(node_id, " ".join(tag_ids))
|
||||
|
||||
self.exec_command(args)
|
||||
self.m_get_client.assert_called_once_with('tag', mock.ANY)
|
||||
self.m_client.assign.assert_called_once_with(node=node_id,
|
||||
tag_ids=tag_ids)
|
||||
|
||||
def test_tag_unassign(self):
|
||||
node_id = 1
|
||||
tag_ids = ['4', '5', '6']
|
||||
args = 'tag unassign -n {} -t {}'.format(node_id, " ".join(tag_ids))
|
||||
|
||||
self.exec_command(args)
|
||||
self.m_get_client.assert_called_once_with('tag', mock.ANY)
|
||||
self.m_client.unassign.assert_called_once_with(node=node_id,
|
||||
tag_ids=tag_ids)
|
111
fuelclient/tests/unit/v2/lib/test_tag.py
Normal file
111
fuelclient/tests/unit/v2/lib/test_tag.py
Normal file
@ -0,0 +1,111 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2016 Vitalii Kulanov
|
||||
#
|
||||
# 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 fuelclient
|
||||
from fuelclient.tests.unit.v2.lib import test_api
|
||||
from fuelclient.tests import utils
|
||||
|
||||
|
||||
class TestTagFacade(test_api.BaseLibTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestTagFacade, self).setUp()
|
||||
self.owner_map = {
|
||||
'release': 'releases',
|
||||
'cluster': 'clusters',
|
||||
'plugin': 'plugins'
|
||||
}
|
||||
self.version = 'v1'
|
||||
self.res_uri = '/api/{version}/'.format(
|
||||
version=self.version)
|
||||
self.fake_tag = utils.get_fake_tag('fake_tag')
|
||||
self.fake_tags = utils.get_fake_tags(10)
|
||||
self.instance_uri = (self.res_uri +
|
||||
'tags/{}/'.format(self.fake_tag['id']))
|
||||
|
||||
self.client = fuelclient.get_client('tag', self.version)
|
||||
|
||||
def _get_class_uri_path(self, tag):
|
||||
owner_type = self.owner_map[tag['owner_type']]
|
||||
return self.res_uri + "{}/{}/tags/".format(owner_type,
|
||||
tag['owner_id'])
|
||||
|
||||
def test_tag_list(self):
|
||||
expected_uri = self._get_class_uri_path(self.fake_tag)
|
||||
matcher = self.m_request.get(expected_uri, json=self.fake_tags)
|
||||
self.client.get_all(self.owner_map[self.fake_tag['owner_type']],
|
||||
self.fake_tag['owner_id'])
|
||||
self.assertTrue(matcher.called)
|
||||
|
||||
def test_tag_download(self):
|
||||
expected_uri = self.instance_uri
|
||||
tag_matcher = self.m_request.get(expected_uri, json=self.fake_tag)
|
||||
tag = self.client.get_by_id(self.fake_tag['id'])
|
||||
|
||||
self.assertTrue(expected_uri, tag_matcher.called)
|
||||
self.assertEqual(tag, self.fake_tag)
|
||||
|
||||
def test_tag_upload(self):
|
||||
expected_uri = self.instance_uri
|
||||
upd_matcher = self.m_request.put(expected_uri, json=self.fake_tag)
|
||||
|
||||
self.client.update(self.fake_tag['id'], self.fake_tag)
|
||||
|
||||
self.assertTrue(upd_matcher.called)
|
||||
self.assertEqual(self.fake_tag, upd_matcher.last_request.json())
|
||||
|
||||
def test_tag_create(self):
|
||||
expected_uri = self._get_class_uri_path(self.fake_tag)
|
||||
post_matcher = self.m_request.post(expected_uri, json=self.fake_tag)
|
||||
|
||||
self.client.create(self.owner_map[self.fake_tag['owner_type']],
|
||||
self.fake_tag['owner_id'],
|
||||
self.fake_tag)
|
||||
|
||||
self.assertTrue(post_matcher.called)
|
||||
self.assertEqual(self.fake_tag, post_matcher.last_request.json())
|
||||
|
||||
def test_tag_delete(self):
|
||||
expected_uri = self.instance_uri
|
||||
delete_matcher = self.m_request.delete(expected_uri, json={})
|
||||
self.client.delete_by_id(self.fake_tag['id'])
|
||||
|
||||
self.assertTrue(delete_matcher.called)
|
||||
|
||||
def test_tag_assign(self):
|
||||
node_id = 1
|
||||
expected_uri = (self.res_uri +
|
||||
'nodes/{}/tags/'.format(node_id))
|
||||
tag_ids = [self.fake_tag['id']]
|
||||
|
||||
post_matcher = self.m_request.post(expected_uri, json=tag_ids)
|
||||
self.client.assign(tag_ids, node_id)
|
||||
|
||||
self.assertTrue(post_matcher.called)
|
||||
self.assertEqual(tag_ids, post_matcher.last_request.json())
|
||||
|
||||
def test_tag_unassign(self):
|
||||
node_id = 1
|
||||
tag_ids = [self.fake_tag['id']]
|
||||
expected_uri = '{0}?tags={1}'.format(
|
||||
self.res_uri + 'nodes/{}/tags/'.format(node_id),
|
||||
','.join(map(str, tag_ids))
|
||||
)
|
||||
|
||||
delete_matcher = self.m_request.delete(expected_uri, json={})
|
||||
self.client.unassign(tag_ids, node_id)
|
||||
|
||||
self.assertTrue(delete_matcher.called)
|
@ -54,6 +54,8 @@ from fuelclient.tests.utils.fake_release import get_fake_release_component
|
||||
from fuelclient.tests.utils.fake_release import get_fake_release_components
|
||||
from fuelclient.tests.utils.fake_role import get_fake_role
|
||||
from fuelclient.tests.utils.fake_role import get_fake_roles
|
||||
from fuelclient.tests.utils.fake_tag import get_fake_tag
|
||||
from fuelclient.tests.utils.fake_tag import get_fake_tags
|
||||
|
||||
|
||||
__all__ = (get_fake_deployment_history,
|
||||
@ -85,4 +87,6 @@ __all__ = (get_fake_deployment_history,
|
||||
get_fake_node_groups,
|
||||
get_fake_openstack_config,
|
||||
get_fake_plugin,
|
||||
get_fake_plugins)
|
||||
get_fake_plugins,
|
||||
get_fake_tag,
|
||||
get_fake_tags)
|
||||
|
37
fuelclient/tests/utils/fake_tag.py
Normal file
37
fuelclient/tests/utils/fake_tag.py
Normal file
@ -0,0 +1,37 @@
|
||||
# -*- 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.
|
||||
|
||||
|
||||
def get_fake_tag(tag_name=None, has_primary=False, owner_id=None,
|
||||
owner_type=None):
|
||||
"""Create a random fake tag
|
||||
|
||||
Returns the serialized and parametrized representation of a dumped Fuel
|
||||
tag. Represents the average amount of data.
|
||||
|
||||
"""
|
||||
return {
|
||||
"id": 1,
|
||||
"tag": tag_name or "controller",
|
||||
"has_primary": has_primary,
|
||||
"owner_id": owner_id or 1,
|
||||
"owner_type": owner_type or 'release'
|
||||
}
|
||||
|
||||
|
||||
def get_fake_tags(tag_count, **kwargs):
|
||||
"""Create a random fake list of tags."""
|
||||
return [get_fake_tag(**kwargs) for _ in range(tag_count)]
|
@ -27,6 +27,7 @@ from distutils.version import StrictVersion
|
||||
from fnmatch import fnmatch
|
||||
|
||||
from fuelclient.cli import error
|
||||
from fuelclient import consts
|
||||
|
||||
|
||||
def _wait_and_check_exit_code(cmd, child):
|
||||
@ -212,3 +213,8 @@ def add_os_cli_parameters(parser):
|
||||
parser.add_argument(
|
||||
'--os-password', metavar='<auth-password>',
|
||||
help='Authentication password, defaults to env[OS_PASSWORD].')
|
||||
|
||||
parser.add_argument(
|
||||
'--mode',
|
||||
choices=list(consts.CLIENT_MODES),
|
||||
help="Application mode, defaults to 'simple'.")
|
||||
|
@ -30,6 +30,7 @@ from fuelclient.v1 import plugins
|
||||
from fuelclient.v1 import sequence
|
||||
from fuelclient.v1 import snapshot
|
||||
from fuelclient.v1 import task
|
||||
from fuelclient.v1 import tag
|
||||
from fuelclient.v1 import vip
|
||||
|
||||
# Please keeps the list in alphabetical order
|
||||
@ -51,4 +52,5 @@ __all__ = ('cluster_settings',
|
||||
'sequence',
|
||||
'snapshot',
|
||||
'task',
|
||||
'tag',
|
||||
'vip')
|
||||
|
47
fuelclient/v1/tag.py
Normal file
47
fuelclient/v1/tag.py
Normal file
@ -0,0 +1,47 @@
|
||||
# -*- 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 TagClient(base_v1.BaseV1Client):
|
||||
|
||||
_entity_wrapper = objects.Tag
|
||||
|
||||
def get_all(self, tag_owner, owner_id):
|
||||
return self._entity_wrapper.get_all(tag_owner, owner_id)
|
||||
|
||||
def update(self, tag_id, data):
|
||||
tag = self._entity_wrapper(obj_id=tag_id)
|
||||
return tag.update_tag(data)
|
||||
|
||||
def create(self, tag_owner, owner_id, data):
|
||||
return self._entity_wrapper.create_tag(tag_owner, owner_id, data)
|
||||
|
||||
def delete_by_id(self, tag_id):
|
||||
tag = self._entity_wrapper(obj_id=tag_id)
|
||||
return tag.delete_tag()
|
||||
|
||||
def assign(self, tag_ids, node):
|
||||
return self._entity_wrapper.assign_tags(tag_ids, node)
|
||||
|
||||
def unassign(self, tag_ids, node):
|
||||
return self._entity_wrapper.unassign_tags(tag_ids, node)
|
||||
|
||||
|
||||
def get_client(connection):
|
||||
return TagClient(connection)
|
@ -114,6 +114,14 @@ fuelclient =
|
||||
role_download=fuelclient.commands.role:RoleDownload
|
||||
role_list=fuelclient.commands.role:RoleList
|
||||
role_update=fuelclient.commands.role:RoleUpdate
|
||||
tag_assign=fuelclient.commands.tag:TagAssign
|
||||
tag_create=fuelclient.commands.tag:TagCreate
|
||||
tag_delete=fuelclient.commands.tag:TagDelete
|
||||
tag_download=fuelclient.commands.tag:TagDownload
|
||||
tag_list=fuelclient.commands.tag:TagList
|
||||
tag_show=fuelclient.commands.tag:TagShow
|
||||
tag_unassign=fuelclient.commands.tag:TagUnassign
|
||||
tag_update=fuelclient.commands.tag:TagUpdate
|
||||
task_delete=fuelclient.commands.task:TaskDelete
|
||||
task_deployment-info_download=fuelclient.commands.task:TaskDeploymentInfoDownload
|
||||
task_history_show=fuelclient.commands.task:TaskHistoryShow
|
||||
|
Loading…
Reference in New Issue
Block a user