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:
Peter Stachowski
2016-02-23 15:11:33 -05:00
parent 634dd615e6
commit cf8fee5fa6
5 changed files with 414 additions and 0 deletions

View File

@@ -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)

View 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)

View File

@@ -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
View 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)

View File

@@ -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')