Add fuel2 diagnostic snapshot commands

This patch adds commands that allow to manipulate
with diagnostic snapshot process creation:

   fuel2 snapshot get-default-config
   fuel2 snapshot create
   fuel2 snapshot get-link

DocImpact

Change-Id: I59542eba922d793406c50b864b1cd0c416c7c891
This commit is contained in:
tivaliy 2016-08-12 08:20:29 +03:00 committed by Vitalii Kulanov
parent 82946ddbf9
commit 5e5edefe24
7 changed files with 363 additions and 0 deletions

View File

@ -72,6 +72,7 @@ def get_client(resource, version='v1', connection=None):
'openstack-config': v1.openstack_config,
'plugins': v1.plugins,
'release': v1.release,
'snapshot': v1.snapshot,
'task': v1.task,
'vip': v1.vip
}

View File

@ -0,0 +1,132 @@
# -*- 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 argparse
import os
from oslo_utils import fileutils
from fuelclient.cli import error
from fuelclient.commands import base
from fuelclient.common import data_utils
from fuelclient import utils
class SnapshotMixIn(object):
entity_name = 'snapshot'
supported_file_formats = ('json', 'yaml')
@staticmethod
def config_file(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_config_path(directory, file_format):
return os.path.join(os.path.abspath(directory),
'snapshot_conf.{}'.format(file_format))
class SnapshotGenerate(SnapshotMixIn, base.BaseCommand):
"""Generate diagnostic snapshot."""
def get_parser(self, prog_name):
parser = super(SnapshotGenerate, self).get_parser(prog_name)
parser.add_argument('-c',
'--config',
required=False,
type=self.config_file,
help='Configuration file.')
return parser
def take_action(self, parsed_args):
file_path = parsed_args.config
config = dict()
if file_path:
file_format = os.path.splitext(file_path)[1].lstrip('.')
try:
with open(file_path, 'r') as stream:
config = data_utils.safe_load(file_format, stream)
except (OSError, IOError):
msg = 'Could not read configuration at {}.'
raise error.InvalidFileException(msg.format(file_path))
result = self.client.create_snapshot(config)
msg = "Diagnostic snapshot generation task with id {id} was started\n"
self.app.stdout.write(msg.format(id=result.id))
class SnapshotConfigGetDefault(SnapshotMixIn, base.BaseCommand):
"""Download default config to generate custom diagnostic snapshot."""
def get_parser(self, prog_name):
parser = super(SnapshotConfigGetDefault, self).get_parser(prog_name)
parser.add_argument('-f',
'--format',
required=True,
choices=self.supported_file_formats,
help='Format of serialized diagnostic snapshot '
'configuration data.')
parser.add_argument('-d',
'--directory',
required=False,
default=os.path.curdir,
help='Destination directory. Defaults to '
'the current directory.')
return parser
def take_action(self, parsed_args):
file_path = self.get_config_path(parsed_args.directory,
parsed_args.format)
config = self.client.get_default_config()
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, config)
except (OSError, IOError):
msg = 'Could not store configuration at {}.'
raise error.InvalidFileException(msg.format(file_path))
msg = "Configuration was stored in {path}\n"
self.app.stdout.write(msg.format(path=file_path))
class SnapshotGetLink(SnapshotMixIn, base.BaseShowCommand):
"""Show link to download diagnostic snapshot."""
columns = ('status',
'link')
def take_action(self, parsed_args):
data = self.client.get_by_id(parsed_args.id)
if data['name'] != 'dump':
msg = "Task with id {0} is not a snapshot generation task"
raise error.ActionException(msg.format(data['id']))
if data['status'] != 'ready':
data['link'] = None
else:
data['link'] = self.client.connection.root + data['message']
data = data_utils.get_display_data_single(self.columns, data)
return self.columns, data

View File

