Skip to content

Commit 847a427

Browse files
authored
Merge pull request #3 from MicrosoftCloudEssentials-LearningHub/across-subscription
Across a Subscription
2 parents fe6d238 + 3864ba4 commit 847a427

3 files changed

Lines changed: 156 additions & 3 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ Last updated: 2025-05-13
5757

5858
## How to enable IOPS (script)
5959

60+
> [Automate the activation of Autoscale IOPS using Python and the Azure REST API](./autoscaleMultiple-IOPS/), ideal for managing multiple servers across resource groups or entire subscriptions. This feature is only supported on General Purpose and Business Critical tiers, and currently not available via Azure CLI or PowerShell.
61+
6062
## How to Monitor IOPS Scaling
6163

6264
> How I know if the IOPS have scaled up or down when the server's using the autoscale IOPS feature? You can use the `metrics available in Azure Monitor`.

autoscaleMultiple-IOPS/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ Review [the script](./scripts/enable_autoscale_iops_byRG.py), and download it to
5151
> - For each server, retrieving its resource group. <br/>
5252
> - Applying the update if the server is in a supported tier (General Purpose or Business Critical). <br/>
5353
54-
Review [the script](./scripts/enable_autoscale_iops.py), and download it to your local machine.
54+
Review [the script](./scripts/enable_autoscale_iops_across_subscription.py), and download it to your local machine.
5555

56-
> Example: enabling Autoscale IOPS on 4 different servers, each hosted in different resource group and same subscription.
56+
> Example: enabling Autoscale IOPS on different servers, each hosted in different resource group and same subscription.
5757
58-
<img width="550" alt="image" src="" />
58+
<https://github.com/user-attachments/assets/7c06f457-d1c5-4277-ab1f-cee6621b6871>
5959

