fuel-web/nailgun/nailgun/api/v1/handlers/node.py

460 lines
14 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Handlers dealing with nodes
"""
from datetime import datetime
import web
from nailgun.api.v1.handlers.base import BaseHandler
from nailgun.api.v1.handlers.base import CollectionHandler
from nailgun.api.v1.handlers.base import handle_errors
from nailgun.api.v1.handlers.base import serialize
from nailgun.api.v1.handlers.base import SingleHandler
from nailgun.api.v1.handlers.base import validate
from nailgun.api.v1.validators.network import NetAssignmentValidator
from nailgun.api.v1.validators import node as node_validators
from nailgun import consts
from nailgun import errors
from nailgun import objects
from nailgun.objects.serializers.node import NodeInterfacesSerializer
from nailgun.db import db
from nailgun.db.sqlalchemy.models import NetworkGroup
from nailgun.db.sqlalchemy.models import Node
from nailgun.db.sqlalchemy.models import NodeNICInterface
from nailgun.task.manager import NodeDeletionTaskManager
from nailgun.logger import logger
from nailgun import notifier
class NodeHandler(SingleHandler):
single = objects.Node
validator = node_validators.NodeValidator
@handle_errors
@validate
@serialize
def PUT(self, obj_id):
""":returns: JSONized Node object.
:http: * 200 (OK)
* 400 (error occured while processing of data)
* 404 (Node not found in db)
"""
obj = self.get_object_or_404(self.single, obj_id)
data = self.checked_data(
self.validator.validate_update,
instance=obj
)
# NOTE(aroma):if node is being assigned to the cluster, and if network
# template has been set for the cluster, network template will
# also be applied to node; in such case relevant errors might
# occur so they must be handled in order to form proper HTTP
# response for user
try:
self.single.update(obj, data)
except errors.NetworkTemplateCannotBeApplied as exc:
raise self.http(400, exc.message)
return self.single.to_json(obj)
@handle_errors
@validate
def DELETE(self, obj_id):
"""Deletes a node from DB and from Cobbler.
:return: JSON-ed deletion task
:http: * 200 (node has been succesfully deleted)
* 202 (node is successfully scheduled for deletion)
* 400 (data validation failed)
* 404 (node not found in db)
* 403 (one of the controllers is in error state or task can't
be started due to already running tasks)
"""
node = self.get_object_or_404(self.single, obj_id)
task_manager = NodeDeletionTaskManager(cluster_id=node.cluster_id)
try:
task = task_manager.execute([node], mclient_remove=False)
except (errors.TaskAlreadyRunning,
errors.ControllerInErrorState) as e:
raise self.http(403, e.message)
self.raise_task(task)
class NodeCollectionHandler(CollectionHandler):
"""Node collection handler"""
validator = node_validators.NodeValidator
collection = objects.NodeCollection
@handle_errors
@validate
@serialize
def GET(self):
"""May receive cluster_id parameter to filter list of nodes
:returns: Collection of JSONized Node objects.
:http: * 200 (OK)
"""
cluster_id = web.input(cluster_id=None).cluster_id
nodes = self.collection.eager_nodes_handlers(None)
if cluster_id == '':
nodes = nodes.filter_by(cluster_id=None)
elif cluster_id:
nodes = nodes.filter_by(cluster_id=cluster_id)
return self.collection.to_list(nodes)
@handle_errors
@validate
@serialize
def PUT(self):
""":returns: Collection of JSONized Node objects.
:http: * 200 (nodes are successfully updated)
* 400 (data validation failed)
"""
data = self.checked_data(
self.validator.validate_collection_update
)
nodes_updated = []
for nd in data:
node = self.collection.single.get_by_meta(nd)
if not node:
raise self.http(404, "Can't find node: {0}".format(nd))
try:
self.collection.single.update(node, nd)
except errors.NetworkTemplateCannotBeApplied as exc:
raise self.http(400, exc.message)
nodes_updated.append(node.id)
# we need eagerload everything that is used in render
nodes = self.collection.filter_by_id_list(
self.collection.eager_nodes_handlers(None),
nodes_updated
)
return self.collection.to_list(nodes)
@handle_errors
@validate
def DELETE(self):
"""Deletes a batch of nodes.
Takes (JSONed) list of node ids to delete.
:return: JSON-ed deletion task
:http: * 200 (nodes have been succesfully deleted)
* 202 (nodes are successfully scheduled for deletion)
* 400 (data validation failed)
* 404 (nodes not found in db)
* 403 (one of the controllers is in error state or task can't
be started due to already running tasks)
"""
# TODO(pkaminski): web.py does not support parsing of array arguments
# in the queryset so we specify the input as comma-separated list
node_ids = self.get_param_as_set('ids', default=[])
node_ids = self.checked_data(
validate_method=self.validator.validate_ids_list,
data=node_ids
)
nodes = self.get_objects_list_or_404(self.collection, node_ids)
task_manager = NodeDeletionTaskManager(cluster_id=nodes[0].cluster_id)
# NOTE(aroma): ditto as in comments for NodeHandler's PUT method;
try:
task = task_manager.execute(nodes, mclient_remove=False)
except (errors.TaskAlreadyRunning,
errors.ControllerInErrorState) as e:
raise self.http(403, e.message)
self.raise_task(task)
class NodeAgentHandler(BaseHandler):
collection = objects.NodeCollection
validator = node_validators.NodeValidator
@handle_errors
@validate
@serialize
def PUT(self):
""":returns: node id.
:http: * 200 (node are successfully updated)
* 304 (node data not changed since last request)
* 400 (data validation failed)
* 404 (node not found)
"""
nd = self.checked_data(
self.validator.validate_update,
data=web.data())
node = self.collection.single.get_by_meta(nd)
if not node:
raise self.http(404, "Can't find node: {0}".format(nd))
node.timestamp = datetime.now()
if not node.online:
node.online = True
msg = u"Node '{0}' is back online".format(node.human_readable_name)
logger.info(msg)
notifier.notify("discover", msg, node_id=node.id)
db().flush()
if 'agent_checksum' in nd and (
node.agent_checksum == nd['agent_checksum']
):
return {'id': node.id, 'cached': True}
nd['is_agent'] = True
self.collection.single.update_by_agent(node, nd)
return {"id": node.id}
class NodeBondAttributesDefaultsHandler(BaseHandler):
"""Bond default attributes handler"""
@handle_errors
@validate
@serialize
def GET(self, node_id):
""":returns: JSONized Bond default attributes.
:http: * 200 (OK)
* 404 (node not found in db)
"""
node = self.get_object_or_404(objects.Node, node_id)
return objects.Node.get_bond_default_attributes(node)
class NodeNICsHandler(BaseHandler):
"""Node network interfaces handler"""
model = NodeNICInterface
validator = NetAssignmentValidator
serializer = NodeInterfacesSerializer
@handle_errors
@validate
@serialize
def GET(self, node_id):
""":returns: Collection of JSONized Node interfaces.
:http: * 200 (OK)
* 404 (node not found in db)
"""
node = self.get_object_or_404(objects.Node, node_id)
return map(self.render, node.interfaces)
@handle_errors
@validate
@serialize
def PUT(self, node_id):
""":returns: Collection of JSONized Node objects.
:http: * 200 (nodes are successfully updated)
* 400 (data validation failed)
"""
interfaces_data = self.checked_data(
self.validator.validate_structure_and_data, node_id=node_id)
node_data = {'id': node_id, 'interfaces': interfaces_data}
objects.Cluster.get_network_manager()._update_attrs(node_data)
node = self.get_object_or_404(objects.Node, node_id)
objects.Node.add_pending_change(
node,
consts.CLUSTER_CHANGES.interfaces
)
return map(self.render, node.interfaces)
class NodeCollectionNICsHandler(BaseHandler):
"""Node collection network interfaces handler"""
model = NetworkGroup
validator = NetAssignmentValidator
serializer = NodeInterfacesSerializer
@handle_errors
@validate
@serialize
def PUT(self):
""":returns: Collection of JSONized Node objects.
:http: * 200 (nodes are successfully updated)
* 400 (data validation failed)
"""
data = self.checked_data(
self.validator.validate_collection_structure_and_data)
updated_nodes_ids = []
for node_data in data:
node_id = objects.Cluster.get_network_manager()._update_attrs(
node_data)
updated_nodes_ids.append(node_id)
updated_nodes = db().query(Node).filter(
Node.id.in_(updated_nodes_ids)
).all()
return [
{
"id": n.id,
"interfaces": map(self.render, n.interfaces)
} for n in updated_nodes
]
class NodeNICsDefaultHandler(BaseHandler):
"""Node default network interfaces handler"""
@handle_errors
@validate
@serialize
def GET(self, node_id):
""":returns: Collection of default JSONized interfaces for node.
:http: * 200 (OK)
* 404 (node not found in db)
"""
node = self.get_object_or_404(objects.Node, node_id)
return self.get_default(node)
def get_default(self, node):
if node.cluster:
return objects.Cluster.get_network_manager(
node.cluster
).get_default_interfaces_configuration(node)
class NodeCollectionNICsDefaultHandler(NodeNICsDefaultHandler):
"""Node collection default network interfaces handler"""
validator = NetAssignmentValidator
@handle_errors
@validate
@serialize
def GET(self):
"""May receive cluster_id parameter to filter list of nodes
:returns: Collection of JSONized Nodes interfaces.
:http: * 200 (OK)
"""
cluster_id = web.input(cluster_id=None).cluster_id
if cluster_id:
nodes = \
objects.NodeCollection.filter_by(None, cluster_id=cluster_id)
else:
nodes = objects.NodeCollection.all()
return filter(lambda x: x is not None, map(self.get_default, nodes))
class NodesAllocationStatsHandler(BaseHandler):
"""Node allocation stats handler"""
@handle_errors
@validate
@serialize
def GET(self):
""":returns: Total and unallocated nodes count.
:http: * 200 (OK)
"""
unallocated_nodes = db().query(Node).filter_by(cluster_id=None).count()
total_nodes = \
db().query(Node).count()
return {'total': total_nodes,
'unallocated': unallocated_nodes}
class NodeAttributesHandler(BaseHandler):
"""Node attributes handler"""
validator = node_validators.NodeAttributesValidator
@handle_errors
@validate
@serialize
def GET(self, node_id):
""":returns: JSONized Node attributes.
:http: * 200 (OK)
* 404 (node not found in db)
"""
node = self.get_object_or_404(objects.Node, node_id)
return objects.Node.get_attributes(node)
@handle_errors
@validate
@serialize
def PUT(self, node_id):
""":returns: JSONized Node attributes.
:http: * 200 (OK)
* 400 (wrong attributes data specified)
* 404 (node not found in db)
"""
node = self.get_object_or_404(objects.Node, node_id)
if not node.cluster:
raise self.http(400, "Node '{}' doesn't belong to any cluster"
.format(node.id))
data = self.checked_data(node=node, cluster=node.cluster)
objects.Node.update_attributes(node, data)
return objects.Node.get_attributes(node)
class NodeAttributesDefaultsHandler(BaseHandler):
"""Node default attributes handler"""
@handle_errors
@validate
@serialize
def GET(self, node_id):
""":returns: JSONized Node default attributes.
:http: * 200 (OK)
* 404 (node not found in db)
"""
node = self.get_object_or_404(objects.Node, node_id)
return objects.Node.get_default_attributes(node)