Add new CLI commands for managing cluster networks and settings
This patch introduces the following commands to the new fuel2 CLI: - fuel2 env network download - fuel2 env network upload - fuel2 env network verify - fuel2 env settings download - fuel2 env settings upload DocImpact Change-Id: Iab92de0f0dbc453d5ea36f83d10252911e37d163
This commit is contained in:
parent
2b1ebf7016
commit
d1ce60ba62
|
@ -13,6 +13,7 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
import os
|
||||||
|
|
||||||
from cliff import command
|
from cliff import command
|
||||||
from cliff import lister
|
from cliff import lister
|
||||||
|
@ -30,6 +31,29 @@ VERSION = 'v1'
|
||||||
class BaseCommand(command.Command):
|
class BaseCommand(command.Command):
|
||||||
"""Base Fuel Client command."""
|
"""Base Fuel Client command."""
|
||||||
|
|
||||||
|
def get_attributes_path(self, attr_type, file_format, ent_id, directory):
|
||||||
|
"""Returnes a path for attributes of an entity
|
||||||
|
|
||||||
|
:param attr_type: Type of the attribute, e. g., disks, networks.
|
||||||
|
:param file_format: The format of the file that contains or will
|
||||||
|
contain the attributes, e. g., json or yaml.
|
||||||
|
:param ent_id: Id of an entity
|
||||||
|
:param directory: Directory that is used to store attributes.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if attr_type not in self.allowed_attr_types:
|
||||||
|
raise ValueError('attr_type must be '
|
||||||
|
'one of {}'.format(self.allowed_attr_types))
|
||||||
|
|
||||||
|
if file_format not in self.supported_file_formats:
|
||||||
|
raise ValueError('file_format must be '
|
||||||
|
'one of {}'.format(self.supported_file_formats))
|
||||||
|
|
||||||
|
return os.path.join(os.path.abspath(directory),
|
||||||
|
'{ent}_{id}'.format(ent=self.entity_name,
|
||||||
|
id=ent_id),
|
||||||
|
'{}.{}'.format(attr_type, file_format))
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(BaseCommand, self).__init__(*args, **kwargs)
|
super(BaseCommand, self).__init__(*args, **kwargs)
|
||||||
self.client = fuelclient.get_client(self.entity_name, VERSION)
|
self.client = fuelclient.get_client(self.entity_name, VERSION)
|
||||||
|
@ -39,6 +63,14 @@ class BaseCommand(command.Command):
|
||||||
"""Name of the Fuel entity."""
|
"""Name of the Fuel entity."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_file_formats(self):
|
||||||
|
raise NotImplemented()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def allowed_attr_types(self):
|
||||||
|
raise NotImplemented()
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class BaseListCommand(lister.Lister, BaseCommand):
|
class BaseListCommand(lister.Lister, BaseCommand):
|
||||||
|
|
|
@ -12,8 +12,15 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from cliff import show
|
import abc
|
||||||
|
import functools
|
||||||
|
import os
|
||||||
|
|
||||||
|
from cliff import show
|
||||||
|
from oslo_utils import fileutils
|
||||||
|
import six
|
||||||
|
|
||||||
|
from fuelclient.cli import error
|
||||||
from fuelclient.commands import base
|
from fuelclient.commands import base
|
||||||
from fuelclient.common import data_utils
|
from fuelclient.common import data_utils
|
||||||
|
|
||||||
|
@ -21,6 +28,122 @@ from fuelclient.common import data_utils
|
||||||
class EnvMixIn(object):
|
class EnvMixIn(object):
|
||||||
entity_name = 'environment'
|
entity_name = 'environment'
|
||||||
|
|
||||||
|
supported_file_formats = ('json', 'yaml')
|
||||||
|
allowed_attr_types = ('network', 'settings')
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class BaseUploadCommand(EnvMixIn, base.BaseCommand):
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def uploader(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def attribute(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(BaseUploadCommand, self).get_parser(prog_name)
|
||||||
|
parser.add_argument('id',
|
||||||
|
type=int,
|
||||||
|
help='Id of environment.')
|
||||||
|
parser.add_argument('-f',
|
||||||
|
'--format',
|
||||||
|
required=True,
|
||||||
|
choices=self.supported_file_formats,
|
||||||
|
help='Format of serialized '
|
||||||
|
'{}.'.format(self.attribute))
|
||||||
|
parser.add_argument('-d',
|
||||||
|
'--directory',
|
||||||
|
required=False,
|
||||||
|
default=os.curdir,
|
||||||
|
help='Source directory. Defaults to the '
|
||||||
|
'current directory.')
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
directory = parsed_args.directory
|
||||||
|
file_path = self.get_attributes_path(self.attribute,
|
||||||
|
parsed_args.format,
|
||||||
|
parsed_args.id,
|
||||||
|
directory)
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r') as stream:
|
||||||
|
attribute = data_utils.safe_load(parsed_args.format, stream)
|
||||||
|
except (IOError, OSError):
|
||||||
|
msg = 'Could not read configuration of {} at {}.'
|
||||||
|
raise error.InvalidFileException(msg.format(self.attribute,
|
||||||
|
file_path))
|
||||||
|
|
||||||
|
self.uploader(parsed_args.id, attribute)
|
||||||
|
|
||||||
|
msg = ('Configuration of {t} for the environment with id '
|
||||||
|
'{env} was loaded from {path}\n')
|
||||||
|
|
||||||
|
self.app.stdout.write(msg.format(t=self.attribute,
|
||||||
|
env=parsed_args.id,
|
||||||
|
path=file_path))
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class BaseDownloadCommand(EnvMixIn, base.BaseCommand):
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def downloader(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def attribute(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(BaseDownloadCommand, self).get_parser(prog_name)
|
||||||
|
parser.add_argument('id',
|
||||||
|
type=int,
|
||||||
|
help='Id of an environment.')
|
||||||
|
parser.add_argument('-f',
|
||||||
|
'--format',
|
||||||
|
required=True,
|
||||||
|
choices=self.supported_file_formats,
|
||||||
|
help='Format of serialized '
|
||||||
|
'{}.'.format(self.attribute))
|
||||||
|
parser.add_argument('-d',
|
||||||
|
'--directory',
|
||||||
|
required=False,
|
||||||
|
default=os.curdir,
|
||||||
|
help='Destination directory. Defaults to the '
|
||||||
|
'current directory.')
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
directory = parsed_args.directory or os.curdir
|
||||||
|
attributes = self.downloader(parsed_args.id)
|
||||||
|
|
||||||
|
file_path = self.get_attributes_path(self.attribute,
|
||||||
|
parsed_args.format,
|
||||||
|
parsed_args.id,
|
||||||
|
directory)
|
||||||
|
|
||||||
|
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, attributes)
|
||||||
|
except (IOError, OSError):
|
||||||
|
msg = 'Could not store configuration of {} at {}.'
|
||||||
|
raise error.InvalidFileException(msg.format(self.attribute,
|
||||||
|
file_path))
|
||||||
|
|
||||||
|
msg = ('Configuration of {t} for the environment with id '
|
||||||
|
'{env} was stored in {path}\n')
|
||||||
|
self.app.stdout.write(msg.format(t=self.attribute,
|
||||||
|
env=parsed_args.id,
|
||||||
|
path=file_path))
|
||||||
|
|
||||||
|
|
||||||
class EnvList(EnvMixIn, base.BaseListCommand):
|
class EnvList(EnvMixIn, base.BaseListCommand):
|
||||||
"""Show list of all available environments."""
|
"""Show list of all available environments."""
|
||||||
|
@ -355,3 +478,77 @@ class EnvSpawnVms(EnvMixIn, base.BaseCommand):
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
return self.client.spawn_vms(parsed_args.id)
|
return self.client.spawn_vms(parsed_args.id)
|
||||||
|
|
||||||
|
|
||||||
|
class EnvNetworkVerify(EnvMixIn, base.BaseCommand):
|
||||||
|
"""Run network verification for specified environment."""
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(EnvNetworkVerify, self).get_parser(prog_name)
|
||||||
|
|
||||||
|
parser.add_argument('id',
|
||||||
|
type=int,
|
||||||
|
help='Id of the environment to verify network.')
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
task = self.client.verify_network(parsed_args.id)
|
||||||
|
msg = 'Network verification 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 EnvNetworkUpload(BaseUploadCommand):
|
||||||
|
"""Upload network configuration and apply it to an environment."""
|
||||||
|
|
||||||
|
attribute = 'network'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def uploader(self):
|
||||||
|
return self.client.set_network_configuration
|
||||||
|
|
||||||
|
|
||||||
|
class EnvNetworkDownload(BaseDownloadCommand):
|
||||||
|
"""Download and store network configuration of an environment."""
|
||||||
|
|
||||||
|
attribute = 'network'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def downloader(self):
|
||||||
|
return self.client.get_network_configuration
|
||||||
|
|
||||||
|
|
||||||
|
class EnvSettingsUpload(BaseUploadCommand):
|
||||||
|
"""Upload and apply environment settings."""
|
||||||
|
|
||||||
|
attribute = 'settings'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def uploader(self):
|
||||||
|
return functools.partial(self.client.set_settings,
|
||||||
|
force=self.force_flag)
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(EnvSettingsUpload, self).get_parser(prog_name)
|
||||||
|
parser.add_argument('--force',
|
||||||
|
action='store_true',
|
||||||
|
help='Force applying the settings.')
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
self.force_flag = parsed_args.force
|
||||||
|
|
||||||
|
super(EnvSettingsUpload, self).take_action(parsed_args)
|
||||||
|
|
||||||
|
|
||||||
|
class EnvSettingsDownload(BaseDownloadCommand):
|
||||||
|
"""Download and store environment settings."""
|
||||||
|
|
||||||
|
attribute = 'settings'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def downloader(self):
|
||||||
|
return self.client.get_settings
|
||||||
|
|
|
@ -44,29 +44,6 @@ class NodeMixIn(object):
|
||||||
numa_topology_info[key] = numa_topology.get(key)
|
numa_topology_info[key] = numa_topology.get(key)
|
||||||
return numa_topology_info
|
return numa_topology_info
|
||||||
|
|
||||||
def get_attributes_path(self, attr_type, file_format, node_id, directory):
|
|
||||||
"""Returnes a path for attributes of a node
|
|
||||||
|
|
||||||
:param attr_type: Attribute type.
|
|
||||||
Should be one of {attributes, disks, interfaces}
|
|
||||||
:param file_format: The format of the file that contains or will
|
|
||||||
contain the attributes. Must be json or yaml.
|
|
||||||
:param node_id: Id of a node
|
|
||||||
:param directory: Directory that is used to store attributes.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if attr_type not in self.allowed_attr_types:
|
|
||||||
raise ValueError('attr_type must be '
|
|
||||||
'one of {}'.format(self.allowed_attr_types))
|
|
||||||
|
|
||||||
if file_format not in self.supported_file_formats:
|
|
||||||
raise ValueError('file_format must be '
|
|
||||||
'one of {}'.format(self.supported_file_formats))
|
|
||||||
|
|
||||||
return os.path.join(os.path.abspath(directory),
|
|
||||||
'node_{0}'.format(node_id),
|
|
||||||
'{}.{}'.format(attr_type, file_format))
|
|
||||||
|
|
||||||
|
|
||||||
class NodeList(NodeMixIn, base.BaseListCommand):
|
class NodeList(NodeMixIn, base.BaseListCommand):
|
||||||
"""Show list of all available nodes."""
|
"""Show list of all available nodes."""
|
||||||
|
|
|
@ -22,6 +22,7 @@ from oslotest import base as oslo_base
|
||||||
|
|
||||||
import fuelclient
|
import fuelclient
|
||||||
from fuelclient.commands import environment as env
|
from fuelclient.commands import environment as env
|
||||||
|
from fuelclient.commands import node as node
|
||||||
from fuelclient import main as main_mod
|
from fuelclient import main as main_mod
|
||||||
from fuelclient.tests import utils
|
from fuelclient.tests import utils
|
||||||
|
|
||||||
|
@ -129,3 +130,42 @@ class BaseCLITest(oslo_base.BaseTestCase):
|
||||||
self.assertEqual('tname', passed_settings.os_username)
|
self.assertEqual('tname', passed_settings.os_username)
|
||||||
self.assertEqual('tpass', passed_settings.os_password)
|
self.assertEqual('tpass', passed_settings.os_password)
|
||||||
self.assertEqual('tten', passed_settings.os_tenant_name)
|
self.assertEqual('tten', passed_settings.os_tenant_name)
|
||||||
|
|
||||||
|
def test_get_attribute_path(self):
|
||||||
|
cmd = node.NodeShow(None, None)
|
||||||
|
|
||||||
|
attr_types = ('attributes', 'interfaces', 'disks')
|
||||||
|
file_format = 'json'
|
||||||
|
node_id = 42
|
||||||
|
directory = '/test'
|
||||||
|
|
||||||
|
for attr_type in attr_types:
|
||||||
|
expected_path = '/test/node_42/{t}.json'.format(t=attr_type)
|
||||||
|
real_path = cmd.get_attributes_path(attr_type, file_format,
|
||||||
|
node_id, directory)
|
||||||
|
|
||||||
|
self.assertEqual(expected_path, real_path)
|
||||||
|
|
||||||
|
def test_get_attribute_path_wrong_attr_type(self):
|
||||||
|
cmd = node.NodeShow(None, None)
|
||||||
|
|
||||||
|
attr_type = 'wrong'
|
||||||
|
file_format = 'json'
|
||||||
|
node_id = 42
|
||||||
|
directory = '/test'
|
||||||
|
|
||||||
|
self.assertRaises(ValueError,
|
||||||
|
cmd.get_attributes_path,
|
||||||
|
attr_type, file_format, node_id, directory)
|
||||||
|
|
||||||
|
def test_get_attribute_path_wrong_file_format(self):
|
||||||
|
cmd = node.NodeShow(None, None)
|
||||||
|
|
||||||
|
attr_type = 'interfaces'
|
||||||
|
file_format = 'wrong'
|
||||||
|
node_id = 42
|
||||||
|
directory = '/test'
|
||||||
|
|
||||||
|
self.assertRaises(ValueError,
|
||||||
|
cmd.get_attributes_path,
|
||||||
|
attr_type, file_format, node_id, directory)
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
import mock
|
import mock
|
||||||
from six import moves
|
from six import moves
|
||||||
|
import yaml
|
||||||
|
|
||||||
from fuelclient.tests.unit.v2.cli import test_engine
|
from fuelclient.tests.unit.v2.cli import test_engine
|
||||||
from fuelclient.tests.utils import fake_env
|
from fuelclient.tests.utils import fake_env
|
||||||
|
@ -200,11 +202,9 @@ class TestEnvCommand(test_engine.BaseCLITest):
|
||||||
args = ('env nodes deploy '
|
args = ('env nodes deploy '
|
||||||
'--nodes {n[0]} {n[1]} --env {e}').format(e=env_id, n=node_ids)
|
'--nodes {n[0]} {n[1]} --env {e}').format(e=env_id, n=node_ids)
|
||||||
|
|
||||||
self.m_client.deploy_nodes.return_value = fake_task.get_fake_task()
|
|
||||||
|
|
||||||
self.exec_command(args)
|
self.exec_command(args)
|
||||||
|
|
||||||
self.m_get_client.assert_called_once_with('environment', mock.ANY)
|
self.m_client.deploy_nodes.return_value = fake_task.get_fake_task()
|
||||||
self.m_client.deploy_nodes.assert_called_once_with(env_id, node_ids)
|
self.m_client.deploy_nodes.assert_called_once_with(env_id, node_ids)
|
||||||
|
|
||||||
def test_env_nodes_provision(self):
|
def test_env_nodes_provision(self):
|
||||||
|
@ -219,3 +219,166 @@ class TestEnvCommand(test_engine.BaseCLITest):
|
||||||
|
|
||||||
self.m_get_client.assert_called_once_with('environment', mock.ANY)
|
self.m_get_client.assert_called_once_with('environment', mock.ANY)
|
||||||
self.m_client.provision_nodes.assert_called_once_with(env_id, node_ids)
|
self.m_client.provision_nodes.assert_called_once_with(env_id, node_ids)
|
||||||
|
|
||||||
|
def test_env_network_verify(self):
|
||||||
|
env_id = 42
|
||||||
|
args = 'env network verify {}'.format(env_id)
|
||||||
|
|
||||||
|
self.exec_command(args)
|
||||||
|
|
||||||
|
self.m_get_client.assert_called_once_with('environment', mock.ANY)
|
||||||
|
self.m_client.verify_network.assert_called_once_with(env_id)
|
||||||
|
|
||||||
|
@mock.patch('json.dump')
|
||||||
|
def test_env_network_download_json(self, m_dump):
|
||||||
|
args = 'env network download --format json -d /tmp 42'
|
||||||
|
test_data = {'foo': 'bar'}
|
||||||
|
expected_path = '/tmp/environment_42/network.json'
|
||||||
|
|
||||||
|
self.m_client.get_network_configuration.return_value = test_data
|
||||||
|
|
||||||
|
m_open = mock.mock_open()
|
||||||
|
with mock.patch('fuelclient.commands.environment.open',
|
||||||
|
m_open, create=True):
|
||||||
|
self.exec_command(args)
|
||||||
|
|
||||||
|
m_open.assert_called_once_with(expected_path, 'w')
|
||||||
|
m_dump.assert_called_once_with(test_data, mock.ANY, indent=4)
|
||||||
|
self.m_get_client.assert_called_once_with('environment', mock.ANY)
|
||||||
|
self.m_client.get_network_configuration.assert_called_once_with(42)
|
||||||
|
|
||||||
|
def test_env_network_upload_json(self):
|
||||||
|
args = 'env network upload --format json -d /tmp 42'
|
||||||
|
config = {'foo': 'bar'}
|
||||||
|
expected_path = '/tmp/environment_42/network.json'
|
||||||
|
|
||||||
|
m_open = mock.mock_open(read_data=json.dumps(config))
|
||||||
|
with mock.patch('fuelclient.commands.environment.open',
|
||||||
|
m_open, create=True):
|
||||||
|
self.exec_command(args)
|
||||||
|
|
||||||
|
m_open.assert_called_once_with(expected_path, 'r')
|
||||||
|
self.m_get_client.assert_called_once_with('environment', mock.ANY)
|
||||||
|
self.m_client.set_network_configuration.assert_called_once_with(42,
|
||||||
|
config)
|
||||||
|
|
||||||
|
@mock.patch('yaml.safe_dump')
|
||||||
|
def test_env_network_download_yaml(self, m_safe_dump):
|
||||||
|
args = 'env network download --format yaml -d /tmp 42'
|
||||||
|
test_data = {'foo': 'bar'}
|
||||||
|
expected_path = '/tmp/environment_42/network.yaml'
|
||||||
|
|
||||||
|
self.m_client.get_network_configuration.return_value = test_data
|
||||||
|
|
||||||
|
m_open = mock.mock_open()
|
||||||
|
with mock.patch('fuelclient.commands.environment.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(test_data, mock.ANY,
|
||||||
|
default_flow_style=False)
|
||||||
|
|
||||||
|
self.m_get_client.assert_called_once_with('environment', mock.ANY)
|
||||||
|
self.m_client.get_network_configuration.assert_called_once_with(42)
|
||||||
|
|
||||||
|
def test_env_network_upload_yaml(self):
|
||||||
|
args = 'env network upload --format yaml -d /tmp 42'
|
||||||
|
config = {'foo': 'bar'}
|
||||||
|
expected_path = '/tmp/environment_42/network.yaml'
|
||||||
|
|
||||||
|
m_open = mock.mock_open(read_data=yaml.dump(config))
|
||||||
|
with mock.patch('fuelclient.commands.environment.open',
|
||||||
|
m_open, create=True):
|
||||||
|
self.exec_command(args)
|
||||||
|
|
||||||
|
m_open.assert_called_once_with(expected_path, 'r')
|
||||||
|
self.m_get_client.assert_called_once_with('environment', mock.ANY)
|
||||||
|
self.m_client.set_network_configuration.assert_called_once_with(42,
|
||||||
|
config)
|
||||||
|
|
||||||
|
@mock.patch('json.dump')
|
||||||
|
def test_env_settings_download_json(self, m_dump):
|
||||||
|
args = 'env settings download --format json -d /tmp 42'
|
||||||
|
test_data = {'foo': 'bar'}
|
||||||
|
expected_path = '/tmp/environment_42/settings.json'
|
||||||
|
|
||||||
|
self.m_client.get_settings.return_value = test_data
|
||||||
|
|
||||||
|
m_open = mock.mock_open()
|
||||||
|
with mock.patch('fuelclient.commands.environment.open',
|
||||||
|
m_open, create=True):
|
||||||
|
self.exec_command(args)
|
||||||
|
|
||||||
|
m_open.assert_called_once_with(expected_path, 'w')
|
||||||
|
m_dump.assert_called_once_with(test_data, mock.ANY, indent=4)
|
||||||
|
self.m_get_client.assert_called_once_with('environment', mock.ANY)
|
||||||
|
self.m_client.get_settings.assert_called_once_with(42)
|
||||||
|
|
||||||
|
def test_env_settings_upload_json(self):
|
||||||
|
args = 'env settings upload --format json -d /tmp 42'
|
||||||
|
config = {'foo': 'bar'}
|
||||||
|
expected_path = '/tmp/environment_42/settings.json'
|
||||||
|
|
||||||
|
m_open = mock.mock_open(read_data=json.dumps(config))
|
||||||
|
with mock.patch('fuelclient.commands.environment.open',
|
||||||
|
m_open, create=True):
|
||||||
|
self.exec_command(args)
|
||||||
|
|
||||||
|
m_open.assert_called_once_with(expected_path, 'r')
|
||||||
|
self.m_get_client.assert_called_once_with('environment', mock.ANY)
|
||||||
|
self.m_client.set_settings.assert_called_once_with(42,
|
||||||
|
config,
|
||||||
|
force=False)
|
||||||
|
|
||||||
|
@mock.patch('yaml.safe_dump')
|
||||||
|
def test_env_settings_download_yaml(self, m_safe_dump):
|
||||||
|
args = 'env settings download --format yaml -d /tmp 42'
|
||||||
|
test_data = {'foo': 'bar'}
|
||||||
|
expected_path = '/tmp/environment_42/settings.yaml'
|
||||||
|
|
||||||
|
self.m_client.get_settings.return_value = test_data
|
||||||
|
|
||||||
|
m_open = mock.mock_open()
|
||||||
|
with mock.patch('fuelclient.commands.environment.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(test_data, mock.ANY,
|
||||||
|
default_flow_style=False)
|
||||||
|
|
||||||
|
self.m_get_client.assert_called_once_with('environment', mock.ANY)
|
||||||
|
self.m_client.get_settings.assert_called_once_with(42)
|
||||||
|
|
||||||
|
def test_env_settings_upload_yaml(self):
|
||||||
|
args = 'env settings upload --format yaml -d /tmp 42'
|
||||||
|
config = {'foo': 'bar'}
|
||||||
|
expected_path = '/tmp/environment_42/settings.yaml'
|
||||||
|
|
||||||
|
m_open = mock.mock_open(read_data=yaml.dump(config))
|
||||||
|
with mock.patch('fuelclient.commands.environment.open',
|
||||||
|
m_open, create=True):
|
||||||
|
self.exec_command(args)
|
||||||
|
|
||||||
|
m_open.assert_called_once_with(expected_path, 'r')
|
||||||
|
self.m_get_client.assert_called_once_with('environment', mock.ANY)
|
||||||
|
self.m_client.set_settings.assert_called_once_with(42,
|
||||||
|
config,
|
||||||
|
force=False)
|
||||||
|
|
||||||
|
def test_env_settings_upload_force(self):
|
||||||
|
args = 'env settings upload --format yaml -d /tmp --force 42'
|
||||||
|
config = {'foo': 'bar'}
|
||||||
|
expected_path = '/tmp/environment_42/settings.yaml'
|
||||||
|
|
||||||
|
m_open = mock.mock_open(read_data=yaml.dump(config))
|
||||||
|
with mock.patch('fuelclient.commands.environment.open',
|
||||||
|
m_open, create=True):
|
||||||
|
self.exec_command(args)
|
||||||
|
|
||||||
|
m_open.assert_called_once_with(expected_path, 'r')
|
||||||
|
self.m_get_client.assert_called_once_with('environment', mock.ANY)
|
||||||
|
self.m_client.set_settings.assert_called_once_with(42,
|
||||||
|
config,
|
||||||
|
force=True)
|
||||||
|
|
|
@ -374,44 +374,3 @@ node-4 ansible_host=10.20.0.5
|
||||||
|
|
||||||
self.m_get_client.assert_called_once_with('node', mock.ANY)
|
self.m_get_client.assert_called_once_with('node', mock.ANY)
|
||||||
self.m_client.upload_attributes.assert_called_once_with(42, None)
|
self.m_client.upload_attributes.assert_called_once_with(42, None)
|
||||||
|
|
||||||
|
|
||||||
class TestNodeMixIn(test_engine.BaseCLITest):
|
|
||||||
def test_get_attribute_path(self):
|
|
||||||
mixin = cmd_node.NodeMixIn()
|
|
||||||
|
|
||||||
attr_types = ('attributes', 'interfaces', 'disks')
|
|
||||||
file_format = 'json'
|
|
||||||
node_id = 42
|
|
||||||
directory = '/test'
|
|
||||||
|
|
||||||
for attr_type in attr_types:
|
|
||||||
expected_path = '/test/node_42/{t}.json'.format(t=attr_type)
|
|
||||||
real_path = mixin.get_attributes_path(attr_type, file_format,
|
|
||||||
node_id, directory)
|
|
||||||
|
|
||||||
self.assertEqual(expected_path, real_path)
|
|
||||||
|
|
||||||
def test_get_attribute_path_wrong_attr_type(self):
|
|
||||||
mixin = cmd_node.NodeMixIn()
|
|
||||||
|
|
||||||
attr_type = 'wrong'
|
|
||||||
file_format = 'json'
|
|
||||||
node_id = 42
|
|
||||||
directory = '/test'
|
|
||||||
|
|
||||||
self.assertRaises(ValueError,
|
|
||||||
mixin.get_attributes_path,
|
|
||||||
attr_type, file_format, node_id, directory)
|
|
||||||
|
|
||||||
def test_get_attribute_path_wrong_file_format(self):
|
|
||||||
mixin = cmd_node.NodeMixIn()
|
|
||||||
|
|
||||||
attr_type = 'interfaces'
|
|
||||||
file_format = 'wrong'
|
|
||||||
node_id = 42
|
|
||||||
directory = '/test'
|
|
||||||
|
|
||||||
self.assertRaises(ValueError,
|
|
||||||
mixin.get_attributes_path,
|
|
||||||
attr_type, file_format, node_id, directory)
|
|
||||||
|
|
|
@ -32,6 +32,9 @@ class TestEnvFacade(test_api.BaseLibTest):
|
||||||
|
|
||||||
self.version = 'v1'
|
self.version = 'v1'
|
||||||
self.res_uri = '/api/{version}/clusters/'.format(version=self.version)
|
self.res_uri = '/api/{version}/clusters/'.format(version=self.version)
|
||||||
|
self.net_conf_uri = '/network_configuration/neutron'
|
||||||
|
self.settings_uri = '/attributes'
|
||||||
|
self.net_verify_uri = '/network_configuration/neutron/verify'
|
||||||
|
|
||||||
self.fake_env = utils.get_fake_env()
|
self.fake_env = utils.get_fake_env()
|
||||||
self.fake_envs = [utils.get_fake_env() for i in range(10)]
|
self.fake_envs = [utils.get_fake_env() for i in range(10)]
|
||||||
|
@ -286,3 +289,106 @@ class TestEnvFacade(test_api.BaseLibTest):
|
||||||
self.assertTrue(matcher.called)
|
self.assertTrue(matcher.called)
|
||||||
self.assertEqual([','.join(str(i) for i in node_ids)],
|
self.assertEqual([','.join(str(i) for i in node_ids)],
|
||||||
matcher.last_request.qs['nodes'])
|
matcher.last_request.qs['nodes'])
|
||||||
|
|
||||||
|
def test_env_network_verify(self):
|
||||||
|
env_id = 42
|
||||||
|
fake_env = utils.get_fake_env(env_id=env_id)
|
||||||
|
test_conf = utils.get_fake_env_network_conf()
|
||||||
|
|
||||||
|
env_uri = self.get_object_uri(self.res_uri, env_id)
|
||||||
|
download_uri = self.get_object_uri(self.res_uri,
|
||||||
|
env_id,
|
||||||
|
self.net_conf_uri)
|
||||||
|
verify_uri = self.get_object_uri(self.res_uri,
|
||||||
|
env_id,
|
||||||
|
self.net_verify_uri)
|
||||||
|
|
||||||
|
m_get = self.m_request.get(env_uri, json=fake_env)
|
||||||
|
m_download = self.m_request.get(download_uri, json=test_conf)
|
||||||
|
m_verify = self.m_request.put(verify_uri, json=utils.get_fake_task())
|
||||||
|
|
||||||
|
self.client.verify_network(env_id)
|
||||||
|
|
||||||
|
self.assertTrue(m_get.called)
|
||||||
|
self.assertTrue(m_download.called)
|
||||||
|
self.assertTrue(m_verify.called)
|
||||||
|
self.assertEqual(test_conf, m_verify.last_request.json())
|
||||||
|
|
||||||
|
def test_env_network_download(self):
|
||||||
|
env_id = 42
|
||||||
|
fake_env = utils.get_fake_env(env_id=env_id)
|
||||||
|
env_uri = self.get_object_uri(self.res_uri, env_id)
|
||||||
|
download_uri = self.get_object_uri(self.res_uri,
|
||||||
|
env_id,
|
||||||
|
self.net_conf_uri)
|
||||||
|
test_conf = utils.get_fake_env_network_conf()
|
||||||
|
|
||||||
|
m_get = self.m_request.get(env_uri, json=fake_env)
|
||||||
|
m_download = self.m_request.get(download_uri, json=test_conf)
|
||||||
|
|
||||||
|
net_conf = self.client.get_network_configuration(env_id)
|
||||||
|
|
||||||
|
self.assertEqual(test_conf, net_conf)
|
||||||
|
self.assertTrue(m_get.called)
|
||||||
|
self.assertTrue(m_download.called)
|
||||||
|
|
||||||
|
def test_env_network_upload(self):
|
||||||
|
env_id = 42
|
||||||
|
fake_env = utils.get_fake_env(env_id=env_id)
|
||||||
|
env_uri = self.get_object_uri(self.res_uri, env_id)
|
||||||
|
upload_uri = self.get_object_uri(self.res_uri,
|
||||||
|
env_id,
|
||||||
|
self.net_conf_uri)
|
||||||
|
test_conf = utils.get_fake_env_network_conf()
|
||||||
|
|
||||||
|
m_get = self.m_request.get(env_uri, json=fake_env)
|
||||||
|
m_upload = self.m_request.put(upload_uri, json={})
|
||||||
|
|
||||||
|
self.client.set_network_configuration(env_id, test_conf)
|
||||||
|
|
||||||
|
self.assertTrue(m_get.called)
|
||||||
|
self.assertTrue(m_upload.called)
|
||||||
|
self.assertEqual(test_conf, m_upload.last_request.json())
|
||||||
|
|
||||||
|
def test_env_settings_download(self):
|
||||||
|
env_id = 42
|
||||||
|
download_uri = self.get_object_uri(self.res_uri,
|
||||||
|
env_id,
|
||||||
|
self.settings_uri)
|
||||||
|
test_settings = {'test-data': 42}
|
||||||
|
|
||||||
|
m_download = self.m_request.get(download_uri, json=test_settings)
|
||||||
|
|
||||||
|
settings = self.client.get_settings(env_id)
|
||||||
|
|
||||||
|
self.assertEqual(test_settings, settings)
|
||||||
|
self.assertTrue(m_download.called)
|
||||||
|
|
||||||
|
def test_env_settings_upload(self):
|
||||||
|
env_id = 42
|
||||||
|
upload_uri = self.get_object_uri(self.res_uri,
|
||||||
|
env_id,
|
||||||
|
self.settings_uri)
|
||||||
|
test_settings = {'test-data': 42}
|
||||||
|
|
||||||
|
m_upload = self.m_request.put(upload_uri, json={})
|
||||||
|
|
||||||
|
self.client.set_settings(env_id, test_settings)
|
||||||
|
|
||||||
|
self.assertTrue(m_upload.called)
|
||||||
|
self.assertEqual(test_settings, m_upload.last_request.json())
|
||||||
|
|
||||||
|
def test_env_settings_upload_force(self):
|
||||||
|
env_id = 42
|
||||||
|
upload_uri = self.get_object_uri(self.res_uri,
|
||||||
|
env_id,
|
||||||
|
self.settings_uri)
|
||||||
|
test_settings = {'test-data': 42}
|
||||||
|
|
||||||
|
m_upload = self.m_request.put(upload_uri, json={})
|
||||||
|
|
||||||
|
self.client.set_settings(env_id, test_settings, force=True)
|
||||||
|
|
||||||
|
self.assertTrue(m_upload.called)
|
||||||
|
self.assertEqual(test_settings, m_upload.last_request.json())
|
||||||
|
self.assertEqual(['1'], m_upload.last_request.qs.get('force'))
|
||||||
|
|
|
@ -21,6 +21,8 @@ from fuelclient.tests.utils.fake_additional_info \
|
||||||
import get_fake_yaml_deployment_info
|
import get_fake_yaml_deployment_info
|
||||||
from fuelclient.tests.utils.fake_additional_info \
|
from fuelclient.tests.utils.fake_additional_info \
|
||||||
import get_fake_yaml_network_conf
|
import get_fake_yaml_network_conf
|
||||||
|
from fuelclient.tests.utils.fake_additional_info \
|
||||||
|
import get_fake_env_network_conf
|
||||||
from fuelclient.tests.utils.fake_deployment_history \
|
from fuelclient.tests.utils.fake_deployment_history \
|
||||||
import get_fake_deployment_history
|
import get_fake_deployment_history
|
||||||
from fuelclient.tests.utils.fake_deployment_history \
|
from fuelclient.tests.utils.fake_deployment_history \
|
||||||
|
@ -51,6 +53,7 @@ __all__ = (get_fake_deployment_history,
|
||||||
get_fake_yaml_deployment_info,
|
get_fake_yaml_deployment_info,
|
||||||
get_fake_yaml_network_conf,
|
get_fake_yaml_network_conf,
|
||||||
get_fake_env,
|
get_fake_env,
|
||||||
|
get_fake_env_network_conf,
|
||||||
get_fake_release,
|
get_fake_release,
|
||||||
get_fake_releases,
|
get_fake_releases,
|
||||||
get_fake_attributes_metadata,
|
get_fake_attributes_metadata,
|
||||||
|
|
|
@ -14,6 +14,9 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
CLUSTER_SETTINGS = '''---
|
CLUSTER_SETTINGS = '''---
|
||||||
editable:
|
editable:
|
||||||
service_user:
|
service_user:
|
||||||
|
@ -88,3 +91,7 @@ def get_fake_yaml_network_conf():
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return NETWORK_CONF
|
return NETWORK_CONF
|
||||||
|
|
||||||
|
|
||||||
|
def get_fake_env_network_conf():
|
||||||
|
return yaml.load(get_fake_yaml_network_conf())
|
||||||
|
|
|
@ -138,6 +138,28 @@ class EnvironmentClient(base_v1.BaseV1Client):
|
||||||
env = self._entity_wrapper(environment_id)
|
env = self._entity_wrapper(environment_id)
|
||||||
env.delete_network_template_data()
|
env.delete_network_template_data()
|
||||||
|
|
||||||
|
def get_network_configuration(self, environment_id):
|
||||||
|
env = self._entity_wrapper(environment_id)
|
||||||
|
return env.get_network_data()
|
||||||
|
|
||||||
|
def set_network_configuration(self, environment_id, new_configuration):
|
||||||
|
env = self._entity_wrapper(environment_id)
|
||||||
|
env.set_network_data(new_configuration)
|
||||||
|
|
||||||
|
def verify_network(self, environment_id):
|
||||||
|
"""Start network verification for an environment."""
|
||||||
|
|
||||||
|
env = self._entity_wrapper(environment_id)
|
||||||
|
return env.verify_network()
|
||||||
|
|
||||||
|
def get_settings(self, environment_id):
|
||||||
|
env = self._entity_wrapper(environment_id)
|
||||||
|
return env.get_settings_data()
|
||||||
|
|
||||||
|
def set_settings(self, environment_id, new_configuration, force=False):
|
||||||
|
env = self._entity_wrapper(environment_id)
|
||||||
|
env.set_settings_data(new_configuration, force=force)
|
||||||
|
|
||||||
|
|
||||||
def get_client(connection):
|
def get_client(connection):
|
||||||
return EnvironmentClient(connection)
|
return EnvironmentClient(connection)
|
||||||
|
|
|
@ -36,8 +36,13 @@ fuelclient =
|
||||||
env_nodes_deploy=fuelclient.commands.environment:EnvDeployNodes
|
env_nodes_deploy=fuelclient.commands.environment:EnvDeployNodes
|
||||||
env_list=fuelclient.commands.environment:EnvList
|
env_list=fuelclient.commands.environment:EnvList
|
||||||
env_nodes_provision=fuelclient.commands.environment:EnvProvisionNodes
|
env_nodes_provision=fuelclient.commands.environment:EnvProvisionNodes
|
||||||
|
env_network_download=fuelclient.commands.environment:EnvNetworkDownload
|
||||||
|
env_network_upload=fuelclient.commands.environment:EnvNetworkUpload
|
||||||
|
env_network_verify=fuelclient.commands.environment:EnvNetworkVerify
|
||||||
env_redeploy=fuelclient.commands.environment:EnvRedeploy
|
env_redeploy=fuelclient.commands.environment:EnvRedeploy
|
||||||
env_remove_nodes=fuelclient.commands.environment:EnvRemoveNodes
|
env_remove_nodes=fuelclient.commands.environment:EnvRemoveNodes
|
||||||
|
env_settings_download=fuelclient.commands.environment:EnvSettingsDownload
|
||||||
|
env_settings_upload=fuelclient.commands.environment:EnvSettingsUpload
|
||||||
env_show=fuelclient.commands.environment:EnvShow
|
env_show=fuelclient.commands.environment:EnvShow
|
||||||
env_spawn-vms=fuelclient.commands.environment:EnvSpawnVms
|
env_spawn-vms=fuelclient.commands.environment:EnvSpawnVms
|
||||||
env_update=fuelclient.commands.environment:EnvUpdate
|
env_update=fuelclient.commands.environment:EnvUpdate
|
||||||
|
|
Loading…
Reference in New Issue