6060
## How to execute it Script to Enable Autoscale IOPS
6161

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""
2+
Azure MySQL Flexible Server Auto IOPS Enabler
3+
4+
This script connects to Azure using the Azure CLI and Azure REST API to:
5+
1. Retrieve the current subscription ID.
6+
2. List all Resource Groups in the current subscription.
7+
3. List all MySQL Flexible Servers in each resource group.
8+
4. Skip resource groups with no MySQL Flexible Servers.
9+
5. Check if each server is eligible for Auto IOPS scaling (only GeneralPurpose or BusinessCritical tiers).
10+
6. Optionally enable Auto IOPS scaling if not already enabled.
11+
7. Print a summary of which servers were updated or skipped.
12+
"""
13+
14+
import subprocess
15+
import requests
16+
import shutil
17+
from azure.identity import AzureCliCredential
18+
19+
SUPPORTED_TIERS = {"GeneralPurpose", "BusinessCritical"}
20+
21+
def mask_value(value, start=4, end=4):
22+
return value[:start] + '*' * (len(value) - start - end) + value[-end:]
23+
24+
def get_subscription_id():
25+
az_path = shutil.which("az")
26+
if az_path is None:
27+
raise FileNotFoundError("Azure CLI (az) not found in system PATH.")
28+
result = subprocess.check_output([az_path, "account", "show", "--query", "id", "-o", "tsv"], text=True)
29+
return result.strip()
30+
31+
def get_access_token():
32+
credential = AzureCliCredential()
33+
token = credential.get_token("https://management.azure.com/.default")
34+
return token.token
35+
36+
def list_resource_groups(subscription_id, access_token):
37+
url = f"https://management.azure.com/subscriptions/{subscription_id}/resourcegroups?api-version=2024-06-01-preview"
38+
headers = {"Authorization": f"Bearer {access_token}"}
39+
response = requests.get(url, headers=headers)
40+
response.raise_for_status()
41+
return response.json()
42+
43+
def list_mysql_servers(subscription_id, resource_group, access_token):
44+
url = (
45+
f"https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/"
46+
f"{resource_group}/providers/Microsoft.DBforMySQL/flexibleServers?api-version=2024-06-01-preview"
47+
)
48+
headers = {"Authorization": f"Bearer {access_token}"}
49+
response = requests.get(url, headers=headers)
50+
if response.status_code == 404:
51+
return {"value": []}
52+
response.raise_for_status()
53+
return response.json()
54+
55+
def get_server_config(subscription_id, resource_group, server_name, access_token):
56+
url = (
57+
f"https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/"
58+
f"providers/Microsoft.DBforMySQL/flexibleServers/{server_name}?api-version=2024-06-01-preview"
59+
)
60+
headers = {"Authorization": f"Bearer {access_token}"}
61+
response = requests.get(url, headers=headers)
62+
response.raise_for_status()
63+
return response.json()
64+
65+
def enable_auto_io_scaling_put(subscription_id, resource_group, server_name, access_token):
66+
url = (
67+
f"https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}/"
68+
f"providers/Microsoft.DBforMySQL/flexibleServers/{server_name}?api-version=2024-06-01-preview"
69+
)
70+
headers = {
71+
"Authorization": f"Bearer {access_token}",
72+
"Content-Type": "application/json"
73+
}
74+
payload = {
75+
"properties": {
76+
"storage": {
77+
"autoIoScaling": "Enabled"
78+
}
79+
}
80+
}
81+
response = requests.patch(url, headers=headers, json=payload)
82+
print(f"PATCH Response: {response.status_code} - {response.text}")
83+
response.raise_for_status()
84+
return response.json()
85+
86+
def main():
87+
try:
88+
print("Script started")
89+
subscription_id = get_subscription_id()
90+
print(f"Using subscription: {mask_value(subscription_id)}")
91+
92+
access_token = get_access_token()
93+
print(f"Access Token: {mask_value(access_token[:10])}...")
94+
95+
resource_groups = list_resource_groups(subscription_id, access_token)
96+
eligible_servers = []
97+
ineligible_servers = []
98+
99+
for rg in resource_groups.get("value", []):
100+
rg_name = rg["name"]
101+
servers = list_mysql_servers(subscription_id, rg_name, access_token).get("value", [])
102+
103+
if not servers:
104+
print(f"[SKIP] No MySQL Flexible Servers found in resource group: {rg_name}")
105+
continue
106+
107+
print(f"\nMySQL Flexible Servers in {rg_name}:")
108+
for server in servers:
109+
print(f" - {server['name']}")
110+
111+
target_server = input(f"Enter a specific server name in {rg_name} (or press Enter to apply to all): ").strip()
112+
113+
for server in servers:
114+
server_name = server["name"]
115+
if target_server and server_name.lower() != target_server.lower():
116+
continue
117+
118+
config = get_server_config(subscription_id, rg_name, server_name, access_token)
119+
tier = config["sku"]["tier"]
120+
auto_io = config["properties"]["storage"].get("autoIoScaling", "Disabled")
121+
122+
if tier not in SUPPORTED_TIERS:
123+
ineligible_servers.append((server_name, tier))
124+
continue
125+
126+
if auto_io == "Enabled":
127+
print(f"[SKIP] Auto IOPS already enabled for {server_name}")
128+
continue
129+
130+
print(f"[UPDATE] Enabling Auto IOPS for {server_name} (Tier: {tier})...")
131+
enable_auto_io_scaling_put(subscription_id, rg_name, server_name, access_token)
132+
eligible_servers.append(server_name)
133+
134+
print("\nSummary:")
135+
if eligible_servers:
136+
print("Auto IOPS enabled for:")
137+
for name in eligible_servers:
138+
print(f" - {name}")
139+
else:
140+
print("No servers were updated.")
141+
142+
if ineligible_servers:
143+
print("\nIneligible servers (unsupported tier):")
144+
for name, tier in ineligible_servers:
145+
print(f" - {name} (Tier: {tier})")
146+
147+
except Exception as e:
148+
print(f"Error: {e}")
149+
150+
if __name__ == "__main__":
151+
main()

0 commit comments

Comments
 (0)