This repository has been archived by the owner on Jun 17, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 191
/
Zero-Downtime-Capacity-Scale.ps1
342 lines (283 loc) · 16.5 KB
/
Zero-Downtime-Capacity-Scale.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# This sample script calls the Power BI API and ARM REST API to programmatically scale capacity resource with no downtime (i.e. embedded content is available during the scaling process).
# It also provides an option to reassign workspaces from one capacity resource to another.
# The procedure creates a temporary capacity resource and reassigns the workspaces to it during the scaling process.
# Once the procedure is completed, the workspaces are assigned back to the original capacity resource and the temporary capacity resource is deleted.
#
#
# MAIN TEMP
# ========== ==========
# | SKU-X | | TEMPORARY|
# STEP 1 - Create temporary capacity | | | CAPACITY |
# |WORKSPACES| | |
# ========== ==========
#
# ========== ==========
# | | | TEMPORARY|
# STEP 2 - Reassign workspaces to temporary capacity| SKU-X |--------->| CAPACITY,|
# | | |WORKSPACES|
# ========== ==========
#
# ========== ==========
# | | | TEMPORARY|
# STEP 3 - Scale main capacity | SCALING | | CAPACITY,|
# | | |WORKSPACES|
# ========== ==========
#
# ========== ==========
# | SKU-Y | | TEMPORARY|
# STEP 4 - Assign workspaces back to main capacity | |<---------| CAPACITY |
# |WORKSPACES| | |
# ========== ==========
#
# ========== ==========
# | SKU-Y | | |
# STEP 5 - Delete temporary capacity | | | DELETED |
# |WORKSPACES| | |
# ========== ==========
# For more information, see the accompanying blog post:
# TODO : Add link to blog
# Instructions:
# 1. Install PowerShell (https://msdn.microsoft.com/en-us/powershell/scripting/setup/installing-windows-powershell), and the Azure PowerShell cmdlets (Install-Module AzureRM)
# 2. Run PowerShell as an administrator
# 3. Follow the instructions below to fill in the client ID
# 4. Change PowerShell directory to where this script is saved
# 5. Run Login-AzureRmAccount (In order to be able to run the script, the user should have an Azure RBAC 'owner' role, or any other Azure RBAC role with write permissions on the resource)
# 6. Run the script with params
# Scale Up example:
# .\Zero-Downtime-Capacity-Scale.ps1 -CapacityName 'democap1' -CapacityResourceGroup 'demorg' -TargetSku A3
#
# Workspaces Migration example:
# .\Zero-Downtime-Capacity-Scale.ps1 -CapacityName 'democap1' -CapacityResourceGroup 'demorg' -AssignWorkspacesOnly $true -SourceCapacityName 'democap2' -SourceCapacityResourceGroup 'demorg'
# Parameters - fill these in before running the script!
# ======================================================
# AAD Client ID:
# To get this, go to the following page and follow the steps to register an app
# https://app.powerbi.com/embedsetup/AppOwnsData
# To get the sample to work, ensure that you have the following fields:
# App Type: Native app
# Redirect URL: urn:ietf:wg:oauth:2.0:oob
# Level of access: check all boxes
[CmdletBinding()]
Param(
[Parameter(Mandatory=$TRUE, HelpMessage="Name of capacity for scaling or target for workspaces migration.")]
[string]$CapacityName,
[Parameter(Mandatory=$TRUE, HelpMessage="ResourceGroup of capacity for scaling or target for workspaces migration")]
[string]$CapacityResourceGroup,
[Parameter(Mandatory=$False, HelpMessage="True if you want to assign all workspaces from srouce capacity only, provide SourceCapacityName and SourceCapacityResourceGroup params")]
[bool]$AssignWorkspacesOnly = $FALSE,
[Parameter(Mandatory=$FALSE, HelpMessage="Target SKU for scaling, e.g. A3")]
[string]$TargetSku,
[Parameter(Mandatory=$False, HelpMessage="Name of source capacity for workspaces migration.")]
[string]$SourceCapacityName,
[Parameter(Mandatory=$False, HelpMessage="ResourceGroup of source capacity for workspaces migration.")]
[string]$SourceCapacityResourceGroup,
[Parameter(Mandatory=$False, HelpMessage="User Name")]
[string]$username,
[Parameter(Mandatory=$False, HelpMessage="Password")]
[string]$Password
)
# =====================================================
# $clientId = "FILL ME IN"
# =====================================================
$apiUri = "https://api.powerbi.com/v1.0/myorg/"
# =====================================================
# Get authentication token for PowerBI API
FUNCTION GetAuthToken
{
Import-Module AzureRm
$redirectUri = "urn:ietf:wg:oauth:2.0:oob"
$resourceAppIdURI = "https://analysis.windows.net/powerbi/api"
$authority = "https://login.microsoftonline.com/common/oauth2/authorize";
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
IF ($username -ne "" -and $Password -ne "")
{
$creds = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential" -ArgumentList $Username,$Password
$authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId, $creds)
}
ELSE
{
$authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId, $redirectUri, 'Always')
}
return $authResult
}
$token = GetAuthToken
# =====================================================
# Building Rest API header with authorization token
$auth_header = @{
'Content-Type'='application/json'
'Authorization'=$token.CreateAuthorizationHeader()
}
# =====================================================
# Helper function used for gettting the capacity object ID by capacity Name
FUNCTION GetCapacityObjectID($capacitiesList, $capacity_name)
{
$done = $False
# loop over capacitis list and find the right capacity by name
$capacitiesList.value | ForEach-Object -Process {
# Find main capacity ID
if ($_.DisplayName -eq $capacity_name)
{
Write-Host ">>> Object ID for" $capacity_name "is" $_.id
$done = $True
return $_.id
}
}
# If capacity was not found then print error and exit
IF ($done -ne $True) {
$errmsg = "Capacity " + $capacity_name + " object ID was not found!"
Write-Error $errmsg
Break Script
}
}
# =====================================================
# Helper function used for assigning workspaces from source to target capacity
FUNCTION AssignWorkspacesToCapacity($source_capacity_objectid, $target_capacity_objectid)
{
$getCapacityGroupsUri = $apiUri + "groups?$" + "filter=capacityId eq " + "'$source_capacity_objectid'"
$capacityWorkspaces = Invoke-RestMethod -Method GET -Headers $auth_header -Uri $getCapacityGroupsUri
# Assign workspaces to temporary capacity
$capacityWorkspaces.value | ForEach-Object -Process {
Write-Host ">>> Assigning workspace Name:" $_.name " Id:" $_.id "to capacity id:" $target_capacity_objectid
$assignToCapacityUri = $apiUri + "groups/" + $_.id + "/AssignToCapacity"
$assignToCapacityBody = @{capacityId=$target_capacity_objectid} | ConvertTo-Json
Invoke-RestMethod -Method Post -Headers $auth_header -Uri $assignToCapacityUri -Body $assignToCapacityBody -ContentType 'application/json'
# Validate workspace to capacity assignment status was completed successfully, if not then exit script
DO
{
$assignToCapacityStatusUri = $apiUri + "groups/" + $_.id + "/CapacityAssignmentStatus"
$status = Invoke-RestMethod -Method Get -Headers $auth_header -Uri $assignToCapacityStatusUri
# Exit script if workspace assignment has failed
IF ($status.status -eq 'AssignmentFailed')
{
$errmsg = "workspace " + $_.id + " assignment has failed!, script will stop."
Break Script
}
Start-Sleep -Milliseconds 200
Write-Host ">>> Assigning workspace Id:" $_.id "to capacity id:" $target_capacity_objectid "Status:" $status.status
} while ($status.status -ne 'CompletedSuccessfully')
}
$getCapacityGroupsUri = $apiUri + "groups?$" + "filter=capacityId eq " + "'$target_capacity_objectid'"
$capacityWorkspaces = Invoke-RestMethod -Method GET -Headers $auth_header -Uri $getCapacityGroupsUri
return $capacityWorkspaces
}
# =====================================================
# Helper function used for validation capacity in Active state
FUNCTION ValidateCapacityInActiveState($capacity_name, $resource_group)
{
# Get capacity and validate its in active state
$getCapacityResult = Get-AzureRmPowerBIEmbeddedCapacity -Name $capacity_name -ResourceGroup $resource_group
IF (!$getCapacityResult -OR $getCapacityResult -eq "")
{
$errmsg = "Capacity " + $capacity_name +" was not found!"
Write-Error -Message $errmsg
Break Script
}
ELSEIF ($getCapacityResult.State.ToString() -ne "Succeeded")
{
$errmsg = "Capacity " + $capacity_name + " is not in active state!"
Write-Error $errmsg
Break Script
}
return $getCapacityResult
}
# Start watch to measure E2E time duration
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
# Get and Validate main capacity is in active state
$mainCapacity = ValidateCapacityInActiveState $CapacityName $CapacityResourceGroup
# Check if script execution is for assigning workspaces only
IF ($AssignWorkspacesOnly -ne $TRUE)
{
# Check if user is a capacity administrator, if not exist script
$context = Get-AzureRmContext
$isUserAdminOnCapacity = $False
$mainCapacity.Administrator | ForEach-Object -Process {
IF ($_ -eq $context.Account.Id)
{
$isUserAdminOnCapacity = $TRUE
}
}
IF ($isUserAdminOnCapacity -eq $False)
{
$errmsg = "User is not capacity administrator!"
Write-Error $errmsg
Break Script
}
# Check if Current SKU is equal to main SKU
IF ($mainCapacity.Sku -eq $TargetSku)
{
Write-Host "Current SKU is equal to the target SKU, No scale is needed!"
Break Script
}
Write-Host
Write-Host "========================================================================================================================" -ForegroundColor DarkGreen
Write-Host " SCALING CAPACITY FROM" $mainCapacity.Sku "To" $TargetSku -ForegroundColor DarkGreen
Write-Host "========================================================================================================================" -ForegroundColor DarkGreen
Write-Host
Write-Host ">>> Capacity" $CapacityName "is available and ready for scaling!"
# Create new temporary capacity to be used for scale
$guid = New-Guid
$temporaryCapacityName = 'tmpcapacity' + $guid.ToString().Replace('-','s').ToLowerInvariant()
$temporarycapacityResourceGroup = $mainCapacity.ResourceGroup
Write-Host
Write-Host ">>> STEP 1 - Creating a temporary capacity name:"$temporaryCapacityName
$newcap = New-AzureRmPowerBIEmbeddedCapacity -ResourceGroupName $mainCapacity.ResourceGroup -Name $temporaryCapacityName -Location $mainCapacity.Location -Sku $TargetSku -Administrator $mainCapacity.Administrator
# Check if new capacity provisioning succeeded
IF (!$newcap -OR $newcap.State.ToString() -ne 'Succeeded')
{
Remove-AzureRmPowerBIEmbeddedCapacity -Name $temporaryCapacityName -ResourceGroupName $temporarycapacityResourceGroup
$errmsg = "Try to remove temporary capacity due to some failure while provisioning!, Please restart script!"
Write-Error -Message $errmsg
Break Script
}
# Get capacities from PowerBI to find the capacities ID
$getCapacityUri = $apiUri + "capacities"
$capacitiesList = Invoke-RestMethod -Method Get -Headers $auth_header -Uri $getCapacityUri
$sourceCapacityObjectId = GetCapacityObjectID $capacitiesList $CapacityName
$targetCapacityObjectId = GetCapacityObjectID $capacitiesList $temporaryCapacityName
Write-Host ">>> STEP 1 - Completed!"
Write-Host
Write-Host ">>> STEP 2 - Assigning workspaces"
$assignedMainCapacityWorkspaces = AssignWorkspacesToCapacity $sourceCapacityObjectId $targetCapacityObjectId
Write-Host ">>> STEP 2 Completed!"
Write-Host
Write-Host ">>> STEP 3 - Scaling capacity " $CapacityName "to" $targetSku
Update-AzureRmPowerBIEmbeddedCapacity -Name $CapacityName -sku $targetSku
$mainCapacity = ValidateCapacityInActiveState $CapacityName $CapacityResourceGroup
Write-Host ">>> STEP 3 completed!" $CapacityName "to" $targetSku
Write-Host
Write-Host ">>> STEP 4 - Assigning workspaces to main capacity"
$AssignedTargetCapacityWorkspaces = AssignWorkspacesToCapacity $targetCapacityObjectId $sourceCapacityObjectId
# validate all workspaces were assigned back to the main capacity
$diff = Compare-Object $AssignedTargetCapacityWorkspaces.value $assignedMainCapacityWorkspaces.value
if ($diff -ne $null)
{
$errmsg = "Something went wrong while assigning workspaces to the main capacity, Please re-execute the script"
Write-Error -Message $errmsg
Break Script
}
Write-Host ">>> STEP 4 Completed!"
Write-Host
Write-Host ">>> STEP 5 - Delete temporary capacity"
# Delete temporary capacity if it was newly created
Remove-AzureRmPowerBIEmbeddedCapacity -Name $temporaryCapacityName -ResourceGroupName $temporarycapacityResourceGroup
Write-Host ">>> STEP 5 Completed!"
}
ELSE
{
# Get capacities from PowerBI to find the capacities ID
$getCapacityUri = $apiUri + "capacities"
$capacitiesList = Invoke-RestMethod -Method Get -Headers $auth_header -Uri $getCapacityUri
ValidateCapacityInActiveState $CapacityName $CapacityResourceGroup
Write-Host ">>> Capacity" $CapacityName "is available and ready!"
$sourceCapacityObjectId = GetCapacityObjectID $capacitiesList $SourceCapacityName
ValidateCapacityInActiveState $SourceCapacityName $SourceCapacityResourceGroup
$targetCapacityObjectId = GetCapacityObjectID $capacitiesList $CapacityName
Write-Host ">>> Capacity" $SourceCapacityName "is available and ready!"
$assignedcapacities = AssignWorkspacesToCapacity $sourceCapacityObjectId $targetCapacityObjectId
}
Write-Host
Write-Host "========================================================================================================================" -ForegroundColor DarkGreen
Write-Host " Completed Successfully" -ForegroundColor DarkGreen
Write-Host " Total Duration" -ForegroundColor DarkGreen
Write-Host " "$stopwatch.Elapsed -ForegroundColor DarkGreen
Write-Host "========================================================================================================================" -ForegroundColor DarkGreen