Add suport for module maintenance commands
This adds support in the python API and Trove CLI for module maintenance commands. These commands include: - module-list - module-show - module-create - module-update - module-delete Partially Implements: blueprint module-management Change-Id: I54d37025275dee4731ad49ebbd21612c4464e4c4
This commit is contained in:
@@ -313,6 +313,7 @@ class Dbaas(object):
|
||||
from troveclient.v1 import limits
|
||||
from troveclient.v1 import management
|
||||
from troveclient.v1 import metadata
|
||||
from troveclient.v1 import modules
|
||||
from troveclient.v1 import quota
|
||||
from troveclient.v1 import root
|
||||
from troveclient.v1 import security_groups
|
||||
@@ -354,6 +355,7 @@ class Dbaas(object):
|
||||
config_parameters = configurations.ConfigurationParameters(self)
|
||||
self.configuration_parameters = config_parameters
|
||||
self.metadata = metadata.Metadata(self)
|
||||
self.modules = modules.Modules(self)
|
||||
self.mgmt_configs = management.MgmtConfigurationParameters(self)
|
||||
self.mgmt_datastore_versions = management.MgmtDatastoreVersions(self)
|
||||
|
||||
|
113
troveclient/tests/test_modules.py
Normal file
113
troveclient/tests/test_modules.py
Normal file
@@ -0,0 +1,113 @@
|
||||
# Copyright 2016 Tesora, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 testtools
|
||||
from troveclient.v1 import modules
|
||||
|
||||
|
||||
class TestModules(testtools.TestCase):
|
||||
def setUp(self):
|
||||
super(TestModules, self).setUp()
|
||||
|
||||
self.mod_init_patcher = mock.patch(
|
||||
'troveclient.v1.modules.Module.__init__',
|
||||
mock.Mock(return_value=None))
|
||||
self.addCleanup(self.mod_init_patcher.stop)
|
||||
self.mod_init_patcher.start()
|
||||
self.mods_init_patcher = mock.patch(
|
||||
'troveclient.v1.modules.Modules.__init__',
|
||||
mock.Mock(return_value=None))
|
||||
self.addCleanup(self.mods_init_patcher.stop)
|
||||
self.mods_init_patcher.start()
|
||||
|
||||
self.module_name = 'mod_1'
|
||||
self.module_id = 'mod-id'
|
||||
self.module = mock.Mock()
|
||||
self.module.id = self.module_id
|
||||
self.module.name = self.module_name
|
||||
|
||||
self.modules = modules.Modules()
|
||||
self.modules.api = mock.Mock()
|
||||
self.modules.api.client = mock.Mock()
|
||||
self.modules.resource_class = mock.Mock(return_value=self.module_name)
|
||||
|
||||
def tearDown(self):
|
||||
super(TestModules, self).tearDown()
|
||||
|
||||
def test_create(self):
|
||||
def side_effect_func(path, body, mod):
|
||||
return path, body, mod
|
||||
|
||||
self.modules._create = mock.Mock(side_effect=side_effect_func)
|
||||
path, body, mod = self.modules.create(
|
||||
self.module_name, "test", "my_contents",
|
||||
description="my desc",
|
||||
all_tenants=False,
|
||||
datastore="ds",
|
||||
datastore_version="ds-version",
|
||||
auto_apply=True,
|
||||
visible=True,
|
||||
live_update=False)
|
||||
self.assertEqual("/modules", path)
|
||||
self.assertEqual("module", mod)
|
||||
self.assertEqual(self.module_name, body["module"]["name"])
|
||||
self.assertEqual("ds", body["module"]["datastore"]["type"])
|
||||
self.assertEqual("ds-version", body["module"]["datastore"]["version"])
|
||||
self.assertFalse(body["module"]["all_tenants"])
|
||||
self.assertTrue(body["module"]["auto_apply"])
|
||||
self.assertTrue(body["module"]["visible"])
|
||||
self.assertFalse(body["module"]["live_update"])
|
||||
|
||||
def test_update(self):
|
||||
resp = mock.Mock()
|
||||
resp.status_code = 200
|
||||
body = {'module': None}
|
||||
self.modules.api.client.put = mock.Mock(return_value=(resp, body))
|
||||
self.modules.update(self.module_id)
|
||||
self.modules.update(self.module_id, name='new_name')
|
||||
self.modules.update(self.module)
|
||||
self.modules.update(self.module, name='new_name')
|
||||
resp.status_code = 500
|
||||
self.assertRaises(Exception, self.modules.update, self.module_name)
|
||||
|
||||
def test_list(self):
|
||||
page_mock = mock.Mock()
|
||||
self.modules._paginated = page_mock
|
||||
limit = "test-limit"
|
||||
marker = "test-marker"
|
||||
self.modules.list(limit, marker)
|
||||
page_mock.assert_called_with(
|
||||
"/modules", "modules", limit, marker, query_strings=None)
|
||||
|
||||
def test_get(self):
|
||||
def side_effect_func(path, inst):
|
||||
return path, inst
|
||||
|
||||
self.modules._get = mock.Mock(side_effect=side_effect_func)
|
||||
self.assertEqual(
|
||||
('/modules/%s' % self.module_name, 'module'),
|
||||
self.modules.get(self.module_name))
|
||||
|
||||
def test_delete(self):
|
||||
resp = mock.Mock()
|
||||
resp.status_code = 200
|
||||
body = None
|
||||
self.modules.api.client.delete = mock.Mock(return_value=(resp, body))
|
||||
self.modules.delete(self.module_name)
|
||||
self.modules.delete(self.module)
|
||||
resp.status_code = 500
|
||||
self.assertRaises(Exception, self.modules.delete, self.module_name)
|
@@ -25,6 +25,7 @@ from troveclient.v1 import instances
|
||||
from troveclient.v1 import limits
|
||||
# from troveclient.v1 import management
|
||||
from troveclient.v1 import metadata
|
||||
from troveclient.v1 import modules
|
||||
from troveclient.v1 import root
|
||||
from troveclient.v1 import security_groups
|
||||
from troveclient.v1 import users
|
||||
@@ -76,6 +77,7 @@ class Client(object):
|
||||
config_parameters = configurations.ConfigurationParameters(self)
|
||||
self.configuration_parameters = config_parameters
|
||||
self.metadata = metadata.Metadata(self)
|
||||
self.modules = modules.Modules(self)
|
||||
|
||||
# self.hosts = Hosts(self)
|
||||
# self.quota = Quotas(self)
|
||||
|
132
troveclient/v1/modules.py
Normal file
132
troveclient/v1/modules.py
Normal file
@@ -0,0 +1,132 @@
|
||||
# Copyright 2016 Tesora, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 base64
|
||||
|
||||
from troveclient import base
|
||||
from troveclient import common
|
||||
|
||||
|
||||
class Module(base.Resource):
|
||||
|
||||
NO_CHANGE_TO_ARG = 'no_change_to_argument'
|
||||
|
||||
def __repr__(self):
|
||||
return "<Module: %s>" % self.name
|
||||
|
||||
|
||||
class Modules(base.ManagerWithFind):
|
||||
"""Manage :class:`Module` resources."""
|
||||
resource_class = Module
|
||||
|
||||
def _encode_string(self, data_str):
|
||||
byte_array = bytearray(data_str, 'utf-8')
|
||||
return base64.b64encode(byte_array)
|
||||
|
||||
def create(self, name, module_type, contents, description=None,
|
||||
all_tenants=None, datastore=None,
|
||||
datastore_version=None, auto_apply=None,
|
||||
visible=None, live_update=None):
|
||||
"""Create a new module."""
|
||||
|
||||
contents = self._encode_string(contents)
|
||||
body = {"module": {
|
||||
"name": name,
|
||||
"module_type": module_type,
|
||||
"contents": contents,
|
||||
}}
|
||||
if description is not None:
|
||||
body["module"]["description"] = description
|
||||
datastore_obj = {}
|
||||
if datastore:
|
||||
datastore_obj["type"] = datastore
|
||||
if datastore_version:
|
||||
datastore_obj["version"] = datastore_version
|
||||
if datastore_obj:
|
||||
body["module"]["datastore"] = datastore_obj
|
||||
if all_tenants is not None:
|
||||
body["module"]["all_tenants"] = int(all_tenants)
|
||||
if auto_apply is not None:
|
||||
body["module"]["auto_apply"] = int(auto_apply)
|
||||
if visible is not None:
|
||||
body["module"]["visible"] = int(visible)
|
||||
if live_update is not None:
|
||||
body["module"]["live_update"] = int(live_update)
|
||||
|
||||
return self._create("/modules", body, "module")
|
||||
|
||||
def update(self, module, name=None, module_type=None,
|
||||
contents=None, description=None,
|
||||
all_tenants=None, datastore=Module.NO_CHANGE_TO_ARG,
|
||||
datastore_version=Module.NO_CHANGE_TO_ARG, auto_apply=None,
|
||||
visible=None, live_update=None):
|
||||
"""Update an existing module. Passing in
|
||||
datastore=None or datastore_version=None has the effect of
|
||||
making it available for all datastores/versions.
|
||||
"""
|
||||
body = {
|
||||
"module": {
|
||||
}
|
||||
}
|
||||
if name is not None:
|
||||
body["module"]["name"] = name
|
||||
if module_type is not None:
|
||||
body["module"]["type"] = module_type
|
||||
if contents is not None:
|
||||
contents = self._encode_string(contents)
|
||||
body["module"]["contents"] = contents
|
||||
if description is not None:
|
||||
body["module"]["description"] = description
|
||||
datastore_obj = {}
|
||||
if datastore is None or datastore != Module.NO_CHANGE_TO_ARG:
|
||||
datastore_obj["type"] = datastore
|
||||
if (datastore_version is None or
|
||||
datastore_version != Module.NO_CHANGE_TO_ARG):
|
||||
datastore_obj["version"] = datastore_version
|
||||
if datastore_obj:
|
||||
body["module"]["datastore"] = datastore_obj
|
||||
if all_tenants is not None:
|
||||
body["module"]["all_tenants"] = int(all_tenants)
|
||||
if auto_apply is not None:
|
||||
body["module"]["auto_apply"] = int(auto_apply)
|
||||
if visible is not None:
|
||||
body["module"]["visible"] = int(visible)
|
||||
if live_update is not None:
|
||||
body["module"]["live_update"] = int(live_update)
|
||||
|
||||
url = "/modules/%s" % base.getid(module)
|
||||
resp, body = self.api.client.put(url, body=body)
|
||||
common.check_for_exceptions(resp, body, url)
|
||||
return Module(self, body['module'], loaded=True)
|
||||
|
||||
def list(self, limit=None, marker=None, datastore=None):
|
||||
"""Get a list of all modules."""
|
||||
query_strings = None
|
||||
if datastore:
|
||||
query_strings = {"datastore": datastore}
|
||||
return self._paginated(
|
||||
"/modules", "modules", limit, marker, query_strings=query_strings)
|
||||
|
||||
def get(self, module):
|
||||
"""Get a specific module."""
|
||||
return self._get(
|
||||
"/modules/%s" % base.getid(module), "module")
|
||||
|
||||
def delete(self, module):
|
||||
"""Delete the specified module."""
|
||||
url = "/modules/%s" % base.getid(module)
|
||||
resp, body = self.api.client.delete(url)
|
||||
common.check_for_exceptions(resp, body, url)
|
@@ -16,6 +16,7 @@
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import time
|
||||
|
||||
@@ -138,6 +139,21 @@ def _find_backup(cs, backup):
|
||||
return utils.find_resource(cs.backups, backup)
|
||||
|
||||
|
||||
def _find_module(cs, module):
|
||||
"""Get a module by ID."""
|
||||
return utils.find_resource(cs.modules, module)
|
||||
|
||||
|
||||
def _find_datastore(cs, datastore):
|
||||
"""Get a datastore by ID."""
|
||||
return utils.find_resource(cs.datastores, datastore)
|
||||
|
||||
|
||||
def _find_datastore_version(cs, datastore_version):
|
||||
"""Get a datastore version by ID."""
|
||||
return utils.find_resource(cs.datastores, datastore_version)
|
||||
|
||||
|
||||
# Flavor related calls
|
||||
@utils.arg('--datastore_type', metavar='<datastore_type>',
|
||||
default=None,
|
||||
@@ -1395,6 +1411,155 @@ def do_metadata_delete(cs, args):
|
||||
cs.metadata.delete(args.instance_id, args.key)
|
||||
|
||||
|
||||
@utils.arg('--datastore', metavar='<datastore>',
|
||||
help='Name or ID of datastore to list modules for.')
|
||||
@utils.service_type('database')
|
||||
def do_module_list(cs, args):
|
||||
"""Lists the modules available."""
|
||||
datastore = None
|
||||
if args.datastore:
|
||||
datastore = _find_datastore(cs, args.datastore)
|
||||
module_list = cs.modules.list(datastore=datastore)
|
||||
utils.print_list(
|
||||
module_list,
|
||||
['id', 'tenant', 'name', 'type', 'datastore',
|
||||
'datastore_version', 'auto_apply', 'visible'],
|
||||
labels={'datastore_version': 'Version'})
|
||||
|
||||
|
||||
@utils.arg('module', metavar='<module>',
|
||||
help='ID or name of the module.')
|
||||
@utils.service_type('database')
|
||||
def do_module_show(cs, args):
|
||||
"""Shows details of a module."""
|
||||
module = _find_module(cs, args.module)
|
||||
_print_object(module)
|
||||
|
||||
|
||||
@utils.arg('name', metavar='<name>', type=str, help='Name of the module.')
|
||||
@utils.arg('type', metavar='<type>', type=str,
|
||||
help='Type of the module. The type must be supported by a '
|
||||
'corresponding module plugin on the datastore it is '
|
||||
'applied to.')
|
||||
@utils.arg('file', metavar='<filename>', type=argparse.FileType('rb', 0),
|
||||
help='File containing data contents for the module.')
|
||||
@utils.arg('--description', metavar='<description>', type=str,
|
||||
help='Description of the module.')
|
||||
@utils.arg('--datastore', metavar='<datastore>',
|
||||
help='Name or ID of datastore this module can be applied to. '
|
||||
'If not specified, module can be applied to all datastores.')
|
||||
@utils.arg('--datastore_version', metavar='<version>',
|
||||
help='Name or ID of datastore version this module can be applied '
|
||||
'to. If not specified, module can be applied to all versions.')
|
||||
@utils.arg('--auto_apply', action='store_true', default=False,
|
||||
help='Automatically apply this module when creating an instance '
|
||||
'or cluster.')
|
||||
@utils.arg('--all_tenants', action='store_true', default=False,
|
||||
help='Module is valid for all tenants (Admin only).')
|
||||
# This option is to suppress the module from module-list for non-admin
|
||||
@utils.arg('--hidden', action='store_true', default=False,
|
||||
help=argparse.SUPPRESS)
|
||||
@utils.arg('--live_update', action='store_true', default=False,
|
||||
help='Allow module to be updated even if it is already applied '
|
||||
'to a current instance or cluster. Automatically attempt to '
|
||||
'reapply this module if the contents change.')
|
||||
@utils.service_type('database')
|
||||
def do_module_create(cs, args):
|
||||
"""Create a module."""
|
||||
|
||||
contents = args.file.read()
|
||||
if not contents:
|
||||
raise exceptions.ValidationError(
|
||||
"The file '%s' must contain some data" % args.file)
|
||||
|
||||
module = cs.modules.create(
|
||||
args.name, args.type, contents, description=args.description,
|
||||
all_tenants=args.all_tenants, datastore=args.datastore,
|
||||
datastore_version=args.datastore_version,
|
||||
auto_apply=args.auto_apply, visible=not args.hidden,
|
||||
live_update=args.live_update)
|
||||
_print_object(module)
|
||||
|
||||
|
||||
@utils.arg('module', metavar='<module>', type=str,
|
||||
help='Name or ID of the module.')
|
||||
@utils.arg('--name', metavar='<name>', type=str, default=None,
|
||||
help='Name of the module.')
|
||||
@utils.arg('--type', metavar='<type>', type=str, default=None,
|
||||
help='Type of the module. The type must be supported by a '
|
||||
'corresponding module plugin on the datastore it is '
|
||||
'applied to.')
|
||||
@utils.arg('--file', metavar='<filename>', type=argparse.FileType('rb', 0),
|
||||
default=None,
|
||||
help='File containing data contents for the module.')
|
||||
@utils.arg('--description', metavar='<description>', type=str, default=None,
|
||||
help='Description of the module.')
|
||||
@utils.arg('--datastore', metavar='<datastore>',
|
||||
help='Name or ID of datastore this module can be applied to. '
|
||||
'If not specified, module can be applied to all datastores.')
|
||||
@utils.arg('--all_datastores', dest='datastore', action='store_const',
|
||||
const=None,
|
||||
help='Module is valid for all datastores.')
|
||||
@utils.arg('--datastore_version', metavar='<version>',
|
||||
help='Name or ID of datastore version this module can be applied '
|
||||
'to. If not specified, module can be applied to all versions.')
|
||||
@utils.arg('--all_datastore_versions', dest='datastore_version',
|
||||
action='store_const', const=None,
|
||||
help='Module is valid for all datastore version.')
|
||||
@utils.arg('--auto_apply', action='store_true', default=None,
|
||||
help='Automatically apply this module when creating an instance '
|
||||
'or cluster.')
|
||||
@utils.arg('--no_auto_apply', dest='auto_apply', action='store_false',
|
||||
default=None,
|
||||
help='Do not automatically apply this module when creating an '
|
||||
'instance or cluster.')
|
||||
@utils.arg('--all_tenants', action='store_true', default=None,
|
||||
help='Module is valid for all tenants (Admin only).')
|
||||
@utils.arg('--no_all_tenants', dest='all_tenants', action='store_false',
|
||||
default=None,
|
||||
help='Module is valid for current tenant only (Admin only).')
|
||||
# This option is to suppress the module from module-list for non-admin
|
||||
@utils.arg('--hidden', action='store_true', default=None,
|
||||
help=argparse.SUPPRESS)
|
||||
# This option is to allow the module to be seen from module-list for non-admin
|
||||
@utils.arg('--no_hidden', dest='hidden', action='store_false', default=None,
|
||||
help=argparse.SUPPRESS)
|
||||
@utils.arg('--live_update', action='store_true', default=None,
|
||||
help='Allow module to be updated or deleted even if it is already '
|
||||
'applied to a current instance or cluster. Automatically '
|
||||
'attempt to reapply this module if the contents change.')
|
||||
@utils.arg('--no_live_update', dest='live_update', action='store_false',
|
||||
default=None,
|
||||
help='Restricts a module from being updated or deleted if it is '
|
||||
'already applied to a current instance or cluster.')
|
||||
@utils.service_type('database')
|
||||
def do_module_update(cs, args):
|
||||
"""Create a module."""
|
||||
module = _find_module(cs, args.module)
|
||||
contents = args.file.read() if args.file else None
|
||||
visible = not args.hidden if args.hidden is not None else None
|
||||
datastore_args = {}
|
||||
if args.datastore:
|
||||
datastore_args['datastore'] = args.datastore
|
||||
if args.datastore_version:
|
||||
datastore_args['datastore_version'] = args.datastore_version
|
||||
updated_module = cs.modules.update(
|
||||
module, name=args.name, module_type=args.type, contents=contents,
|
||||
description=args.description, all_tenants=args.all_tenants,
|
||||
auto_apply=args.auto_apply, visible=visible,
|
||||
live_update=args.live_update, **datastore_args)
|
||||
_print_object(updated_module)
|
||||
|
||||
|
||||
@utils.arg('module', metavar='<module>',
|
||||
help='ID or name of the module.')
|
||||
@utils.service_type('database')
|
||||
def do_module_delete(cs, args):
|
||||
"""Delete a module."""
|
||||
module = _find_module(cs, args.module)
|
||||
cs.modules.delete(module)
|
||||
|
||||
|
||||
@utils.arg('instance', metavar='<instance>',
|
||||
help='Id or Name of the instance.')
|
||||
@utils.service_type('database')
|
||||
|
Reference in New Issue
Block a user