Added commands to manage deployment sequences
Commands: - fuel2 sequence create -r <release> -n <name> -t <graph_type1> [graph_typeN] - fuel2 sequence upload -r <release> --file <file_path> - fuel2 sequence download <id> [--file <file path>] - fuel2 sequence delete <id> - fuel2 sequence update <id> [-name <name>] [-t <graph_type1> [graph_typeN]] - fuel2 sequence list -r <release_id> | -e <env_id> - fuel2 sequence show <id> - fuel2 sequence execute <id> -e <env_id> [--force] [--dry-run] [--noop] DocImpact Change-Id: I6eb688c5cc91b2b3dbaa2fe5c52a69fe062da664 Partial-Bug: 1620620
This commit is contained in:
parent
bff20a7123
commit
bac19f24b3
|
@ -73,6 +73,7 @@ def get_client(resource, version='v1', connection=None):
|
||||||
'plugins': v1.plugins,
|
'plugins': v1.plugins,
|
||||||
'release': v1.release,
|
'release': v1.release,
|
||||||
'role': v1.role,
|
'role': v1.role,
|
||||||
|
'sequence': v1.sequence,
|
||||||
'snapshot': v1.snapshot,
|
'snapshot': v1.snapshot,
|
||||||
'task': v1.task,
|
'task': v1.task,
|
||||||
'vip': v1.vip
|
'vip': v1.vip
|
||||||
|
|
|
@ -76,6 +76,8 @@ class BaseCommand(command.Command):
|
||||||
class BaseListCommand(lister.Lister, BaseCommand):
|
class BaseListCommand(lister.Lister, BaseCommand):
|
||||||
"""Lists all entities showing some information."""
|
"""Lists all entities showing some information."""
|
||||||
|
|
||||||
|
filters = {}
|
||||||
|
|
||||||
@abc.abstractproperty
|
@abc.abstractproperty
|
||||||
def columns(self):
|
def columns(self):
|
||||||
"""Names of columns in the resulting table."""
|
"""Names of columns in the resulting table."""
|
||||||
|
@ -106,7 +108,13 @@ class BaseListCommand(lister.Lister, BaseCommand):
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
data = self.client.get_all()
|
filters = {}
|
||||||
|
for name, prop in self.filters.items():
|
||||||
|
value = getattr(parsed_args, prop, None)
|
||||||
|
if value is not None:
|
||||||
|
filters[name] = value
|
||||||
|
|
||||||
|
data = self.client.get_all(**filters)
|
||||||
data = data_utils.get_display_data_multi(self.columns, data)
|
data = data_utils.get_display_data_multi(self.columns, data)
|
||||||
|
|
||||||
scolumn_ids = [self.columns.index(col)
|
scolumn_ids = [self.columns.index(col)
|
||||||
|
|
|
@ -0,0 +1,239 @@
|
||||||
|
# -*- 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 serializers
|
||||||
|
from fuelclient.commands import base
|
||||||
|
from fuelclient.common import data_utils
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceMixIn(object):
|
||||||
|
entity_name = 'sequence'
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceCreate(SequenceMixIn, base.show.ShowOne, base.BaseCommand):
|
||||||
|
"""Create a new deployment sequence."""
|
||||||
|
|
||||||
|
columns = ("id", "release_id", "name")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(SequenceCreate, self).get_parser(prog_name)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-r", "--release",
|
||||||
|
type=int,
|
||||||
|
required=True,
|
||||||
|
help="Release object id, sequence will be linked to."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-n', '--name',
|
||||||
|
required=True,
|
||||||
|
help='The unique name for sequence'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-t', '--graph-type',
|
||||||
|
dest='graph_types',
|
||||||
|
nargs='+',
|
||||||
|
required=True,
|
||||||
|
help='Graph types, which will be included to sequence.\n'
|
||||||
|
'Note: Order is important'
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, args):
|
||||||
|
new_sequence = self.client.create(
|
||||||
|
args.release, args.name, args.graph_types
|
||||||
|
)
|
||||||
|
self.app.stdout.write("Sequence was successfully created:\n")
|
||||||
|
data = data_utils.get_display_data_single(self.columns, new_sequence)
|
||||||
|
|
||||||
|
return self.columns, data
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceUpload(SequenceMixIn, base.show.ShowOne, base.BaseCommand):
|
||||||
|
"""Upload a new deployment sequence."""
|
||||||
|
|
||||||
|
columns = ("id", "release_id", "name")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(SequenceUpload, self).get_parser(prog_name)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-r", "--release",
|
||||||
|
type=int,
|
||||||
|
required=True,
|
||||||
|
help="Release object id, sequence will be linked to."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--file',
|
||||||
|
required=True,
|
||||||
|
help='YAML file which contains deployment sequence properties.'
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, args):
|
||||||
|
serializer = serializers.FileFormatBasedSerializer()
|
||||||
|
new_sequence = self.client.upload(
|
||||||
|
args.release, serializer.read_from_file(args.file)
|
||||||
|
)
|
||||||
|
self.app.stdout.write("Sequence was successfully created:\n")
|
||||||
|
data = data_utils.get_display_data_single(self.columns, new_sequence)
|
||||||
|
return self.columns, data
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceDownload(SequenceMixIn, base.BaseCommand):
|
||||||
|
"""Download deployment sequence data."""
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(SequenceDownload, self).get_parser(prog_name)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"id",
|
||||||
|
type=int,
|
||||||
|
help="Sequence ID."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--file',
|
||||||
|
help='The file path where data will be saved.'
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, args):
|
||||||
|
data = self.client.download(args.id)
|
||||||
|
if args.file:
|
||||||
|
serializer = serializers.FileFormatBasedSerializer()
|
||||||
|
serializer.write_to_file(args.file, data)
|
||||||
|
else:
|
||||||
|
serializer = serializers.Serializer("yaml")
|
||||||
|
serializer.write_to_file(self.app.stdout, data)
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceUpdate(SequenceMixIn, base.BaseShowCommand):
|
||||||
|
"""Update existing sequence"""
|
||||||
|
|
||||||
|
columns = ("id", "name")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(SequenceUpdate, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'-n', '--name',
|
||||||
|
required=False,
|
||||||
|
help='The unique name for sequence'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-t', '--graph-type',
|
||||||
|
dest='graph_types',
|
||||||
|
nargs='+',
|
||||||
|
required=False,
|
||||||
|
help='Graph types, which will be included to sequence.\n'
|
||||||
|
'Note: Order is important'
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, args):
|
||||||
|
sequence = self.client.update(
|
||||||
|
args.id, name=args.name, graph_types=args.graph_types
|
||||||
|
)
|
||||||
|
|
||||||
|
if sequence:
|
||||||
|
self.app.stdout.write("Sequence was successfully updated:\n")
|
||||||
|
data = data_utils.get_display_data_single(self.columns, sequence)
|
||||||
|
return self.columns, data
|
||||||
|
else:
|
||||||
|
self.app.stdout.write("Nothing to update.\n")
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceDelete(SequenceMixIn, base.BaseDeleteCommand):
|
||||||
|
"""Delete existing sequence"""
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceShow(SequenceMixIn, base.BaseShowCommand):
|
||||||
|
"""Display information about sequence"""
|
||||||
|
columns = ("id", "release_id", "name", "graphs")
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceList(SequenceMixIn, base.BaseListCommand):
|
||||||
|
"""Delete existing sequence"""
|
||||||
|
columns = ("id", "release_id", "name")
|
||||||
|
filters = {'release': 'release', 'cluster': 'env'}
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(SequenceList, self).get_parser(prog_name)
|
||||||
|
group = parser.add_mutually_exclusive_group(required=True)
|
||||||
|
group.add_argument(
|
||||||
|
'-r', '--release',
|
||||||
|
type=int,
|
||||||
|
help='The Release object ID'
|
||||||
|
)
|
||||||
|
group.add_argument(
|
||||||
|
'-e', '--env',
|
||||||
|
type=int,
|
||||||
|
help='The environment object id.\n'
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceExecute(SequenceMixIn, base.BaseCommand):
|
||||||
|
"""Executes sequence on specified environment."""
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(SequenceExecute, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'id',
|
||||||
|
type=int,
|
||||||
|
help='Id of the Sequence.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-e', '--env',
|
||||||
|
type=int,
|
||||||
|
required=True,
|
||||||
|
help='Id of the environment'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--dry-run',
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help='Specifies to dry-run a deployment by configuring '
|
||||||
|
'task executor to dump the deployment graph to a dot file.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--force',
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help='Force run all deployment tasks '
|
||||||
|
'without evaluating conditions.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--noop',
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help='Specifies noop-run deployment configuring '
|
||||||
|
'tasks executor to run puppet and shell tasks in '
|
||||||
|
'noop mode and skip all other. Stores noop-run '
|
||||||
|
'result summary in nailgun database.'
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, args):
|
||||||
|
result = self.client.execute(
|
||||||
|
sequence_id=args.id,
|
||||||
|
env_id=args.env,
|
||||||
|
dry_run=args.dry_run,
|
||||||
|
noop_run=args.noop,
|
||||||
|
force=args.force
|
||||||
|
)
|
||||||
|
msg = 'Deployment task with id {t} for the environment {e} ' \
|
||||||
|
'has been started.\n'.format(t=result.data['id'],
|
||||||
|
e=result.data['cluster'])
|
||||||
|
self.app.stdout.write(msg)
|
|
@ -29,3 +29,4 @@ from fuelclient.objects.task import Task
|
||||||
from fuelclient.objects.fuelversion import FuelVersion
|
from fuelclient.objects.fuelversion import FuelVersion
|
||||||
from fuelclient.objects.network_group import NetworkGroup
|
from fuelclient.objects.network_group import NetworkGroup
|
||||||
from fuelclient.objects.plugins import Plugins
|
from fuelclient.objects.plugins import Plugins
|
||||||
|
from fuelclient.objects.sequence import Sequence
|
||||||
|
|
|
@ -60,9 +60,9 @@ class BaseObject(object):
|
||||||
return self._data
|
return self._data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all_data(cls):
|
def get_all_data(cls, **kwargs):
|
||||||
return cls.connection.get_request(cls.class_api_path)
|
return cls.connection.get_request(cls.class_api_path, params=kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all(cls):
|
def get_all(cls, **kwargs):
|
||||||
return map(cls.init_with_data, cls.get_all_data())
|
return map(cls.init_with_data, cls.get_all_data(**kwargs))
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# 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.objects.base import BaseObject
|
||||||
|
|
||||||
|
|
||||||
|
class Sequence(BaseObject):
|
||||||
|
|
||||||
|
class_api_path = "sequences/"
|
||||||
|
instance_api_path = "sequences/{0}/"
|
|
@ -0,0 +1,82 @@
|
||||||
|
# -*- 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
|
||||||
|
|
||||||
|
from fuelclient.tests.unit.v2.cli import test_engine
|
||||||
|
|
||||||
|
|
||||||
|
class TestSequenceActions(test_engine.BaseCLITest):
|
||||||
|
def test_create(self):
|
||||||
|
self.exec_command(
|
||||||
|
'sequence create -r 1 -n test -t test_graph'
|
||||||
|
)
|
||||||
|
self.m_client.create.assert_called_once_with(
|
||||||
|
1, 'test', ['test_graph']
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_upload(self):
|
||||||
|
m_open = mock.mock_open(read_data='name: test\ngraphs: [test]')
|
||||||
|
module_path = 'fuelclient.cli.serializers.open'
|
||||||
|
with mock.patch(module_path, m_open, create=True):
|
||||||
|
self.exec_command(
|
||||||
|
'sequence upload -r 1 --file sequence.yaml'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.m_client.upload.assert_called_once_with(
|
||||||
|
1, {'name': 'test', 'graphs': ['test']}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_download(self):
|
||||||
|
self.m_client.download.return_value = {"name": "test"}
|
||||||
|
m_open = mock.mock_open()
|
||||||
|
module_path = 'fuelclient.cli.serializers.open'
|
||||||
|
with mock.patch(module_path, m_open, create=True):
|
||||||
|
self.exec_command(
|
||||||
|
'sequence download 1 --file sequence.json'
|
||||||
|
)
|
||||||
|
self.m_client.download.assert_called_once_with(1)
|
||||||
|
with mock.patch('sys.stdout') as stdout_mock:
|
||||||
|
self.exec_command('sequence download 1')
|
||||||
|
stdout_mock.write.assert_called_with("name: test\n")
|
||||||
|
|
||||||
|
def test_update(self):
|
||||||
|
self.exec_command(
|
||||||
|
'sequence update 1 -n test -t test_graph'
|
||||||
|
)
|
||||||
|
self.m_client.update.assert_called_once_with(
|
||||||
|
1, name='test', graph_types=['test_graph']
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_show(self):
|
||||||
|
self.exec_command('sequence show 1')
|
||||||
|
self.m_client.get_by_id.assert_called_once_with(1)
|
||||||
|
|
||||||
|
def test_list(self):
|
||||||
|
self.exec_command('sequence list -r 1')
|
||||||
|
self.m_client.get_all.assert_called_once_with(release=1)
|
||||||
|
|
||||||
|
def test_delete(self):
|
||||||
|
self.exec_command('sequence delete 1')
|
||||||
|
self.m_client.delete_by_id.assert_called_once_with(1)
|
||||||
|
|
||||||
|
def test_execute(self):
|
||||||
|
self.exec_command(
|
||||||
|
'sequence execute 1 -e 2 --dry-run --force'
|
||||||
|
)
|
||||||
|
self.m_client.execute.assert_called_once_with(
|
||||||
|
sequence_id=1, env_id=2, dry_run=True, noop_run=False, force=True
|
||||||
|
)
|
|
@ -0,0 +1,105 @@
|
||||||
|
# -*- 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 fuelclient
|
||||||
|
from fuelclient.tests.unit.v2.lib import test_api
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeploymentSequence(test_api.BaseLibTest):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestDeploymentSequence, self).setUp()
|
||||||
|
self.version = 'v1'
|
||||||
|
self.client = fuelclient.get_client('sequence', self.version)
|
||||||
|
self.env_id = 1
|
||||||
|
self.sequence_body = {
|
||||||
|
'id': 1, 'release_id': 1, 'name': 'test',
|
||||||
|
'graphs': [{'type': 'graph1'}]
|
||||||
|
}
|
||||||
|
|
||||||
|
def _check_sequence_object(self, sequence):
|
||||||
|
self.assertEqual(self.sequence_body, sequence)
|
||||||
|
|
||||||
|
def test_create(self):
|
||||||
|
matcher_post = self.m_request.post(
|
||||||
|
'/api/v1/sequences/', json=self.sequence_body
|
||||||
|
)
|
||||||
|
sequence = self.client.create(1, name='test', graph_types=['graph1'])
|
||||||
|
self.assertTrue(matcher_post.called)
|
||||||
|
self._check_sequence_object(sequence)
|
||||||
|
|
||||||
|
def test_update(self):
|
||||||
|
matcher_put = self.m_request.put(
|
||||||
|
'/api/v1/sequences/1/', json=self.sequence_body
|
||||||
|
)
|
||||||
|
sequence = self.client.update(1, name='test')
|
||||||
|
self.assertTrue(matcher_put.called)
|
||||||
|
self.assertEqual('{"name": "test"}', matcher_put.last_request.body)
|
||||||
|
self._check_sequence_object(sequence)
|
||||||
|
|
||||||
|
def test_delete(self):
|
||||||
|
mathcher_delete = self.m_request.delete(
|
||||||
|
'/api/v1/sequences/1/', status_code=204
|
||||||
|
)
|
||||||
|
self.client.delete_by_id(1)
|
||||||
|
self.assertTrue(mathcher_delete.called)
|
||||||
|
|
||||||
|
def test_get_one(self):
|
||||||
|
matcher_get = self.m_request.get(
|
||||||
|
'/api/v1/sequences/1/', json=self.sequence_body
|
||||||
|
)
|
||||||
|
sequence = self.client.get_by_id(1)
|
||||||
|
self.assertTrue(matcher_get.called)
|
||||||
|
self.assertEqual('test', sequence['name'])
|
||||||
|
self.assertEqual('graph1', sequence['graphs'])
|
||||||
|
|
||||||
|
def test_get_all(self):
|
||||||
|
matcher_get = self.m_request.get(
|
||||||
|
'/api/v1/sequences/?release=1', json=[self.sequence_body]
|
||||||
|
)
|
||||||
|
sequences = self.client.get_all(release=1)
|
||||||
|
self.assertTrue(matcher_get.called)
|
||||||
|
self.assertEqual(1, len(sequences))
|
||||||
|
self._check_sequence_object(sequences[0])
|
||||||
|
|
||||||
|
def test_execute(self):
|
||||||
|
self.m_request.get('/api/v1/nodes/?cluster_id=2', json=[])
|
||||||
|
self.m_request.get('/api/v1/cluster/2', json={'id': 2})
|
||||||
|
|
||||||
|
matcher_post = self.m_request.post(
|
||||||
|
'/api/v1/sequences/1/execute/', json={'id': 10, 'cluster': 2}
|
||||||
|
)
|
||||||
|
self.client.execute(1, 2, dry_run=True, noop_run=False)
|
||||||
|
self.assertTrue(matcher_post.called)
|
||||||
|
self.assertIn('"cluster": 2', matcher_post.last_request.body)
|
||||||
|
self.assertIn('"noop_run": false', matcher_post.last_request.body)
|
||||||
|
self.assertIn('"dry_run": true', matcher_post.last_request.body)
|
||||||
|
|
||||||
|
def test_upload(self):
|
||||||
|
matcher_post = self.m_request.post(
|
||||||
|
'/api/v1/sequences/', json=self.sequence_body
|
||||||
|
)
|
||||||
|
sequence = self.client.upload(1, self.sequence_body)
|
||||||
|
self.assertTrue(matcher_post.called)
|
||||||
|
self._check_sequence_object(sequence)
|
||||||
|
|
||||||
|
def test_download(self):
|
||||||
|
matcher_get = self.m_request.get(
|
||||||
|
'/api/v1/sequences/1/', json=self.sequence_body
|
||||||
|
)
|
||||||
|
sequence = self.client.download(1)
|
||||||
|
self.assertTrue(matcher_get.called)
|
||||||
|
self._check_sequence_object(sequence)
|
|
@ -25,6 +25,7 @@ from fuelclient.v1 import openstack_config
|
||||||
from fuelclient.v1 import release
|
from fuelclient.v1 import release
|
||||||
from fuelclient.v1 import role
|
from fuelclient.v1 import role
|
||||||
from fuelclient.v1 import plugins
|
from fuelclient.v1 import plugins
|
||||||
|
from fuelclient.v1 import sequence
|
||||||
from fuelclient.v1 import snapshot
|
from fuelclient.v1 import snapshot
|
||||||
from fuelclient.v1 import task
|
from fuelclient.v1 import task
|
||||||
from fuelclient.v1 import vip
|
from fuelclient.v1 import vip
|
||||||
|
@ -43,6 +44,7 @@ __all__ = ('cluster_settings',
|
||||||
'plugins',
|
'plugins',
|
||||||
'release',
|
'release',
|
||||||
'role',
|
'role',
|
||||||
|
'sequence',
|
||||||
'snapshot',
|
'snapshot',
|
||||||
'task',
|
'task',
|
||||||
'vip')
|
'vip')
|
||||||
|
|
|
@ -38,8 +38,8 @@ class BaseV1Client(object):
|
||||||
{'connection': self.connection}
|
{'connection': self.connection}
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_all(self):
|
def get_all(self, **kwargs):
|
||||||
result = self._entity_wrapper.get_all_data()
|
result = self._entity_wrapper.get_all_data(**kwargs)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
# -*- 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 SequenceClient(base_v1.BaseV1Client):
|
||||||
|
_entity_wrapper = objects.Sequence
|
||||||
|
|
||||||
|
executor_path = _entity_wrapper.instance_api_path + 'execute/'
|
||||||
|
|
||||||
|
def create(self, release_id, name, graph_types):
|
||||||
|
"""Creates new sequence object.
|
||||||
|
|
||||||
|
:param release_id: the release object id
|
||||||
|
:param name: the sequence name
|
||||||
|
:param graph_types: the types of graphs
|
||||||
|
:returns: created object
|
||||||
|
"""
|
||||||
|
data = {'name': name}
|
||||||
|
graphs = data['graphs'] = []
|
||||||
|
for graph_type in graph_types:
|
||||||
|
graphs.append({'type': graph_type})
|
||||||
|
|
||||||
|
return self.upload(release_id, data)
|
||||||
|
|
||||||
|
def upload(self, release_id, data):
|
||||||
|
"""Creates new sequence object from data.
|
||||||
|
|
||||||
|
:param release_id: release object id
|
||||||
|
:param data: the sequence properties
|
||||||
|
:returns: created object
|
||||||
|
"""
|
||||||
|
url = self._entity_wrapper.class_api_path
|
||||||
|
data['release'] = release_id
|
||||||
|
return self.connection.post_request(url, data)
|
||||||
|
|
||||||
|
def download(self, sequence_id):
|
||||||
|
"""Get raw content of sequence."""
|
||||||
|
return super(SequenceClient, self).get_by_id(sequence_id)
|
||||||
|
|
||||||
|
def update(self, sequence_id, name=None, graph_types=None):
|
||||||
|
"""Updates existing object.
|
||||||
|
|
||||||
|
:param sequence_id: the sequence object id
|
||||||
|
:param name: new name
|
||||||
|
:param graph_types: new graph types
|
||||||
|
:returns: updated object or False if nothing to update
|
||||||
|
"""
|
||||||
|
data = {}
|
||||||
|
if name:
|
||||||
|
data['name'] = name
|
||||||
|
if graph_types:
|
||||||
|
graphs = data['graphs'] = []
|
||||||
|
for graph_type in graph_types:
|
||||||
|
graphs.append({'type': graph_type})
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
url = self._entity_wrapper.instance_api_path.format(sequence_id)
|
||||||
|
return self.connection.put_request(url, data)
|
||||||
|
|
||||||
|
def get_by_id(self, sequence_id):
|
||||||
|
"""Gets formatted sequence data by id."""
|
||||||
|
data = super(SequenceClient, self).get_by_id(sequence_id)
|
||||||
|
data['graphs'] = ', '.join(g['type'] for g in data['graphs'])
|
||||||
|
return data
|
||||||
|
|
||||||
|
def delete_by_id(self, sequence_id):
|
||||||
|
"""Deletes existed object.
|
||||||
|
|
||||||
|
:param sequence_id: the sequence object id
|
||||||
|
"""
|
||||||
|
url = self._entity_wrapper.instance_api_path.format(sequence_id)
|
||||||
|
self.connection.delete_request(url)
|
||||||
|
|
||||||
|
def execute(self, sequence_id, env_id, **kwargs):
|
||||||
|
"""Executes sequence on cluster.
|
||||||
|
|
||||||
|
:param sequence_id: the sequence object id
|
||||||
|
:param env_id: the cluster id
|
||||||
|
:param kwargs: options - force, dry_run and noop.
|
||||||
|
"""
|
||||||
|
data = {'cluster': env_id}
|
||||||
|
data.update(kwargs)
|
||||||
|
url = self.executor_path.format(sequence_id)
|
||||||
|
deploy_data = self.connection.post_request(url, data)
|
||||||
|
return objects.DeployTask.init_with_data(deploy_data)
|
||||||
|
|
||||||
|
|
||||||
|
def get_client(connection):
|
||||||
|
return SequenceClient(connection)
|
|
@ -110,6 +110,14 @@ fuelclient =
|
||||||
task_network-configuration_download=fuelclient.commands.task:TaskNetworkConfigurationDownload
|
task_network-configuration_download=fuelclient.commands.task:TaskNetworkConfigurationDownload
|
||||||
task_settings_download=fuelclient.commands.task:TaskClusterSettingsDownload
|
task_settings_download=fuelclient.commands.task:TaskClusterSettingsDownload
|
||||||
task_show=fuelclient.commands.task:TaskShow
|
task_show=fuelclient.commands.task:TaskShow
|
||||||
|
sequence_create=fuelclient.commands.sequence:SequenceCreate
|
||||||
|
sequence_delete=fuelclient.commands.sequence:SequenceDelete
|
||||||
|
sequence_download=fuelclient.commands.sequence:SequenceDownload
|
||||||
|
sequence_execute=fuelclient.commands.sequence:SequenceExecute
|
||||||
|
sequence_list=fuelclient.commands.sequence:SequenceList
|
||||||
|
sequence_show=fuelclient.commands.sequence:SequenceShow
|
||||||
|
sequence_update=fuelclient.commands.sequence:SequenceUpdate
|
||||||
|
sequence_upload=fuelclient.commands.sequence:SequenceUpload
|
||||||
snapshot_create=fuelclient.commands.snapshot:SnapshotGenerate
|
snapshot_create=fuelclient.commands.snapshot:SnapshotGenerate
|
||||||
snapshot_get-default-config=fuelclient.commands.snapshot:SnapshotConfigGetDefault
|
snapshot_get-default-config=fuelclient.commands.snapshot:SnapshotConfigGetDefault
|
||||||
snapshot_get-link=fuelclient.commands.snapshot:SnapshotGetLink
|
snapshot_get-link=fuelclient.commands.snapshot:SnapshotGetLink
|
||||||
|
|
Loading…
Reference in New Issue