Repository: cloud-custodian/cloud-custodian

Issue:
Azure - SQL - Action to resize SKU



Discussion:

Resolving patch:
diff --git a/tools/c7n_azure/c7n_azure/resources/sqldatabase.py b/tools/c7n_azure/c7n_azure/resources/sqldatabase.py
--- a/tools/c7n_azure/c7n_azure/resources/sqldatabase.py
+++ b/tools/c7n_azure/c7n_azure/resources/sqldatabase.py
@@ -17,7 +17,7 @@
 import logging
 
 import six
-from azure.mgmt.sql.models import BackupLongTermRetentionPolicy
+from azure.mgmt.sql.models import BackupLongTermRetentionPolicy, DatabaseUpdate, Sku
 from msrestazure.azure_exceptions import CloudError
 
 from c7n.filters import Filter
@@ -473,3 +473,59 @@ def _copy_retention_policy(self, retention_policy):
         new_retention_policy[BackupRetentionPolicyHelper.WEEK_OF_YEAR] = \
             retention_policy[BackupRetentionPolicyHelper.WEEK_OF_YEAR]
         return new_retention_policy
+
+
+@SqlDatabase.action_registry.register('resize')
+class Resize(AzureBaseAction):
+    """
+    Action to scale database.
+    Required arguments: capacity in DTUs and tier (Basic, Standard or Premium).
+    Max data size (in bytes) is optional.
+
+    :example:
+    This policy will resize database to Premium tier with 500 DTU and set max data size to 750 GB
+
+    .. code-block:: yaml
+
+        policies:
+          - name: resize-db
+            resource: azure.sqldatabase
+            filters:
+              - type: value
+                key: name
+                value: cctestdb
+            actions:
+              - type: resize
+                tier: Premium
+                capacity: 500
+                max_size_bytes: 805306368000
+
+    """
+
+    schema = type_schema(
+        'resize',
+        required=['capacity', 'tier'],
+        **{
+            'capacity': {'type': 'number'},
+            'tier': {'enum': ['Basic', 'Standard', 'Premium']},
+            'max_size_bytes': {'type': 'number'}
+        })
+
+    def __init__(self, data, manager=None):
+        super(Resize, self).__init__(data, manager)
+        self.capacity = self.data['capacity']
+        self.tier = self.data['tier']
+        self.max_size_bytes = self.data.get('max_size_bytes', 0)
+
+    def _prepare_processing(self):
+        self.client = self.manager.get_client()
+
+    def _process_resource(self, database):
+        sku = Sku(capacity=self.capacity, tier=self.tier, name=self.tier)
+        max_size_bytes = self.max_size_bytes if not 0 else database['properties']['maxSizeBytes']
+        self.client.databases.update(
+            database['resourceGroup'],
+            ResourceIdParser.get_resource_name(database['c7n:parent-id']),
+            database['name'],
+            DatabaseUpdate(sku=sku, max_size_bytes=max_size_bytes)
+        )


