Do not satisfy min-ready requests if at capacity

If a provider gets a min-ready request, but it is already at capacity,
it should decline the request so as to not wedge the provider (because
a min-ready does not reuse READY nodes, provider would have to wait until
a node was freed to satisfy the min-ready request).

Change-Id: I37dc0802c1b30714833d6f46cc19b86deb58852b
This commit is contained in:
David Shrewsbury 2017-10-06 08:08:04 -04:00
parent 2d3543bd90
commit c739eec853
5 changed files with 99 additions and 1 deletions

View File

@ -486,6 +486,15 @@ class OpenStackNodeRequestHandler(NodeRequestHandler):
if len(self.request.node_types) > self.pool.max_servers: if len(self.request.node_types) > self.pool.max_servers:
declined_reasons.append('it would exceed quota') declined_reasons.append('it would exceed quota')
# For min-ready requests, which do not re-use READY nodes, let's
# decline if this provider is already at capacity. Otherwise, we
# could end up wedged until another request frees up a node.
if self.request.requestor == "NodePool:min-ready":
current_count = self.zk.countPoolNodes(self.provider.name,
self.pool.name)
if current_count == self.pool.max_servers:
declined_reasons.append("provider cannot satisify min-ready")
if declined_reasons: if declined_reasons:
self.log.debug("Declining node request %s because %s", self.log.debug("Declining node request %s because %s",
self.request.id, ', '.join(declined_reasons)) self.request.id, ', '.join(declined_reasons))

View File

@ -786,7 +786,7 @@ class NodePool(threading.Thread):
req.requestor = "NodePool:min-ready" req.requestor = "NodePool:min-ready"
req.node_types.append(label_name) req.node_types.append(label_name)
req.reuse = False # force new node launches req.reuse = False # force new node launches
self.zk.storeNodeRequest(req) self.zk.storeNodeRequest(req, priority="100")
if label_name not in self._submittedRequests: if label_name not in self._submittedRequests:
self._submittedRequests[label_name] = [] self._submittedRequests[label_name] = []
self._submittedRequests[label_name].append(req) self._submittedRequests[label_name].append(req)

View File

@ -0,0 +1,47 @@
elements-dir: .
images-dir: '{images_dir}'
zookeeper-servers:
- host: {zookeeper_host}
port: {zookeeper_port}
chroot: {zookeeper_chroot}
labels:
- name: fake-label
min-ready: 0
providers:
- name: fake-provider
cloud: fake
driver: fake
region-name: fake-region
rate: 0.0001
diskimages:
- name: fake-image
meta:
key: value
key2: value
pools:
- name: main
max-servers: 1
availability-zones:
- az1
networks:
- net-name
labels:
- name: fake-label
diskimage: fake-image
min-ram: 8192
flavor-name: 'Fake'
diskimages:
- name: fake-image
elements:
- fedora
- vm
release: 21
env-vars:
TMPDIR: /opt/dib_tmp
DIB_IMAGE_CACHE: /opt/dib_cache
DIB_CLOUD_IMAGES: http://download.fedoraproject.org/pub/fedora/linux/releases/test/21-Beta/Cloud/Images/x86_64/
BASE_IMAGE_FILE: Fedora-Cloud-Base-20141029-21_Beta.x86_64.qcow2

View File

@ -228,6 +228,35 @@ class TestLauncher(tests.DBTestCase):
self.assertEqual(req.state, zk.FAILED) self.assertEqual(req.state, zk.FAILED)
self.assertNotEqual(req.declined_by, []) self.assertNotEqual(req.declined_by, [])
def test_fail_minready_request_at_capacity(self):
'''
A min-ready request to a provider that is already at capacity should
be declined.
'''
configfile = self.setup_config('node_min_ready_capacity.yaml')
self._useBuilder(configfile)
self.waitForImage('fake-provider', 'fake-image')
pool = self.useNodepool(configfile, watermark_sleep=1)
pool.start()
# Get an initial node ready
req = zk.NodeRequest()
req.state = zk.REQUESTED
req.node_types.append("fake-label")
self.zk.storeNodeRequest(req)
req = self.waitForNodeRequest(req)
self.assertEqual(req.state, zk.FULFILLED)
# Now simulate a min-ready request
min_ready_req = zk.NodeRequest()
min_ready_req.state = zk.REQUESTED
min_ready_req.node_types.append("fake-label")
min_ready_req.requestor = "NodePool:min-ready"
self.zk.storeNodeRequest(min_ready_req)
min_ready_req = self.waitForNodeRequest(min_ready_req)
self.assertEqual(min_ready_req.state, zk.FAILED)
self.assertNotEqual(min_ready_req.declined_by, [])
def test_invalid_image_fails(self): def test_invalid_image_fails(self):
''' '''
Test that an invalid image declines and fails the request. Test that an invalid image declines and fails the request.

View File

@ -1629,3 +1629,16 @@ class ZooKeeper(object):
req = self.getNodeRequest(req_id) req = self.getNodeRequest(req_id)
if req: if req:
yield req yield req
def countPoolNodes(self, provider_name, pool_name):
'''
Count the number of nodes that exist for the given provider pool.
:param str provider_name: The provider name.
:param str pool_name: The pool name.
'''
count = 0
for node in self.nodeIterator():
if node.provider == provider_name and node.pool == pool_name:
count = count + 1
return count