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

Commit

Permalink
Added hosts endpoint and tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
ashcrow committed Jan 8, 2016
1 parent 6b08aa4 commit a7ec949
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 36 deletions.
4 changes: 4 additions & 0 deletions conf/logger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ loggers:
level: DEBUG
handlers: [console]
propagate: no
resources:
level: DEBUG
handlers: [console]
propagate: no
root:
level: DEBUG
handlers: [console]
18 changes: 11 additions & 7 deletions doc/endpoints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,23 @@ Retrieve a list of hosts.
[
{
"address": string,
"status": enum(string),
"os": string,
"cpus": int,
"memory": int,
"space": int,
"last_check": string
"address": string, // The IP address of the cluster host
"status": enum(string), // The status of the cluster host
"os": enum(string), // The OS name
"cpus": int, // The number of CPUs on the cluster host
"memory": int, // The memory of the cluster host in kilobytes
"space": int, // The diskspace on the cluster host
"last_check": string // ISO date format the cluster host was last checked
}...
]
.. note::
See :ref:`host-statuses` for a list and description of host statuses.

.. note::
See :ref:`host-os` for a list and description of host statuses.



Example
~~~~~~~
Expand Down
18 changes: 16 additions & 2 deletions doc/statuses.rst → doc/enums.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
Enums
=====

.. _host-os:

OS's
----

* **atomic**: http://www.projectatomic.io/
* **rhel**: http://www.redhat.com/en/technologies/linux-platforms/enterprise-linux
* **fedora**: https://getfedora.org/



Statuses
========
--------

.. _host-statuses:

Host Statuses
-------------
~~~~~~~~~~~~~

* **investigating**: The host has passed credentials to commissaire which is now looking at the system.
* **bootstrapping**: The host is going through changes to become active.
Expand Down
2 changes: 1 addition & 1 deletion doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Contents:

authentication
endpoints
statuses
enums

API Documentation:

Expand Down
75 changes: 68 additions & 7 deletions src/commissaire/handlers/hosts.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,38 +22,78 @@


class Host(Model):
"""
Representation of a Host.
"""
_json_type = dict
_attributes = (
'address', 'status', 'os', 'cpus', 'memory', 'space', 'last_check')


class Hosts(Model):
"""
Representation of a group of one or more Hosts.
"""
_json_type = list
_attributes = ('hosts', )


class HostsResource(Resource):
"""
Resource for working with Hosts.
"""

def on_get(self, req, resp):
hosts_dir = self.store.get('/testing/hosts/')
"""
Handles GET requests for Hosts.
:param req: Request instance that will be passed through.
:type req: falcon.Request
:param resp: Response instance that will be passed through.
:type resp: falcon.Response
"""
try:
hosts_dir = self.store.get('/commissaire/hosts/')
except etcd.EtcdKeyNotFound:
self.logger.warn(
'Etcd does not have any hosts. Returning [] and 404.')
resp.status = falcon.HTTP_404
req.context['model'] = None
return
results = []
# Don't let an empty host directory through
if hosts_dir.value is not None:
if len(hosts_dir._children):
for host in hosts_dir.leaves:
results.append(Host(**json.loads(host.value)))
resp.status = falcon.HTTP_200
req.context['model'] = Hosts(hosts=results)
else:
self.logger.debug(
'Etcd has a hosts directory but no content.')
resp.status = falcon.HTTP_200
req.context['model'] = None


