Add ZK API methods for node requests

Adds ZooKeeper API methods to get the current list of outstanding node
requests, and to get the data for an individual node request. A new
NodeRequest object is introduced to the data model. The model will be
expanded on in future reviews.

Change-Id: I4af96e4e307cc5ce5d3208462e7335c24eece952
This commit is contained in:
David Shrewsbury 2017-01-04 12:14:31 -05:00
parent 0456fc5f37
commit 08b720364f
2 changed files with 130 additions and 10 deletions

View File

@ -437,27 +437,58 @@ class TestZooKeeper(tests.DBTestCase):
self.assertEqual(1, len(launchers))
self.assertEqual(name, launchers[0])
def test_getNodeRequests_empty(self):
self.assertEqual([], self.zk.getNodeRequests())
def test_getNodeRequests(self):
r1 = self.zk._requestPath("500-123")
r2 = self.zk._requestPath("100-456")
r3 = self.zk._requestPath("100-123")
r4 = self.zk._requestPath("400-123")
self.zk.client.create(r1, makepath=True, ephemeral=True)
self.zk.client.create(r2, makepath=True, ephemeral=True)
self.zk.client.create(r3, makepath=True, ephemeral=True)
self.zk.client.create(r4, makepath=True, ephemeral=True)
self.assertEqual(
["100-123", "100-456", "400-123", "500-123"],
self.zk.getNodeRequests()
)
def test_getNodeRequest(self):
r = zk.NodeRequest("500-123")
r.state = zk.READY
path = self.zk._requestPath(r.id)
self.zk.client.create(path, value=self.zk._dictToStr(r.toDict()),
makepath=True, ephemeral=True)
o = self.zk.getNodeRequest(r.id)
self.assertIsInstance(o, zk.NodeRequest)
self.assertEqual(r.id, o.id)
def test_getNodeRequest_not_found(self):
self.assertIsNone(self.zk.getNodeRequest("invalid"))
class TestZKModel(tests.BaseTestCase):
def setUp(self):
super(TestZKModel, self).setUp()
def test_BaseBuilderModel_bad_id(self):
def test_BaseModel_bad_id(self):
with testtools.ExpectedException(
TypeError, "'id' attribute must be a string type"
):
zk.BaseBuilderModel(123)
zk.BaseModel(123)
def test_BaseBuilderModel_bad_state(self):
def test_BaseModel_bad_state(self):
with testtools.ExpectedException(
TypeError, "'blah' is not a valid state"
):
o = zk.BaseBuilderModel('0001')
o = zk.BaseModel('0001')
o.state = 'blah'
def test_BaseBuilderModel_toDict(self):
o = zk.BaseBuilderModel('0001')
def test_BaseModel_toDict(self):
o = zk.BaseModel('0001')
o.state = zk.BUILDING
d = o.toDict()
self.assertNotIn('id', d)
@ -524,3 +555,23 @@ class TestZKModel(tests.BaseTestCase):
self.assertEqual(o.state_time, d['state_time'])
self.assertEqual(o.external_id, d['external_id'])
self.assertEqual(o.external_name, d['external_name'])
def test_NodeRequest_toDict(self):
o = zk.NodeRequest("500-123")
d = o.toDict()
self.assertNotIn('id', d)
self.assertIn('state', d)
self.assertIn('state_time', d)
def test_NodeRequest_fromDict(self):
now = int(time.time())
req_id = "500-123"
d = {
'state': zk.READY,
'state_time': now
}
o = zk.NodeRequest.fromDict(d, req_id)
self.assertEqual(o.id, req_id)
self.assertEqual(o.state, d['state'])
self.assertEqual(o.state_time, d['state_time'])

View File

@ -106,7 +106,7 @@ class ZooKeeperWatchEvent(object):
self.image = image
class BaseBuilderModel(object):
class BaseModel(object):
def __init__(self, o_id):
if o_id:
self.id = o_id
@ -137,7 +137,7 @@ class BaseBuilderModel(object):
def toDict(self):
'''
Convert a BaseBuilderModel object's attributes to a dictionary.
Convert a BaseModel object's attributes to a dictionary.
'''
d = {}
d['state'] = self.state
@ -157,7 +157,7 @@ class BaseBuilderModel(object):
self.state_time = d['state_time']
class ImageBuild(BaseBuilderModel):
class ImageBuild(BaseModel):
'''
Class representing a DIB image build within the ZooKeeper cluster.
'''
@ -216,7 +216,7 @@ class ImageBuild(BaseBuilderModel):
return o
class ImageUpload(BaseBuilderModel):
class ImageUpload(BaseModel):
'''
Class representing a provider image upload within the ZooKeeper cluster.
'''
@ -277,6 +277,42 @@ class ImageUpload(BaseBuilderModel):
return o
class NodeRequest(BaseModel):
'''
Class representing a node request.
'''
def __init__(self, id=None):
super(NodeRequest, self).__init__(id)
def __repr__(self):
d = self.toDict()
d['id'] = self.id
d['stat'] = self.stat
return '<NodeRequest %s>' % d
def toDict(self):
'''
Convert a NodeRequest object's attributes to a dictionary.
'''
d = super(NodeRequest, self).toDict()
return d
@staticmethod
def fromDict(d, o_id=None):
'''
Create a NodeRequest object from a dictionary.
:param dict d: The dictionary.
:param str o_id: The object ID.
:returns: An initialized ImageBuild object.
'''
o = NodeRequest(o_id)
super(NodeRequest, o).fromDict(d)
return o
class ZooKeeper(object):
'''
Class implementing the ZooKeeper interface.
@ -297,6 +333,7 @@ class ZooKeeper(object):
IMAGE_ROOT = "/nodepool/images"
LAUNCHER_ROOT = "/nodepool/launchers"
REQUEST_ROOT = "/nodepool/requests"
def __init__(self):
'''
@ -341,6 +378,9 @@ class ZooKeeper(object):
def _launcherPath(self, launcher):
return "%s/%s" % (self.LAUNCHER_ROOT, launcher)
def _requestPath(self, request):
return "%s/%s" % (self.REQUEST_ROOT, request)
def _dictToStr(self, data):
return json.dumps(data)
@ -1024,3 +1064,32 @@ class ZooKeeper(object):
return []
return launchers
def getNodeRequests(self):
'''
Get the current list of all node requests in priority sorted order.
:returns: A list of request nodes.
'''
try:
requests = self.client.get_children(self.REQUEST_ROOT)
except kze.NoNodeError:
return []
return sorted(requests)
def getNodeRequest(self, request):
'''
Get the data for a specific node request.
:returns: The request data, or None if the request was not found.
'''
path = self._requestPath(request)
try:
data, stat = self.client.get(path)
except kze.NoNodeError:
return None
d = NodeRequest.fromDict(self._strToDict(data), request)
d.stat = stat
return d