@ -0,0 +1,131 @@
# -*- 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.cli import error
from fuelclient.tests.unit.v2.cli import test_engine
class TestSnapshotCommand(test_engine.BaseCLITest):
@mock.patch('json.dump')
def test_snapshot_config_download_json(self, m_dump):
args = 'snapshot get-default-config -f json -d /tmp'
test_data = {'foo': 'bar'}
expected_path = '/tmp/snapshot_conf.json'
self.m_client.get_default_config.return_value = test_data
m_open = mock.mock_open()
with mock.patch('fuelclient.commands.snapshot.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('snapshot', mock.ANY)
self.m_client.get_default_config.assert_called_once_with()
@mock.patch('yaml.safe_dump')
def test_snapshot_config_download_yaml(self, m_safe_dump):
args = 'snapshot get-default-config -f yaml -d /tmp'
test_data = {'foo': 'bar'}
expected_path = '/tmp/snapshot_conf.yaml'
self.m_client.get_default_config.return_value = test_data
m_open = mock.mock_open()
with mock.patch('fuelclient.commands.snapshot.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('snapshot', mock.ANY)
self.m_client.get_default_config.assert_called_once_with()
def test_snapshot_create(self):
args = 'snapshot create'
test_data = {}
self.exec_command(args)
self.m_get_client.assert_called_once_with('snapshot', mock.ANY)
self.m_client.create_snapshot.assert_called_once_with(test_data)
@mock.patch('fuelclient.utils.file_exists', mock.Mock(return_value=True))
def test_snapshot_create_w_config_json(self):
args = 'snapshot create -c /tmp/snapshot_conf.json'
test_data = {'foo': 'bar'}
expected_path = '/tmp/snapshot_conf.json'
m_open = mock.mock_open(read_data=json.dumps(test_data))
with mock.patch('fuelclient.commands.snapshot.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('snapshot', mock.ANY)
self.m_client.create_snapshot.assert_called_once_with(test_data)
@mock.patch('fuelclient.utils.file_exists', mock.Mock(return_value=True))
def test_snapshot_create_w_config_yaml(self):
args = 'snapshot create -c /tmp/snapshot_conf.yaml'
test_data = {'foo': 'bar'}
expected_path = '/tmp/snapshot_conf.yaml'
m_open = mock.mock_open(read_data=yaml.dump(test_data))
with mock.patch('fuelclient.commands.snapshot.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('snapshot', mock.ANY)
self.m_client.create_snapshot.assert_called_once_with(test_data)
def test_snapshot_get_link(self):
task_id = 45
args = 'snapshot get-link {}'.format(task_id)
test_data = {'id': task_id,
'name': 'dump',
'status': 'ready',
'message': 'fake_message'}
self.m_client.get_by_id.return_value = test_data
self.exec_command(args)
self.m_get_client.assert_called_once_with('snapshot', mock.ANY)
self.m_client.get_by_id.assert_called_once_with(task_id)
@mock.patch('sys.stderr')
def test_snapshot_get_link_fail(self, mocked_stderr):
task_id = 45
args = 'snapshot get-link {}'.format(task_id)
test_data = {'id': task_id,
'name': 'not_dump_name',
'status': 'ready',
'message': 'fake_message'}
self.m_client.get_by_id.return_value = test_data
self.assertRaises(error.ActionException, self.exec_command, args)
self.assertIn('Task with id {} is not a snapshot generation '
'task'.format(task_id),
mocked_stderr.write.call_args_list[0][0][0])

View File

@ -0,0 +1,61 @@
# -*- 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 TestSnapshotFacade(test_api.BaseLibTest):
def setUp(self):
super(TestSnapshotFacade, self).setUp()
self.version = 'v1'
self.res_uri = '/api/{version}/logs/package'.format(
version=self.version)
self.client = fuelclient.get_client('snapshot', self.version)
self.fake_task = utils.get_fake_task()
def test_snapshot_config_download(self):
fake_resp = {'test_key': 'test_value'}
expected_uri = '/api/{version}/logs/package/config/default/'.format(
version=self.version)
matcher = self.m_request.get(expected_uri, json=fake_resp)
conf = self.client.get_default_config()
self.assertTrue(matcher.called)
self.assertEqual(conf, fake_resp)
def test_snapshot_create(self):
fake_config = {}
matcher = self.m_request.put(self.res_uri, json=self.fake_task)
self.client.create_snapshot(fake_config)
self.assertTrue(matcher.called)
self.assertEqual(fake_config, matcher.last_request.json())
def test_snapshot_create_w_config(self):
fake_config = {'key_value': 'data_value'}
matcher = self.m_request.put(self.res_uri, json=self.fake_task)
self.client.create_snapshot(fake_config)
self.assertTrue(matcher.called)
self.assertEqual(fake_config, matcher.last_request.json())

View File

@ -24,6 +24,7 @@ from fuelclient.v1 import node
from fuelclient.v1 import openstack_config
from fuelclient.v1 import release
from fuelclient.v1 import plugins
from fuelclient.v1 import snapshot
from fuelclient.v1 import task
from fuelclient.v1 import vip
@ -40,5 +41,6 @@ __all__ = ('cluster_settings',
'openstack_config',
'plugins',
'release',
'snapshot',
'task',
'vip')

33
fuelclient/v1/snapshot.py Normal file
View File

@ -0,0 +1,33 @@
# -*- 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.
from fuelclient import objects
from fuelclient.v1 import base_v1
class SnapshotClient(base_v1.BaseV1Client):
_entity_wrapper = objects.SnapshotTask
def create_snapshot(self, config):
return self._entity_wrapper.start_snapshot_task(config)
def get_default_config(self):
return self._entity_wrapper.get_default_config()
def get_client(connection):
return SnapshotClient(connection)

View File

@ -103,6 +103,9 @@ fuelclient =
task_network-configuration_download=fuelclient.commands.task:TaskNetworkConfigurationDownload
task_settings_download=fuelclient.commands.task:TaskClusterSettingsDownload
task_show=fuelclient.commands.task:TaskShow
snapshot_create=fuelclient.commands.snapshot:SnapshotGenerate
snapshot_get-default-config=fuelclient.commands.snapshot:SnapshotConfigGetDefault
snapshot_get-link=fuelclient.commands.snapshot:SnapshotGetLink
vip_create=fuelclient.commands.vip:VipCreate
vip_download=fuelclient.commands.vip:VipDownload
vip_upload=fuelclient.commands.vip:VipUpload