# TODO
'''
class HostResource(Resource):
"""
Resource for working with a single Host.
"""
def on_get(self, req, resp, address):
"""
Handles retrieval of an existing Host.
:param req: Request instance that will be passed through.
:type req: falcon.Request
:param resp: Response instance that will be passed through.
:type resp: falcon.Response
:param address: The address of the Host being requested.
:type address: str
"""
# TODO: Verify input
try:
host = self.store.get('/testing/hosts/{0}'.format(address))
host = self.store.get('/commissaire/hosts/{0}'.format(address))
except etcd.EtcdKeyNotFound:
resp.status = falcon.HTTP_404
return
Expand All @@ -63,25 +103,46 @@ def on_get(self, req, resp, address):
req.context['model'] = Host(**json.loads(host.value))
def on_put(self, req, resp, address):
"""
Handles the creation of a new Host.
:param req: Request instance that will be passed through.
:type req: falcon.Request
:param resp: Response instance that will be passed through.
:type resp: falcon.Response
:param address: The address of the Host being requested.
:type address: str
"""
# TODO: Verify input
try:
host = self.store.get('/testing/hosts/{0}'.format(address))
host = self.store.get('/commissaire/hosts/{0}'.format(address))
resp.status = falcon.HTTP_409
return
except etcd.EtcdKeyNotFound:
data = req.stream.read()
print(data.decode())
host = Host(**json.loads(data.decode()))
new_host = self.store.set(
'/testing/hosts/{0}'.format(address), host.to_json())
'/commissaire/hosts/{0}'.format(address), host.to_json())
resp.status = falcon.HTTP_201
req.context['model'] = Host(**json.loads(new_host.value))
def on_delete(self, req, resp, address):
"""
Handles the Deletion of a Host.
:param req: Request instance that will be passed through.
:type req: falcon.Request
:param resp: Response instance that will be passed through.
:type resp: falcon.Response
:param address: The address of the Host being requested.
:type address: str
"""
resp.body = '{}'
try:
host = self.store.delete(
'/testing/hosts/{0}'.format(address))
'/commissaire/hosts/{0}'.format(address))
falcon.status = falcon.HTTP_410
except etcd.EtcdKeyNotFound:
resp.status = falcon.HTTP_404
'''
3 changes: 3 additions & 0 deletions src/commissaire/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import logging


class Resource:

def __init__(self, store, queue=None, **kwargs):
self.store = store
self.queue = queue
self.logger = logging.getLogger('resources')
38 changes: 21 additions & 17 deletions src/commissaire/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
from gevent.queue import Queue, Empty
from gevent.pywsgi import WSGIServer

from commissaire.handlers.hosts import HostsResource, HostResource
from commissaire.handlers.hosts import HostsResource # , HostResource
from commissaire.authentication.httpauth import HTTPBasicAuthByFile
from commissaire.middleware import JSONify


ROUTER_QUEUE = Queue()
Expand All @@ -40,7 +41,7 @@
}


def host_watcher(q, c):
def host_watcher(q, c): # pragma: no cover
logger = logging.getLogger('watcher')
next_idx = None
logger.info('Starting watcher from latest index')
Expand All @@ -57,7 +58,7 @@ def host_watcher(q, c):


# TODO: multiprocess?
def router(q):
def router(q): # pragma: no cover
logger = logging.getLogger('router')
logger.info('Starting router')
while True:
Expand Down Expand Up @@ -91,32 +92,35 @@ def router(q):
change.etcd_index, sent_to))


def main():
def create_app(ds): # pragma: no cover
# TODO: make the loading configurable
http_auth = HTTPBasicAuthByFile('./conf/users.yaml')
app = falcon.API(middleware=[http_auth, JSONify()])

# app.add_route('/api/v0/hosts/{address}', HostResource(ds))
app.add_route('/api/v0/hosts', HostsResource(ds, ROUTER_QUEUE))
return app


def main(): # pragma: no cover
import yaml
from commissaire.middleware import JSONify
# TODO: make the loading configurable
logging.config.dictConfig(yaml.safe_load(open('./conf/logger.yaml', 'r')))

ds = etcd.Client(port=2379)

watch_thread = gevent.spawn(host_watcher, ROUTER_QUEUE, ds)
router_thread = gevent.spawn(router, ROUTER_QUEUE)

# TODO: make the loading configurable
http_auth = HTTPBasicAuthByFile('./conf/users.yaml')
app = falcon.API(middleware=[http_auth, JSONify()])
# watch_thread = gevent.spawn(host_watcher, ROUTER_QUEUE, ds)
# router_thread = gevent.spawn(router, ROUTER_QUEUE)

# app.add_route('/streaming', HelloApp())
app.add_route('/api/v0/hosts/{address}', HostResource(ds))
app.add_route('/api/v0/hosts', HostsResource(ds, ROUTER_QUEUE))
app = create_app(ds)
try:
WSGIServer(('127.0.0.1', 8000), app).serve_forever()
except KeyboardInterrupt:
pass

watch_thread.kill()
router_thread.kill()
# watch_thread.kill()
# router_thread.kill()


if __name__ == '__main__':
if __name__ == '__main__': # pragma: no cover
main()
1 change: 1 addition & 0 deletions test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ requests
nose
pep8
coverage
mock
4 changes: 2 additions & 2 deletions test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import unittest
from falcon.testing import TestBase


class TestCase(unittest.TestCase):
class TestCase(TestBase):
"""
Parent class for all unittests.
"""
Expand Down

0 comments on commit a7ec949

Please sign in to comment.