From 51795b8e50bb4055d857a28fe5b91b6762e0a0f6 Mon Sep 17 00:00:00 2001 From: David Daeschler Date: Mon, 27 Jul 2015 13:20:25 -0500 Subject: [PATCH 1/3] Add Rackspace HA database support to pyrax --- pyrax/clouddatabases.py | 131 ++++++++++++++++++++++++++--- tests/unit/test_cloud_databases.py | 26 ++++++ 2 files changed, 147 insertions(+), 10 deletions(-) diff --git a/pyrax/clouddatabases.py b/pyrax/clouddatabases.py index b2c0476a..6ebd88f9 100644 --- a/pyrax/clouddatabases.py +++ b/pyrax/clouddatabases.py @@ -25,6 +25,7 @@ from pyrax.manager import BaseManager from pyrax.resource import BaseResource import pyrax.utils as utils +from six.moves import urllib def assure_instance(fnc): @@ -36,6 +37,19 @@ def _wrapped(self, instance, *args, **kwargs): return fnc(self, instance, *args, **kwargs) return _wrapped +def check_type_and_version(body, type, version): + """ + Makes sure that for database creation statements the database + type and version are specified and then inserts them into the + datastore parameter + """ + if type is not None or version is not None: + required = (type, version) + if all(required): + body['datastore'] = {"type": type, "version": version} + else: + raise exc.MissingCloudDatabaseParameter("Specifying a datastore" + " requires both the datastore type as well as the version.") class CloudDatabaseVolume(object): @@ -64,7 +78,6 @@ def get(self, att): return getattr(self, att) - class CloudDatabaseManager(BaseManager): """ This class manages communication with Cloud Database instances. @@ -103,15 +116,9 @@ def _create_body(self, name, flavor=None, volume=None, databases=None, "users": users, }} - if type is not None or version is not None: - required = (type, version) - if all(required): - body['instance']['datastore'] = {"type": type, "version": version} - else: - raise exc.MissingCloudDatabaseParameter("Specifying a datastore" - " requires both the datastore type as well as the version.") - return body + check_type_and_version(body['instance'], type, version) + return body def create_backup(self, instance, name, description=None): """ @@ -337,6 +344,94 @@ def list(self, instance=None, limit=20, marker=0): return self.api._manager._list_backups_for_instance(instance, limit=limit, marker=marker) +class CloudDatabaseHAInstanceACL(BaseResource): + """ + This class represents an HA MySQL ACL + """ + def __init__(self, *args, **kwargs): + super(CloudDatabaseHAInstanceACL, self).__init__(*args, **kwargs) + + def delete(self): + """This class doesn't have an 'id', so pass the address.""" + self.manager.delete(urllib.parse.quote(self.address, safe='')) + + +class CloudDatabaseHAInstanceACLManager(BaseManager): + """ + This class manages ACLs for HA instances + """ + def _create_body(self, name, address): + body = {"address": address} + + return body + + +class CloudDatabaseHAInstance(BaseResource): + """ + This class represents an HA MySQL instance in the cloud. + """ + def __init__(self, *args, **kwargs): + super(CloudDatabaseHAInstance, self).__init__(*args, **kwargs) + + self._acl_manager = CloudDatabaseHAInstanceACLManager(self.manager.api, + resource_class=CloudDatabaseHAInstanceACL, + response_key="acl", + uri_base="ha/%s/acls" % self.id) + + def add_acl(self, address): + self._acl_manager.create(None, address, return_none=True) + + def list_acls(self): + return self._acl_manager.list() + + +class CloudDatabaseSpec(object): + """ + This class holds information on the type of cloud database to be created + during an HA create operation + """ + def __init__(self, name, volume, flavor): + self.name = name + self.volume = volume + self.flavor = flavor + + +class CloudDatabaseHAManager(BaseManager): + def _create_body(self, name, type, version, replica_source, replicas): + """ + Creates a new highly available database instance + """ + source_flavor_ref = self.api._get_flavor_ref(replica_source.flavor) + + body = {"ha": { + "name": name, + "replica_source": [ + { + "volume": { + "size": replica_source.volume + }, + "flavorRef": source_flavor_ref, + "name": replica_source.name + } + ], + "replicas": [] + }} + + for replica in replicas: + replica_flavor_ref = self.api._get_flavor_ref(replica.flavor) + body['ha']['replicas'].append( + { + "volume": { + "size": replica.volume + }, + "flavorRef": replica_flavor_ref, + "name": replica.name + } + ) + + check_type_and_version(body['ha'], type, version) + + return body class CloudDatabaseInstance(BaseResource): @@ -672,7 +767,6 @@ class CloudDatabaseBackup(BaseResource): _non_display = ["locationRef"] - class CloudDatabaseClient(BaseClient): """ This is the primary class for interacting with Cloud Databases. @@ -693,6 +787,9 @@ def _configure_manager(self): self._backup_manager = CloudDatabaseBackupManager(self, resource_class=CloudDatabaseBackup, response_key="backup", uri_base="backups") + self._ha_manager = CloudDatabaseHAManager(self, + resource_class=CloudDatabaseHAInstance, response_key="ha_instance", + uri_base="ha") @assure_instance @@ -925,3 +1022,17 @@ def restore_backup(self, backup, name, flavor, volume): instance, as well as a flavor and size (in GB) for the instance. """ return self._manager.restore_backup(backup, name, flavor, volume) + + def create_ha(self, name, type, version, replica_source, replicas): + """ + Creates a new highly available database instance. The name of the HA + instance, the database type and version, a replica source, and a + list of replicas must be specified. + """ + return self._ha_manager.create(name, type, version, replica_source, replicas) + + def list_ha(self): + """ + Lists all high availability instances + """ + return self._ha_manager.list() diff --git a/tests/unit/test_cloud_databases.py b/tests/unit/test_cloud_databases.py index 65118aeb..9d56a302 100644 --- a/tests/unit/test_cloud_databases.py +++ b/tests/unit/test_cloud_databases.py @@ -13,6 +13,7 @@ from pyrax.clouddatabases import CloudDatabaseUser from pyrax.clouddatabases import CloudDatabaseVolume from pyrax.clouddatabases import assure_instance +from pyrax.clouddatabases import CloudDatabaseSpec import pyrax.exceptions as exc from pyrax.resource import BaseResource import pyrax.utils as utils @@ -921,6 +922,31 @@ def test_create_body_datastore(self): "datastore": {"type": "MariaDB", "version": "10"}}} self.assertEqual(ret, expected) + @patch("pyrax.manager.BaseManager", new=fakes.FakeManager) + def test_list_ha(self): + clt = self.client + clt._ha_manager.list = Mock() + limit = utils.random_unicode() + marker = utils.random_unicode() + clt.list_ha() + clt._ha_manager.list.assert_called_once() + + @patch("pyrax.manager.BaseManager", new=fakes.FakeManager) + def test_create_ha(self): + clt = self.client + clt._ha_manager.create = Mock() + + replicasrc = CloudDatabaseSpec(name="src", volume=1, flavor=2) + replica1 = CloudDatabaseSpec(name="rep", volume=1, flavor=2) + replicas = [replica1] + + #name, type, version, replica_source, replicas + + db = clt.create_ha(name="test", type="mysql", version="1.0", + replica_source=replicasrc, replicas=replicas) + + clt._ha_manager.create.assert_called_once_with("test", + "mysql", "1.0", replicasrc, replicas) if __name__ == "__main__": unittest.main() From 0f64144e9eb65f5252238a610d8b03ee2e45a79c Mon Sep 17 00:00:00 2001 From: David Daeschler Date: Mon, 27 Jul 2015 13:39:48 -0500 Subject: [PATCH 2/3] Shorten line to pass ci checks --- pyrax/clouddatabases.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyrax/clouddatabases.py b/pyrax/clouddatabases.py index 6ebd88f9..fb6d889f 100644 --- a/pyrax/clouddatabases.py +++ b/pyrax/clouddatabases.py @@ -1029,7 +1029,8 @@ def create_ha(self, name, type, version, replica_source, replicas): instance, the database type and version, a replica source, and a list of replicas must be specified. """ - return self._ha_manager.create(name, type, version, replica_source, replicas) + return self._ha_manager.create(name, type, version, replica_source, + replicas) def list_ha(self): """ From a261a7915f3e31efd484d976987b18f33933e3fb Mon Sep 17 00:00:00 2001 From: David Daeschler Date: Mon, 27 Jul 2015 14:02:34 -0500 Subject: [PATCH 3/3] Fix assert_called_once to pass on CI's Mock --- tests/unit/test_cloud_databases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_cloud_databases.py b/tests/unit/test_cloud_databases.py index 9d56a302..f389600d 100644 --- a/tests/unit/test_cloud_databases.py +++ b/tests/unit/test_cloud_databases.py @@ -929,7 +929,7 @@ def test_list_ha(self): limit = utils.random_unicode() marker = utils.random_unicode() clt.list_ha() - clt._ha_manager.list.assert_called_once() + clt._ha_manager.list.assert_called_once_with() @patch("pyrax.manager.BaseManager", new=fakes.FakeManager) def test_create_ha(self):