300 lines
9.3 KiB
Python
300 lines
9.3 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.
|
|
|
|
from nailgun.api.validators.base import BasicValidator
|
|
from nailgun.api.validators.json_schema.disks import disks_simple_format_schema
|
|
from nailgun.api.validators.json_schema.node import node_format_schema
|
|
|
|
from nailgun import consts
|
|
|
|
from nailgun import objects
|
|
|
|
from nailgun.db import db
|
|
from nailgun.db.sqlalchemy.models import Node
|
|
from nailgun.db.sqlalchemy.models import NodeNICInterface
|
|
from nailgun.errors import errors
|
|
|
|
|
|
class MetaInterfacesValidator(BasicValidator):
|
|
@classmethod
|
|
def _validate_data(cls, interfaces):
|
|
if not isinstance(interfaces, list):
|
|
raise errors.InvalidInterfacesInfo(
|
|
"Meta.interfaces should be list",
|
|
log_message=True
|
|
)
|
|
|
|
return interfaces
|
|
|
|
@classmethod
|
|
def validate_create(cls, interfaces):
|
|
interfaces = cls._validate_data(interfaces)
|
|
|
|
def filter_valid_nic(nic):
|
|
for key in ('mac', 'name'):
|
|
if not key in nic or not isinstance(nic[key], basestring)\
|
|
or not nic[key]:
|
|
return False
|
|
return True
|
|
|
|
return filter(filter_valid_nic, interfaces)
|
|
|
|
@classmethod
|
|
def validate_update(cls, interfaces):
|
|
interfaces = cls._validate_data(interfaces)
|
|
|
|
for nic in interfaces:
|
|
if not isinstance(nic, dict):
|
|
raise errors.InvalidInterfacesInfo(
|
|
"Interface in meta.interfaces must be dict",
|
|
log_message=True
|
|
)
|
|
|
|
return interfaces
|
|
|
|
|
|
class MetaValidator(BasicValidator):
|
|
@classmethod
|
|
def _validate_data(cls, meta):
|
|
if not isinstance(meta, dict):
|
|
raise errors.InvalidMetadata(
|
|
"Invalid data: 'meta' should be dict",
|
|
log_message=True
|
|
)
|
|
|
|
@classmethod
|
|
def validate_create(cls, meta):
|
|
cls._validate_data(meta)
|
|
if 'interfaces' in meta:
|
|
meta['interfaces'] = MetaInterfacesValidator.validate_create(
|
|
meta['interfaces']
|
|
)
|
|
else:
|
|
raise errors.InvalidInterfacesInfo(
|
|
"Failed to discover node: "
|
|
"invalid interfaces info",
|
|
log_message=True
|
|
)
|
|
return meta
|
|
|
|
@classmethod
|
|
def validate_update(cls, meta):
|
|
cls._validate_data(meta)
|
|
if 'interfaces' in meta:
|
|
meta['interfaces'] = MetaInterfacesValidator.validate_update(
|
|
meta['interfaces']
|
|
)
|
|
return meta
|
|
|
|
|
|
class NodeValidator(BasicValidator):
|
|
@classmethod
|
|
def validate(cls, data):
|
|
# TODO(enchantner): rewrite validators to use Node object
|
|
data = cls.validate_json(data)
|
|
cls.validate_schema(data, node_format_schema)
|
|
|
|
if data.get("status", "") != "discover":
|
|
raise errors.NotAllowed(
|
|
"Only bootstrap nodes are allowed to be registered."
|
|
)
|
|
|
|
if 'mac' not in data:
|
|
raise errors.InvalidData(
|
|
"No mac address specified",
|
|
log_message=True
|
|
)
|
|
|
|
if cls.does_node_exist_in_db(data):
|
|
raise errors.AlreadyExists(
|
|
"Node with mac {0} already "
|
|
"exists - doing nothing".format(data["mac"]),
|
|
log_level="info"
|
|
)
|
|
|
|
if cls.validate_existent_node_mac_create(data):
|
|
raise errors.AlreadyExists(
|
|
"Node with mac {0} already "
|
|
"exists - doing nothing".format(data["mac"]),
|
|
log_level="info"
|
|
)
|
|
|
|
if 'meta' in data:
|
|
MetaValidator.validate_create(data['meta'])
|
|
|
|
return data
|
|
|
|
@classmethod
|
|
def does_node_exist_in_db(cls, data):
|
|
mac = data['mac']
|
|
q = db().query(Node)
|
|
|
|
if q.filter(Node.mac == mac).first() or \
|
|
q.join(NodeNICInterface, Node.nic_interfaces).filter(
|
|
NodeNICInterface.mac == mac).first():
|
|
return True
|
|
return False
|
|
|
|
@classmethod
|
|
def _validate_existent_node(cls, data, validate_method):
|
|
if 'meta' in data:
|
|
data['meta'] = validate_method(data['meta'])
|
|
if 'interfaces' in data['meta']:
|
|
existent_node = db().query(Node).\
|
|
join(NodeNICInterface, Node.nic_interfaces).\
|
|
filter(NodeNICInterface.mac.in_(
|
|
[n['mac'] for n in data['meta']['interfaces']]
|
|
)).first()
|
|
return existent_node
|
|
|
|
@classmethod
|
|
def validate_existent_node_mac_create(cls, data):
|
|
return cls._validate_existent_node(
|
|
data,
|
|
MetaValidator.validate_create)
|
|
|
|
@classmethod
|
|
def validate_existent_node_mac_update(cls, data):
|
|
return cls._validate_existent_node(
|
|
data,
|
|
MetaValidator.validate_update)
|
|
|
|
@classmethod
|
|
def validate_roles(cls, data, node):
|
|
if 'roles' in data:
|
|
if not isinstance(data['roles'], list) or \
|
|
any(not isinstance(role, (
|
|
str, unicode)) for role in data['roles']):
|
|
raise errors.InvalidData(
|
|
"Role list must be list of strings",
|
|
log_message=True
|
|
)
|
|
|
|
@classmethod
|
|
def validate_update(cls, data, instance=None):
|
|
if isinstance(data, (str, unicode)):
|
|
d = cls.validate_json(data)
|
|
else:
|
|
d = data
|
|
|
|
if "status" in d and d["status"] not in consts.NODE_STATUSES:
|
|
raise errors.InvalidData(
|
|
"Invalid status for node",
|
|
log_message=True
|
|
)
|
|
|
|
if not d.get("mac") and not d.get("id") and not instance:
|
|
raise errors.InvalidData(
|
|
"Neither MAC nor ID is specified",
|
|
log_message=True
|
|
)
|
|
|
|
q = db().query(Node)
|
|
if "mac" in d:
|
|
if not d["mac"]:
|
|
raise errors.InvalidData(
|
|
"Null MAC is specified",
|
|
log_message=True
|
|
)
|
|
else:
|
|
existent_node = q.filter_by(mac=d["mac"]).first() \
|
|
or cls.validate_existent_node_mac_update(d)
|
|
if not existent_node:
|
|
raise errors.InvalidData(
|
|
"Invalid MAC is specified",
|
|
log_message=True
|
|
)
|
|
|
|
if "id" in d and d["id"]:
|
|
existent_node = q.get(d["id"])
|
|
if not existent_node:
|
|
raise errors.InvalidData(
|
|
"Invalid ID specified",
|
|
log_message=True
|
|
)
|
|
|
|
if "roles" in d:
|
|
if instance:
|
|
node = instance
|
|
else:
|
|
node = objects.Node.get_by_mac_or_uid(
|
|
mac=d.get("mac"),
|
|
node_uid=d.get("id")
|
|
)
|
|
cls.validate_roles(d, node)
|
|
|
|
if 'meta' in d:
|
|
d['meta'] = MetaValidator.validate_update(d['meta'])
|
|
return d
|
|
|
|
@classmethod
|
|
def validate_delete(cls, instance):
|
|
pass
|
|
|
|
@classmethod
|
|
def validate_collection_update(cls, data):
|
|
d = cls.validate_json(data)
|
|
if not isinstance(d, list):
|
|
raise errors.InvalidData(
|
|
"Invalid json list",
|
|
log_message=True
|
|
)
|
|
|
|
for nd in d:
|
|
cls.validate_update(nd)
|
|
return d
|
|
|
|
|
|
class NodeDisksValidator(BasicValidator):
|
|
@classmethod
|
|
def validate(cls, data):
|
|
dict_data = cls.validate_json(data)
|
|
cls.validate_schema(dict_data, disks_simple_format_schema)
|
|
cls.at_least_one_disk_exists(dict_data)
|
|
cls.sum_of_volumes_not_greater_than_disk_size(dict_data)
|
|
return dict_data
|
|
|
|
@classmethod
|
|
def at_least_one_disk_exists(cls, data):
|
|
if len(data) < 1:
|
|
raise errors.InvalidData(u'Node seems not to have disks')
|
|
|
|
@classmethod
|
|
def sum_of_volumes_not_greater_than_disk_size(cls, data):
|
|
for disk in data:
|
|
volumes_size = sum([volume['size'] for volume in disk['volumes']])
|
|
|
|
if volumes_size > disk['size']:
|
|
raise errors.InvalidData(
|
|
u'Not enough free space on disk: %s' % disk)
|
|
|
|
|
|
class NodesFilterValidator(BasicValidator):
|
|
|
|
@classmethod
|
|
def validate(cls, nodes):
|
|
"""Used for filtering nodes
|
|
:param nodes: list of ids in string representation.
|
|
Example: "1,99,3,4"
|
|
|
|
:returns: list of integers
|
|
"""
|
|
try:
|
|
node_ids = set(map(int, nodes.split(',')))
|
|
except ValueError:
|
|
raise errors.InvalidData('Provided id is not integer')
|
|
|
|
return node_ids
|