Add support for Azure Spot VM scale sets (#406)

* Add VMSS priorty settings in example configuration files. By setting
  priority to `Low`, Azure Spot instances can be used for the cluster,
  thereby leading to potentially significant cost savings.
* Update Azure VMSS documentation for the multiple VMSS scenario.
* Update doc for running Muchos (for Azure clusters) on Ubuntu clients
  to clarify Python package installation.
* Add additional validations to ensure only Azure Spot capable VM SKUs
  are used when the cluster is configured to use Azure Spot.
diff --git a/ansible/roles/azure/tasks/create_multiple_vmss.yml b/ansible/roles/azure/tasks/create_multiple_vmss.yml
index 906c43a..dd17caf 100644
--- a/ansible/roles/azure/tasks/create_multiple_vmss.yml
+++ b/ansible/roles/azure/tasks/create_multiple_vmss.yml
@@ -36,6 +36,7 @@
     location: "{{ location }}"
     name: "{{ vmss_name }}-{{ item.name_suffix }}"
     vm_size: "{{ item.sku }}"
+    priority: "{{ item.vmss_priority | default('Regular') }}"
     admin_username: "{{ cluster_user }}"
     ssh_password_enabled: false
     ssh_public_keys:
diff --git a/ansible/roles/azure/tasks/create_vmss.yml b/ansible/roles/azure/tasks/create_vmss.yml
index 2812f58..b520f18 100644
--- a/ansible/roles/azure/tasks/create_vmss.yml
+++ b/ansible/roles/azure/tasks/create_vmss.yml
@@ -45,6 +45,7 @@
     location: "{{ location }}"
     name: "{{ vmss_name }}"
     vm_size: "{{ vm_sku }}"
+    priority: "{{ vmss_priority | default('Regular') }}"
     admin_username: "{{ cluster_user }}"
     ssh_password_enabled: false
     ssh_public_keys:
diff --git a/conf/azure_multiple_vmss_vars.yml.example b/conf/azure_multiple_vmss_vars.yml.example
index 176c770..5d2f964 100644
--- a/conf/azure_multiple_vmss_vars.yml.example
+++ b/conf/azure_multiple_vmss_vars.yml.example
@@ -70,8 +70,11 @@
     roles:
       metrics: 1
       client: 1
+
+  # As an example, the below is also configured as an Azure Spot (Low-Priority) VMSS
   - name_suffix: vmss6
     sku: Standard_D8s_v3
+    vmss_priority: Low
     perf_profile: azd8s
     data_disk_count: 8
     data_disk_sku: Standard_LRS
diff --git a/conf/muchos.props.example b/conf/muchos.props.example
index 982e1f2..56a427f 100644
--- a/conf/muchos.props.example
+++ b/conf/muchos.props.example
@@ -116,6 +116,12 @@
 subnet_cidr = 10.1.0.0/16
 #Optional. If set to True, will create multiple VMSS based on multiple_vmss_vars.yml
 use_multiple_vmss = False
+# The below "vmss_priority" controls whether Azure Spot is used (or not).
+# When vmss_priority is set to Low, the cluster uses Azure Spot VM Scale Sets.
+# When vmss_priority is set to Regular (which is the default), Azure Spot is NOT used.
+# Note that for multiple-VMSS deployments, this setting can be overridden at a 
+# per-VMSS level by specifying vmss_priority in the conf/azure_multiple_vmss_vars.yml file.
+vmss_priority = Regular
 # Azure image reference defined as a pipe-delimited string in the format offer|publisher|sku|version|
 # Please refer 'Launching an Azure cluster' section of the README before making changes
 azure_image_reference = CentOS|OpenLogic|7.5|latest|
diff --git a/docs/azure-multiple-vmss.md b/docs/azure-multiple-vmss.md
index 100febf..18d6818 100644
--- a/docs/azure-multiple-vmss.md
+++ b/docs/azure-multiple-vmss.md
@@ -22,6 +22,7 @@
 |-----------|------------------------|---------|-------------|
 | `name_suffix` | Required | - | The name of each VMSS is constructed by concatenating the Muchos cluster name with this string. As an example, if your Muchos cluster is called `test`, and this field has a value of `ldr`, then the VMSS is created with a name `test-ldr`|
 | `sku` | Required | - | A string identifier specifying the Azure VM size. Refer to the [Azure documentation](https://docs.microsoft.com/en-us/azure/virtual-machines/dv3-dsv3-series) to lookup these strings. An example VM size is `Standard_D32s_v3` for a 32-vCPU [Dsv3](https://docs.microsoft.com/en-us/azure/virtual-machines/dv3-dsv3-series#dsv3-series) VM|
+| `vmss_priority` | Optional | Regular | If this not specified at each VM level, the value for `vmss_priority` from the `azure` section in [muchos.props](../conf/muchos.props.example) is used | This can be set to `Regular`, for regular VMs, or `Low` for [Spot VMs](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/spot-vms).|
 | `perf_profile` | Required | - | A string identifying a corresponding performance profile configuration section in muchos.props which contains perf profile parameters |
 | `azure_disk_device_path`| Optional | If not specified, the corresponding `azure_disk_device_path` value from the `azure` section in [muchos.props](../conf/muchos.props.example) is used | This is a device path used to enumerate attached SCSI or NVME disks to use for persistent local storage |
 | `azure_disk_device_pattern`| Optional | If not specified, the corresponding `azure_disk_device_pattern` value from the `azure` section in [muchos.props](../conf/muchos.props.example) is used | This is a device name wildcard pattern used (internally) in conjunction with `azure_disk_device_path` to enumerate attached SCSI or NVME disks to use for persistent local storage |
diff --git a/docs/azure-ubuntu-1804.md b/docs/azure-ubuntu-1804.md
index 1cdac95..d3573ea 100644
--- a/docs/azure-ubuntu-1804.md
+++ b/docs/azure-ubuntu-1804.md
@@ -10,14 +10,17 @@
 # https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-apt?view=azure-cli-latest
 
 # Install Ansible Azure for Python 3.  The main reason these tips were written
-# was to save you time on the following steps.  Muchos uses Python 3.  If
-# python-pip and pip were installed and used, those would go against Python 2
+# was to save you time on the following steps.  Muchos needs Python 3.6 and above.
+# If python-pip and pip were installed and used, those would go against Python 2
 # and would not work.
 sudo apt install python3-pip
-sudo pip3 install ansible[azure]==2.9.13
+sudo pip3 install -r lib/requirements.txt
+# Current versions of Ansible separate out the Azure-specific modules into a
+# separate "collection". To install that, and associated pre-requisites, a helper
+# script has been provided. Please be sure to execute this script:
+./scripts/install-ansible-for-azure
 ```
 
 A virtual python environment is not needed in Ubuntu.  The instructions that
 mention that are targeted for Centos 7.  The version of Python 3 and pip3 that
 ship with Ubuntu 18.04 suffice.
-
diff --git a/lib/muchos/config/azure.py b/lib/muchos/config/azure.py
index 06e131e..817350a 100644
--- a/lib/muchos/config/azure.py
+++ b/lib/muchos/config/azure.py
@@ -308,6 +308,11 @@
         return self.get("azure", "azure_proxy_host_vm_sku")
 
     @ansible_host_var
+    @is_valid(is_in(["Regular", "Low"]))
+    def vmss_priority(self):
+        return self.get("azure", "vmss_priority")
+
+    @ansible_host_var
     @default("Standard_LRS")
     @is_valid(is_in(["Standard_LRS", "Premium_LRS", "StandardSSD_LRS"]))
     def data_disk_sku(self):
@@ -356,6 +361,26 @@
             )
         )
 
+    def spot_capable_skus(self):
+        return list(
+            map(
+                lambda r: r.name,
+                filter(
+                    lambda s: len(
+                        list(
+                            filter(
+                                lambda c: c.name == "LowPriorityCapable"
+                                and c.value == "True",
+                                s.capabilities,
+                            )
+                        )
+                    )
+                    > 0,
+                    self.vm_skus_for_location,
+                ),
+            )
+        )
+
     def max_data_disks_for_skus(self):
         n = list(map(lambda r: r.name, self.vm_skus_for_location))
         d = list(
diff --git a/lib/muchos/config/azurevalidations.py b/lib/muchos/config/azurevalidations.py
index 7fb69dd..cdbdc39 100644
--- a/lib/muchos/config/azurevalidations.py
+++ b/lib/muchos/config/azurevalidations.py
@@ -93,6 +93,32 @@
             "when use_multiple_vmss == True, any VMSS with sku "
             "must be a valid VM SKU for the selected location",
         ),
+        # Cannot specify Spot (Low Priority) if VMSS SKU is / are not capable
+        ConfigValidator(
+            lambda config, client: config.getboolean(
+                "azure", "use_multiple_vmss"
+            )
+            or not config.vmss_priority() == "Low"
+            or config.vm_sku() in config.spot_capable_skus(),
+            "azure.vm_sku must be an Azure Spot (low priority) capable VM SKU",
+        ),
+        ConfigValidator(
+            lambda config, client: not config.getboolean(
+                "azure", "use_multiple_vmss"
+            )
+            or all(
+                [
+                    vmss.get("sku") in config.spot_capable_skus()
+                    if vmss.get("vmss_priority") == "Low"
+                    else True
+                    for vmss in config.azure_multiple_vmss_vars.get(
+                        "vars_list", []
+                    )
+                ]
+            ),
+            "when use_multiple_vmss == True, any VMSS set to use Azure Spot "
+            "(low priority) must use an Azure Spot-capable VM SKU",
+        ),
         # data_disk_sku in
         # ['Standard_LRS', 'StandardSSD_LRS', Premium_LRS']
         ConfigValidator(