Skip to content
This repository has been archived by the owner on Feb 2, 2018. It is now read-only.

Commit

Permalink
Merge pull request #25 from mbarnes/host-initial-cluster
Browse files Browse the repository at this point in the history
Support specifying cluster in host creation
  • Loading branch information
ashcrow committed Feb 9, 2016
2 parents b5b9b37 + d4a05b1 commit b2d4972
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 24 deletions.
2 changes: 2 additions & 0 deletions doc/endpoints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ Creates a new host record.
{
"address": string // The IP address of the cluster host
"ssh_priv_key": string // base64 encoded ssh private key
"cluster": string // The cluster the host should be associated with
}
.. note::
Expand All @@ -317,6 +318,7 @@ Example
{
"address": "192.168.100.50",
"cluster": "default",
"ssh_priv_key": "dGVzdAo..."
}
Expand Down
33 changes: 33 additions & 0 deletions example/rest_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,39 @@ def expected_status(r, code):
print(r.json())
expected_status(r, 404)

print("=> Creating Host 10.2.0.3 With Bad Cluster (Should Fail)")
r = requests.put(
SERVER + '/api/v0/host/10.2.0.3', auth=AUTH,
json={
"address": "10.2.0.3",
"ssh_priv_key": "dGVzdAo=",
"cluster": "headache"
})
print(r.json())
expected_status(r, 409)

print("=> Make Sure There Are No New Clusters")
r = requests.get(SERVER + '/api/v0/clusters', auth=AUTH)
print(r.json())
expect = ['honeynut']
expected_json(r.json(), expect) and expected_status(r, 200)

print("=> Creating Host 10.2.0.3 With 'honeynut' Cluster")
r = requests.put(
SERVER + '/api/v0/host/10.2.0.3', auth=AUTH,
json={
"address": "10.2.0.3",
"ssh_priv_key": "dGVzdAo=",
"cluster": "honeynut"
})
print(r.json())
expected_status(r, 201)

print("=> Verify Host 10.2.0.3 In Cluster 'honeynut'")
r = requests.get(SERVER + '/api/v0/cluster/honeynut/hosts/10.2.0.3', auth=AUTH)
print(r.json())
expected_status(r, 200)

print("=> Initiate Cluster Upgrade Without Auth (Should Fail)")
r = requests.put(
SERVER + '/api/v0/cluster/honeynut/upgrade',
Expand Down
4 changes: 2 additions & 2 deletions src/commissaire/handlers/clusters.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def on_get(self, req, resp, name):
self.logger.debug('{0}'.format(etcd_resp))
except etcd.EtcdKeyNotFound:
self.logger.info(
'Request of non-existent cluster {0} requested.'.format(name))
'Request for non-existent cluster {0}.'.format(name))
resp.status = falcon.HTTP_404
return

Expand Down Expand Up @@ -204,7 +204,7 @@ def get_cluster_model(self, name):
self.logger.debug('{0}'.format(etcd_resp))
except etcd.EtcdKeyNotFound:
self.logger.info(
'Request of non-existent cluster {0} requested.'.format(name))
'Request for non-existent cluster {0}.'.format(name))
return None
return Cluster(**json.loads(etcd_resp.value))

Expand Down
72 changes: 54 additions & 18 deletions src/commissaire/handlers/hosts.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,24 +106,60 @@ def on_put(self, req, resp, address):
resp.status = falcon.HTTP_409
return
except etcd.EtcdKeyNotFound:
data = req.stream.read().decode()
host_creation = json.loads(data)
ssh_priv_key = host_creation['ssh_priv_key']
host_creation['address'] = address
host_creation['os'] = ''
host_creation['status'] = 'investigating'
host_creation['cpus'] = -1
host_creation['memory'] = -1
host_creation['space'] = -1
host_creation['ssh_priv_key'] = ssh_priv_key
host_creation['last_check'] = None
host = Host(**host_creation)
new_host = self.store.set(
'/commissaire/hosts/{0}'.format(
address), host.to_json(secure=True))
INVESTIGATE_QUEUE.put((host_creation, ssh_priv_key))
resp.status = falcon.HTTP_201
req.context['model'] = Host(**json.loads(new_host.value))
pass