Test patch:
diff --git a/tools/c7n_azure/tests/cassettes/SqlDatabaseTest.test_resize_action.json b/tools/c7n_azure/tests/cassettes/SqlDatabaseTest.test_resize_action.json
new file mode 100644
--- /dev/null
+++ b/tools/c7n_azure/tests/cassettes/SqlDatabaseTest.test_resize_action.json
@@ -0,0 +1,330 @@
+{
+    "version": 1,
+    "interactions": [
+        {
+            "request": {
+                "method": "GET",
+                "uri": "https://management.azure.com/subscriptions/ea42f556-5106-4743-99b0-c129bfa71a47/providers/Microsoft.Sql/servers?api-version=2015-05-01-preview",
+                "body": null,
+                "headers": {}
+            },
+            "response": {
+                "status": {
+                    "code": 200,
+                    "message": "OK"
+                },
+                "headers": {
+                    "cache-control": [
+                        "no-cache"
+                    ],
+                    "content-type": [
+                        "application/json; charset=utf-8"
+                    ],
+                    "date": [
+                        "Wed, 03 Jul 2019 23:54:59 GMT"
+                    ],
+                    "content-length": [
+                        "428"
+                    ]
+                },
+                "body": {
+                    "data": {
+                        "value": [
+                            {
+                                "kind": "v12.0",
+                                "properties": {
+                                    "administratorLogin": "custodian",
+                                    "version": "12.0",
+                                    "state": "Ready",
+                                    "fullyQualifiedDomainName": "cctestsqlserverj6y6cbbtaphs4.database.windows.net"
+                                },
+                                "location": "eastus2",
+                                "id": "/subscriptions/ea42f556-5106-4743-99b0-c129bfa71a47/resourceGroups/test_sqlserver/providers/Microsoft.Sql/servers/cctestsqlserverj6y6cbbtaphs4",
+                                "name": "cctestsqlserverj6y6cbbtaphs4",
+                                "type": "Microsoft.Sql/servers"
+                            }
+                        ]
+                    }
+                }
+            }
+        },
+        {
+            "request": {
+                "method": "GET",
+                "uri": "https://management.azure.com/subscriptions/ea42f556-5106-4743-99b0-c129bfa71a47/resourceGroups/test_sqlserver/providers/Microsoft.Sql/servers/cctestsqlserverj6y6cbbtaphs4/databases?api-version=2017-10-01-preview",
+                "body": null,
+                "headers": {}
+            },
+            "response": {
+                "status": {
+                    "code": 200,
+                    "message": "OK"
+                },
+                "headers": {
+                    "cache-control": [
+                        "no-cache"
+                    ],
+                    "content-type": [
+                        "application/json; charset=utf-8"
+                    ],
+                    "date": [
+                        "Wed, 03 Jul 2019 23:54:59 GMT"
+                    ],
+                    "content-length": [
+                        "2737"
+                    ]
+                },
+                "body": {
+                    "data": {
+                        "value": [
+                            {
+                                "sku": {
+                                    "name": "Standard",
+                                    "tier": "Standard",
+                                    "capacity": 10
+                                },
+                                "kind": "v12.0,user",
+                                "properties": {
+                                    "collation": "SQL_Latin1_General_CP1_CI_AS",
+                                    "maxSizeBytes": 268435456000,
+                                    "status": "Online",
+                                    "databaseId": "4308a849-0209-4581-8dfa-49cf61f9b158",
+                                    "creationDate": "2019-07-03T23:48:35Z",
+                                    "currentServiceObjectiveName": "S0",
+                                    "requestedServiceObjectiveName": "S0",
+                                    "defaultSecondaryLocation": "centralus",
+                                    "catalogCollation": "SQL_Latin1_General_CP1_CI_AS",
+                                    "zoneRedundant": false,
+                                    "earliestRestoreDate": "2019-07-04T00:18:35Z",
+                                    "readScale": "Disabled",
+                                    "readReplicaCount": 0,
+                                    "currentSku": {
+                                        "name": "Standard",
+                                        "tier": "Standard",
+                                        "capacity": 10
+                                    }
+                                },
+                                "location": "eastus2",
+                                "id": "/subscriptions/ea42f556-5106-4743-99b0-c129bfa71a47/resourceGroups/test_sqlserver/providers/Microsoft.Sql/servers/cctestsqlserverj6y6cbbtaphs4/databases/cclongtermretentiondb",
+                                "name": "cclongtermretentiondb",
+                                "type": "Microsoft.Sql/servers/databases"
+                            },
+                            {
+                                "sku": {
+                                    "name": "Standard",
+                                    "tier": "Standard",
+                                    "capacity": 100
+                                },
+                                "kind": "v12.0,user",
+                                "properties": {
+                                    "collation": "SQL_Latin1_General_CP1_CI_AS",
+                                    "maxSizeBytes": 21474836480,
+                                    "status": "Online",
+                                    "databaseId": "88543364-fe75-488a-9853-e1a507728d9c",
+                                    "creationDate": "2019-07-03T23:48:55.703Z",
+                                    "currentServiceObjectiveName": "S3",
+                                    "requestedServiceObjectiveName": "S3",
+                                    "defaultSecondaryLocation": "centralus",
+                                    "catalogCollation": "SQL_Latin1_General_CP1_CI_AS",
+                                    "zoneRedundant": false,
+                                    "earliestRestoreDate": "2019-07-03T23:50:22.5Z",
+                                    "readScale": "Disabled",
+                                    "readReplicaCount": 0,
+                                    "currentSku": {
+                                        "name": "Standard",
+                                        "tier": "Standard",
+                                        "capacity": 100
+                                    }
+                                },
+                                "location": "eastus2",
+                                "id": "/subscriptions/ea42f556-5106-4743-99b0-c129bfa71a47/resourceGroups/test_sqlserver/providers/Microsoft.Sql/servers/cctestsqlserverj6y6cbbtaphs4/databases/cctestdb",
+                                "name": "cctestdb",
+                                "type": "Microsoft.Sql/servers/databases"
+                            },
+                            {
+                                "sku": {
+                                    "name": "System",
+                                    "tier": "System",
+                                    "capacity": 0
+                                },
+                                "kind": "v12.0,system",
+                                "managedBy": "/subscriptions/ea42f556-5106-4743-99b0-c129bfa71a47/resourceGroups/test_sqlserver/providers/Microsoft.Sql/servers/cctestsqlserverj6y6cbbtaphs4",
+                                "properties": {
+                                    "collation": "SQL_Latin1_General_CP1_CI_AS",
+                                    "maxSizeBytes": 32212254720,
+                                    "status": "Online",
+                                    "databaseId": "4f832eae-3f33-4644-b4a5-1216722a273d",
+                                    "creationDate": "2019-07-03T23:47:32.947Z",
+                                    "currentServiceObjectiveName": "System2",
+                                    "requestedServiceObjectiveName": "System2",
+                                    "defaultSecondaryLocation": "centralus",
+                                    "catalogCollation": "SQL_Latin1_General_CP1_CI_AS",
+                                    "zoneRedundant": false,
+                                    "readScale": "Disabled",
+                                    "readReplicaCount": 0,
+                                    "currentSku": {
+                                        "name": "System",
+                                        "tier": "System",
+                                        "capacity": 0
+                                    }
+                                },
+                                "location": "eastus2",
+                                "id": "/subscriptions/ea42f556-5106-4743-99b0-c129bfa71a47/resourceGroups/test_sqlserver/providers/Microsoft.Sql/servers/cctestsqlserverj6y6cbbtaphs4/databases/master",
+                                "name": "master",
+                                "type": "Microsoft.Sql/servers/databases"
+                            }
+                        ]
+                    }
+                }
+            }
+        },
+        {
+            "request": {
+                "method": "PATCH",
+                "uri": "https://management.azure.com/subscriptions/ea42f556-5106-4743-99b0-c129bfa71a47/resourceGroups/test_sqlserver/providers/Microsoft.Sql/servers/cctestsqlserverj6y6cbbtaphs4/databases/cctestdb?api-version=2017-10-01-preview",
+                "body": "mock_body",
+                "headers": {}
+            },
+            "response": {
+                "status": {
+                    "code": 202,
+                    "message": "Accepted"
+                },
+                "headers": {
+                    "azure-asyncoperation": [
+                        "https://management.azure.com/subscriptions/aa98974b-5d2a-4d98-a78a-382f3715d07e/resourceGroups/test_sqlserver/providers/Microsoft.Sql/locations/eastus2/databaseAzureAsyncOperation/1d8850ed-6aa0-484f-8f14-bab98c617913?api-version=2017-10-01-preview"
+                    ],
+                    "x-ms-ratelimit-remaining-subscription-writes": [
+                        "1199"
+                    ],
+                    "location": [
+                        "https://management.azure.com/subscriptions/aa98974b-5d2a-4d98-a78a-382f3715d07e/resourceGroups/test_sqlserver/providers/Microsoft.Sql/locations/eastus2/databaseOperationResults/1d8850ed-6aa0-484f-8f14-bab98c617913?api-version=2017-10-01-preview"
+                    ],
+                    "cache-control": [
+                        "no-cache"
+                    ],
+                    "content-length": [
+                        "76"
+                    ],
+                    "content-type": [
+                        "application/json; charset=utf-8"
+                    ],
+                    "date": [
+                        "Wed, 03 Jul 2019 23:55:00 GMT"
+                    ]
+                },
+                "body": {
+                    "data": {
+                        "operation": "UpdateLogicalDatabase",
+                        "startTime": "2019-07-03T23:55:00.803Z"
+                    }
+                }
+            }
+        },
+        {
+            "request": {
+                "method": "GET",
+                "uri": "https://management.azure.com/subscriptions/ea42f556-5106-4743-99b0-c129bfa71a47/resourceGroups/test_sqlserver/providers/Microsoft.Sql/servers/cctestsqlserverj6y6cbbtaphs4/databases/cctestdb?api-version=2017-10-01-preview",
+                "body": null,
+                "headers": {}
+            },
+            "response": {
+                "status": {
+                    "code": 200,
+                    "message": "OK"
+                },
+                "headers": {
+                    "cache-control": [
+                        "no-cache"
+                    ],
+                    "content-type": [
+                        "application/json; charset=utf-8"
+                    ],
+                    "date": [
+                        "Wed, 03 Jul 2019 23:55:00 GMT"
+                    ],
+                    "content-length": [
+                        "866"
+                    ]
+                },
+                "body": {
+                    "data": {
+                        "sku": {
+                            "name": "Standard",
+                            "tier": "Standard",
+                            "capacity": 100
+                        },
+                        "kind": "v12.0,user",
+                        "properties": {
+                            "collation": "SQL_Latin1_General_CP1_CI_AS",
+                            "maxSizeBytes": 21474836480,
+                            "status": "Online",
+                            "databaseId": "88543364-fe75-488a-9853-e1a507728d9c",
+                            "creationDate": "2019-07-03T23:48:55.703Z",
+                            "currentServiceObjectiveName": "S3",
+                            "requestedServiceObjectiveName": "S3",
+                            "defaultSecondaryLocation": "centralus",
+                            "catalogCollation": "SQL_Latin1_General_CP1_CI_AS",
+                            "zoneRedundant": false,
+                            "earliestRestoreDate": "2019-07-03T23:50:22.5Z",
+                            "readScale": "Disabled",
+                            "readReplicaCount": 0,
+                            "currentSku": {
+                                "name": "Standard",
+                                "tier": "Standard",
+                                "capacity": 100
+                            }
+                        },
+                        "location": "eastus2",
+                        "id": "/subscriptions/ea42f556-5106-4743-99b0-c129bfa71a47/resourceGroups/test_sqlserver/providers/Microsoft.Sql/servers/cctestsqlserverj6y6cbbtaphs4/databases/cctestdb",
+                        "name": "cctestdb",
+                        "type": "Microsoft.Sql/servers/databases"
+                    }
+                }
+            }
+        },
+        {
+            "request": {
+                "method": "PATCH",
+                "uri": "https://management.azure.com/subscriptions/ea42f556-5106-4743-99b0-c129bfa71a47/resourceGroups/test_sqlserver/providers/Microsoft.Sql/servers/cctestsqlserverj6y6cbbtaphs4/databases/cctestdb?api-version=2017-10-01-preview",
+                "body": "mock_body",
+                "headers": {}
+            },
+            "response": {
+                "status": {
+                    "code": 202,
+                    "message": "Accepted"
+                },
+                "headers": {
+                    "azure-asyncoperation": [
+                        "https://management.azure.com/subscriptions/aa98974b-5d2a-4d98-a78a-382f3715d07e/resourceGroups/test_sqlserver/providers/Microsoft.Sql/locations/eastus2/databaseAzureAsyncOperation/8fbd7d78-557b-4b37-b35d-dfd7337e612d?api-version=2017-10-01-preview"
+                    ],
+                    "x-ms-ratelimit-remaining-subscription-writes": [
+                        "1199"
+                    ],
+                    "location": [
+                        "https://management.azure.com/subscriptions/aa98974b-5d2a-4d98-a78a-382f3715d07e/resourceGroups/test_sqlserver/providers/Microsoft.Sql/locations/eastus2/databaseOperationResults/8fbd7d78-557b-4b37-b35d-dfd7337e612d?api-version=2017-10-01-preview"
+                    ],
+                    "cache-control": [
+                        "no-cache"
+                    ],
+                    "content-length": [
+                        "76"
+                    ],
+                    "content-type": [
+                        "application/json; charset=utf-8"
+                    ],
+                    "date": [
+                        "Wed, 03 Jul 2019 23:55:01 GMT"
+                    ]
+                },
+                "body": {
+                    "data": {
+                        "operation": "UpdateLogicalDatabase",
+                        "startTime": "2019-07-03T23:55:01.937Z"
+                    }
+                }
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/tools/c7n_azure/tests/test_lock_action.py b/tools/c7n_azure/tests/test_lock_action.py
--- a/tools/c7n_azure/tests/test_lock_action.py
+++ b/tools/c7n_azure/tests/test_lock_action.py
@@ -165,7 +165,7 @@ def test_lock_action_child_resource(self):
         locks = [r.serialize(True) for r in self.client.management_locks.list_at_resource_level(
             'test_sqlserver',
             'Microsoft.Sql/servers',
-            'cctestsqlserverj6y6cbbtaphs4',
+            ResourceIdParser.get_resource_name(self.resources[0]['c7n:parent-id']),
             'databases',
             'cctestdb')]
 
diff --git a/tools/c7n_azure/tests/test_sqldatabase.py b/tools/c7n_azure/tests/test_sqldatabase.py
--- a/tools/c7n_azure/tests/test_sqldatabase.py
+++ b/tools/c7n_azure/tests/test_sqldatabase.py
@@ -11,8 +11,10 @@
 # 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 azure.mgmt.sql.models import DatabaseUpdate, Sku
 from azure_common import BaseTest, arm_template
+from c7n_azure.utils import ResourceIdParser
+
 from c7n.exceptions import PolicyValidationError
 from c7n.utils import local_session
 from c7n_azure.resources.sqldatabase import (
@@ -22,6 +24,10 @@
 
 class SqlDatabaseTest(BaseTest):
 
+    def setUp(self):
+        super(SqlDatabaseTest, self).setUp()
+        self.client = local_session(Session).client('azure.mgmt.sql.SqlManagementClient')
+
     def test_sql_database_schema_validate(self):
         with self.sign_out_patch():
             p = self.load_policy({
@@ -73,6 +79,52 @@ def _assert_found_only_test_database(self, resources):
 
         self.assertEqual(db.get('name'), 'cctestdb')
 
+    @arm_template('sqlserver.json')
+    def test_resize_action(self):
+        p = self.load_policy({
+            'name': 'resize-sqldatabase',
+            'resource': 'azure.sqldatabase',
+            'filters': [
+                {
+                    'type': 'value',
+                    'key': 'name',
+                    'value': 'cctestdb'
+                }
+            ],
+            'actions': [
+                {
+                    'type': 'resize',
+                    'tier': 'Standard',
+                    'capacity': 100,
+                    'max_size_bytes': 21474836480
+                }
+            ],
+        })
+
+        self.resources = p.run()
+        self.assertEqual(len(self.resources), 1)
+        self.assertEqual(self.resources[0]['name'], 'cctestdb')
+
+        updated_database = self.client.databases.get(
+            'test_sqlserver',
+            ResourceIdParser.get_resource_name(self.resources[0]['c7n:parent-id']),
+            'cctestdb')
+
+        self.assertEqual(updated_database.sku.capacity, 100)
+        self.assertEqual(updated_database.sku.tier, 'Standard')
+        # The value for max_size_bytes returned by api is stale,
+        # so we can't make an assertion until it's fixed
+        # self.assertEqual(database.max_size_bytes, 21474836480)
+
+        # Revert action
+        self.client.databases.update(
+            'test_sqlserver',
+            ResourceIdParser.get_resource_name(self.resources[0]['c7n:parent-id']),
+            'cctestdb',
+            DatabaseUpdate(sku=Sku(capacity=125, tier='Premium', name='Premium'),
+                           max_size_bytes=2147483648)
+        )
+
 
 class ShortTermBackupRetentionPolicyFilterTest(BaseTest):