257 lines
8.6 KiB
Python
257 lines
8.6 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.models import Node
|
|
from nailgun.api.validators.base import BasicValidator
|
|
from nailgun.api.validators.json_schema.disks \
|
|
import disks_simple_format_schema
|
|
from nailgun.db import db
|
|
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):
|
|
d = cls.validate_json(data)
|
|
if not isinstance(d, dict):
|
|
raise errors.InvalidData(
|
|
"Node data must be dict",
|
|
log_message=True
|
|
)
|
|
if "mac" not in d:
|
|
raise errors.InvalidData(
|
|
"No mac address specified",
|
|
log_message=True
|
|
)
|
|
else:
|
|
q = db().query(Node)
|
|
if q.filter(Node.mac == d["mac"]).first():
|
|
raise errors.AlreadyExists(
|
|
"Node with mac {0} already "
|
|
"exists - doing nothing".format(d["mac"]),
|
|
log_level="info"
|
|
)
|
|
if cls.validate_existent_node_mac_create(d):
|
|
raise errors.AlreadyExists(
|
|
"Node with mac {0} already "
|
|
"exists - doing nothing".format(d["mac"]),
|
|
log_level="info"
|
|
)
|
|
if 'meta' in d:
|
|
MetaValidator.validate_create(d['meta'])
|
|
return d
|
|
|
|
# TODO(NAME): fix this using DRY
|
|
@classmethod
|
|
def validate_existent_node_mac_create(cls, data):
|
|
if 'meta' in data:
|
|
data['meta'] = MetaValidator.validate_create(data['meta'])
|
|
if 'interfaces' in data['meta']:
|
|
existent_node = db().query(Node).filter(Node.mac.in_(
|
|
[n['mac'] for n in data['meta']['interfaces']])).first()
|
|
return existent_node
|
|
|
|
@classmethod
|
|
def validate_existent_node_mac_update(cls, data):
|
|
if 'meta' in data:
|
|
data['meta'] = MetaValidator.validate_update(data['meta'])
|
|
if 'interfaces' in data['meta']:
|
|
existent_node = db().query(Node).filter(Node.mac.in_(
|
|
[n['mac'] for n in data['meta']['interfaces']])).first()
|
|
return existent_node
|
|
|
|
@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):
|
|
d = cls.validate_json(data)
|
|
if "status" in d and d["status"] not in Node.NODE_STATUSES:
|
|
raise errors.InvalidData(
|
|
"Invalid status for node",
|
|
log_message=True
|
|
)
|
|
if 'roles' in d and 'id' in d:
|
|
node = db().query(Node).get(d['id'])
|
|
cls.validate_roles(d, node)
|
|
if 'meta' in d:
|
|
d['meta'] = MetaValidator.validate_update(d['meta'])
|
|
return d
|
|
|
|
@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
|
|
)
|
|
|
|
q = db().query(Node)
|
|
for nd in d:
|
|
if not nd.get("mac") and not nd.get("id"):
|
|
raise errors.InvalidData(
|
|
"Neither MAC nor ID is specified",
|
|
log_message=True
|
|
)
|
|
if "mac" in nd and not nd["mac"]:
|
|
raise errors.InvalidData(
|
|
"Null MAC is specified",
|
|
log_message=True
|
|
)
|
|
else:
|
|
if nd.get("mac"):
|
|
existent_node = q.filter_by(mac=nd["mac"]).first() \
|
|
or cls.validate_existent_node_mac_update(nd)
|
|
if not existent_node:
|
|
raise errors.InvalidData(
|
|
"Invalid MAC specified",
|
|
log_message=True
|
|
)
|
|
if nd.get("id"):
|
|
existent_node = q.get(nd["id"])
|
|
if not existent_node:
|
|
raise errors.InvalidData(
|
|
"Invalid ID specified",
|
|
log_message=True
|
|
)
|
|
if 'roles' in nd:
|
|
cls.validate_roles(nd, existent_node)
|
|
if 'meta' in nd:
|
|
nd['meta'] = MetaValidator.validate_update(nd['meta'])
|
|
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
|