data = req.stream.read().decode()
host_creation = json.loads(data)
ssh_priv_key = host_creation['ssh_priv_key']
host_creation['address'] = address
host_creation['os'] = ''
host_creation['status'] = 'investigating'
host_creation['cpus'] = -1
host_creation['memory'] = -1
host_creation['space'] = -1
host_creation['last_check'] = None

# Don't store the cluster name in etcd.
cluster_name = host_creation.pop('cluster', None)

# Verify the cluster exists, if given. Do it now
# so we can fail before writing anything to etcd.
if cluster_name:
# XXX: Based on ClusterSingleHostResource.on_put().
# Add a util module to share common operations.
cluster_key = '/commissaire/clusters/{0}'.format(cluster_name)
try:
etcd_resp = self.store.get(cluster_key)
self.logger.info(
'Request for cluster {0}'.format(cluster_name))
self.logger.debug('{0}'.format(etcd_resp))
except etcd.EtcdKeyNotFound:
self.logger.info(
'Request for non-existent cluster {0}.'.format(
cluster_name))
resp.status = falcon.HTTP_409
return
cluster = Cluster(**json.loads(etcd_resp.value))
hostset = set(cluster.hostset)
hostset.add(address) # Ensures no duplicates
cluster.hostset = list(hostset)

host = Host(**host_creation)
new_host = self.store.set(
'/commissaire/hosts/{0}'.format(
address), host.to_json(secure=True))
INVESTIGATE_QUEUE.put((host_creation, ssh_priv_key))

# Add host to the requested cluster.
if cluster_name:
# FIXME: Should guard against races here, since we're fetching
# the cluster record and writing it back with some parts
# unmodified. Use either locking or a conditional write
# with the etcd 'modifiedIndex'. Deferring for now.
self.store.set(cluster_key, cluster.to_json(secure=True))

resp.status = falcon.HTTP_201
req.context['model'] = Host(**json.loads(new_host.value))

def on_delete(self, req, resp, address):
"""
Expand Down
25 changes: 21 additions & 4 deletions test/test_handlers_hosts.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ class Test_HostResource(TestCase):
' "cpus": 2, "memory": 11989228, "space": 487652,'
' "last_check": "2015-12-17T15:48:18.710454"}')

etcd_cluster = '{"status": "ok", "hostset": []}'

def before(self):
self.api = falcon.API(middleware = [JSONify()])
self.datasource = etcd.Client()
Expand Down Expand Up @@ -245,16 +247,31 @@ def test_host_create(self):
"""
Verify creation of a Host.
"""
self.datasource.get.side_effect = etcd.EtcdKeyNotFound

self.datasource.get.side_effect = (
etcd.EtcdKeyNotFound, MagicMock(value=self.etcd_cluster))
self.return_value.value = self.etcd_host
data = '{"address": "10.2.0.2", "ssh_priv_key": "dGVzdAo=", "cluster": "testing"}'
data = ('{"address": "10.2.0.2",'
' "ssh_priv_key": "dGVzdAo=",'
' "cluster": "testing"}')
body = self.simulate_request(
'/api/v0/host/10.2.0.2', method='PUT', body=data)
# datasource's set should have been called once
self.assertEquals(1, self.datasource.set.call_count)
# datasource's set should have been called twice
self.assertEquals(2, self.datasource.set.call_count)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
self.assertEqual(json.loads(self.ahost), json.loads(body[0]))

# Make sure creation fails if the cluster doesn't exist
self.datasource.get.side_effect = etcd.EtcdKeyNotFound
self.datasource.get.reset_mock()
self.datasource.set.reset_mock()
body = self.simulate_request(
'/api/v0/host/10.2.0.2', method='PUT', body=data)
# datasource's set should not have been called
self.assertEquals(0, self.datasource.set.call_count)
self.assertEqual(self.srmock.status, falcon.HTTP_409)
self.assertEqual({}, json.loads(body[0]))

# Make sure that if the host exists creation doesn't happen
self.datasource.get.side_effect = None
self.datasource.get.reset_mock()
Expand Down

0 comments on commit b2d4972

Please sign in to comment.