fuel-main/nailgun/api/handlers.py
2012-09-11 11:45:48 +00:00

438 lines
13 KiB
Python

# -*- coding: utf-8 -*-
import json
import web
import ipaddr
import netaddr
from models import Release, Cluster, Node, Role, Network, Vlan
from settings import settings
import rpc
def check_client_content_type(handler):
content_type = web.ctx.env.get("CONTENT_TYPE", "application/json")
if web.ctx.path.startswith("/api")\
and not content_type.startswith("application/json"):
raise web.unsupportedmediatype
return handler()
handlers = {}
class HandlerRegistrator(type):
def __init__(cls, name, bases, dct):
super(HandlerRegistrator, cls).__init__(name, bases, dct)
if hasattr(cls, 'model'):
key = cls.model.__name__
if key in handlers:
raise Exception("Handler for %s already registered" % key)
handlers[key] = cls
class JSONHandler(object):
__metaclass__ = HandlerRegistrator
fields = []
@classmethod
def render(cls, instance, fields=None):
json_data = {}
use_fields = fields if fields else cls.fields
if not use_fields:
raise ValueError("No fields for serialize")
for field in use_fields:
if isinstance(field, (tuple,)):
if field[1] == '*':
subfields = None
else:
subfields = field[1:]
value = getattr(instance, field[0])
rel = getattr(
instance.__class__, field[0]).impl.__class__.__name__
if value is None:
pass
elif rel == 'ScalarObjectAttributeImpl':
handler = handlers[value.__class__.__name__]
json_data[field[0]] = handler.render(
value, fields=subfields
)
elif rel == 'CollectionAttributeImpl':
if not value:
json_data[field[0]] = []
else:
handler = handlers[value[0].__class__.__name__]
json_data[field[0]] = [
handler.render(v, fields=subfields) for v in value
]
else:
value = getattr(instance, field)
if value is None:
pass
else:
f = getattr(instance.__class__, field)
if hasattr(f, "impl"):
rel = f.impl.__class__.__name__
if rel == 'ScalarObjectAttributeImpl':
json_data[field] = value.id
elif rel == 'CollectionAttributeImpl':
json_data[field] = [v.id for v in value]
else:
json_data[field] = value
else:
json_data[field] = value
return json_data
class ClusterHandler(JSONHandler):
fields = (
"id",
"name",
("nodes", "*"),
("release", "*")
)
model = Cluster
@classmethod
def render(cls, instance, fields=None):
json_data = JSONHandler.render(instance, fields=cls.fields)
json_data["nodes"] = map(
NodeHandler.render,
instance.nodes
)
return json_data
def GET(self, cluster_id):
web.header('Content-Type', 'application/json')
q = web.ctx.orm.query(Cluster)
cluster = q.filter(Cluster.id == cluster_id).first()
if not cluster:
return web.notfound()
return json.dumps(
self.render(cluster),
indent=4
)
def PUT(self, cluster_id):
web.header('Content-Type', 'application/json')
q = web.ctx.orm.query(Cluster).filter(Cluster.id == cluster_id)
cluster = q.first()
if not cluster:
return web.notfound()
# additional validation needed?
data = Cluster.validate_json(web.data())
# /additional validation needed?
for key, value in data.iteritems():
if key == "nodes":
map(cluster.nodes.remove, cluster.nodes)
nodes = web.ctx.orm.query(Node).filter(
Node.id.in_(value)
)
map(cluster.nodes.append, nodes)
else:
setattr(cluster, key, value)
web.ctx.orm.add(cluster)
web.ctx.orm.commit()
return json.dumps(
self.render(cluster),
indent=4
)
def DELETE(self, cluster_id):
cluster = web.ctx.orm.query(Cluster).filter(
Cluster.id == cluster_id
).first()
if not cluster:
return web.notfound()
web.ctx.orm.delete(cluster)
web.ctx.orm.commit()
raise web.webapi.HTTPError(
status="204 No Content",
data=""
)
class ClusterCollectionHandler(JSONHandler):
def GET(self):
web.header('Content-Type', 'application/json')
return json.dumps(map(
ClusterHandler.render,
web.ctx.orm.query(Cluster).all()
), indent=4)
def POST(self):
web.header('Content-Type', 'application/json')
data = Cluster.validate(web.data())
release = web.ctx.orm.query(Release).get(data["release"])
cluster = Cluster(
name=data["name"],
release=release
)
# TODO: discover how to add multiple objects
if 'nodes' in data and data['nodes']:
nodes = web.ctx.orm.query(Node).filter(
Node.id.in_(data['nodes'])
)
map(cluster.nodes.append, nodes)
web.ctx.orm.add(cluster)
web.ctx.orm.commit()
used_nets = [n.cidr for n in web.ctx.orm.query(Network).all()]
used_vlans = [v.id for v in web.ctx.orm.query(Vlan).all()]
for network in release.networks_metadata:
new_vlan = sorted(list(set(settings.VLANS) - set(used_vlans)))[0]
vlan_db = Vlan(id=new_vlan)
web.ctx.orm.add(vlan_db)
pool = settings.NETWORK_POOLS[network['access']]
nets_free_set = netaddr.IPSet(pool) -\
netaddr.IPSet(settings.NET_EXCLUDE) -\
netaddr.IPSet(used_nets)
free_cidrs = sorted(list(nets_free_set._cidrs))
new_net = list(free_cidrs[0].subnet(24, count=1))[0]
nw_db = Network(
release=release.id,
name=network['name'],
access=network['access'],
cidr=str(new_net),
gateway=str(new_net[1]),
vlan_id=vlan_db.id
)
web.ctx.orm.add(nw_db)
web.ctx.orm.commit()
used_vlans.append(new_vlan)
used_nets.append(str(new_net))
raise web.webapi.created(json.dumps(
ClusterHandler.render(cluster),
indent=4
))
class ClusterChangesHandler(JSONHandler):
fields = (
"id",
"name",
)
def PUT(self, cluster_id):
web.header('Content-Type', 'application/json')
q = web.ctx.orm.query(Cluster).filter(Cluster.id == cluster_id)
cluster = q.first()
if not cluster:
return web.notfound()
message = {"method": "deploy", "args": {"var1": "Hello from nailgun"}}
rpc.cast('mcollective', message)
return json.dumps(
self.render(cluster),
indent=4
)
class ReleaseHandler(JSONHandler):
fields = (
"id",
"name",
"version",
"description",
"networks_metadata",
("roles", "name")
)
model = Release
def GET(self, release_id):
web.header('Content-Type', 'application/json')
q = web.ctx.orm.query(Release)
release = q.filter(Release.id == release_id).first()
if not release:
return web.notfound()
return json.dumps(
self.render(release),
indent=4
)
def PUT(self, release_id):
web.header('Content-Type', 'application/json')
q = web.ctx.orm.query(Release)
release = q.filter(Release.id == release_id).first()
if not release:
return web.notfound()
# additional validation needed?
data = Release.validate_json(web.data())
# /additional validation needed?
for key, value in data.iteritems():
setattr(release, key, value)
web.ctx.orm.commit()
return json.dumps(
self.render(release),
indent=4
)
def DELETE(self, release_id):
release = web.ctx.orm.query(Release).filter(
Release.id == release_id
).first()
if not release:
return web.notfound()
web.ctx.orm.delete(release)
web.ctx.orm.commit()
raise web.webapi.HTTPError(
status="204 No Content",
data=""
)
class ReleaseCollectionHandler(JSONHandler):
def GET(self):
web.header('Content-Type', 'application/json')
return json.dumps(map(
ReleaseHandler.render,
web.ctx.orm.query(Release).all()
), indent=4)
def POST(self):
web.header('Content-Type', 'application/json')
data = Release.validate(web.data())
release = Release()
for key, value in data.iteritems():
setattr(release, key, value)
web.ctx.orm.add(release)
web.ctx.orm.commit()
raise web.webapi.created(json.dumps(
ReleaseHandler.render(release),
indent=4
))
class NodeHandler(JSONHandler):
fields = ('id', 'name', 'info', ('roles', '*'), ('new_roles', '*'),
'status', 'mac', 'fqdn', 'ip', 'manufacturer', 'platform_name',
'redeployment_needed', 'os_platform')
model = Node
def GET(self, node_id):
web.header('Content-Type', 'application/json')
q = web.ctx.orm.query(Node)
node = q.filter(Node.id == node_id).first()
if not node:
return web.notfound()
return json.dumps(
self.render(node),
indent=4
)
def PUT(self, node_id):
web.header('Content-Type', 'application/json')
q = web.ctx.orm.query(Node)
node = q.filter(Node.id == node_id).first()
if not node:
return web.notfound()
# additional validation needed?
data = Node.validate_update(web.data())
if not data:
raise web.badrequest()
# /additional validation needed?
for key, value in data.iteritems():
setattr(node, key, value)
web.ctx.orm.commit()
return json.dumps(
self.render(node),
indent=4
)
def DELETE(self, node_id):
node = web.ctx.orm.query(Node).filter(
Node.id == node_id
).first()
if not node:
return web.notfound()
web.ctx.orm.delete(node)
web.ctx.orm.commit()
raise web.webapi.HTTPError(
status="204 No Content",
data=""
)
class NodeCollectionHandler(JSONHandler):
def GET(self):
web.header('Content-Type', 'application/json')
return json.dumps(map(
NodeHandler.render,
web.ctx.orm.query(Node).all()
), indent=4)
def POST(self):
web.header('Content-Type', 'application/json')
data = Node.validate(web.data())
node = Node()
for key, value in data.iteritems():
setattr(node, key, value)
web.ctx.orm.add(node)
web.ctx.orm.commit()
raise web.webapi.created(json.dumps(
NodeHandler.render(node),
indent=4
))
class RoleCollectionHandler(JSONHandler):
def GET(self):
web.header('Content-Type', 'application/json')
data = web.data() if web.data() else {}
if data:
data = Role.validate_json(data)
if 'release_id' in data:
return json.dumps(
map(
RoleHandler.render,
web.ctx.orm.query(Role).filter(
Role.id == data["release_id"]
)
), indent=4)
roles = web.ctx.orm.query(Role).all()
if 'node_id' in data:
result = []
for role in roles:
# TODO role filtering
# use request.form.cleaned_data['node_id'] to filter roles
if False:
continue
# if the role is suitable for the node, set 'available' field
# to True. If it is not, set it to False and also describe the
# reason in 'reason' field of rendered_role
rendered_role = RoleHandler.render(role)
rendered_role['available'] = True
result.append(rendered_role)
return json.dumps(result)
else:
return json.dumps(map(RoleHandler.render, roles))
class RoleHandler(JSONHandler):
fields = ('id', 'name', ('release', 'id', 'name'))
model = Role
def GET(self, role_id):
q = web.ctx.orm.query(Role)
role = q.filter(Role.id == role_id).first()
if not role:
return web.notfound()
return json.dumps(
self.render(role),
indent=4
)