Implement CLI v1 for openstack configuration
Add openstack-config commands for fuel Examples for fuel: fuel openstack-config --env 1 --list fuel openstack-config --env 1 [--node 1 | --role controller] --upload --file config.yaml fuel openstack-config --config 1 --download --file config.yaml fuel openstack-config --env 1 [--node 1 | --role controller] --execute Change-Id: Id701d4b8408bd275ac6e7bc53ca23b9f3deb4f6d Implements: blueprint openstack-config-change
This commit is contained in:
parent
4f9a873b1a
commit
88274ba134
|
@ -32,6 +32,7 @@ from fuelclient.cli.actions.node import NodeAction
|
|||
from fuelclient.cli.actions.nodegroup import NodeGroupAction
|
||||
from fuelclient.cli.actions.notifications import NotificationsAction
|
||||
from fuelclient.cli.actions.notifications import NotifyAction
|
||||
from fuelclient.cli.actions.openstack_config import OpenstackConfigAction
|
||||
from fuelclient.cli.actions.release import ReleaseAction
|
||||
from fuelclient.cli.actions.role import RoleAction
|
||||
from fuelclient.cli.actions.settings import SettingsAction
|
||||
|
@ -68,6 +69,7 @@ actions_tuple = (
|
|||
GraphAction,
|
||||
FuelVersionAction,
|
||||
NetworkGroupAction,
|
||||
OpenstackConfigAction,
|
||||
)
|
||||
|
||||
actions = dict(
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
# 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.cli.actions.base import Action
|
||||
from fuelclient.cli.actions.base import check_all
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.arguments import group
|
||||
from fuelclient.cli.formatting import format_table
|
||||
from fuelclient.objects.openstack_config import OpenstackConfig
|
||||
|
||||
|
||||
class OpenstackConfigAction(Action):
|
||||
"""Manage openstack configuration"""
|
||||
|
||||
action_name = 'openstack-config'
|
||||
acceptable_keys = ('id', 'is_active', 'config_type',
|
||||
'cluster_id', 'node_id', 'node_role')
|
||||
|
||||
def __init__(self):
|
||||
super(OpenstackConfigAction, self).__init__()
|
||||
self.args = (
|
||||
Args.get_env_arg(),
|
||||
Args.get_file_arg("Openstack configuration file"),
|
||||
Args.get_single_node_arg("Node ID"),
|
||||
Args.get_single_role_arg("Node role"),
|
||||
Args.get_config_id_arg("Openstack config ID"),
|
||||
Args.get_deleted_arg("Get deleted configurations"),
|
||||
group(
|
||||
Args.get_list_arg("List openstack configurations"),
|
||||
Args.get_download_arg(
|
||||
"Download current openstack configuration"),
|
||||
Args.get_upload_arg("Upload new openstack configuration"),
|
||||
Args.get_delete_arg("Delete openstack configuration"),
|
||||
Args.get_execute_arg("Apply openstack configuration"),
|
||||
required=True,
|
||||
)
|
||||
)
|
||||
|
||||
self.flag_func_map = (
|
||||
('list', self.list),
|
||||
('download', self.download),
|
||||
('upload', self.upload),
|
||||
('delete', self.delete),
|
||||
('execute', self.execute)
|
||||
)
|
||||
|
||||
def list(self, params):
|
||||
"""List all available configurations:
|
||||
fuel openstack-config --list --env 1
|
||||
fuel openstack-config --list --env 1 --node 1
|
||||
fuel openstack-config --list --env 1 --deleted
|
||||
"""
|
||||
filters = {}
|
||||
|
||||
if 'env' in params:
|
||||
filters['cluster_id'] = params.env
|
||||
|
||||
if 'deleted' in params:
|
||||
filters['is_active'] = int(not params.deleted)
|
||||
|
||||
if 'node' in params:
|
||||
filters['node_id'] = params.node
|
||||
|
||||
if 'role' in params:
|
||||
filters['node_role'] = params.role
|
||||
|
||||
configs = OpenstackConfig.get_filtered_data(**filters)
|
||||
|
||||
self.serializer.print_to_output(
|
||||
configs,
|
||||
format_table(
|
||||
configs,
|
||||
acceptable_keys=self.acceptable_keys
|
||||
)
|
||||
)
|
||||
|
||||
@check_all('config-id')
|
||||
def download(self, params):
|
||||
"""Download an existing configuration to file:
|
||||
fuel openstack-config --download --config-id 1 --file config.yaml
|
||||
"""
|
||||
config_id = getattr(params, 'config-id')
|
||||
config = OpenstackConfig(config_id)
|
||||
data = config.data
|
||||
OpenstackConfig.write_file(params.file, {
|
||||
'configuration': data['configuration']})
|
||||
|
||||
@check_all('env')
|
||||
def upload(self, params):
|
||||
"""Upload new configuration from file:
|
||||
fuel openstack-config --upload --env 1 --file config.yaml
|
||||
fuel openstack-config --upload --env 1 --node 1 --file config.yaml
|
||||
fuel openstack-config --upload --env 1
|
||||
--role controller --file config.yaml
|
||||
"""
|
||||
node_id = getattr(params, 'node', None)
|
||||
node_role = getattr(params, 'role', None)
|
||||
data = OpenstackConfig.read_file(params.file)
|
||||
|
||||
config = OpenstackConfig.create(
|
||||
cluster_id=params.env,
|
||||
configuration=data['configuration'],
|
||||
node_id=node_id, node_role=node_role)
|
||||
print("Openstack configuration with id {0} "
|
||||
"has been uploaded from file '{1}'"
|
||||
"".format(config.id, params.file))
|
||||
|
||||
@check_all('config-id')
|
||||
def delete(self, params):
|
||||
"""Delete an existing configuration:
|
||||
fuel openstack-config --delete --config 1
|
||||
"""
|
||||
config_id = getattr(params, 'config-id')
|
||||
config = OpenstackConfig(config_id)
|
||||
config.delete()
|
||||
print("Openstack configuration '{0}' "
|
||||
"has been deleted.".format(config_id))
|
||||
|
||||
@check_all('env')
|
||||
def execute(self, params):
|
||||
"""Deploy configuration:
|
||||
fuel openstack-config --execute --env 1
|
||||
fuel openstack-config --execute --env 1 --node 1
|
||||
fuel openstack-config --execute --env 1 --role controller
|
||||
"""
|
||||
node_id = getattr(params, 'node', None)
|
||||
node_role = getattr(params, 'role', None)
|
||||
task_result = OpenstackConfig.execute(
|
||||
cluster_id=params.env, node_id=node_id,
|
||||
node_role=node_role)
|
||||
if task_result['status'] == 'error':
|
||||
print(
|
||||
'Error applying openstack configuration: {0}.'.format(
|
||||
task_result['message'])
|
||||
)
|
||||
else:
|
||||
print('Openstack configuration update is started.')
|
|
@ -284,6 +284,10 @@ def get_role_arg(help_msg):
|
|||
return get_set_type_arg("role", flags=("-r",), help=help_msg)
|
||||
|
||||
|
||||
def get_single_role_arg(help_msg):
|
||||
return get_str_arg("role", flags=('--role', ), help=help_msg)
|
||||
|
||||
|
||||
def get_check_arg(help_msg):
|
||||
return get_set_type_arg("check", help=help_msg)
|
||||
|
||||
|
@ -415,6 +419,10 @@ def get_delete_arg(help_msg):
|
|||
return get_boolean_arg("delete", help=help_msg)
|
||||
|
||||
|
||||
def get_execute_arg(help_msg):
|
||||
return get_boolean_arg("execute", help=help_msg)
|
||||
|
||||
|
||||
def get_assign_arg(help_msg):
|
||||
return get_boolean_arg("assign", help=help_msg)
|
||||
|
||||
|
@ -486,6 +494,10 @@ def get_node_arg(help_msg):
|
|||
return get_arg("node", **default_kwargs)
|
||||
|
||||
|
||||
def get_single_node_arg(help_msg):
|
||||
return get_int_arg('node', flags=('--node-id',), help=help_msg)
|
||||
|
||||
|
||||
def get_task_arg(help_msg):
|
||||
return get_array_arg(
|
||||
'task',
|
||||
|
@ -494,6 +506,17 @@ def get_task_arg(help_msg):
|
|||
)
|
||||
|
||||
|
||||
def get_config_id_arg(help_msg):
|
||||
return get_int_arg(
|
||||
'config-id',
|
||||
help=help_msg)
|
||||
|
||||
|
||||
def get_deleted_arg(help_msg):
|
||||
return get_boolean_arg(
|
||||
'deleted', help=help_msg)
|
||||
|
||||
|
||||
def get_plugin_install_arg(help_msg):
|
||||
return get_str_arg(
|
||||
"install",
|
||||
|
|
|
@ -88,14 +88,17 @@ class Serializer(object):
|
|||
|
||||
def write_to_path(self, path, data):
|
||||
full_path = self.prepare_path(path)
|
||||
return self.write_to_full_path(full_path, data)
|
||||
|
||||
def write_to_full_path(self, path, data):
|
||||
try:
|
||||
with open(full_path, "w") as file_to_write:
|
||||
with open(path, "w") as file_to_write:
|
||||
self.write_to_file(file_to_write, data)
|
||||
except IOError as e:
|
||||
raise error.InvalidFileException(
|
||||
"Can't write to file '{0}': {1}.".format(
|
||||
full_path, e.strerror))
|
||||
return full_path
|
||||
path, e.strerror))
|
||||
return path
|
||||
|
||||
def read_from_file(self, path):
|
||||
return self.read_from_full_path(self.prepare_path(path))
|
||||
|
|
|
@ -20,6 +20,7 @@ from fuelclient.objects.base import BaseObject
|
|||
from fuelclient.objects.environment import Environment
|
||||
from fuelclient.objects.node import Node
|
||||
from fuelclient.objects.node import NodeCollection
|
||||
from fuelclient.objects.openstack_config import OpenstackConfig
|
||||
from fuelclient.objects.release import Release
|
||||
from fuelclient.objects.task import DeployTask
|
||||
from fuelclient.objects.task import SnapshotTask
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
# 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.
|
||||
|
||||
import os
|
||||
|
||||
import six
|
||||
|
||||
from fuelclient.cli import error
|
||||
from fuelclient.cli.serializers import Serializer
|
||||
from fuelclient.objects.base import BaseObject
|
||||
|
||||
|
||||
class OpenstackConfig(BaseObject):
|
||||
|
||||
class_api_path = 'openstack-config/'
|
||||
instance_api_path = 'openstack-config/{0}/'
|
||||
execute_api_path = 'openstack-config/execute/'
|
||||
|
||||
@classmethod
|
||||
def _prepare_params(cls, filters):
|
||||
return dict((k, v) for k, v in six.iteritems(filters) if v is not None)
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
params = cls._prepare_params(kwargs)
|
||||
data = cls.connection.post_request(cls.class_api_path, params)
|
||||
return cls.init_with_data(data)
|
||||
|
||||
def delete(self):
|
||||
return self.connection.delete_request(
|
||||
self.instance_api_path.format(self.id))
|
||||
|
||||
@classmethod
|
||||
def execute(cls, **kwargs):
|
||||
params = cls._prepare_params(kwargs)
|
||||
return cls.connection.put_request(cls.execute_api_path, params)
|
||||
|
||||
@classmethod
|
||||
def get_filtered_data(cls, **kwargs):
|
||||
url = cls.class_api_path
|
||||
params = cls._prepare_params(kwargs)
|
||||
return cls.connection.get_request(url, params=params)
|
||||
|
||||
@classmethod
|
||||
def read_file(cls, path):
|
||||
if not os.path.exists(path):
|
||||
raise error.InvalidFileException(
|
||||
"File '{0}' doesn't exist.".format(path))
|
||||
|
||||
serializer = Serializer()
|
||||
return serializer.read_from_full_path(path)
|
||||
|
||||
@classmethod
|
||||
def write_file(cls, path, data):
|
||||
serializer = Serializer()
|
||||
return serializer.write_to_full_path(path, data)
|
|
@ -0,0 +1,105 @@
|
|||
# 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.
|
||||
|
||||
import mock
|
||||
import yaml
|
||||
|
||||
from fuelclient.tests.unit.v1 import base
|
||||
from fuelclient.tests import utils
|
||||
|
||||
|
||||
class TestOpenstackConfigActions(base.UnitTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOpenstackConfigActions, self).setUp()
|
||||
|
||||
self.config = utils.get_fake_openstack_config()
|
||||
|
||||
def test_config_download(self):
|
||||
m_get = self.m_request.get(
|
||||
'/api/v1/openstack-config/42/', json=self.config)
|
||||
m_open = mock.mock_open()
|
||||
with mock.patch('fuelclient.cli.serializers.open',
|
||||
m_open, create=True):
|
||||
self.execute(['fuel', 'openstack-config',
|
||||
'--config-id', '42', '--download',
|
||||
'--file', 'config.yaml'])
|
||||
|
||||
self.assertTrue(m_get.called)
|
||||
content = m_open().write.mock_calls[0][1][0]
|
||||
content = yaml.safe_load(content)
|
||||
self.assertEqual(self.config['configuration'],
|
||||
content['configuration'])
|
||||
|
||||
def test_config_upload(self):
|
||||
m_post = self.m_request.post(
|
||||
'/api/v1/openstack-config/', json=self.config)
|
||||
m_open = mock.mock_open(read_data=yaml.safe_dump(
|
||||
{'configuration': self.config['configuration']}))
|
||||
with mock.patch('fuelclient.cli.serializers.open',
|
||||
m_open, create=True):
|
||||
with mock.patch('fuelclient.objects.openstack_config.os'):
|
||||
self.execute(['fuel', 'openstack-config', '--env', '1',
|
||||
'--upload', '--file', 'config.yaml'])
|
||||
self.assertTrue(m_post.called)
|
||||
|
||||
def test_config_list(self):
|
||||
m_get = self.m_request.get(
|
||||
'/api/v1/openstack-config/?cluster_id=84', json=[
|
||||
utils.get_fake_openstack_config(id=1, cluster_id=32),
|
||||
utils.get_fake_openstack_config(id=2, cluster_id=64)
|
||||
])
|
||||
self.execute(['fuel', 'openstack-config', '--env', '84', '--list'])
|
||||
self.assertTrue(m_get.called)
|
||||
|
||||
def test_config_list_w_filters(self):
|
||||
m_get = self.m_request.get(
|
||||
'/api/v1/openstack-config/?cluster_id=84&node_role=controller',
|
||||
json=[utils.get_fake_openstack_config(id=1, cluster_id=32)])
|
||||
self.execute(['fuel', 'openstack-config', '--env', '84',
|
||||
'--role', 'controller', '--list'])
|
||||
self.assertTrue(m_get.called)
|
||||
|
||||
m_get = self.m_request.get(
|
||||
'/api/v1/openstack-config/?cluster_id=84&node_id=42', json=[
|
||||
utils.get_fake_openstack_config(id=1, cluster_id=32),
|
||||
])
|
||||
self.execute(['fuel', 'openstack-config', '--env', '84',
|
||||
'--node', '42', '--list'])
|
||||
self.assertTrue(m_get.called)
|
||||
|
||||
def test_config_delete(self):
|
||||
m_del = self.m_request.delete(
|
||||
'/api/v1/openstack-config/42/', json={})
|
||||
self.execute(['fuel', 'openstack-config',
|
||||
'--config-id', '42', '--delete'])
|
||||
self.assertTrue(m_del.called)
|
||||
|
||||
def test_config_execute(self):
|
||||
m_put = self.m_request.put('/api/v1/openstack-config/execute/',
|
||||
json={'status': 'ready'})
|
||||
self.execute(['fuel', 'openstack-config', '--env', '42', '--execute'])
|
||||
self.assertTrue(m_put.called)
|
||||
|
||||
def test_config_execute_fail(self):
|
||||
message = 'Some error'
|
||||
m_put = self.m_request.put(
|
||||
'/api/v1/openstack-config/execute/',
|
||||
json={'status': 'error', 'message': message})
|
||||
|
||||
with mock.patch("sys.stdout") as m_stdout:
|
||||
self.execute(['fuel', 'openstack-config',
|
||||
'--env', '42', '--execute'])
|
||||
self.assertTrue(m_put.called)
|
||||
self.assertIn(message, m_stdout.write.call_args_list[0][0][0])
|
|
@ -24,6 +24,8 @@ from fuelclient.tests.utils.fake_fuel_version import get_fake_fuel_version
|
|||
from fuelclient.tests.utils.fake_task import get_fake_task
|
||||
from fuelclient.tests.utils.fake_node_group import get_fake_node_group
|
||||
from fuelclient.tests.utils.fake_node_group import get_fake_node_groups
|
||||
from fuelclient.tests.utils.fake_openstack_config \
|
||||
import get_fake_openstack_config
|
||||
|
||||
|
||||
__all__ = (get_fake_env,
|
||||
|
@ -35,4 +37,5 @@ __all__ = (get_fake_env,
|
|||
get_fake_task,
|
||||
random_string,
|
||||
get_fake_node_group,
|
||||
get_fake_node_groups)
|
||||
get_fake_node_groups,
|
||||
get_fake_openstack_config)
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# 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.
|
||||
|
||||
|
||||
def get_fake_openstack_config(
|
||||
id=None, config_type=None, cluster_id=None, node_id=None,
|
||||
node_role=None, configuration=None):
|
||||
config = {
|
||||
'id': id or 42,
|
||||
'is_active': True,
|
||||
'config_type': config_type or 'cluster',
|
||||
'cluster_id': cluster_id or 84,
|
||||
'node_id': node_id or None,
|
||||
'node_role': node_role or None,
|
||||
'configuration': configuration or {
|
||||
'nova_config': {
|
||||
'DEFAULT/debug': {
|
||||
'value': True,
|
||||
},
|
||||
},
|
||||
'keystone_config': {
|
||||
'DEFAULT/debug': {
|
||||
'value': True,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return config
|
Loading…
Reference in New Issue