From 19e8f2788cd76ca7b2c91d234b31a4836d031954 Mon Sep 17 00:00:00 2001
From: Monty Taylor <mordred@inaugust.com>
Date: Mon, 27 Mar 2017 12:10:13 -0500
Subject: [PATCH] Fetch list of AZs from nova if it's not configured

Nova has an API call that can fetch the list of available AZs. Use it to
provide a default list so that we can provide sane choices to the
scheduler related to multi-node requests rather than just letting nova
pick on a per-request basis.

Change-Id: I1418ab8a513280318bc1fe6e59301fda5cf7b890
---
 doc/source/configuration.rst | 16 +++++++++-------
 nodepool/fakeprovider.py     |  3 +++
 nodepool/nodepool.py         |  5 +++--
 nodepool/provider_manager.py | 12 ++++++++++++
 requirements.txt             |  2 +-
 5 files changed, 28 insertions(+), 10 deletions(-)

diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index db0d20ad6..729dfba35 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -365,14 +365,16 @@ Example::
 **optional**
 
   ``availability-zones`` (list)
-    Without it nodepool will rely on nova to schedule an availability zone.
+    A list of availability zones to use.
 
-    If it is provided, the value should be a list of availability zone names.
-    If you have more than one availability zone, you should specify them all
-    here, rather than letting one get selected by nova. By doing so, you allow
-    nodepool to group all nodes allocated for a node request into the same
-    zone, which will be selected at random from the list. If you do not list
-    your zones here, this grouping cannot be guaranteed.
+    If this setting is omitted, nodepool will fetch the list of all
+    availability zones from nova.  To restrict nodepool to a subset
+    of availability zones, supply a list of availability zone names
+    in this setting.
+
+    Nodepool chooses an availability zone from the list at random
+    when creating nodes but ensures that all nodes for a given
+    request are placed in the same availability zone.
 
   ``networks`` (list)
     Specify custom Neutron networks that get attached to each
diff --git a/nodepool/fakeprovider.py b/nodepool/fakeprovider.py
index ccf74259c..fc32f70ba 100644
--- a/nodepool/fakeprovider.py
+++ b/nodepool/fakeprovider.py
@@ -230,6 +230,9 @@ class FakeOpenStackCloud(object):
     def delete_server(self, name_or_id, delete_ips=True):
         self._delete(name_or_id, self._server_list)
 
+    def list_availability_zone_names(self):
+        return ['fake-az1', 'fake-az2']
+
 
 class FakeUploadFailCloud(FakeOpenStackCloud):
     log = logging.getLogger("nodepool.FakeUploadFailCloud")
diff --git a/nodepool/nodepool.py b/nodepool/nodepool.py
index 7cc4e4638..30ac20714 100644
--- a/nodepool/nodepool.py
+++ b/nodepool/nodepool.py
@@ -661,8 +661,9 @@ class NodeRequestHandler(object):
             if not got_a_node:
                 # Select grouping AZ if we didn't set AZ from a selected,
                 # pre-existing node
-                if not self.chosen_az and self.pool.azs:
-                    self.chosen_az = random.choice(self.pool.azs)
+                if not self.chosen_az:
+                    self.chosen_az = random.choice(
+                        self.pool.azs or self.manager.getAZs())
 
                 # If we calculate that we're at capacity, pause until nodes
                 # are released by Zuul and removed by the DeletedNodeWorker.
diff --git a/nodepool/provider_manager.py b/nodepool/provider_manager.py
index ca14f3063..c9d62f8e0 100644
--- a/nodepool/provider_manager.py
+++ b/nodepool/provider_manager.py
@@ -86,6 +86,7 @@ class ProviderManager(object):
         self._images = {}
         self._networks = {}
         self.__flavors = {}
+        self.__azs = None
         self._use_taskmanager = use_taskmanager
         self._taskmanager = None
 
@@ -331,6 +332,17 @@ class ProviderManager(object):
         with shade_inner_exceptions():
             self._client.delete_unattached_floating_ips()
 
+    def getAZs(self):
+        if self.__azs is None:
+            self.__azs = self._client.list_availability_zone_names()
+            if not self.__azs:
+                # If there are no zones, return a list containing None so that
+                # random.choice can pick None and pass that to Nova. If this
+                # feels dirty, please direct your ire to policy.json and the
+                # ability to turn off random portions of the OpenStack API.
+                self.__azs = [None]
+        return self.__azs
+
 
 class FakeProviderManager(ProviderManager):
     def __init__(self, provider, use_taskmanager):
diff --git a/requirements.txt b/requirements.txt
index 53ebc2e75..865b01ae7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,7 +10,7 @@ PrettyTable>=0.6,<0.8
 # shade has a looser requirement on six than nodepool, so install six first
 six>=1.7.0
 os-client-config>=1.2.0
-shade>=1.12.0
+shade>=1.18.1
 diskimage-builder>=1.21.0
 voluptuous
 kazoo