diff --git a/201-vm-linux-comprehensive/azuredeploy.json b/201-vm-linux-comprehensive/azuredeploy.json
index fadb1d3..eab8eb4 100644
--- a/201-vm-linux-comprehensive/azuredeploy.json
+++ b/201-vm-linux-comprehensive/azuredeploy.json
@@ -239,33 +239,13 @@
}
}
},
- {
- "type": "Microsoft.Compute/virtualMachines/extensions",
- "name": "[concat(variables('vmName'),'/LinuxVMAccessExtension')]",
- "apiVersion": "2016-03-30",
- "location": "[resourceGroup().location]",
- "dependsOn": [
- "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'),'/extensions/LinuxCustomScriptExtension')]"
- ],
- "properties": {
- "publisher": "Microsoft.OSTCExtensions",
- "type": "VMAccessForLinux",
- "typeHandlerVersion": "1.3",
- "autoUpgradeMinorVersion": "true",
- "settings": { },
- "protectedSettings": {
- "username": "[parameters('adminUsername')]",
- "password": "[parameters('adminPassword')]"
- }
- }
- },
{
"type": "Microsoft.Compute/virtualMachines/extensions",
"name": "[concat(variables('vmName'),'/installospatching')]",
"apiVersion": "2016-03-30",
"location": "[resourceGroup().location]",
"dependsOn": [
- "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'),'/extensions/LinuxVMAccessExtension')]"
+ "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'),'/extensions/LinuxCustomScriptExtension')]"
],
"properties": {
"publisher": "Microsoft.OSTCExtensions",
@@ -294,4 +274,4 @@
"outputs": {
}
-}
\ No newline at end of file
+}
diff --git a/301-availability-set-elastic-storage-ecs/README.md b/301-availability-set-elastic-storage-ecs/README.md
new file mode 100644
index 0000000..d9982a7
--- /dev/null
+++ b/301-availability-set-elastic-storage-ecs/README.md
@@ -0,0 +1,152 @@
+# Deploy Multinode DELLEMC ECS Elastic Storage S3 Community Edition in an AzureStack Availabilty Set
+
+
+
+
+
+
+
+
+
+
+
+## Prerequisites
+The required VM Types need to have at least 4vCPU and 16GB memory.
+With the current AzureStrack VM Sizes, this would be 28G Memory per machine, so make sure you ASDK has enough Memory...
+
+To depoloy to an ASDK Admin Tenant, just click on "ASDK Admin Tenant"
+To depoloy to an ASDK User Tenant, just click on "Deploy to ASDK"
+
+To deploy this template using the scripts from the root of this repo: (change the folder name below to match the folder name for this sample)
+
+```PowerShell
+.\Deploy-AzureResourceGroup.ps1 -ResourceGroupLocation 'local' -ArtifactsStagingDirectory '301-availability-set-elastic-storage-ecs'
+```
+```bash
+azure-group-deploy.sh -a '301-availability-set-elastic-storage-ecs' -l local
+```
+
+This template deploys a **multinode dellemc ecs community edition**. The **ecs community edition** is a **elastic cloud storage solution providing object storage (sr, atoms, cas)**
+
+`Tags: arm, centos, ecs, ecs community edition`
+
+## Solution overview and deployed resources
+
+This is an overview of the solution
+
+The following resources are deployed as part of the solution
+
+#### Storage Accounts
+
+Storage ressources provided per vm
+
++ **vm storage account**: holds the os copy from image (CentOS) and up to 8 data disks per node
++ **diagnostic storage account**: storage for vm diagnostics
+
+#### Supported OSimage Publisher / SKU /Versions
+the following images are supported:
++ **CentOS** this is based on the RogueWave Centos Image using Publicher OpenLogic, SKU CentOS, Version 7.4
++ **CentOS-7** this is based on the original Centos CloudImage using Publisher CentOS, SKU Centos-7, Version Centos-7.4
+The Centos-7 must be loaded manually or using [Azurestack-Kickstart](https://github.com/bottkars/Azurestack-Kickstart)
+
+#### networkSecurityGroups
+
+Firewall rules for Network
+
+#### networkLoadbalancer
+Public loadbalancer for ECS Nodes
++ **lbRulesA**: Load Balancing rules for ECS Ports 111,2049,9020.9021,9022,9023,9024,9025,10000
++ **inboundNatRules**: ports 220x are forwarded to port 22 on NodeX
+
+#### OSTCExtensions
+Custom Script Extensions for Linux
+The deployment utilizes the custom script extension
++ **configurenode**: Used on the Node to configure installer Prerequirements, used on nodes 2-N
++ **install_ecs**: the ecs installer, runĀ“s on node 1
+
+Password Change extension
+the Deployment utilizes the Password change extension for linux
++ **resetpassword** to overcome some issues where cloudinit does not accept password
+the default password is Subscription#**id**, example Subscription#8c21cadc-9e41-459e-bf4b-9b5aa2fad938
+
+## Deployment steps
+
+### deploy to Azure from this site
+You can click the "deploy to Azure" button at the beginning of this document or follow the instructions for command line deployment using the scripts in the root of this repo.
+the parameters section will be the same
+
+### from new azure template from azure portal
+From the azure Portal, click new and type in template.
+select the new custom template dialog
+
+![new deploy](images/template_new.png "new template from azure portal")
+
+
+once open, click on the upload and load the azuredeploy.json
+
+![new deploy](images/template_load.png "new template from azure portal")
+
+
+fill in all parameters to you need and make sure the dns prefix is not used
+![new deploy](images/template_edit.png "new template from azure portal")
+### quickstart template
+
+
+Also, you can use Visual Studio to deploy the template. If you have installed the ressourcegroup extensions creater a new deployment and select '301-availability-set-elastic-storage-ecs' from the quickstart templates
+
+
+### visual studio example
+
+![new deploy](images/new_rg.png "Create new deployment from Visual Studio")
+
+#### parameters of resource group
+![deploy](images/rg_parameter.png "parameters for resource group")
+
+The ressource group deployment will take between 10 and 15 Minutes, depneding on VM Types
+
+![rg](images/rg_done.png "parameters for resource group")
+
+once the resource group deployment has finished, the ecs installer will be started from
+[ecs.sh](scripts/ecs.sh)
+
+#### monitor installation
+ssh into the first node (use the external dns name ), port 2201
+```bash
+sudo su
+tail -f /root/install.log
+```
+![log](images/log.png "installation logs")
+the system will do a reboot after package installation.
+the reboot(s) will be controlled by a systemd service [ecs-installer.service](scripts/ecs-installer.service)
+
+after the reboot, the ECS ansible installer starts withn step1 and step2
+the progress is also logged
+```bash
+sudo su
+tail -f /root/install.log
+```
+![log](images/ansible.png "ansible logger")
+
+#### Connect
+
+once the installation has finished step2, you can connect to port 443 of your externa DNS from a webbrowser.
+the initial user / password is root:ChangeMe
+
+![log](images/dashboard.png "ECS Dashboard")
+
+
+#### Management
+
+For more information, see
+[DellEMC ECS Community Edition readthedocs](http://ecsce.readthedocs.io/en/latest/installation/ECS-Installation.html)
+
+and
+[DellEMC ECS Documentation](https://community.emc.com/docs/DOC-56978)
+
+## Notes
+
+future improvementĀ“s
++ feedback of installation logs to arm
++ ubntu based install ( pending verification )
++ singlenode from same deployment
++ nested template for loadbalancer as copy set
\ No newline at end of file
diff --git a/301-availability-set-elastic-storage-ecs/azuredeploy.json b/301-availability-set-elastic-storage-ecs/azuredeploy.json
new file mode 100644
index 0000000..284031a
--- /dev/null
+++ b/301-availability-set-elastic-storage-ecs/azuredeploy.json
@@ -0,0 +1,732 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "adminUsername": {
+ "type": "string",
+ "metadata": {
+ "description": "The name of the Administrator of the new VMs"
+ },
+ "defaultValue": "ecsadmin"
+ },
+ "adminPassword": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The password for the Administrator account of the new VMs. Default value is Subscription#"
+ },
+ "defaultValue": "[concat('Subscription#',substring(resourcegroup().id,15,36))]"
+ },
+ "numberOfInstances": {
+ "type": "int",
+ "defaultValue": 3,
+ "allowedValues": [
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "metadata": {
+ "description": "Number of VMs to deploy, limit 5 since this sample is using a single storage account"
+ }
+ },
+ "dataDiskCount": {
+ "type": "int",
+ "defaultValue": 3,
+ "allowedValues": [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "metadata": {
+ "description": "NUmber of ECS Data Disks"
+ }
+ },
+ "dataDiskSize": {
+ "type": "int",
+ "defaultValue": 150,
+ "minValue": 150,
+ "maxValue": 1023,
+ "metadata": {
+ "description": "Size of the Data Disk in GB"
+ }
+ },
+ "vmNamePrefix": {
+ "type": "string",
+ "defaultValue": "ecs",
+ "metadata": {
+ "description": "VM name prefix"
+ }
+ },
+ "vmSize": {
+ "allowedValues": [
+ "Standard_A6",
+ "Standard_D4",
+ "Standard_D12",
+ "Standard_D4_v2",
+ "Standard_D12_v2",
+ "Standard_DS4",
+ "Standard_DS12",
+ "Standard_DS4_v2",
+ "Standard_DS12_v2"
+ ],
+ "defaultValue": "Standard_D4",
+ "metadata": {
+ "description": "This is the size of your ECS Nodes. Minimum is A6/D4"
+ },
+ "type": "string"
+ },
+ "dnsPrefix": {
+ "type": "string",
+ "defaultValue": "[concat('ecs', uniquestring(resourceGroup().id))]",
+ "metadata": {
+ "description": "dns name prefix. this is the external dns prefix to reach your ECS via the loadbalancer"
+ }
+ },
+ "osImagePublisher": {
+ "type": "string",
+ "defaultValue": "OpenLogic",
+ "allowedValues": [
+ "Centos",
+ "OpenLogic"
+ ],
+ "metadata": {
+ "description": "The Image Publisher, use Openlogic for default Marketplace"
+ }
+ },
+ "osImageOffer": {
+ "type": "string",
+ "defaultValue": "CentOS",
+ "allowedValues": [
+ "Centos-7",
+ "CentOS"
+ ],
+ "metadata": {
+ "description": "The Image Offer, use centos for default Marketplace"
+ }
+ },
+ "osImageSKU": {
+ "type": "string",
+ "defaultValue": "7.4",
+ "allowedValues": [
+ "Centos-7.4",
+ "7.4"
+ ],
+ "metadata": {
+ "description": "The Linux version for the VM. Use 7.4 for default Marketplace"
+ }
+ },
+ "_artifactsLocation": {
+ "type": "string",
+ "metadata": {
+ "description": "The base URI where artifacts required by this template are located. When the template is deployed using the accompanying scripts, a private location in the subscription will be used and this value will be automatically generated."
+ },
+ "defaultValue": "https://raw.githubusercontent.com/Azure/AzureStack-QuickStart-templates/master/301-availability-set-elastic-storage-ecs"
+ },
+ "_artifactsLocationSasToken": {
+ "type": "securestring",
+ "metadata": {
+ "description": "The sasToken required to access _artifactsLocation. When the template is deployed using the accompanying scripts, a sasToken will be automatically generated."
+ },
+ "defaultValue": ""
+ }
+ },
+ "variables": {
+ "availabilitySetName": "[toLower(concat('ECSaSet-', resourceGroup().name))]",
+ "storageAccountType": "Standard_LRS",
+ "vmSize": "[parameters('vmSize')]",
+ "dnsPrefix": "[parameters('dnsPrefix')]",
+ "osImageVersion": "latest",
+ "addressPrefix": "10.0.0.0/16",
+ "virtualNetworkName": "[tolower(concat('vNet-',resourceGroup().name))]",
+ "vnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]",
+ "NICPrefix": "vnic-",
+ "subnetPrefix": "10.0.0.0/24",
+ "subnetName": "vmstaticsubnet",
+ "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
+ "storageName": "[concat('sa', uniquestring(resourceGroup().id))]",
+ "loadBalancerProbeName": "443Probe",
+ "publicLBName": "[tolower(concat('external-lb-', resourceGroup().name))]",
+ "publiclbID": "[resourceId('Microsoft.Network/loadBalancers',variables('publicLBName'))]",
+ "lbFE": "[tolower(concat('external-lb-fe-',resourceGroup().name))]",
+ "publiclbFEConfigID": "[concat(variables('publiclbID'),'/frontendIPConfigurations/',variables('lbFE'))]",
+ "publicIPAddressName": "[tolower(concat('public-ip',resourceGroup().name))]",
+ "nsgName": "[tolower(concat('vmnsg',resourceGroup().name))]",
+ "nsgID": "[resourceId('Microsoft.Network/networkSecurityGroups',variables('nsgName'))]",
+ "vmContainerName": "vhds",
+ "diagnosticsStorageAccountName": "[concat('diag', uniquestring(resourceGroup().id))]",
+ "diagnosticsStorageAccountType": "Standard_LRS"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Storage/storageAccounts",
+ "name": "[variables('storageName')]",
+ "apiVersion": "2016-01-01",
+ "location": "[resourceGroup().location]",
+ "properties": {
+ "accountType": "[variables('storageAccountType')]"
+ },
+ "dependsOn": [
+ "[variables('publiclbName')]"
+ ]
+ },
+ {
+ "name": "[variables('diagnosticsStorageAccountName')]",
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2016-01-01",
+ "location": "[resourceGroup().location]",
+ "properties": {
+ "accountType": "[variables('diagnosticsStorageAccountType')]"
+ }
+ },
+ {
+ "type": "Microsoft.Network/networkSecurityGroups",
+ "name": "[variables('nsgName')]",
+ "apiVersion": "2015-06-15",
+ "location": "[resourceGroup().location]",
+ "properties": {
+ "securityRules": [
+ {
+ "name": "rule1",
+ "properties": {
+ "protocol": "*",
+ "sourcePortRange": "*",
+ "destinationPortRange": "*",
+ "sourceAddressPrefix": "*",
+ "destinationAddressPrefix": "*",
+ "access": "Allow",
+ "priority": 101,
+ "direction": "Inbound"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "type": "Microsoft.Compute/availabilitySets",
+ "name": "[variables('availabilitySetName')]",
+ "apiVersion": "2016-03-30",
+ "location": "[resourceGroup().location]",
+ "properties": {
+ "platformFaultDomainCount": 1,
+ "platformUpdateDomainCount": 1
+ }
+ },
+ {
+ "type": "Microsoft.Network/publicIPAddresses",
+ "name": "[variables('publicIPAddressName')]",
+ "apiVersion": "2015-06-15",
+ "location": "[resourceGroup().location]",
+ "properties": {
+ "publicIPAllocationMethod": "Dynamic",
+ "dnsSettings": {
+ "domainNameLabel": "[variables('dnsPrefix')]"
+ }
+ },
+ "dependsOn": [
+ "[variables('vnetID')]"
+ ]
+ },
+ {
+ "type": "Microsoft.Network/virtualNetworks",
+ "name": "[variables('virtualNetworkName')]",
+ "apiVersion": "2015-06-15",
+ "location": "[resourceGroup().location]",
+ "properties": {
+ "addressSpace": {
+ "addressPrefixes": [
+ "[variables('addressPrefix')]"
+ ]
+ },
+ "subnets": [
+ {
+ "name": "[variables('subnetName')]",
+ "properties": {
+ "addressPrefix": "[variables('subnetPrefix')]",
+ "networkSecurityGroup": {
+ "id": "[variables('nsgID')]"
+ }
+ }
+ }
+ ]
+ },
+ "dependsOn": [
+ "[variables('nsgID')]"
+ ]
+ },
+ {
+ "apiVersion": "2015-06-15",
+ "name": "[variables('publiclbName')]",
+ "type": "Microsoft.Network/loadBalancers",
+ "location": "[resourceGroup().location]",
+ "dependsOn": [
+ "[variables('vnetID')]",
+ "[variables('publicIPAddressName')]"
+ ],
+ "properties": {
+ "frontendIPConfigurations": [
+ {
+ "name": "[variables('lbFE')]",
+ "properties": {
+ "publicIPAddress": {
+ "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
+ }
+ }
+ }
+ ],
+ "backendAddressPools": [
+ {
+ "name": "LoadBalancerBackend"
+ }
+ ],
+ "loadBalancingRules": [
+ {
+ "name": "443_HTTPS_LBRule",
+ "properties": {
+ "frontendIPConfiguration": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/frontendIPConfigurations/', variables('lbFE'))]"
+ },
+ "backendAddressPool": {
+ "id": "[concat(variables('publiclbID'), '/backendAddressPools/LoadBalancerBackend')]"
+ },
+ "protocol": "Tcp",
+ "frontendPort": 443,
+ "backendPort": 443,
+ "enableFloatingIP": false,
+ "idleTimeoutInMinutes": 5,
+ "probe": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/probes/', variables('loadBalancerProbeName'))]"
+ }
+ }
+ },
+ {
+ "name": "9020_S3_HTTP",
+ "properties": {
+ "frontendIPConfiguration": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/frontendIPConfigurations/', variables('lbFE'))]"
+ },
+ "backendAddressPool": {
+ "id": "[concat(variables('publiclbID'), '/backendAddressPools/LoadBalancerBackend')]"
+ },
+ "protocol": "Tcp",
+ "frontendPort": 9020,
+ "backendPort": 9020,
+ "enableFloatingIP": false,
+ "idleTimeoutInMinutes": 5,
+ "probe": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/probes/', variables('loadBalancerProbeName'))]"
+ }
+ }
+ },
+ {
+ "name": "9021_S3_HTTPS",
+ "properties": {
+ "frontendIPConfiguration": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/frontendIPConfigurations/', variables('lbFE'))]"
+ },
+ "backendAddressPool": {
+ "id": "[concat(variables('publiclbID'), '/backendAddressPools/LoadBalancerBackend')]"
+ },
+ "protocol": "Tcp",
+ "frontendPort": 9021,
+ "backendPort": 9021,
+ "enableFloatingIP": false,
+ "idleTimeoutInMinutes": 5,
+ "probe": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/probes/', variables('loadBalancerProbeName'))]"
+ }
+ }
+ },
+ {
+ "name": "9022_ATMOS_HTTP",
+ "properties": {
+ "frontendIPConfiguration": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/frontendIPConfigurations/', variables('lbFE'))]"
+ },
+ "backendAddressPool": {
+ "id": "[concat(variables('publiclbID'), '/backendAddressPools/LoadBalancerBackend')]"
+ },
+ "protocol": "Tcp",
+ "frontendPort": 9022,
+ "backendPort": 9022,
+ "enableFloatingIP": false,
+ "idleTimeoutInMinutes": 5,
+ "probe": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/probes/', variables('loadBalancerProbeName'))]"
+ }
+ }
+ },
+ {
+ "name": "9023_ATMOS_HTTPS",
+ "properties": {
+ "frontendIPConfiguration": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/frontendIPConfigurations/', variables('lbFE'))]"
+ },
+ "backendAddressPool": {
+ "id": "[concat(variables('publiclbID'), '/backendAddressPools/LoadBalancerBackend')]"
+ },
+ "protocol": "Tcp",
+ "frontendPort": 9023,
+ "backendPort": 9023,
+ "enableFloatingIP": false,
+ "idleTimeoutInMinutes": 5,
+ "probe": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/probes/', variables('loadBalancerProbeName'))]"
+ }
+ }
+ },
+ {
+ "name": "9024_Swift_HTTP",
+ "properties": {
+ "frontendIPConfiguration": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/frontendIPConfigurations/', variables('lbFE'))]"
+ },
+ "backendAddressPool": {
+ "id": "[concat(variables('publiclbID'), '/backendAddressPools/LoadBalancerBackend')]"
+ },
+ "protocol": "Tcp",
+ "frontendPort": 9024,
+ "backendPort": 9024,
+ "enableFloatingIP": false,
+ "idleTimeoutInMinutes": 5,
+ "probe": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/probes/', variables('loadBalancerProbeName'))]"
+ }
+ }
+ },
+ {
+ "name": "9025_Swift_HTTPS",
+ "properties": {
+ "frontendIPConfiguration": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/frontendIPConfigurations/', variables('lbFE'))]"
+ },
+ "backendAddressPool": {
+ "id": "[concat(variables('publiclbID'), '/backendAddressPools/LoadBalancerBackend')]"
+ },
+ "protocol": "Tcp",
+ "frontendPort": 9025,
+ "backendPort": 9025,
+ "enableFloatingIP": false,
+ "idleTimeoutInMinutes": 5,
+ "probe": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/probes/', variables('loadBalancerProbeName'))]"
+ }
+ }
+ },
+ {
+ "name": "4443_Management_API",
+ "properties": {
+ "frontendIPConfiguration": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/frontendIPConfigurations/', variables('lbFE'))]"
+ },
+ "backendAddressPool": {
+ "id": "[concat(variables('publiclbID'), '/backendAddressPools/LoadBalancerBackend')]"
+ },
+ "protocol": "Tcp",
+ "frontendPort": 4443,
+ "backendPort": 4443,
+ "enableFloatingIP": false,
+ "idleTimeoutInMinutes": 5,
+ "probe": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/probes/', variables('loadBalancerProbeName'))]"
+ }
+ }
+ },
+ {
+ "name": "2049_mountd",
+ "properties": {
+ "frontendIPConfiguration": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/frontendIPConfigurations/', variables('lbFE'))]"
+ },
+ "backendAddressPool": {
+ "id": "[concat(variables('publiclbID'), '/backendAddressPools/LoadBalancerBackend')]"
+ },
+ "protocol": "Tcp",
+ "frontendPort": 2049,
+ "backendPort": 2049,
+ "enableFloatingIP": false,
+ "idleTimeoutInMinutes": 5,
+ "probe": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/probes/', variables('loadBalancerProbeName'))]"
+ }
+ }
+ },
+ {
+ "name": "111_portmap",
+ "properties": {
+ "frontendIPConfiguration": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/frontendIPConfigurations/', variables('lbFE'))]"
+ },
+ "backendAddressPool": {
+ "id": "[concat(variables('publiclbID'), '/backendAddressPools/LoadBalancerBackend')]"
+ },
+ "protocol": "Tcp",
+ "frontendPort": 111,
+ "backendPort": 111,
+ "enableFloatingIP": false,
+ "idleTimeoutInMinutes": 5,
+ "probe": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/probes/', variables('loadBalancerProbeName'))]"
+ }
+ }
+ },
+ {
+ "name": "10000_lockd",
+ "properties": {
+ "frontendIPConfiguration": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/frontendIPConfigurations/', variables('lbFE'))]"
+ },
+ "backendAddressPool": {
+ "id": "[concat(variables('publiclbID'), '/backendAddressPools/LoadBalancerBackend')]"
+ },
+ "protocol": "Tcp",
+ "frontendPort": 10000,
+ "backendPort": 10000,
+ "enableFloatingIP": false,
+ "idleTimeoutInMinutes": 5,
+ "probe": {
+ "id": "[concat(resourceId('Microsoft.Network/loadBalancers', variables('publicLBName')), '/probes/', variables('loadBalancerProbeName'))]"
+ }
+ }
+ }
+ ],
+ "probes": [
+ {
+ "name": "[variables('loadBalancerProbeName')]",
+ "properties": {
+ "protocol": "Tcp",
+ "port": 443,
+ "intervalInSeconds": 5,
+ "numberOfProbes": 2
+ }
+ }
+ ]
+ }
+ },
+ {
+ "apiVersion": "2015-06-15",
+ "type": "Microsoft.Network/loadBalancers/inboundNatRules",
+ "name": "[concat(variables('publicLBName'), '/ssh-VM', copyIndex(1))]",
+ "location": "[resourceGroup().location]",
+ "copy": {
+ "name": "lbNatLoop",
+ "count": "[parameters('numberOfInstances')]"
+ },
+ "dependsOn": [
+ "[concat('Microsoft.Network/loadBalancers/', variables('publiclbName'))]"
+ ],
+ "properties": {
+ "frontendIPConfiguration": {
+ "id": "[variables('publiclbFEConfigID')]"
+ },
+ "protocol": "tcp",
+ "frontendPort": "[copyIndex(2201)]",
+ "backendPort": 22,
+ "enableFloatingIP": false
+ }
+ },
+ {
+ "type": "Microsoft.Network/networkInterfaces",
+ "name": "[concat(variables('NICPrefix'), parameters('vmNamePrefix'), copyIndex(1))]",
+ "apiVersion": "2015-06-15",
+ "location": "[resourceGroup().location]",
+ "copy": {
+ "name": "nicLoop",
+ "count": "[parameters('numberOfInstances')]"
+ },
+ "dependsOn": [
+ "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]",
+ "[concat('Microsoft.Network/loadBalancers/', variables('publicLBName'))]",
+ "[concat('Microsoft.Network/loadBalancers/', variables('publicLBName'), '/inboundNatRules/', 'ssh-VM', copyIndex(1))]"
+ ],
+ "properties": {
+ "ipConfigurations": [
+ {
+ "name": "ipconfig1",
+ "properties": {
+ "privateIPAllocationMethod": "Static",
+ "privateIPAddress": "[concat('10.0.0.',copyIndex(4))]",
+ "subnet": {
+ "id": "[variables('subnetRef')]"
+ },
+ "loadBalancerBackendAddressPools": [
+ {
+ "id": "[concat(variables('publiclbID'), '/backendAddressPools/LoadBalancerBackend')]"
+ }
+ ],
+ "loadBalancerInboundNatRules": [
+ {
+ "id": "[concat(variables('publiclbID'), '/inboundNatRules/ssh-VM', copyIndex(1))]"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "type": "Microsoft.Compute/virtualMachines",
+ "name": "[concat(parameters('vmNamePrefix'), copyIndex(1))]",
+ "apiVersion": "2015-06-15",
+ "location": "[resourceGroup().location]",
+ "copy": {
+ "name": "virtualMachineLoop",
+ "count": "[parameters('numberOfInstances')]"
+ },
+ "dependsOn": [
+ "[concat('Microsoft.Storage/storageAccounts/',variables('storageName'))]",
+ "[resourceId('Microsoft.Network/networkInterfaces', concat(variables('NICPrefix'), parameters('vmNamePrefix'), copyIndex(1)))]",
+ "[concat('Microsoft.Compute/availabilitySets/', variables('availabilitySetName'))]"
+ ],
+ "properties": {
+ "availabilitySet": {
+ "id": "[resourceId('Microsoft.Compute/availabilitySets',variables('availabilitySetName'))]"
+ },
+ "hardwareProfile": {
+ "vmSize": "[variables('vmSize')]"
+ },
+ "osProfile": {
+ "computerName": "[concat(parameters('vmNamePrefix'), copyIndex(1))]",
+ "adminUsername": "[parameters('adminUsername')]",
+ "adminPassword": "[parameters('adminPassword')]"
+ },
+ "storageProfile": {
+ "imageReference": {
+ "publisher": "[parameters('osImagePublisher')]",
+ "offer": "[parameters('osImageOffer')]",
+ "sku": "[parameters('osImageSKU')]",
+ "version": "[variables('osImageVersion')]"
+ },
+ "osDisk": {
+ "name": "osdisk",
+ "vhd": {
+ "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageName')),providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).primaryEndpoints.blob, variables('vmContainerName'),'/', parameters('vmNamePrefix'), copyIndex(1),'-osdisk.vhd')]"
+ },
+ "caching": "ReadWrite",
+ "createOption": "FromImage"
+ },
+ "copy": [
+ {
+ "name": "dataDisks",
+ "count": "[parameters('dataDiskCount')]",
+ "input": {
+ "vhd": {
+ "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageName')),providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).primaryEndpoints.blob, variables('vmContainerName'),'/', parameters('vmNamePrefix'), copyIndex(1),'-data-', copyIndex('dataDisks'), '.vhd')]"
+ },
+ "name": "[concat(parameters('vmNamePrefix'), copyIndex(1),'-data-', copyIndex('dataDisks'), '.vhd')]",
+ "lun": "[copyIndex('dataDisks')]",
+ "createOption": "Empty",
+ "diskSizeGB": "[parameters('dataDiskSize')]"
+ }
+ }
+ ]
+ },
+ "networkProfile": {
+ "networkInterfaces": [
+ {
+ "id": "[resourceId('Microsoft.Network/networkInterfaces', concat(variables('NICPrefix'), parameters('vmNamePrefix'), copyIndex(1)))]"
+ }
+ ]
+ },
+ "diagnosticsProfile": {
+ "bootDiagnostics": {
+ "enabled": "true",
+ "storageUri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageName')),providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).primaryEndpoints.blob)]"
+ }
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Compute/virtualMachines/extensions",
+ "apiVersion": "2016-03-30",
+ "copy": {
+ "count": "[parameters('numberOfInstances')]",
+ "mode": "Parallel",
+ "name": "vmLoopNode"
+ },
+ "dependsOn": [
+ "[concat('Microsoft.Compute/virtualMachines/', parameters('VMNamePrefix'), parameters('numberOfInstances'))]"
+ ],
+ "location": "[resourceGroup().location]",
+ "name": "[concat(parameters('VMNamePrefix'), copyIndex(1), '/resetpassword')]",
+ "properties": {
+ "publisher": "Microsoft.OSTCExtensions",
+ "type": "VMAccessForLinux",
+ "typeHandlerVersion": "1.4",
+ "autoUpgradeMinorVersion": true,
+ "settings": {},
+ "protectedSettings": {
+ "username": "[parameters('adminUsername')]",
+ "password": "[parameters('adminPassword')]",
+ "reset_ssh": true
+ }
+ }
+ },
+ {
+ "apiVersion": "2016-03-30",
+ "copy": {
+ "count": "[sub(parameters('numberOfInstances'),1)]",
+ "mode": "Parallel",
+ "name": "vmLoopNode"
+ },
+ "dependsOn": [
+ "[concat('Microsoft.Compute/virtualMachines/', parameters('VMNamePrefix'), copyIndex(2),'/extensions/resetpassword')]"
+ ],
+ "location": "[resourceGroup().location]",
+ "name": "[concat(parameters('VMNamePrefix'), copyIndex(2), '/configurenode')]",
+ "properties": {
+ "publisher": "Microsoft.OSTCExtensions",
+ "settings": {
+ "commandToExecute": "./ecs_pre.sh",
+ "fileUris": [ "[concat(parameters('_artifactsLocation'),'/scripts/ecs_pre.sh')]" ]
+ },
+ "type": "CustomScriptForLinux",
+ "typeHandlerVersion": "1.4",
+ "autoUpgradeMinorVersion": true
+ },
+ "type": "Microsoft.Compute/virtualMachines/extensions"
+ },
+ {
+ "apiVersion": "2016-03-30",
+ "location": "[resourceGroup().location]",
+ "name": "[concat(parameters('VMNamePrefix'),'1','/installecs')]",
+ "dependsOn": [
+ "[concat('Microsoft.Compute/virtualMachines/', parameters('VMNamePrefix'),'1','/extensions/resetpassword')]"
+ ],
+ "properties": {
+ "publisher": "Microsoft.OSTCExtensions",
+ "settings": {
+ "fileUris": [
+ "[concat(parameters('_artifactsLocation'),'/scripts/ecs_pre.sh')]",
+ "[concat(parameters('_artifactsLocation'),'/scripts/deploy.yml')]",
+ "[concat(parameters('_artifactsLocation'),'/scripts/ecs.sh')]",
+ "[concat(parameters('_artifactsLocation'),'/scripts/ecs-installer.service')]"
+ ]
+ },
+ "protectedSettings": {
+ "commandToExecute": "[concat('bash ecs.sh --DISKNUM ',parameters('dataDiskCount'),' --NODENUM ',parameters('numberOfInstances'),' --NODEPREFIX ',parameters('VMNamePrefix'),' --ECSUSER ',parameters('adminUsername'),' --ECSPASSWORD ',parameters('adminPassword'))]"
+ },
+ "type": "CustomScriptForLinux",
+ "typeHandlerVersion": "1.4",
+ "autoUpgradeMinorVersion": true
+ },
+ "type": "Microsoft.Compute/virtualMachines/extensions"
+ }
+ ],
+ "outputs": {
+ "fileruris": {
+ "value": "reference('fileUris')",
+ "type": "string"
+ }
+ }
+
+
+
+}
diff --git a/301-availability-set-elastic-storage-ecs/azuredeploy.parameters.json b/301-availability-set-elastic-storage-ecs/azuredeploy.parameters.json
new file mode 100644
index 0000000..682076f
--- /dev/null
+++ b/301-availability-set-elastic-storage-ecs/azuredeploy.parameters.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "adminUsername": {
+ "value": "GEN-UNIQUE"
+ },
+ "adminPassword": {
+ "value": "GEN-PASSWORD"
+ },
+ "dnsLabelPrefix": {
+ "value": "GEN-UNIQUE"
+ }
+ }
+}
\ No newline at end of file
diff --git a/301-availability-set-elastic-storage-ecs/deploy-azureresourcegroup.ps1 b/301-availability-set-elastic-storage-ecs/deploy-azureresourcegroup.ps1
new file mode 100644
index 0000000..d7cd7d8
--- /dev/null
+++ b/301-availability-set-elastic-storage-ecs/deploy-azureresourcegroup.ps1
@@ -0,0 +1,118 @@
+#Requires -Version 3.0
+
+Param(
+ [string] [Parameter(Mandatory=$true)] $ResourceGroupLocation,
+ [string] $ResourceGroupName = 'ecs_compatible',
+ [switch] $UploadArtifacts,
+ [string] $StorageAccountName,
+ [string] $StorageContainerName = $ResourceGroupName.ToLowerInvariant() + '-stageartifacts',
+ [string] $TemplateFile = 'azuredeploy.json',
+ [string] $TemplateParametersFile = 'azuredeploy.parameters.json',
+ [string] $ArtifactStagingDirectory = '.',
+ [string] $DSCSourceFolder = 'DSC',
+ [switch] $ValidateOnly
+)
+
+try {
+ [Microsoft.Azure.Common.Authentication.AzureSession]::ClientFactory.AddUserAgent("VSAzureTools-$UI$($host.name)".replace(' ','_'), '3.0.0')
+} catch { }
+
+$ErrorActionPreference = 'Stop'
+Set-StrictMode -Version 3
+
+function Format-ValidationOutput {
+ param ($ValidationOutput, [int] $Depth = 0)
+ Set-StrictMode -Off
+ return @($ValidationOutput | Where-Object { $_ -ne $null } | ForEach-Object { @(' ' * $Depth + ': ' + $_.Message) + @(Format-ValidationOutput @($_.Details) ($Depth + 1)) })
+}
+
+$OptionalParameters = New-Object -TypeName Hashtable
+$TemplateFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateFile))
+$TemplateParametersFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateParametersFile))
+
+if ($UploadArtifacts) {
+ # Convert relative paths to absolute paths if needed
+ $ArtifactStagingDirectory = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $ArtifactStagingDirectory))
+ $DSCSourceFolder = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $DSCSourceFolder))
+
+ # Parse the parameter file and update the values of artifacts location and artifacts location SAS token if they are present
+ $JsonParameters = Get-Content $TemplateParametersFile -Raw | ConvertFrom-Json
+ if (($JsonParameters | Get-Member -Type NoteProperty 'parameters') -ne $null) {
+ $JsonParameters = $JsonParameters.parameters
+ }
+ $ArtifactsLocationName = '_artifactsLocation'
+ $ArtifactsLocationSasTokenName = '_artifactsLocationSasToken'
+ $OptionalParameters[$ArtifactsLocationName] = $JsonParameters | Select -Expand $ArtifactsLocationName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore
+ $OptionalParameters[$ArtifactsLocationSasTokenName] = $JsonParameters | Select -Expand $ArtifactsLocationSasTokenName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore
+
+ # Create DSC configuration archive
+ if (Test-Path $DSCSourceFolder) {
+ $DSCSourceFilePaths = @(Get-ChildItem $DSCSourceFolder -File -Filter '*.ps1' | ForEach-Object -Process {$_.FullName})
+ foreach ($DSCSourceFilePath in $DSCSourceFilePaths) {
+ $DSCArchiveFilePath = $DSCSourceFilePath.Substring(0, $DSCSourceFilePath.Length - 4) + '.zip'
+ Publish-AzureRmVMDscConfiguration $DSCSourceFilePath -OutputArchivePath $DSCArchiveFilePath -Force -Verbose
+ }
+ }
+
+ # Create a storage account name if none was provided
+ if ($StorageAccountName -eq '') {
+ $StorageAccountName = 'stage' + ((Get-AzureRmContext).Subscription.SubscriptionId).Replace('-', '').substring(0, 19)
+ }
+
+ $StorageAccount = (Get-AzureRmStorageAccount | Where-Object{$_.StorageAccountName -eq $StorageAccountName})
+
+ # Create the storage account if it doesn't already exist
+ if ($StorageAccount -eq $null) {
+ $StorageResourceGroupName = 'ARM_Deploy_Staging'
+ New-AzureRmResourceGroup -Location "$ResourceGroupLocation" -Name $StorageResourceGroupName -Force
+ $StorageAccount = New-AzureRmStorageAccount -StorageAccountName $StorageAccountName -Type 'Standard_LRS' -ResourceGroupName $StorageResourceGroupName -Location "$ResourceGroupLocation"
+ }
+
+ # Generate the value for artifacts location if it is not provided in the parameter file
+ if ($OptionalParameters[$ArtifactsLocationName] -eq $null) {
+ $OptionalParameters[$ArtifactsLocationName] = $StorageAccount.Context.BlobEndPoint + $StorageContainerName
+ }
+
+ # Copy files from the local storage staging location to the storage account container
+ New-AzureStorageContainer -Name $StorageContainerName -Context $StorageAccount.Context -ErrorAction SilentlyContinue *>&1
+
+ $ArtifactFilePaths = Get-ChildItem $ArtifactStagingDirectory -Recurse -File | ForEach-Object -Process {$_.FullName}
+ foreach ($SourcePath in $ArtifactFilePaths) {
+ Set-AzureStorageBlobContent -File $SourcePath -Blob $SourcePath.Substring($ArtifactStagingDirectory.length + 1) `
+ -Container $StorageContainerName -Context $StorageAccount.Context -Force
+ }
+
+ # Generate a 4 hour SAS token for the artifacts location if one was not provided in the parameters file
+ if ($OptionalParameters[$ArtifactsLocationSasTokenName] -eq $null) {
+ $OptionalParameters[$ArtifactsLocationSasTokenName] = ConvertTo-SecureString -AsPlainText -Force `
+ (New-AzureStorageContainerSASToken -Container $StorageContainerName -Context $StorageAccount.Context -Permission r -ExpiryTime (Get-Date).AddHours(4))
+ }
+}
+
+# Create or update the resource group using the specified template file and template parameters file
+New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force
+
+if ($ValidateOnly) {
+ $ErrorMessages = Format-ValidationOutput (Test-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName `
+ -TemplateFile $TemplateFile `
+ -TemplateParameterFile $TemplateParametersFile `
+ @OptionalParameters)
+ if ($ErrorMessages) {
+ Write-Output '', 'Validation returned the following errors:', @($ErrorMessages), '', 'Template is invalid.'
+ }
+ else {
+ Write-Output '', 'Template is valid.'
+ }
+}
+else {
+ New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) `
+ -ResourceGroupName $ResourceGroupName `
+ -TemplateFile $TemplateFile `
+ -TemplateParameterFile $TemplateParametersFile `
+ @OptionalParameters `
+ -Force -Verbose `
+ -ErrorVariable ErrorMessages
+ if ($ErrorMessages) {
+ Write-Output '', 'Template deployment returned the following errors:', @(@($ErrorMessages) | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") })
+ }
+}
\ No newline at end of file
diff --git a/301-availability-set-elastic-storage-ecs/images/ansible.png b/301-availability-set-elastic-storage-ecs/images/ansible.png
new file mode 100644
index 0000000..2f8fcc5
Binary files /dev/null and b/301-availability-set-elastic-storage-ecs/images/ansible.png differ
diff --git a/301-availability-set-elastic-storage-ecs/images/dashboard.png b/301-availability-set-elastic-storage-ecs/images/dashboard.png
new file mode 100644
index 0000000..28e2cb9
Binary files /dev/null and b/301-availability-set-elastic-storage-ecs/images/dashboard.png differ
diff --git a/301-availability-set-elastic-storage-ecs/images/deploytoasdk.png b/301-availability-set-elastic-storage-ecs/images/deploytoasdk.png
new file mode 100644
index 0000000..acbc6a2
Binary files /dev/null and b/301-availability-set-elastic-storage-ecs/images/deploytoasdk.png differ
diff --git a/301-availability-set-elastic-storage-ecs/images/deploytoasdkadmin.png b/301-availability-set-elastic-storage-ecs/images/deploytoasdkadmin.png
new file mode 100644
index 0000000..104e74f
Binary files /dev/null and b/301-availability-set-elastic-storage-ecs/images/deploytoasdkadmin.png differ
diff --git a/301-availability-set-elastic-storage-ecs/images/log.png b/301-availability-set-elastic-storage-ecs/images/log.png
new file mode 100644
index 0000000..e9ff37b
Binary files /dev/null and b/301-availability-set-elastic-storage-ecs/images/log.png differ
diff --git a/301-availability-set-elastic-storage-ecs/images/new_rg.png b/301-availability-set-elastic-storage-ecs/images/new_rg.png
new file mode 100644
index 0000000..94d251b
Binary files /dev/null and b/301-availability-set-elastic-storage-ecs/images/new_rg.png differ
diff --git a/301-availability-set-elastic-storage-ecs/images/rg_done.png b/301-availability-set-elastic-storage-ecs/images/rg_done.png
new file mode 100644
index 0000000..554ef96
Binary files /dev/null and b/301-availability-set-elastic-storage-ecs/images/rg_done.png differ
diff --git a/301-availability-set-elastic-storage-ecs/images/rg_parameter.png b/301-availability-set-elastic-storage-ecs/images/rg_parameter.png
new file mode 100644
index 0000000..0206580
Binary files /dev/null and b/301-availability-set-elastic-storage-ecs/images/rg_parameter.png differ
diff --git a/301-availability-set-elastic-storage-ecs/images/template_edit.png b/301-availability-set-elastic-storage-ecs/images/template_edit.png
new file mode 100644
index 0000000..e06c608
Binary files /dev/null and b/301-availability-set-elastic-storage-ecs/images/template_edit.png differ
diff --git a/301-availability-set-elastic-storage-ecs/images/template_load.png b/301-availability-set-elastic-storage-ecs/images/template_load.png
new file mode 100644
index 0000000..e0f8a67
Binary files /dev/null and b/301-availability-set-elastic-storage-ecs/images/template_load.png differ
diff --git a/301-availability-set-elastic-storage-ecs/images/template_new.png b/301-availability-set-elastic-storage-ecs/images/template_new.png
new file mode 100644
index 0000000..a854496
Binary files /dev/null and b/301-availability-set-elastic-storage-ecs/images/template_new.png differ
diff --git a/301-availability-set-elastic-storage-ecs/metadata.json b/301-availability-set-elastic-storage-ecs/metadata.json
new file mode 100644
index 0000000..eabdbd6
--- /dev/null
+++ b/301-availability-set-elastic-storage-ecs/metadata.json
@@ -0,0 +1,7 @@
+{
+ "itemDisplayName": "301-availability-set-elastic-storage-ecs",
+ "description": "deploys a multinote elastic cloud storage into an availability set",
+ "summary": "this template automates the deployment of dellemc ecs elastic cloud storage community edition",
+ "githubUsername": "bottkars",
+ "dateUpdated": "2018-03-07"
+ }
\ No newline at end of file
diff --git a/301-availability-set-elastic-storage-ecs/scripts/deploy.yml b/301-availability-set-elastic-storage-ecs/scripts/deploy.yml
new file mode 100644
index 0000000..58e58d2
--- /dev/null
+++ b/301-availability-set-elastic-storage-ecs/scripts/deploy.yml
@@ -0,0 +1,205 @@
+# deploy.yml reference implementation v2.6.0
+
+# [Optional]
+# By changing the license_accepted boolean value to "true" you are
+# declaring your agreement to the terms of the license agreement
+# contained in the license.txt file included with this software
+# distribution.
+licensing:
+ license_accepted: true
+
+autonames:
+ custom: [myhosts]
+# - ecs04
+# - ecs05
+# - ecs06
+
+# [Required]
+# Deployment facts reference
+facts:
+
+ # [Required]
+ # Node IP or resolvable hostname from which installations will be launched
+ # The only supported configuration is to install from the same node as the
+ # bootstrap.sh script is run.
+ # NOTE: if the install node is to be migrated into an island environment,
+ # the hostname or IP address listed here should be the one in the
+ # island environment.
+ install_node: 10.0.0.4
+
+ # [Required]
+ # IPs of machines that will be whitelisted in the firewall and allowed
+ # to access management ports of all nodes. If this is set to the
+ # wildcard (0.0.0.0/0) then anyone can access management ports.
+ management_clients:
+ - 0.0.0.0/0
+
+ # [Required]
+ # These credentials must be the same across all nodes. Ansible uses these credentials to
+ # gain initial access to each node in the deployment and set up ssh public key authentication.
+ # If these are not correct, the deployment will fail.
+ ssh_defaults:
+ # [Required]
+ # Username to use when logging in to nodes
+ ssh_username: ECSUSER
+ # [Required]
+ # Password to use with SSH login
+ # *** Set to same value as ssh_username to enable SSH public key authentication ***
+ ssh_password: ECSPASSWORD
+ # [Required when enabling SSH public key authentication]
+ # Password to give to sudo when gaining root access.
+ ansible_become_pass: ECSPASSWORD
+ # [Required]
+ # Select the type of crypto to use when dealing with ssh public key
+ # authentication. Valid values here are:
+ # - "rsa" (Default)
+ # - "ed25519"
+ ssh_crypto: rsa
+
+ # [Required]
+ # Environment configuration for this deployment.
+ node_defaults:
+ dns_domain: local
+ dns_servers:
+ - 168.63.129.16
+ ntp_servers:
+ - 88.198.197.205
+ #
+ # [Optional]
+ # VFS path to source of randomness
+ # Defaults to /dev/urandom for speed considerations. If you prefer /dev/random, put that here.
+ # If you have a /dev/srandom implementation or special entropy hardware, you may use that too
+ # so long as it implements a /dev/random type device.
+ entropy_source: /dev/urandom
+ #
+ # [Optional]
+ # Picklist for node names.
+ # Available options:
+ # - "moons" (ECS CE default)
+ # - "cities" (ECS SKU-flavored)
+ # - "custom" (uncomment and use the top-level autonames block to define these)
+ autonaming: custom
+
+ #
+ # [Optional]
+ # If your ECS comes with differing default credentials, you can specify those here
+ # ecs_root_user: root
+ # ecs_root_pass: ChangeMe
+
+ # [Optional]
+ # Storage pool defaults. Configure to your liking.
+ # All block devices that will be consumed by ECS on ALL nodes must be listed under the
+ # ecs_block_devices option. This can be overridden by the storage pool configuration.
+ # At least ONE (1) block device is REQUIRED for a successful install. More is better.
+ storage_pool_defaults:
+ is_cold_storage_enabled: false
+ is_protected: false
+ description: Default storage pool description
+ ecs_block_devices: [mydisks]
+ # [Required]
+ # Storage pool layout. You MUST have at least ONE (1) storage pool for a successful install.
+ storage_pools:
+ - name: sp1
+ members: [mymembers]
+ options:
+ is_protected: false
+ is_cold_storage_enabled: false
+ description: My First SP
+ ecs_block_devices: [mydisks]
+
+ # [Optional]
+ # VDC defaults. Configure to your liking.
+ virtual_data_center_defaults:
+ description: Default virtual data center description
+
+ # [Required]
+ # Virtual data center layout. You MUST have at least ONE (1) VDC for a successful install.
+ # Multi-VDC deployments are not yet implemented
+ virtual_data_centers:
+ - name: vdc1
+ members:
+ - sp1
+ options:
+ description: AzureStack VDC
+
+ # [Optional]
+ # Replication group defaults. Configure to your liking.
+ replication_group_defaults:
+ description: Default replication group description
+ enable_rebalancing: true
+ allow_all_namespaces: true
+ is_full_rep: false
+
+ # [Optional, required for namespaces]
+ # Replication group layout. You MUST have at least ONE (1) RG to provision namespaces.
+ replication_groups:
+ - name: rg1
+ members:
+ - vdc1
+ options:
+ description: AzureStack RG
+ enable_rebalancing: true
+ allow_all_namespaces: true
+ is_full_rep: false
+
+ # [Optional]
+ # Management User defaults
+ management_user_defaults:
+ is_system_admin: false
+ is_system_monitor: false
+
+ # [Optional]
+ # Management Users
+ management_users:
+ - username: admin1
+ password: ChangeMe
+ options:
+ is_system_admin: true
+ - username: monitor1
+ password: ChangeMe
+ options:
+ is_system_monitor: true
+
+ # [Optional]
+ # Namespace defaults
+ namespace_defaults:
+ is_stale_allowed: false
+ is_compliance_enabled: false
+
+ # [Optional]
+ # Namespace layout
+ namespaces:
+ - name: ns1
+ replication_group: rg1
+ administrators:
+ - root
+ options:
+ is_stale_allowed: false
+ is_compliance_enabled: false
+
+ # [Optional]
+ # Object User defaults
+ object_user_defaults:
+ # Comma-separated list of Swift authorization groups
+ swift_groups_list:
+ - users
+ # Lifetime of S3 secret key in minutes
+ s3_expiry_time: 2592000
+
+ # [Optional]
+ # Object Users
+ object_users:
+ - username: object_admin1
+ namespace: ns1
+ options:
+ swift_password: ChangeMe
+ swift_groups_list:
+ - admin
+ - users
+ s3_secret_key: ChangeMeChangeMeChangeMeChangeMeChangeMe
+ s3_expiry_time: 2592000
+ - username: object_user1
+ namespace: ns1
+ options:
+ swift_password: ChangeMe
+ s3_secret_key: ChangeMeChangeMeChangeMeChangeMeChangeMe
diff --git a/301-availability-set-elastic-storage-ecs/scripts/ecs-installer.service b/301-availability-set-elastic-storage-ecs/scripts/ecs-installer.service
new file mode 100644
index 0000000..71da17e
--- /dev/null
+++ b/301-availability-set-elastic-storage-ecs/scripts/ecs-installer.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=EMC ECS Installer start
+After=docker.service
+[Service]
+Type=simple
+Restart=no
+ExecStart=/root/ecs.sh
+ExecStop=
+[Install]
+WantedBy=default.target
\ No newline at end of file
diff --git a/301-availability-set-elastic-storage-ecs/scripts/ecs.sh b/301-availability-set-elastic-storage-ecs/scripts/ecs.sh
new file mode 100644
index 0000000..d8de4e9
--- /dev/null
+++ b/301-availability-set-elastic-storage-ecs/scripts/ecs.sh
@@ -0,0 +1,141 @@
+#!/bin/bash
+edit_template(){
+# this creates an ansible list for disks in forma ['/dev/sdc',...]
+chars=( {c..z} )
+n=$1
+for ((i=0; i> /root/install.log
+sed -i -e 's/mydisks/'"$disklist"'/g' /root/ECS-CommunityEdition/deploy.yml
+
+
+# this creates an ansible list for nodes in formar ['ecs1',...]
+n=$2
+for ((i=1; i<=n; i++))
+do
+ hosts[i]=$3$i
+done
+hostlist="$(echo "'${hosts[*]}'" | tr ' ' ,)"
+hostlist=${hostlist//","/"','"}
+echo "replacing myhosts with hostlist $hostlist" >> /root/install.log
+sed -i -e 's/myhosts/'"$hostlist"'/g' /root/ECS-CommunityEdition/deploy.yml
+
+# this creates an ansible list for members in format ['10.0.0',...]
+n=$2
+for ((i=4; i<=n+3; i++))
+do
+ members[i]=10.0.0.$i
+done
+memberlist="$(echo "'${members[*]}'" | tr ' ' ,)"
+memberlist=${memberlist//","/"','"}
+echo "replacing mymembers with memberlist $memberlist" >> /root/install.log
+sed -i -e 's/mymembers/'"$memberlist"'/g' /root/ECS-CommunityEdition/deploy.yml
+echo "replacing ECSUSER with $4" >> /root/install.log
+sed -i -e 's/ECSUSER/'"$4"'/g' /root/ECS-CommunityEdition/deploy.yml
+echo "replacing ECSPASSWORD with $5" >> /root/install.log
+sed -i -e 's/ECSPASSWORD/'"$5"'/g' /root/ECS-CommunityEdition/deploy.yml
+
+}
+before_reboot(){
+yum install git firewalld -y
+# for openlogic
+# update, we handle this with reset password in extension
+# sed -i -e 's/#GatewayPorts no/GatewayPorts yes/g' /etc/ssh/sshd_config
+# sed -i -e 's/#PasswordAuthentication yes/PasswordAuthentication yes/g' /etc/ssh/sshd_config
+systemctl disable rpcbind
+cp ecs.sh /root/
+chmod +X /root/ecs.sh
+chmod 755 /root/ecs.sh
+cp ecs-installer.service /etc/systemd/system/
+systemctl daemon-reload
+systemctl enable ecs-installer.service
+git clone https://github.com/emcecs/ecs-communityedition /root/ECS-CommunityEdition
+cp deploy.yml /root/ECS-CommunityEdition
+edit_template $1 $2 $3 $4 $5
+echo "$1 $2 $3 $4 $5" >> /root/parameters.txt
+myreboot &
+echo $?
+}
+myreboot () {
+ sleep 60
+ shutdown -r now
+}
+after_bootstrap(){
+ cd /root/ECS-CommunityEdition
+ /usr/bin/step1 |& tee -a /root/install.log
+ /usr/bin/step2 |& tee -a /root/install.log
+ echo "finished customizing at $(date)" |& tee -a /root/install.log
+}
+
+after_waagent(){
+ cd /root/ECS-CommunityEdition
+ ./bootstrap.sh -c ./deploy.yml -y |& tee -a /root/install.log
+}
+
+POSITIONAL=()
+while [[ $# -gt 0 ]]
+do
+key="$1"
+
+case $key in
+ -d|--DISKNUM)
+ DISKNUM="$2"
+ shift # past argument
+ shift # past value
+ ;;
+ -n|--NODENUM)
+ NODENUM="$2"
+ shift # past argument
+ shift # past value
+ ;;
+ -u|--ECSUSER)
+ ECSUSER="$2"
+ shift # past argument
+ shift # past value
+ ;;
+ -s|--ECSPASSWORD)
+ ECSPASSWORD="$2"
+ shift # past argument
+ shift # past value
+ ;;
+ -p|--NODEPREFIX)
+ NODEPREFIX="$2"
+ shift # past argument
+ ;;
+ *) # unknown option
+ POSITIONAL+=("$1") # save it in an array for later
+ shift # past argument
+ ;;
+esac
+done
+set -- "${POSITIONAL[@]}" # restore positional parameters
+
+echo "DISKNUM = ${DISKNUM}" >> /root/install.log
+echo "NODENUM = ${NODENUM}" >> /root/install.log
+echo "NODEPREFIX = ${NODEPREFIX}" >> /root/install.log
+echo "ECSUSER = ${ECSUSER}" >> /root/install.log
+echo "ECSPASSWORD = ${ECSPASSWORD}" >> /root/install.log
+if [[ -n $1 ]]; then
+ echo "Last line of file specified as non-opt/last argument:"
+ tail -1 "$1"
+fi
+
+if [ -f /root/rebooting-for-bootstrap ]; then
+ after_bootstrap
+ rm /root/rebooting-for-bootstrap
+ systemctl disable ecs-installer.service
+elif [ -f /root/rebooting-for-waagent ]; then
+ rm /root/rebooting-for-waagent
+ touch /root/rebooting-for-bootstrap
+ after_waagent
+else
+ touch /root/rebooting-for-waagent
+ echo "$DISKNUM $NODENUM $NODEPREFIX $ECSUSER $ECSPASSWORD" >> /root/parameters.txt
+ before_reboot $DISKNUM $NODENUM $NODEPREFIX $ECSUSER $ECSPASSWORD
+fi
+
diff --git a/301-availability-set-elastic-storage-ecs/scripts/ecs_pre.sh b/301-availability-set-elastic-storage-ecs/scripts/ecs_pre.sh
new file mode 100644
index 0000000..11447b9
--- /dev/null
+++ b/301-availability-set-elastic-storage-ecs/scripts/ecs_pre.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+# yum update -y &>> /root/install.log
+myreboot () {
+ sleep 60
+ shutdown -r now
+}
+yum install firewalld libselinux-python docker ntp pigz python-docker-py -y
+# for openlogic
+# update, we handle this with reset password in extension
+# sed -i -e 's/#GatewayPorts no/GatewayPorts yes/g' /etc/ssh/sshd_config
+# sed -i -e 's/#PasswordAuthentication yes/PasswordAuthentication yes/g' /etc/ssh/sshd_config
+yum remove *nfs* -y
+systemctl disable rpcbind
+myreboot &
+echo $?
diff --git a/bootstorm-vm-boot-time/VMBootAll.ps1 b/bootstorm-vm-boot-time/VMBootAll.ps1
new file mode 100644
index 0000000..8d0878f
--- /dev/null
+++ b/bootstorm-vm-boot-time/VMBootAll.ps1
@@ -0,0 +1,208 @@
+#
+# Copyright="?Microsoft Corporation. All rights reserved."
+#
+
+Configuration ConfigureVMBootAll
+{
+ param (
+ [Parameter(Mandatory)]
+ [PSCredential]$AzureAccountCreds,
+ [Parameter(Mandatory)]
+ [string]$TenantId,
+ [Parameter(Mandatory)]
+ [string]$Location,
+ [Parameter(Mandatory)]
+ [string]$VMName,
+ [Parameter(Mandatory)]
+ [int32]$VMCount,
+ [Parameter(Mandatory)]
+ [PSCredential]$VMAdminCreds,
+ [Parameter(Mandatory)]
+ [string]$AzureStorageAccount,
+ [Parameter(Mandatory)]
+ [string]$AzureStorageAccessKey,
+ [Parameter(Mandatory)]
+ [string]$AzureStorageEndpoint,
+ [Parameter(Mandatory)]
+ [string]$AzureSubscription
+ )
+
+ # Turn off private firewall
+ netsh advfirewall set privateprofile state off
+ # Get full path and name of the script being run
+ $PSPath = $PSCommandPath
+
+ # Local file storage location
+ $localPath = "$env:SystemDrive"
+
+ # Log file
+ $logFileName = "VMBootDSC.log"
+ $logFilePath = "$localPath\$logFileName"
+
+ $AzureAccountUsername = $AzureAccountCreds.UserName
+ $AzureAccountPasswordBSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($AzureAccountCreds.Password)
+ $AzureAccountPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($AzureAccountPasswordBSTR)
+
+ $VMAdminUserName = $VMAdminCreds.UserName
+ $VMAdminPasswordBSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($VMAdminCreds.Password)
+ $VMAdminPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($VMAdminPasswordBSTR)
+
+ # DSC Script Resource - VM Bootstorm
+ Script VMBAll
+ {
+ TestScript = { (Get-ScheduledTask -TaskName "VMBootAll" -ErrorAction SilentlyContinue) -ne $null }
+
+ GetScript = { return @{"TaskName" = "VMBootAll"} }
+
+ SetScript = {
+ function IsAzureStack
+ {
+ param
+ (
+ [Parameter(Mandatory)]
+ $Location
+ )
+ if($Location -eq "AzureCloud" -or $Location -eq "AzureChinaCloud" -or $Location -eq "AzureUSGovernment" -or $Location -eq "AzureGermanCloud") {
+ return $false
+ }
+ return $true
+
+ }
+
+ # Azure uses AzureAdApplicationId and AzureAdApplicationPassword values as AzureUserName and AzurePassword parameters respectively
+ # AzureStack uses tenant UserName and Password values as AzureUserName and AzurePassword parameters respectively
+ $azureUsername = $using:AzureAccountUsername
+ $azurePassword = $using:AzureAccountPassword
+ $tenant = $using:TenantId
+ $location = $using:Location
+ $vmName = $using:VMName
+ $vmCount = $using:VMCount
+ # Scheduled task execution credentials without any logged-in user
+ $vmAdminUserName = $using:VMAdminUserName
+ $vmAdminPassword = $using:VMAdminPassword
+ $storageAccount = $using:AzureStorageAccount
+ $storageKey = $using:AzureStorageAccessKey
+ $storageEndpoint = $using:AzureStorageEndpoint
+ $subscription = $using:AzureSubscription
+
+ $storageEndpoint = $storageEndpoint.ToLower()
+ # Prepare storage context to upload results to Azure storage table
+ if($storageEndpoint.Contains("blob")) {
+ $storageEndpoint = $storageEndpoint.Substring($storageEndpoint.LastIndexOf("blob") + "blob".Length + 1)
+ $storageEndpoint = $storageEndpoint.replace("/", "")
+ # Remove port number from storage endpoint e.g. http://saiostorm.blob.azurestack.local:3456/
+ if($storageEndpoint.Contains(":")) {
+ $storageEndpoint = $storageEndpoint.Substring(0, $storageEndpoint.LastIndexOf(":"))
+ }
+ }
+
+ "Storage endpoint given: $using:AzureStorageEndpoint Storage endpoint passed to script: $storageEndpoint" | Tee-Object -FilePath $logFilePath -Append
+
+ $psPath = $using:PSPath
+ $psScriptDir = Split-Path -Parent -Path $psPath
+ $psScriptName = "VMBootAllScript.ps1"
+ $psScriptPath = "$psScriptDir\$psScriptName"
+ $action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument "& $psScriptPath -azureUserName $azureUsername -azurePassword $azurePassword -tenant $tenant -location $location -vmName $vmName -vmCount $vmCount -azureStorageAccount $storageAccount -azureStorageAccessKey $storageKey -azureStorageEndpoint $storageEndpoint -AzureSubscription $subscription -Verbose" -ErrorAction Ignore
+ $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(5) -RepetitionInterval (New-TimeSpan -Minutes 10) -RepetitionDuration (New-TimeSpan -Minutes 30) -ErrorAction Ignore
+ $settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -RunOnlyIfNetworkAvailable -DontStopOnIdleEnd -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 2) -ErrorAction Ignore
+ Unregister-ScheduledTask -TaskName "VMBootAll" -Confirm:0 -ErrorAction Ignore
+ Register-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -TaskName "VMBootAll" -Description "VMBootstorm" -User $vmAdminUserName -Password $vmAdminPassword -RunLevel Highest -ErrorAction Ignore
+
+ ######################
+ ### AZURE RM SETUP ###
+ ######################
+ $logFilePath = $using:logFilePath
+ $azureStackSdkPath = $using:azureStackSdkPath
+ $azureStackInstallerPath = $using:azureStackInstallerPath
+
+ if((IsAzureStack -Location $location) -eq $true) {
+ # Ignore server certificate errors to avoid https://api.azurestack.local/ certificate error
+ add-type @"
+ using System.Net;
+ using System.Security.Cryptography.X509Certificates;
+ public class TrustAllCertsPolicy : ICertificatePolicy {
+ public bool CheckValidationResult(
+ ServicePoint srvPoint, X509Certificate certificate,
+ WebRequest request, int certificateProblem) {
+ return true;
+ }
+ }
+"@
+ [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
+ Write-Warning -Message "CertificatePolicy set to ignore all server certificate errors"
+
+ $count = 0
+ $installFinished = $false
+ while (!$installFinished -and $count -lt 5) {
+ try {
+ Install-Module -Name AzureRM -RequiredVersion 1.2.10 -Scope AllUsers -ErrorAction Stop -Confirm:0
+ $installFinished = $true
+ }
+ catch {
+ $count++
+ Start-Sleep -Seconds 10
+ Write-Warning "Could not install AzureRM module. Trying again ($count / 5)"
+ }
+ }
+
+ # Import Azure Resource Manager PS module if already present
+ try {
+ "Importing Azure module" | Tee-Object -FilePath $logFilePath -Append
+ Import-Module AzureRm -ErrorAction Stop | Out-Null
+ } catch [Exception] {
+ "Cannot import AzureRm module. Cannot proceed further without AzureRm module. Exception: $_" | Tee-Object -FilePath $logFilePath -Append
+ }
+ }
+ # Azure Cloud
+ else {
+ # Import Azure Resource Manager PS module if already present
+ try {
+ "Importing Azure module" | Tee-Object -FilePath $logFilePath -Append
+ Import-Module AzureRm -ErrorAction Stop | Out-Null
+ }
+ # Install Azure Resource Manager PS module
+ catch {
+ # Suppress prompts
+ $ConfirmPreference = 'None'
+ "Cannot import Azure module, proceeding with installation" | Tee-Object -FilePath $logFilePath -Append
+
+ # Install AzureRM
+ try {
+ Get-PackageProvider -Name nuget -ForceBootstrap -Force | Out-Null
+ Install-Module AzureRm -repository PSGallery -Force -Confirm:0 -Scope AllUsers | Out-Null
+ }
+ catch {
+ "Installation of AzureRm module failed." | Tee-Object -FilePath $logFilePath -Append
+ }
+
+ # Import AzureRM
+ try {
+ Import-Module AzureRm -ErrorAction Stop | Out-Null
+ } catch {
+ "Cannot import Azure module. Try importing after restart PowerShell instance. Cannot proceed further without Azure module." | Tee-Object -FilePath $logFilePath -Append
+ }
+ }
+ }
+ Disable-AzureRmDataCollection
+
+ # Test status file
+ $statusFilePath = "$localPath\VMBootStatus.log"
+
+ # Wait for VM bootstorm test to finish if vm count is small and DSC can finish within 90 minutes
+ $waitCount = 75
+ while(((Test-Path $statusFilePath) -eq $false) -and ($waitCount -gt 0)) {
+ "Waiting for bootstorm test to finish $waitCount" | Tee-Object -FilePath $logFilePath -Append
+ Start-Sleep -Seconds 60
+ $waitCount--
+ }
+
+ if((Test-Path $statusFilePath) -eq $false) {
+ "Bootstorm test finished successfully." | Tee-Object -FilePath $logFilePath -Append
+ }
+ else {
+ "Bootstorm test not finished successfully." | Tee-Object -FilePath $logFilePath -Append
+ }
+ }
+ }
+}
+
diff --git a/bootstorm-vm-boot-time/VMBootAll.zip b/bootstorm-vm-boot-time/VMBootAll.zip
index cc562c2..e58bc21 100644
Binary files a/bootstorm-vm-boot-time/VMBootAll.zip and b/bootstorm-vm-boot-time/VMBootAll.zip differ
diff --git a/bootstorm-vm-boot-time/VMBootAllScript.ps1 b/bootstorm-vm-boot-time/VMBootAllScript.ps1
new file mode 100644
index 0000000..1b87d59
--- /dev/null
+++ b/bootstorm-vm-boot-time/VMBootAllScript.ps1
@@ -0,0 +1,647 @@
+#
+# Copyright="?Microsoft Corporation. All rights reserved."
+#
+
+param (
+ [Parameter(Mandatory)]
+ [string]$AzureUsername,
+ [Parameter(Mandatory)]
+ [string]$AzurePassword,
+ [Parameter(Mandatory)]
+ [string]$TenantId,
+ [Parameter(Mandatory)]
+ [string]$Location,
+ [Parameter(Mandatory)]
+ [string]$VMName,
+ [Parameter(Mandatory)]
+ [int32]$VMCount,
+ [Parameter(Mandatory)]
+ [string]$AzureStorageAccount,
+ [Parameter(Mandatory)]
+ [string]$AzureStorageAccessKey,
+ [Parameter(Mandatory)]
+ [string]$AzureStorageEndpoint,
+ [Parameter(Mandatory)]
+ [string]$AzureSubscription
+)
+
+"Importing Azure module" | Tee-Object -FilePath $logFilePath -Append
+Import-Module AzureRm -ErrorAction Stop | Out-Null
+
+function VMBootAll {
+
+ # Azure uses AzureAdApplicationId and AzureAdApplicationPassword values as AzureUserName and AzurePassword parameters respectively
+ # AzureStack uses tenant UserName and Password values as AzureUserName and AzurePassword parameters respectively
+ $azureUsername = $AzureUsername
+ $azurePassword = $AzurePassword
+ $tenant = $TenantId
+ $location = $Location
+ $vmName = $VMName
+ $vmCount = $VMCount
+ $storageAccountName = $AzureStorageAccount
+ $storageAccountKey = $AzureStorageAccessKey
+ $storageEndpoint = $AzureStorageEndpoint
+
+ # Local file storage location
+ $localPath = "$env:SystemDrive"
+
+ # Log file
+ $logFileName = "VMBoot.log"
+ $logFilePath = "$localPath\$logFileName"
+
+ # Test status file
+ $statusFilePath = "$localPath\VMBootStatus.log"
+
+ if(Test-Path $logFilePath) {
+ "Log file $logFilePath already exists. Skipped text execution" | Tee-Object -FilePath $logFilePath -Append
+ return
+ }
+
+ # Turn off private firewall
+ netsh advfirewall set privateprofile state off
+
+ # PS Credentials
+ $pw = ConvertTo-SecureString -AsPlainText -Force -String $azurePassword
+ $pscred = New-Object -TypeName System.Management.Automation.PSCredential -argumentlist $azureUsername,$pw
+ if($pscred -eq $null) {
+ "Powershell Credential object is null. Cannot proceed." | Tee-Object -FilePath $logFilePath -Append
+ return
+ }
+ $azureCreds = Get-Credential -Credential $pscred
+ if($azureCreds -eq $null) {
+ "Get-Credential returned null. Cannot proceed." | Tee-Object -FilePath $logFilePath -Append
+ return
+ }
+
+ #TODO Query the ARM endpoint or parameterize it
+ $resourceManagerEndpoint = $("https://management.$storageEndpoint".ToLowerInvariant())
+ $endptres = Invoke-RestMethod "${ResourceManagerEndpoint}/metadata/endpoints?api-version=1.0"
+
+ $activeDirectoryServiceEndpointResourceId = $($endptres.authentication.audiences[0])
+ $aadTenantId = $tenant
+ $activeDirectoryEndpoint = $($endptres.authentication.loginEndpoint)
+ $activeDirectoryEndpoint = $($endptres.authentication.loginEndpoint).TrimEnd("/") + "/"
+ $galleryEndpoint = $endptres.galleryEndpoint
+ $graphEndpoint = $endptres.graphEndpoint
+ $storageEndpointSuffix="$($storageEndpoint)".ToLowerInvariant()
+ $azureKeyVaultDnsSuffix="vault.$($storageEndpoint)".ToLowerInvariant()
+
+ # Get AzureToken
+ $resourceManagerEndpoint = $("https://management.$storageEndpoint".ToLowerInvariant())
+ $endptres = Invoke-RestMethod "${ResourceManagerEndpoint}/metadata/endpoints?api-version=1.0"
+
+ $activeDirectoryServiceEndpointResourceId = $($endptres.authentication.audiences[0])
+ $aadTenantId = $tenant
+ $activeDirectoryEndpoint = $($endptres.authentication.loginEndpoint).TrimEnd("/") + "/"
+ $clientId = "1950a258-227b-4e31-a9cf-717495945fc2"
+
+ $contextAuthorityEndpoint = ([System.IO.Path]::Combine($activeDirectoryEndpoint, $aadTenantId)).Replace('\','/')
+ $authContext = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext($contextAuthorityEndpoint, $false)
+ $userCredential = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential($azureCreds.UserName, $azureCreds.Password)
+
+ $azureToken = ($authContext.AcquireToken($activeDirectoryServiceEndpointResourceId, $clientId, $userCredential)).AccessToken
+
+ if(!$azureToken)
+ {
+ "Error: Unable to generate Authorization token" | Tee-Object -FilePath $logFilePath -Append
+
+ return;
+ }
+
+ # AzureStack
+ # determine proper way to detect we are running on AzureStack
+ if((IsAzureStack -Location $location) -eq $true) {
+ # Authenticate to AzureStack
+ try {
+ $envName = "AzureStackCloud"
+
+ Add-AzureRmEnvironment -Name ($envName) `
+ -ActiveDirectoryEndpoint ($activeDirectoryEndpoint) `
+ -ActiveDirectoryServiceEndpointResourceId ($activeDirectoryServiceEndpointResourceId) `
+ -ResourceManagerEndpoint ($resourceManagerEndpoint) `
+ -GalleryEndpoint ($galleryEndpoint) `
+ -GraphEndpoint ($graphEndpoint) `
+ -StorageEndpointSuffix ($storageEndpointSuffix) `
+ -AzureKeyVaultDnsSuffix ($azureKeyVaultDnsSuffix) | Out-Null
+
+ Add-AzureRmAccount -EnvironmentName $envName -Credential $azureCreds -TenantId $aadTenantId | Out-Null
+ }
+ catch [Exception] {
+ "Failed to authenticate with Azure Stack. Exception details $_" | Tee-Object -FilePath $logFilePath -Append
+ }
+ }
+ # AzureCloud
+ else {
+ if($azureCreds -eq $null) {
+ "Powershell Credential object is null. Cannot proceed." | Tee-Object -FilePath $logFilePath -Append
+ return
+ }
+ # Authenticate to Azure using AzureAdApplication
+ try {
+ Add-AzureRmAccount -Credential $azureCreds -ServicePrincipal -Tenant $tenant
+ }
+ catch [Exception] {
+ "Failed to authenticate with Azure. Exception details $_" | Tee-Object -FilePath $logFilePath -Append
+ }
+ }
+
+ try {
+ "Subscription ID $AzureSubscription" | Tee-Object -FilePath $logFilePath -Append
+ "Setting subscription" | Tee-Object -FilePath $logFilePath -Append
+ Select-AzureRmSubscription -SubscriptionId $AzureSubscription | Tee-Object -FilePath $logFilePath -Append
+ }
+ catch {
+ "Failed to select Azure subscription (id: $AzureSubscription)" | Tee-Object -FilePath $logFilePath -Append
+ "Exception: $_" | Tee-Object -FilePath $logFilePath -Append
+ }
+
+ ##############################
+ ### VM PRE-BOOTSTORM SETUP ###
+ ##############################
+ # Get VMs
+ "Getting VMs" | Tee-Object -FilePath $logFilePath -Append
+
+ $vms = Get-AzureRmVM | Where-Object {$_.Name -match $vmName}
+
+ $resourceGroupName = $null
+ # Turn off all VMs (except jump box VM which stores results)
+ foreach($vm in $vms) {
+ $_vmName = $vm.Name
+ # All VMs except jump box VM
+ if($_vmName -match "[0-9]$") {
+ $_date = Get-Date -Format hh:mmtt
+ $logFile = $localPath + "\VmBoot_" + $_vmName + ".log"
+ "Turning off VM $_vmName in parallel at $_date" | Tee-Object -FilePath $logFilePath -Append
+
+ Start-Job -ScriptBlock {
+ param($_vmName,$_resourceGroupName,$location,$logFile,$azureSubscription,$armEndpoint,$token)
+
+ # Ignore server certificate errors to avoid https://api.azurestack.local/ certificate error
+ add-type @"
+ using System.Net;
+ using System.Security.Cryptography.X509Certificates;
+ public class TrustAllCertsPolicy : ICertificatePolicy {
+ public bool CheckValidationResult(
+ ServicePoint srvPoint, X509Certificate certificate,
+ WebRequest request, int certificateProblem) {
+ return true;
+ }
+ }
+"@
+ [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
+ Write-Warning -Message "CertificatePolicy set to ignore all server certificate errors"
+
+ $d = Get-Date
+ "$d Stopping VM $_vmName" | Tee-Object -FilePath $logFile -Append
+ try {
+ # Make the calls using REST and passing in token to avoid too many fast Auth calls
+ $stopAzureVM = @{
+ Uri ='{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Compute/virtualMachines/{3}/powerOff?api-version=2015-06-15' `
+ -f $armEndpoint, $azureSubscription, $_resourceGroupName, $_vmName
+ Method = "POST"
+ Headers = @{ "Authorization" = "Bearer " + $token }
+ ContentType = "application/json"
+ }
+
+ "URI: $($stopAzureVM.Uri)" | Tee-Object -FilePath $logFile -Append
+
+ $result = Invoke-RestMethod @stopAzureVM
+
+ } catch [Exception] {
+ "Failed to turn off $_vmName VM. Exception details $_" | Tee-Object -FilePath $logFile -Append
+ }
+ $d = Get-Date
+ "$d VM $_vmName Stopped" | Tee-Object -FilePath $logFile -Append
+ } -ArgumentList $_vmName,$vm.ResourceGroupName,$location,$logFile,$AzureSubscription,$resourceManagerEndpoint,$azureToken | Out-Null
+
+ $resourceGroupName = $vm.ResourceGroupName
+ }
+ }
+
+ $numberOfRetries = 60
+
+ # Wait for background jobs
+ $jobs = Get-Job | Where-Object {$_.State -eq "Running"}
+ $noOfRetries = $numberOfRetries
+ while(($jobs.Count -gt 0) -and ($noOfRetries -gt 0)) {
+ "Waiting for VMs to all be stopped. $($jobs.Count) jobs left. Retries left: $noOfRetries" | Tee-Object -FilePath $logFilePath -Append
+ Start-Sleep -Seconds 15
+ $noOfRetries--
+ $jobs = Get-Job | Where-Object {$_.State -eq "Running"}
+ }
+ "Done waiting for VMs to all be stopped." | Tee-Object -FilePath $logFilePath -Append
+
+ # Clear background jobs
+ Get-Job | Remove-Job -Force -Confirm:0
+
+ # Check if all VMs are deallocated (i.e. turned off)
+ $noOfRetries = $numberOfRetries
+ [System.Collections.ArrayList]$runningVMs = $vms
+ #1 VM = the controller VM
+ while(($noOfRetries -gt 0) -and ($runningVMs.Count -gt 1)) {
+ Start-Sleep -Seconds 30
+
+ # All VMs except jump box VM
+ [System.Collections.ArrayList]$removeArray = @()
+
+ foreach($vm in $runningVMs) {
+ if($vm.Name -match "[0-9]$") {
+ try {
+ "$(Get-Date -displayhint Time) Start Get-AzureRmVM on $($vm.Name)" | Tee-Object -FilePath $logFilePath -Append
+ $vmStatus = Get-AzureRmVM -Name $vm.Name -ResourceGroupName $vm.ResourceGroupName -Status
+ "$(Get-Date -displayhint Time) End Get-AzureRmVM on $($vm.Name) : Status '$($vmStatus.Statuses[1].Code)'" | Tee-Object -FilePath $logFilePath -Append
+ } catch {
+ "Get-AzureRmVM on $($vm.Name) failed $_" | Tee-Object -FilePath $logFilePath -Append
+ }
+
+ $isVmRunning = $vmStatus.Statuses[1].Code.Contains("running")
+
+ if($isVmRunning -eq $false) {
+ $removeArray.Add($vm)
+ }
+ }
+ }
+
+ foreach($vm in $RemoveArray) {
+ $runningVMs.Remove($vm)
+ }
+
+ $RemoveArray.Clear()
+
+ "Waiting for all VMs to no longer be running. Running VMs: $($runningVMs.Count). Retries left: $noOfRetries" | Tee-Object -FilePath $logFilePath -Append
+ $noOfRetries -= 1
+ }
+ if($noOfRunningVMs -gt 0) {
+ "$noOfRunningVMs out of $vmCount VMs failed to turn off." | Tee-Object -FilePath $logFilePath -Append
+ }
+ else {
+ "All $vmCount VMs are turned off" | Tee-Object -FilePath $logFilePath -Append
+ }
+
+ # Get Token again incase it needs refreshing
+ $azureToken = ($authContext.AcquireToken($activeDirectoryServiceEndpointResourceId, $clientId, $userCredential)).AccessToken
+
+ ####################
+ ### VM BOOTSTORM ###
+ ####################
+ # Boot all VMs at the same time
+ foreach($vm in $vms) {
+ $_vmName = $vm.Name
+ # All VMs except jump box VM
+ if($_vmName -match "[0-9]$") {
+ $_date = Get-Date -Format hh:mmtt
+ $logFile = $localPath + "\VmBoot_" + $_vmName + ".log"
+ "Booting VM $_vmName at $_date" | Tee-Object -FilePath $logFilePath -Append
+
+ Start-Job -ScriptBlock {
+ param($_vmName,$_resourceGroupName,$location,$logFile,$azureSubscription, $armEndpoint, $token)
+
+ # Ignore server certificate errors to avoid https://api.azurestack.local/ certificate error
+ add-type @"
+ using System.Net;
+ using System.Security.Cryptography.X509Certificates;
+ public class TrustAllCertsPolicy : ICertificatePolicy {
+ public bool CheckValidationResult(
+ ServicePoint srvPoint, X509Certificate certificate,
+ WebRequest request, int certificateProblem) {
+ return true;
+ }
+ }
+"@
+ [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
+ Write-Warning -Message "CertificatePolicy set to ignore all server certificate errors"
+
+ # Get VM Boot Start Time
+ $_statusBootStartTime = Get-Date
+ "Starting boot at $_statusBootStartTime" | Tee-Object -FilePath $logFile -Append
+ try {
+ # Make the calls using REST and passing in token to avoid too many fast Auth calls
+ $startAzureVM = @{
+ Uri ='{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Compute/virtualMachines/{3}/start?api-version=2015-06-15' `
+ -f $armEndpoint, $azureSubscription, $_resourceGroupName, $_vmName
+ Method = "POST"
+ Headers = @{ "Authorization" = "Bearer " + $token }
+ ContentType = "application/json"
+ }
+
+ "URI: $($startAzureVM.Uri)" | Tee-Object -FilePath $logFile -Append
+
+ $result = Invoke-RestMethod @startAzureVM
+
+ } catch [Exception] {
+ "Failed to turn on VM $_vmName Exception: $_" | Tee-Object -FilePath $logFile -Append
+ }
+ $_dateAfterBoot = Get-Date
+ "Boot succeeded at $_dateAfterBoot" | Tee-Object -FilePath $logFile -Append
+ # Get VM Boot End Time (Ignore NULL values of Time)
+
+ $numberOfRetries = 60
+ $retries = 0
+
+ do
+ {
+ "Waiting for boot. Retry Count $retries" | Tee-Object -FilePath $logFile -Append
+
+ Start-Sleep -Seconds 60
+ $retries++
+
+ # Make the calls using REST and passing in token to avoid too many fast Auth calls
+ $getAzureVM = @{
+ Uri ='{0}/subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Compute/virtualMachines/{3}?$expand=instanceView&api-version=2015-06-15' `
+ -f $armEndpoint, $azureSubscription, $_resourceGroupName, $_vmName
+ Method = "Get"
+ Headers = @{ "Authorization" = "Bearer " + $token }
+ ContentType = "application/json"
+ }
+
+ "URI: $($getAzureVM.Uri)" | Tee-Object -FilePath $logFile -Append
+
+ $result = Invoke-RestMethod @getAzureVM
+
+ $_statusBootEndTime = $result.Properties.InstanceView.Statuses[0].Time
+ }while(!$_statusBootEndTime -and $retries -lt $numberOfRetries)
+
+ # Create custom vm boot result object
+ $_vmBootResult = "" | Select-Object VMName, VMBootStartTime, VMBootEndTime, VMBootTimeInSeconds
+ $_vmBootResult.VMName = ($_vmName).Trim()
+ $_vmBootResult.VMBootStartTime = ([DateTimeOffset]$_statusBootStartTime).DateTime
+ $_vmBootResult.VMBootEndTime = ([DateTimeOffset]$_statusBootEndTime).DateTime
+ if($_vmBootResult.VMBootEndTime -gt $_vmBootResult.VMBootStartTime) {
+ $_vmBootResult.VMBootTimeInSeconds = [float]($_vmBootResult.VMBootEndTime - $_vmBootResult.VMBootStartTime).TotalSeconds
+ }
+ else {
+ $_vmBootResult.VMBootTimeInSeconds = 0
+ "VM boot end time is invalid for VM $_vmName" | Tee-Object -FilePath $logFile -Append
+ }
+
+ return $_vmBootResult
+
+ } -ArgumentList $vm.Name,$vm.ResourceGroupName,$location,$logFile,$AzureSubscription,$resourceManagerEndpoint, $azureToken | Out-Null
+ }
+ }
+
+ # Wait for background jobs
+ $jobs = Get-Job | Where-Object {$_.State -eq "Running"}
+ while($jobs.Count -gt 0) {
+ "$($jobs.Count) VMs still booting" | Tee-Object -FilePath $logFilePath -Append
+ Start-Sleep -Seconds 15
+ $jobs = Get-Job | Where-Object {$_.State -eq "Running"}
+ }
+
+ # Receive job results
+ $vmbootResults = @()
+ foreach($job in Get-Job) { $vmbootResults += ,(Receive-Job -Job $job) }
+
+ # Clear background jobs
+ Get-Job | Remove-Job -Force -Confirm:0
+
+ # Prepare storage context to upload results to Azure storage table
+ "Azure ad resource id is $storageEndpoint" | Tee-Object -FilePath $logFilePath -Append
+
+ "Azure storage endpoint $storageEndpoint is being used." | Tee-Object -FilePath $logFilePath -Append
+ $storageContext = $null
+ $storageTable = $null
+ try {
+ $storageContext = New-AzureStorageContext $storageAccountName -StorageAccountKey $storageAccountKey -Endpoint $storageEndpoint
+ if($storageContext -eq $null) {
+ "Azure Storage context is null." | Tee-Object -FilePath $logFilePath -Append
+ }
+ $storageTableName = "VMBootResults"
+
+ # Retrieve the table if it already exists.
+ try {
+ $storageTable = Get-AzureStorageTable -Name $storageTableName -Context $storageContext -ErrorAction SilentlyContinue
+ if($storageTable -ne $null) {
+ Remove-AzureStorageTable -Name $storageTableName -Context $storageContext -Force -ErrorAction SilentlyContinue
+ }
+ } catch {
+ "Storage table $storageTableName does not exists. Creating a new table." | Tee-Object -FilePath $logFilePath -Append
+ }
+
+ # Create a new table if it does not exist.
+ if ($storageTable -eq $null) {
+ try {
+ $storageTable = New-AzureStorageTable -Name $storageTableName -Context $storageContext
+ } catch [Exception] {
+ "Storage table $storageTableName cannot be created. Exception: $_" | Tee-Object -FilePath $logFilePath -Append
+
+ try {
+ $storageTable = New-AzureStorageTable -Name $storageTableName -Context $storageContext
+ } catch [Exception] {
+ "Storage table $storageTableName cannot be created. Exception: $_" | Tee-Object -FilePath $logFilePath -Append
+ }
+ }
+ }
+ }
+ catch {
+ "Azure storage context cannot be created for a given storage account $storageAccountName" | Tee-Object -FilePath $logFilePath -Append
+ }
+
+ # Function to add a result row to the Azure Storage Table
+ function Add-TableEntity() {
+ [CmdletBinding()]
+ param(
+ $table,
+ [String]$PartitionKey,
+ [String]$RowKey,
+ [String]$Value
+ )
+
+ if($table -eq $null) {
+ "Value cannot be inserted into null table" | Tee-Object -FilePath $logFilePath -Append
+ }
+
+ $entity = New-Object -TypeName Microsoft.WindowsAzure.Storage.Table.DynamicTableEntity -ArgumentList $PartitionKey, $RowKey
+ $entity.Properties.Add("Value", $Value)
+ if($entity -eq $null) {
+ "Entity cannot be created for partition key $PartitionKey and row $RowKey" | Tee-Object -FilePath $logFilePath -Append
+ }
+
+ $result = $table.CloudTable.Execute([Microsoft.WindowsAzure.Storage.Table.TableOperation]::Insert($entity))
+ return $result
+ }
+
+ # Display boot results
+ $vmbootResultFile = "$env:SystemDrive\VMBootResult.log"
+
+ # Skip test if already executed
+ if(Test-Path $vmbootResultFile -ErrorAction SilentlyContinue) {
+ "Result file already exists. Skipping test execution." | Tee-Object -FilePath $logFilePath -Append
+ return
+ }
+
+ if($vmbootResults.Count -gt 0) {
+
+ "----------------------------------------------------------" | Tee-Object -FilePath $vmbootResultFile
+ "VM Name `t`tVM Boot Time (sec)" | Tee-Object -FilePath $vmbootResultFile -Append
+ "----------------------------------------------------------" | Tee-Object -FilePath $vmbootResultFile -Append
+
+ $_vmBootFailedCount = 0
+ $_vmBootTimeCount = 0
+ $_vmBootTimeSum = 0.0
+ $_vmBootTimeAvg = 0.0
+ $_vmBootTimeAbsolute = 0.0
+ $_vmBootTimeAbsoluteStart = Get-Date
+ $_vmBootTimeAbsoluteEnd = (Get-Date).AddDays(-30)
+ $_vmBootTimeMax = 0.0
+ $_vmBootTimeMin = [float]::MaxValue
+ $_executionId = Get-Date -Format yyyyMMdd_HHmm
+
+ $vmResultIndex = 0
+ foreach($vmbootResult in $vmbootResults) {
+ if($vmbootResult -ne $null) {
+ # Remove extra array object with properties Environment,Account,Tenant,Subscription,CurrentStorageAccount
+ $vmbootResult = $vmbootResult | Where-Object { $_.GetType().ToString().Contains("System.Management.Automation.PSCustomObject") }
+
+ $_vmName = $vmbootResult.VMName
+ $_vmBootTime = ([double]::Parse($vmbootResult.VMBootTimeInSeconds))
+ $_vmBootTimeString = "{0:N0}" -f [float]($_vmBootTime)
+ if(($_vmBootTime -le 0) -and ($_vmBootTime -ge [Int32]::MaxValue)) {
+ "Skipping invalid boot time $_vmBootTimeString for VM $_vmname" | Tee-Object -FilePath $logFilePath -Append
+ $_vmBootFailedCount++
+ continue
+ }
+
+ "$_vmName `t`t$_vmBootTimeString" | Tee-Object -FilePath $vmbootResultFile -Append
+
+ # Add vm boot results to the Azure storage table
+ if($storageTable -ne $null) {
+ try {
+ Add-TableEntity -Table $storageTable -PartitionKey $_executionId -RowKey $_vmName -Value $_vmBootTimeString
+ } catch {
+ Write-Verbose "Adding Azure storage table entry for row $vmResultIndex failed."
+ "Adding Azure storage table entry for row $vmResultIndex failed." | Tee-Object -FilePath $logFilePath -Append
+ }
+ }
+ else {
+ "Azure storage table object $storageTable is null" | Tee-Object -FilePath $logFilePath -Append
+ }
+
+ $_vmBootTimeSum += $_vmBootTime
+ $_vmBootTimeCount += 1
+ if($_vmBootTimeAbsoluteStart -gt $vmbootResult.VMBootStartTime) {
+ $_vmBootTimeAbsoluteStart = $vmbootResult.VMBootStartTime
+ }
+ if($_vmBootTimeAbsoluteEnd -lt $vmbootResult.VMBootEndTime) {
+ $_vmBootTimeAbsoluteEnd = $vmbootResult.VMBootEndTime
+ }
+ if($_vmBootTimeMax -lt $_vmBootTime) {
+ $_vmBootTimeMax = $_vmBootTime
+ }
+ if($_vmBootTimeMin -gt $_vmBootTime) {
+ $_vmBootTimeMin = $_vmBootTime
+ }
+ }
+ $vmResultIndex += 1
+ }
+
+ $_vmBootTimeAvg = "{0:N0}" -f [float]($_vmBootTimeSum/$_vmBootTimeCount)
+ $_vmBootTimeAbsolute = "{0:N0}" -f [float](($_vmBootTimeAbsoluteEnd - $_vmBootTimeAbsoluteStart).TotalSeconds)
+ $_vmBootTimeMax = "{0:N0}" -f [float]($_vmBootTimeMax)
+ $_vmBootTimeMin = "{0:N0}" -f [float]($_vmBootTimeMin)
+ "----------------------------------------------------------" | Tee-Object -FilePath $logFilePath -Append
+ if($VMCount -gt $_vmBootTimeCount) {
+ "$_vmBootTimeCount out of $VMCount Azure A1-sized VMs cold booted in $_vmBootTimeAbsolute seconds at an average start time $_vmBootTimeAvg seconds/VM." | Tee-Object -FilePath $logFilePath -Append
+ }
+ else {
+ "$_vmBootTimeCount Azure A1-sized VMs cold booted in $_vmBootTimeAbsolute seconds at an average start time $_vmBootTimeAvg seconds/VM." | Tee-Object -FilePath $logFilePath -Append
+ }
+
+ "----------------------------------------------------------" | Tee-Object -FilePath $logFilePath -Append
+ "Minimum vm boot time is $_vmBootTimeMin sec and maximum vm boot time is $_vmBootTimeMax sec" | Tee-Object -FilePath $logFilePath -Append
+ "----------------------------------------------------------" | Tee-Object -FilePath $logFilePath -Append
+ "$_vmBootTimeCount A1 VMs in $_vmBootTimeAbsolute sec @ $_vmBootTimeAvg sec/VM" | Tee-Object -FilePath $logFilePath -Append
+ "----------------------------------------------------------" | Tee-Object -FilePath $logFilePath -Append
+ if($_vmBootFailedCount -gt 0) {
+ "Failed to get boot time for $_vmBootFailedCount VMs" | Tee-Object -FilePath $logFilePath -Append
+ "----------------------------------------------------------" | Tee-Object -FilePath $logFilePath -Append
+ }
+
+ # Add vm average boot results to the Azure storage table
+ if($storageTable -ne $null) {
+ try {
+ # Average vm boot time
+ Add-TableEntity -Table $storageTable -PartitionKey $_executionId -RowKey "AverageBootTime" -Value $_vmBootTimeAvg
+ $vmResultIndex += 1
+
+ # Absolute vm boot time
+ Add-TableEntity -Table $storageTable -PartitionKey $_executionId -RowKey "AbsoluteBootTime" -Value $_vmBootTimeAbsolute
+ $vmResultIndex += 1
+
+ # Max vm boot time
+ Add-TableEntity -Table $storageTable -PartitionKey $_executionId -RowKey "MaxBootTime" -Value $_vmBootTimeMax
+ $vmResultIndex += 1
+
+ # Max vm boot time
+ Add-TableEntity -Table $storageTable -PartitionKey $_executionId -RowKey "MinBootTime" -Value $_vmBootTimeMin
+ $vmResultIndex += 1
+
+ # Summary
+ Add-TableEntity -Table $storageTable -PartitionKey $_executionId -RowKey "Summary" -Value "$_vmBootTimeCount A1 VMs in $_vmBootTimeAbsolute sec @ $_vmBootTimeAvg sec/VM"
+ $vmResultIndex += 1
+
+ } catch {
+ "Adding Azure storage table summary entry for row $vmResultIndex failed." | Tee-Object -FilePath $logFilePath -Append
+ }
+ } else {
+ "Azure storage table object $storageTable is null" | Tee-Object -FilePath $logFilePath -Append
+ }
+
+ "----------------------------------------------------------" | Tee-Object -FilePath $vmbootResultFile -Append
+ "$_vmBootTimeCount Azure A1-sized VMs cold booted in $_vmBootTimeAbsolute seconds at an average start time $_vmBootTimeAvg seconds/VM" | Tee-Object -FilePath $vmbootResultFile -Append
+ "----------------------------------------------------------" | Tee-Object -FilePath $vmbootResultFile -Append
+ "Minimum vm boot time is $_vmBootTimeMin sec and maximum vm boot time is $_vmBootTimeMax sec" | Tee-Object -FilePath $vmbootResultFile -Append
+ "----------------------------------------------------------" | Tee-Object -FilePath $vmbootResultFile -Append
+ "$_vmBootTimeCount A1 VMs in $_vmBootTimeAbsolute sec @ $_vmBootTimeAvg sec/VM" | Tee-Object -FilePath $vmbootResultFile -Append
+ "----------------------------------------------------------" | Tee-Object -FilePath $vmbootResultFile -Append
+ if($_vmBootFailedCount -gt 0) {
+ "Failed to get boot time for $_vmBootFailedCount VMs" | Tee-Object -FilePath $vmbootResultFile -Append
+ "----------------------------------------------------------" | Tee-Object -FilePath $vmbootResultFile -Append
+ }
+ }
+ else {
+ Write-Error "Failed to get VM boot results" -ForegroundColor Red
+ "Failed to get VM boot results" | Tee-Object -FilePath $logFilePath -Append
+ "Failed to get VM boot results" | Tee-Object -FilePath $vmbootResultFile -Append
+ }
+
+ "VM boot storm test finished." | Tee-Object -FilePath $logFilePath -Append
+ "VM boot storm test finished." | Tee-Object -FilePath $statusFilePath -Append
+
+ # Upload VM boot results and logs file to Azure storage container
+ $storageContainerName = "results-vmbootstorm"
+ if($storageContext -ne $null) {
+ try {
+ New-AzureStorageContainer -Name $storageContainerName -Permission Blob -Context $storageContext -Verbose -ErrorAction Stop;
+ try {
+ Set-AzureStorageBlobContent -File $vmbootResultFile -Container $storageContainerName -Context $storageContext -ErrorAction Stop;
+ Set-AzureStorageBlobContent -File $logFilePath -Container $storageContainerName -Context $storageContext -ErrorAction Stop;
+ }
+ catch {
+ "Failed to upload VM boot storm result and log file." | Tee-Object -FilePath $logFilePath -Append
+ }
+ } catch {
+ "Failed to create storage container $storageContainerName to upload result and log file." | Tee-Object -FilePath $logFilePath -Append
+ }
+ }
+ else {
+ "Cannot upload VM boot storm result and log file as storage context is null." | Tee-Object -FilePath $logFilePath -Append
+ }
+}
+
+function IsAzureStack
+{
+ param
+ (
+ [Parameter(Mandatory)]
+ $Location
+ )
+ if($Location -eq "AzureCloud" -or $Location -eq "AzureChinaCloud" -or $Location -eq "AzureUSGovernment" -or $Location -eq "AzureGermanCloud") {
+ return $false
+ }
+ return $true
+
+}
+
+VMBootAll
+
\ No newline at end of file
diff --git a/bootstorm-vm-boot-time/azuredeploy.json b/bootstorm-vm-boot-time/azuredeploy.json
index 4df63f5..6fb29a7 100644
--- a/bootstorm-vm-boot-time/azuredeploy.json
+++ b/bootstorm-vm-boot-time/azuredeploy.json
@@ -1,339 +1,347 @@
-{
- "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
- "contentVersion": "1.16.08.15",
- "parameters": {
- "azureAccountUsername": {
- "type": "string",
- "metadata": {
- "description": "Azure tenant user name to authenticate inside controller VM."
- }
- },
- "azureAccountPassword": {
- "type": "string",
- "defaultValue": "[subscription().subscriptionId]",
- "metadata": {
- "description": "Azure tenant user password to authenticate inside controller VM."
- }
- },
- "tenantId": {
- "type": "string",
- "metadata": {
- "description": "Azure Subscription Tenant Id."
- }
- },
- "vmCount": {
- "type": "int",
- "defaultValue": 2,
- "metadata": {
- "description": "Number of VMs to create."
- }
- },
- "vmSize": {
- "type": "string",
- "defaultValue": "Standard_A2",
- "allowedValues": [
- "Standard_A0",
- "Standard_A1",
- "Standard_A2",
- "Standard_A3",
- "Standard_A4",
- "Standard_A5",
- "Standard_A6",
- "Standard_A7"
- ],
- "metadata": {
- "description": "VM size supported by Azure Stack."
- }
- },
- "vmOsSku": {
- "type": "string",
- "defaultValue": "2012-R2-Datacenter",
- "allowedValues": [
- "2012-R2-Datacenter",
- "2016-Datacenter"
- ],
- "metadata": {
- "description": "VM size supported by Azure Stack."
- }
- }
- },
- "variables": {
- "imagePublisher": "MicrosoftWindowsServer",
- "imageOffer": "WindowsServer",
- "addressPrefix": "10.0.0.0/16",
- "location": "[resourceGroup().location]",
- "subnetName": "[concat('vsn', resourceGroup().name)]",
- "subnetPrefix": "10.0.0.0/16",
- "storageAccountType": "Standard_LRS",
- "uniqueStorageAccountName": "[concat('sa', resourceGroup().name)]",
- "uniqueStorageAccountContainerName": "[concat('sc', resourceGroup().name)]",
- "vmOsSku": "[parameters('vmOsSku')]",
- "vmAdminUsername": "vmadministrator",
- "vmAdminPassword": "pwd0a!8b7",
- "vmName": "[concat('vm', resourceGroup().name)]",
- "vmOsDiskName": "[concat('od', resourceGroup().name)]",
- "vmNicName": "[concat('nc', resourceGroup().name)]",
- "virtualNetworkName": "[concat('vn', resourceGroup().name)]",
- "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]",
- "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
- "nsgName": "[concat('ng', resourceGroup().name)]",
- "nsgID": "[resourceId('Microsoft.Network/networkSecurityGroups',variables('nsgName'))]",
- "modulesPath": "https://raw.githubusercontent.com/Azure/AzureStack-QuickStart-Templates/master/bootstorm-vm-boot-time/",
- "moduleVMBootAll": "VMBootAll.zip",
- "modulesUrlVMBootAll": "[concat(variables('modulesPath'),variables('moduleVMBootAll'))]",
- "configurationFunctionVMBootAll": "VMBootAll.ps1\\ConfigureVMBootAll",
- "controllerVmSize": "Standard_A2"
- },
- "resources": [
- {
- "apiVersion": "2015-06-15",
- "type": "Microsoft.Storage/storageAccounts",
- "name": "[tolower(variables('uniqueStorageAccountName'))]",
- "location": "[variables('location')]",
- "properties": {
- "accountType": "[variables('storageAccountType')]"
- }
- },
- {
- "apiVersion": "2015-06-15",
- "type": "Microsoft.Network/networkSecurityGroups",
- "name": "[variables('nsgName')]",
- "location": "[variables('location')]",
- "properties": {
- "securityRules": [
- {
- "name": "nsgsrule",
- "properties": {
- "protocol": "*",
- "sourcePortRange": "*",
- "destinationPortRange": "*",
- "sourceAddressPrefix": "*",
- "destinationAddressPrefix": "*",
- "access": "Allow",
- "priority": 101,
- "direction": "Inbound"
- }
- }
- ]
- }
- },
- {
- "apiVersion": "2015-06-15",
- "type": "Microsoft.Network/virtualNetworks",
- "name": "[variables('virtualNetworkName')]",
- "location": "[variables('location')]",
- "dependsOn": [ "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]" ],
- "properties": {
- "addressSpace": {
- "addressPrefixes": [
- "[variables('addressPrefix')]"
- ]
- },
- "subnets": [
- {
- "name": "[variables('subnetName')]",
- "properties": {
- "addressPrefix": "[variables('subnetPrefix')]",
- "networkSecurityGroup": {
- "id": "[variables('nsgID')]"
- }
- }
- }
- ]
- }
- },
- {
- "apiVersion": "2015-06-15",
- "type": "Microsoft.Network/networkInterfaces",
- "name": "[variables('vmNicName')]",
- "location": "[variables('location')]",
- "dependsOn": [
- "[concat('Microsoft.Network/virtualNetworks/',variables('virtualNetworkName'))]"
- ],
- "properties": {
- "ipConfigurations": [
- {
- "name": "ipconfigpri",
- "properties": {
- "privateIPAllocationMethod": "Dynamic",
- "subnet": {
- "id": "[variables('subnetRef')]"
- }
- }
- }
- ]
- }
- },
- {
- "apiVersion": "2015-06-15",
- "type": "Microsoft.Network/networkInterfaces",
- "name": "[concat(variables('vmNicName'),copyIndex())]",
- "location": "[variables('location')]",
- "copy": {
- "name": "nicLoop",
- "count": "[parameters('vmCount')]"
- },
- "dependsOn": [
- "[concat('Microsoft.Network/virtualNetworks/',variables('virtualNetworkName'))]"
- ],
- "properties": {
- "ipConfigurations": [
- {
- "name": "ipconfigprivate",
- "properties": {
- "privateIPAllocationMethod": "Dynamic",
- "subnet": {
- "id": "[variables('subnetRef')]"
- }
- }
- }
- ]
- }
- },
- {
- "apiVersion": "2015-06-15",
- "type": "Microsoft.Compute/virtualMachines",
- "name": "[variables('vmName')]",
- "location": "[variables('location')]",
- "dependsOn": [
- "[concat('Microsoft.Storage/storageAccounts/',variables('uniqueStorageAccountName'))]",
- "[concat('Microsoft.Network/networkInterfaces/',variables('vmNicName'))]"
- ],
- "properties": {
- "hardwareProfile": {
- "vmSize": "[variables('controllerVmSize')]"
- },
- "osProfile": {
- "computerName": "[variables('vmName')]",
- "adminUsername": "[variables('vmAdminUsername')]",
- "adminPassword": "[variables('vmAdminPassword')]"
- },
- "storageProfile": {
- "imageReference": {
- "publisher": "[variables('imagePublisher')]",
- "offer": "[variables('imageOffer')]",
- "sku": "[variables('vmOsSku')]",
- "version": "latest"
- },
- "osDisk": {
- "name": "osdisk",
- "vhd": {
- "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('uniqueStorageAccountName')),providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).primaryEndpoints.blob,variables('uniqueStorageAccountContainerName'),'/',variables('vmOsDiskName'),'.vhd')]"
- },
- "caching": "ReadWrite",
- "createOption": "FromImage"
- }
- },
- "networkProfile": {
- "networkInterfaces": [
- {
- "id": "[resourceId('Microsoft.Network/networkInterfaces',concat(variables('vmNicName')))]"
- }
- ]
- },
- "diagnosticsProfile": {
- "bootDiagnostics": {
- "enabled": "true",
- "storageUri": "[reference(concat('Microsoft.Storage/storageAccounts/', variables('uniqueStorageAccountName')),providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).primaryEndpoints.blob]"
- }
- }
- }
- },
- {
- "apiVersion": "2015-06-15",
- "type": "Microsoft.Compute/virtualMachines",
- "name": "[concat(variables('vmName'),copyIndex())]",
- "location": "[variables('location')]",
- "copy": {
- "name": "vmLoop",
- "count": "[parameters('vmCount')]"
- },
- "dependsOn": [
- "[concat('Microsoft.Storage/storageAccounts/',variables('uniqueStorageAccountName'))]",
- "[concat('Microsoft.Network/networkInterfaces/',variables('vmNicName'),copyIndex())]"
- ],
- "properties": {
- "hardwareProfile": {
- "vmSize": "[parameters('vmSize')]"
- },
- "osProfile": {
- "computerName": "[variables('vmName')]",
- "adminUsername": "[variables('vmAdminUsername')]",
- "adminPassword": "[variables('vmAdminPassword')]"
- },
- "storageProfile": {
- "imageReference": {
- "publisher": "[variables('imagePublisher')]",
- "offer": "[variables('imageOffer')]",
- "sku": "[variables('vmOsSku')]",
- "version": "latest"
- },
- "osDisk": {
- "name": "osdisk",
- "vhd": {
- "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('uniqueStorageAccountName')),providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).primaryEndpoints.blob,variables('uniqueStorageAccountContainerName'),copyIndex(),'/',concat(variables('vmOsDiskName'),copyIndex()),'.vhd')]"
- },
- "caching": "ReadWrite",
- "createOption": "FromImage"
- }
- },
- "networkProfile": {
- "networkInterfaces": [
- {
- "id": "[resourceId('Microsoft.Network/networkInterfaces',concat(variables('vmNicName'),copyIndex()))]"
- }
- ]
- },
- "diagnosticsProfile": {
- "bootDiagnostics": {
- "enabled": "true",
- "storageUri": "[reference(concat('Microsoft.Storage/storageAccounts/', variables('uniqueStorageAccountName')),providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).primaryEndpoints.blob]"
- }
- }
- }
- },
- {
- "apiVersion": "2015-06-15",
- "type": "Microsoft.Compute/virtualMachines/extensions",
- "name": "[concat(variables('vmName'),'/dscExtension')]",
- "location": "[variables('location')]",
- "dependsOn": [
- "[concat('Microsoft.Compute/virtualMachines/',variables('vmName'))]"
- ],
- "properties": {
- "publisher": "Microsoft.Powershell",
- "type": "DSC",
- "typeHandlerVersion": "2.15",
- "autoUpgradeMinorVersion": true,
- "settings": {
- "modulesUrl": "[variables('modulesUrlVMBootAll')]",
- "configurationFunction": "[variables('configurationFunctionVMBootAll')]",
- "properties": {
- "AzureAccountCreds": {
- "userName": "[parameters('azureAccountUsername')]",
- "password": "PrivateSettingsRef:azureAccountPassword"
- },
- "TenantId": "[parameters('tenantId')]",
- "Location": "[variables('location')]",
- "VMName": "[variables('vmName')]",
- "VMCount": "[parameters('vmCount')]",
- "VMAdminCreds": {
- "userName": "[variables('vmAdminUsername')]",
- "password": "PrivateSettingsRef:vmAdminPassword"
- },
- "AzureStorageAccount": "[variables('uniqueStorageAccountName')]",
- "AzureStorageAccessKey": "[listKeys(concat('Microsoft.Storage/storageAccounts/', variables('uniqueStorageAccountName')), '2015-06-15').key1]",
- "AzureStorageEndpoint": "[reference(resourceId('Microsoft.Storage/storageAccounts', variables('uniqueStorageAccountName')), '2015-06-15').primaryEndpoints['blob']]",
- "AzureSubscription": "[subscription().subscriptionId]"
- }
- },
- "protectedSettings": {
- "items": {
- "vmAdminPassword": "[variables('vmAdminPassword')]",
- "azureAccountPassword": "[parameters('azureAccountPassword')]"
- }
- }
- }
- }
- ]
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+ "contentVersion": "1.16.08.15",
+ "parameters": {
+ "azureAccountUsername": {
+ "type": "string",
+ "metadata": {
+ "description": "Azure tenant user name to authenticate inside controller VM."
+ }
+ },
+ "azureAccountPassword": {
+ "type": "string",
+ "defaultValue": "[subscription().subscriptionId]",
+ "metadata": {
+ "description": "Azure tenant user password to authenticate inside controller VM."
+ }
+ },
+ "tenantId": {
+ "type": "string",
+ "metadata": {
+ "description": "Azure Subscription Tenant Id."
+ }
+ },
+ "vmCount": {
+ "type": "int",
+ "defaultValue": 2,
+ "metadata": {
+ "description": "Number of VMs to create."
+ }
+ },
+ "batchSize": {
+ "type": "int",
+ "defaultValue": "[parameters('vmCount')]",
+ "metadata": {
+ "description": "Number of VMs to create in a batch."
+ }
+ },
+ "vmSize": {
+ "type": "string",
+ "defaultValue": "Standard_A2",
+ "allowedValues": [
+ "Standard_A0",
+ "Standard_A1",
+ "Standard_A2",
+ "Standard_A3",
+ "Standard_A4",
+ "Standard_A5",
+ "Standard_A6",
+ "Standard_A7"
+ ],
+ "metadata": {
+ "description": "VM size supported by Azure Stack."
+ }
+ },
+ "vmOsSku": {
+ "type": "string",
+ "defaultValue": "2012-R2-Datacenter",
+ "allowedValues": [
+ "2012-R2-Datacenter",
+ "2016-Datacenter"
+ ],
+ "metadata": {
+ "description": "VM size supported by Azure Stack."
+ }
+ }
+ },
+ "variables": {
+ "imagePublisher": "MicrosoftWindowsServer",
+ "imageOffer": "WindowsServer",
+ "addressPrefix": "10.0.0.0/16",
+ "location": "[resourceGroup().location]",
+ "subnetName": "[concat('vsn', resourceGroup().name)]",
+ "subnetPrefix": "10.0.0.0/16",
+ "storageAccountType": "Standard_LRS",
+ "uniqueStorageAccountName": "[concat('sa', resourceGroup().name)]",
+ "uniqueStorageAccountContainerName": "[concat('sc', resourceGroup().name)]",
+ "vmOsSku": "[parameters('vmOsSku')]",
+ "vmAdminUsername": "vmadministrator",
+ "vmAdminPassword": "pwd0a!8b7",
+ "vmName": "[concat('vm', resourceGroup().name)]",
+ "vmOsDiskName": "[concat('od', resourceGroup().name)]",
+ "vmNicName": "[concat('nc', resourceGroup().name)]",
+ "virtualNetworkName": "[concat('vn', resourceGroup().name)]",
+ "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]",
+ "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
+ "nsgName": "[concat('ng', resourceGroup().name)]",
+ "nsgID": "[resourceId('Microsoft.Network/networkSecurityGroups',variables('nsgName'))]",
+ "modulesPath": "https://raw.githubusercontent.com/BrianLPeterson/AzureStack-QuickStart-Templates/bootstormfix/bootstorm-vm-boot-time/",
+ "moduleVMBootAll": "VMBootAll.zip",
+ "modulesUrlVMBootAll": "[concat(variables('modulesPath'),variables('moduleVMBootAll'))]",
+ "configurationFunctionVMBootAll": "VMBootAll.ps1\\ConfigureVMBootAll",
+ "controllerVmSize": "Standard_A4"
+ },
+ "resources": [
+ {
+ "apiVersion": "2015-06-15",
+ "type": "Microsoft.Storage/storageAccounts",
+ "name": "[tolower(variables('uniqueStorageAccountName'))]",
+ "location": "[variables('location')]",
+ "properties": {
+ "accountType": "[variables('storageAccountType')]"
+ }
+ },
+ {
+ "apiVersion": "2015-06-15",
+ "type": "Microsoft.Network/networkSecurityGroups",
+ "name": "[variables('nsgName')]",
+ "location": "[variables('location')]",
+ "properties": {
+ "securityRules": [
+ {
+ "name": "nsgsrule",
+ "properties": {
+ "protocol": "*",
+ "sourcePortRange": "*",
+ "destinationPortRange": "*",
+ "sourceAddressPrefix": "*",
+ "destinationAddressPrefix": "*",
+ "access": "Allow",
+ "priority": 101,
+ "direction": "Inbound"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "apiVersion": "2015-06-15",
+ "type": "Microsoft.Network/virtualNetworks",
+ "name": "[variables('virtualNetworkName')]",
+ "location": "[variables('location')]",
+ "dependsOn": [ "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]" ],
+ "properties": {
+ "addressSpace": {
+ "addressPrefixes": [
+ "[variables('addressPrefix')]"
+ ]
+ },
+ "subnets": [
+ {
+ "name": "[variables('subnetName')]",
+ "properties": {
+ "addressPrefix": "[variables('subnetPrefix')]",
+ "networkSecurityGroup": {
+ "id": "[variables('nsgID')]"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "apiVersion": "2015-06-15",
+ "type": "Microsoft.Network/networkInterfaces",
+ "name": "[variables('vmNicName')]",
+ "location": "[variables('location')]",
+ "dependsOn": [
+ "[concat('Microsoft.Network/virtualNetworks/',variables('virtualNetworkName'))]"
+ ],
+ "properties": {
+ "ipConfigurations": [
+ {
+ "name": "ipconfigpri",
+ "properties": {
+ "privateIPAllocationMethod": "Dynamic",
+ "subnet": {
+ "id": "[variables('subnetRef')]"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "apiVersion": "2015-06-15",
+ "type": "Microsoft.Network/networkInterfaces",
+ "name": "[concat(variables('vmNicName'),copyIndex())]",
+ "location": "[variables('location')]",
+ "copy": {
+ "name": "nicLoop",
+ "count": "[parameters('vmCount')]"
+ },
+ "dependsOn": [
+ "[concat('Microsoft.Network/virtualNetworks/',variables('virtualNetworkName'))]"
+ ],
+ "properties": {
+ "ipConfigurations": [
+ {
+ "name": "ipconfigprivate",
+ "properties": {
+ "privateIPAllocationMethod": "Dynamic",
+ "subnet": {
+ "id": "[variables('subnetRef')]"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "apiVersion": "2015-06-15",
+ "type": "Microsoft.Compute/virtualMachines",
+ "name": "[variables('vmName')]",
+ "location": "[variables('location')]",
+ "dependsOn": [
+ "vmLoop"
+ ],
+ "properties": {
+ "hardwareProfile": {
+ "vmSize": "[variables('controllerVmSize')]"
+ },
+ "osProfile": {
+ "computerName": "[variables('vmName')]",
+ "adminUsername": "[variables('vmAdminUsername')]",
+ "adminPassword": "[variables('vmAdminPassword')]"
+ },
+ "storageProfile": {
+ "imageReference": {
+ "publisher": "[variables('imagePublisher')]",
+ "offer": "[variables('imageOffer')]",
+ "sku": "[variables('vmOsSku')]",
+ "version": "latest"
+ },
+ "osDisk": {
+ "name": "osdisk",
+ "vhd": {
+ "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('uniqueStorageAccountName')),providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).primaryEndpoints.blob,variables('uniqueStorageAccountContainerName'),'/',variables('vmOsDiskName'),'.vhd')]"
+ },
+ "caching": "ReadWrite",
+ "createOption": "FromImage"
+ }
+ },
+ "networkProfile": {
+ "networkInterfaces": [
+ {
+ "id": "[resourceId('Microsoft.Network/networkInterfaces',concat(variables('vmNicName')))]"
+ }
+ ]
+ },
+ "diagnosticsProfile": {
+ "bootDiagnostics": {
+ "enabled": "true",
+ "storageUri": "[reference(concat('Microsoft.Storage/storageAccounts/', variables('uniqueStorageAccountName')),providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).primaryEndpoints.blob]"
+ }
+ }
+ }
+ },
+ {
+ "apiVersion": "2015-06-15",
+ "type": "Microsoft.Compute/virtualMachines",
+ "name": "[concat(variables('vmName'),copyIndex())]",
+ "location": "[variables('location')]",
+ "copy": {
+ "name": "vmLoop",
+ "count": "[parameters('vmCount')]",
+ "mode": "Serial",
+ "batchSize": "[parameters('batchSize')]"
+ },
+ "dependsOn": [
+ "[concat('Microsoft.Storage/storageAccounts/',variables('uniqueStorageAccountName'))]",
+ "[concat('Microsoft.Network/networkInterfaces/',variables('vmNicName'),copyIndex())]"
+ ],
+ "properties": {
+ "hardwareProfile": {
+ "vmSize": "[parameters('vmSize')]"
+ },
+ "osProfile": {
+ "computerName": "[variables('vmName')]",
+ "adminUsername": "[variables('vmAdminUsername')]",
+ "adminPassword": "[variables('vmAdminPassword')]"
+ },
+ "storageProfile": {
+ "imageReference": {
+ "publisher": "[variables('imagePublisher')]",
+ "offer": "[variables('imageOffer')]",
+ "sku": "[variables('vmOsSku')]",
+ "version": "latest"
+ },
+ "osDisk": {
+ "name": "osdisk",
+ "vhd": {
+ "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('uniqueStorageAccountName')),providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).primaryEndpoints.blob,variables('uniqueStorageAccountContainerName'),copyIndex(),'/',concat(variables('vmOsDiskName'),copyIndex()),'.vhd')]"
+ },
+ "caching": "ReadWrite",
+ "createOption": "FromImage"
+ }
+ },
+ "networkProfile": {
+ "networkInterfaces": [
+ {
+ "id": "[resourceId('Microsoft.Network/networkInterfaces',concat(variables('vmNicName'),copyIndex()))]"
+ }
+ ]
+ },
+ "diagnosticsProfile": {
+ "bootDiagnostics": {
+ "enabled": "true",
+ "storageUri": "[reference(concat('Microsoft.Storage/storageAccounts/', variables('uniqueStorageAccountName')),providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).primaryEndpoints.blob]"
+ }
+ }
+ }
+ },
+ {
+ "apiVersion": "2015-06-15",
+ "type": "Microsoft.Compute/virtualMachines/extensions",
+ "name": "[concat(variables('vmName'),'/dscExtension')]",
+ "location": "[variables('location')]",
+ "dependsOn": [
+ "[concat('Microsoft.Compute/virtualMachines/',variables('vmName'))]"
+ ],
+ "properties": {
+ "publisher": "Microsoft.Powershell",
+ "type": "DSC",
+ "typeHandlerVersion": "2.15",
+ "autoUpgradeMinorVersion": true,
+ "settings": {
+ "modulesUrl": "[variables('modulesUrlVMBootAll')]",
+ "configurationFunction": "[variables('configurationFunctionVMBootAll')]",
+ "properties": {
+ "AzureAccountCreds": {
+ "userName": "[parameters('azureAccountUsername')]",
+ "password": "PrivateSettingsRef:azureAccountPassword"
+ },
+ "TenantId": "[parameters('tenantId')]",
+ "Location": "[variables('location')]",
+ "VMName": "[variables('vmName')]",
+ "VMCount": "[parameters('vmCount')]",
+ "VMAdminCreds": {
+ "userName": "[variables('vmAdminUsername')]",
+ "password": "PrivateSettingsRef:vmAdminPassword"
+ },
+ "AzureStorageAccount": "[variables('uniqueStorageAccountName')]",
+ "AzureStorageAccessKey": "[listKeys(concat('Microsoft.Storage/storageAccounts/', variables('uniqueStorageAccountName')), '2015-06-15').key1]",
+ "AzureStorageEndpoint": "[reference(resourceId('Microsoft.Storage/storageAccounts', variables('uniqueStorageAccountName')), '2015-06-15').primaryEndpoints['blob']]",
+ "AzureSubscription": "[subscription().subscriptionId]"
+ }
+ },
+ "protectedSettings": {
+ "items": {
+ "vmAdminPassword": "[variables('vmAdminPassword')]",
+ "azureAccountPassword": "[parameters('azureAccountPassword')]"
+ }
+ }
+ }
+ }
+ ]
}
\ No newline at end of file
diff --git a/bootstorm-vm-boot-time/azuredeploy.parameters.json b/bootstorm-vm-boot-time/azuredeploy.parameters.json
index 68bab09..9d73be5 100644
--- a/bootstorm-vm-boot-time/azuredeploy.parameters.json
+++ b/bootstorm-vm-boot-time/azuredeploy.parameters.json
@@ -1,21 +1,21 @@
-{
- "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentParameters.json#",
- "contentVersion": "1.16.08.15",
- "parameters": {
- "azureAccountUsername": {
- "value": "AZURE-AD-ACCOUNT-USERNAME"
- },
- "azureAccountPassword": {
- "value": "AZURE-AD-ACCOUNT-PASSWORD"
- },
- "tenantId": {
- "value": "AZURE-AD-ACCOUNT-TENANT-ID"
- },
- "vmCount": {
- "value": 2
- },
- "vmSize": {
- "value": "Standard_A1"
- }
- }
+{
+ "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentParameters.json#",
+ "contentVersion": "1.16.08.15",
+ "parameters": {
+ "azureAccountUsername": {
+ "value": "AZURE-AD-ACCOUNT-USERNAME"
+ },
+ "azureAccountPassword": {
+ "value": "AZURE-AD-ACCOUNT-PASSWORD"
+ },
+ "tenantId": {
+ "value": "AZURE-AD-ACCOUNT-TENANT-ID"
+ },
+ "vmCount": {
+ "value": 2
+ },
+ "vmSize": {
+ "value": "Standard_A1"
+ }
+ }
}
\ No newline at end of file
diff --git a/iostorm-vm-iops-latency/VMIOWorkloadController.zip b/iostorm-vm-iops-latency/VMIOWorkloadController.zip
index f86e160..5d5f929 100644
Binary files a/iostorm-vm-iops-latency/VMIOWorkloadController.zip and b/iostorm-vm-iops-latency/VMIOWorkloadController.zip differ