<commit_before>/*
 * Copyright (c) 2012, Joyent, Inc. All rights reserved.
 *
 * A brief overview of this source file: what is its purpose.
 */


var assert = require('assert');
var restify = require('restify');
var async = require('async');
var format = require('util').format;

var errors = require('../errors');
var common = require('./vm-common');


var UUID_RE = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
/*JSSTYLED*/
var ALIAS_RE = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
/*JSSTYLED*/
var IP_RE = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/;
var PW_SUFFIX = /^(.*)_pw$/;

var VALID_VM_BRANDS = [
    'joyent-minimal',
    'joyent',
    'kvm',
    'sngl'
];

var DEFAULT_QUOTA = 10; // GiB
var MIN_SWAP = 256;     // MiB


/*
 * Validates if a string is a UUID
 */
function validUUID(uuid) {
    return UUID_RE.test(uuid);
}

exports.validUUID = validUUID;



/*
 * Validates if an array contains strings
 */
function validStrings(object) {
    var array;
    var string;

    if (Array.isArray(object)) {
        array = object;
    } else if (typeof (object) === 'string') {
        array = object.split(',');
    } else {
        return false;
    }

    for (var i = 0; i < array.length; i++) {
        string = array[i];
        if (typeof (string) !== 'string') {
            return false;
        }
    }

    return true;
}

exports.validStrings = validStrings;



/*
 * Validates if an array contains UUIDs
 */
function validUUIDs(object) {
    var array;
    var uuid;

    if (Array.isArray(object)) {
        array = object;
    } else if (typeof (object) === 'string') {
        array = object.split(',');
    } else {
        return false;
    }

    for (var i = 0; i < array.length; i++) {
        uuid = array[i];
        if (!UUID_RE.test(uuid)) {
            return false;
        }
    }

    return true;
}

exports.validUUIDs = validUUIDs;


// Just make sure it works in case someone decides to send 'false'
function isPrimary(prm) {
    return (prm === undefined ? false : (prm === true || prm === 'true'));
}


/*
 * Validates if a comma separated string contains UUIDs
 * If isProvision is true then it will assume a new VM that has no NICs yet,
 * therefore marking the first NIC as primary if not explicitly done for others.
 * If isProvision is false it means that we can't deafult a NIC as primary
 */
function validNetworks(object, isProvision) {
    var networks = [];
    var primaries = 0;
    var array, obj, uuid;

    if (Array.isArray(object)) {
        array = object;
    } else if (typeof (object) === 'string') {
        array = object.split(',');
    } else {
        throw new Error('Malformed networks object');
    }

    for (var i = 0; i < array.length; i++) {
        obj = array[i];

        // Legacy: [ uuid1, uuid2 ]
        // New: [ { uuid: uuid1, primary: true }, { uuid: uuid2, ip: ip2 } ]
        if (typeof (obj) == 'string') {
            uuid = obj;
            obj = { uuid: uuid };
        } else if (isPrimary(obj.primary)) {
            primaries++;
        }

        if (obj.uuid && !UUID_RE.test(obj.uuid)) {
            throw new Error(format('Invalid uuid %s', obj.uuid));
        } else if (!obj.uuid && !obj.name) {
            throw new Error('Network object must specify a UUID or a name');
        }
        networks.push(obj);
    }

    // Two primaries were specified
    if (primaries > 1) {
        throw new Error('Cannot specify more than one primary network');
    } else if (isProvision === true && primaries === 0) {
        networks[0].primary = true;
    } // else just one primary which is fine

    return networks;
}

exports.validNetworks = validNetworks;



/*
 * Two validations at the moment:
 * - reordering: if you provide 'interface' for one nic, all others must have
 *      the same attribute as well
 * - reassigning primary: only one nic can be primary
 */
function validNics(vm, object) {
    var nics = [];
    var primaries = 0;
    var interfaces = 0;
    var array, obj;

    if (Array.isArray(object)) {
        array = object;
    } else if (typeof (object) === 'string') {
        array = object.split(',');
    } else {
        throw new Error('Malformed NICs object');
    }
    if (array.length === 0) {
        throw new Error('At least one NIC must be updated');
    }

    var nic;
    for (var i = 0; i < array.length; i++) {
        nic = {};
        obj = array[i];

        if (obj.mac === undefined) {
            throw new Error('All NICs must have a `mac` attribute');
        }
        nic.mac = obj.mac;

        if (isPrimary(obj.primary)) {
            nic.primary = obj.primary;
            primaries++;
        }
        if (obj['interface'] !== undefined) {
            nic['interface'] = obj['interface'];
            interfaces++;
        }

        var antiSpoofParams = ['allow_dhcp_spoofing', 'allow_ip_spoofing',
            'allow_mac_spoofing', 'allow_restricted_traffic'];
        antiSpoofParams.forEach(function (spoofParam) {
            if (obj.hasOwnProperty(spoofParam)) {
                nic[spoofParam] = obj[spoofParam];
            }
        });

        nics.push(nic);
    }

    // Two primaries were specified
    if (primaries > 1) {
        throw new Error('Cannot specify more than one primary NIC');
    } else if (interfaces > 0 && interfaces !== vm.nics.length) {
        throw new Error('If reordering, must specify a new `interface` ' +
            'for every NIC that the VM currently has');
    }

    return nics;
}

exports.validNics = validNics;



/*
 * Validates if an alias is url compatible
 */
function validAlias(alias) {
    return ALIAS_RE.test(alias);
}

exports.validAlias = validAlias;



/*
 * Validates if a vm brand is either joyent or kvm
 */
function validBrand(brand) {
    return VALID_VM_BRANDS.indexOf(brand) != -1;
}

exports.validBrand = validBrand;



/*
 * Validates if a param is a valid number. If gezero (greater or equal than
 * zero) is true, then >= will be used instead of >
 */
function validNumber(param, gezero) {
    var number = parseInt(param, 10);
    if (gezero === true) {
        return (number >= 0 ? true : false);
    } else {
        return (number > 0 ? true : false);
    }
}

exports.validNumber = validNumber;



/*
 * Validates if a vm is owned by the owner_uuid in the params
 */
function validOwner(vm, params) {
    var owner_uuid = params.owner_uuid;

    if (owner_uuid) {
        if (!validUUID(owner_uuid)) {
            var error = [ errors.invalidUuidErr('owner_uuid') ];
            throw new errors.ValidationFailedError('Invalid Parameters', error);
        }

        if (owner_uuid != vm.owner_uuid) {
            throw new restify.ResourceNotFoundError('VM not found');
        }
    }

    return true;
}

exports.validOwner = validOwner;



/*
 * Validates if a metadata object contains only strings, numbers or booleans
 */
function validMetadata(name, obj) {
    var types = ['string', 'boolean', 'number'];
    var error;

    for (var key in obj) {
        if (types.indexOf(typeof (obj[key])) == -1) {
            error = [ errors.invalidParamErr(name + '.' + key,
                'Forbidden Data Type') ];
            throw new errors.ValidationFailedError(
                'Invalid Parameter', error);
        } else if (name === 'customer_metadata' && PW_SUFFIX.test(key)) {
            error = [ errors.invalidParamErr(name + '.' + key,
                'Forbidden Metadata Key') ];
            throw new errors.ValidationFailedError(
                'Invalid Parameter', error);
        }
    }

    return true;
}

exports.validMetadata = validMetadata;



/*
 * Validates that the disks for the KVM VM have a valid format
 */
function validDisks(disks, errs) {
    var i;
    var ndisks = disks.length;
    var disk0 = disks[0];

    if (disk0['image_uuid'] === undefined) {
        errs.push(errors.missingParamErr('disks.0.image_uuid'));
    } else if (!validUUID(disk0['image_uuid'])) {
        errs.push(errors.invalidUuidErr('disks.0.image_uuid'));
    }

    if (disk0.size !== undefined) {
        errs.push(errors.invalidParamErr('disks.0.size', 'Not Allowed'));
    }


    for (i = 1; i < ndisks; i++) {
        var disk = disks[i];

        if (disk['image_uuid'] !== undefined) {
            errs.push(errors.invalidParamErr('disks.' + i + '.image_uuid',
                'Not Allowed'));
        }

        if (disk.size === undefined) {
            errs.push(errors.missingParamErr('disks.' + i + '.size'));
        }
    }

    return true;
}



/*
 * Does additional validation depending on the VM brand. This function only
 * populates the errors array that was passed in case some fields are not valid,
 * and later on the main validation code will throw an exception if needed
 */
function validateBrandParams(params, errs) {
    if (params.brand === 'kvm') {
        // Skip disks validation if billing_id was passed. The disks will be
        // created from the package definition
        if (params.billing_id && !params.disks) {
            return;
        } else if (!params.disks) {
            errs.push(errors.missingParamErr('disks'));
            return;
        } else if (typeof (params.disks) === 'string') {
            try {
                params.disks = JSON.parse(params.disks);
            } catch (e) {
                errs.push(errors.invalidParamErr('disks', 'Malformed JSON'));
                return;
            }
        }

        if (!Array.isArray(params.disks) || (params.disks.length < 1)) {
            errs.push(errors.invalidParamErr('disks'));
        } else {
            validDisks(params.disks, errs);
        }

    } else {
        // Only non-kvm vms require image_uuid
        if (params['image_uuid'] === undefined) {
            errs.push(errors.missingParamErr('image_uuid'));
        } else if (!validUUID(params['image_uuid'])) {
            errs.push(errors.invalidUuidErr('image_uuid'));
        }
    }
}



/*
 * Validates and creates metadata object to be used for vmadm update
 *
 * - vm: VM to update
 * - mdataKey: any of customer_metadata, internal_metadata or tags
 * - params: params to be sent to WFAPI
 * - obj: input object from the request
 */
function createMetadataObject(vm, mdataKey, params, obj) {
    var metadata;
    var error;

    if (typeof (obj) === 'object') {
        metadata = obj;

    } else if (typeof (obj) === 'string') {
        try {
            metadata = JSON.parse(obj);
        } catch (e) {
            error = [ errors.invalidParamErr(mdataKey, 'Malformed JSON') ];
            throw new errors.ValidationFailedError('Invalid Parameters', error);
        }
    } else {
        error = [ errors.invalidParamErr(mdataKey, 'Invalid data type') ];
        throw new errors.ValidationFailedError('Invalid Parameters', error);
    }


    validMetadata(mdataKey, metadata);
    var updateObject = common.setMetadata(vm, mdataKey, metadata);

    if (updateObject['set_' + mdataKey]) {
        params['set_' + mdataKey] = updateObject['set_' + mdataKey];
    }

    if (updateObject['remove_' + mdataKey]) {
        params['remove_' + mdataKey] = updateObject['remove_' + mdataKey];
    }

    return true;
}


/*
 * Validates a vm representation given a set of request parameters
 */
exports.validateVm = function (vmapi, params, callback) {
    return validateParams(vmapi, params, callback);
};



/*
 * Validates a vm representation given a set of request parameters
 */
function validateParams(vmapi, params, callback) {
    var message;
    var errs = [];

    // uuid
    if (params.uuid && !validUUID(params.uuid)) {
        errs.push(errors.invalidUuidErr('uuid'));
    }

    // owner_uuid
    if (!params.owner_uuid) {
        errs.push(errors.missingParamErr('owner_uuid'));
    } else if (!validUUID(params.owner_uuid)) {
        errs.push(errors.invalidUuidErr('owner_uuid'));
    }

    // server_uuid
    if (params.server_uuid && !validUUID(params.server_uuid)) {
        errs.push(errors.invalidUuidErr('server_uuid'));
    }

    // billing_id
    if (params.billing_id && !validUUID(params.billing_id)) {
        errs.push(errors.invalidUuidErr('billing_id'));
    }

    // brand
    if (!params.brand) {
        errs.push(errors.missingParamErr('brand'));
    } else if (!validBrand(params.brand)) {
        message = 'Must be one of: ' + VALID_VM_BRANDS.join(', ');
        errs.push(errors.invalidParamErr('brand', message));
    }

    // when no package is passed, we want to validate presence of ram,
    // max_physical_memory and disks (when kvm) at least
    if (!params.billing_id) {
        if (params.brand === 'kvm' && !params.ram) {
            errs.push(errors.missingParamErr('ram', 'Is required for KVM'));
        } else if (!params.max_physical_memory && !params.ram) {
            errs.push(errors.missingParamErr('ram'));
        }
    }

    if (params.ram && !validNumber(params.ram)) {
        errs.push(errors.invalidParamErr('ram', 'Not a valid number'));
    }

    if (params.max_physical_memory &&
        !validNumber(params.max_physical_memory)) {
        errs.push(errors.invalidParamErr('max_physical_memory',
            'Not a valid number'));
    }

    // networks
    if (params.networks) {
        try {
            params.networks = validNetworks(params.networks, true);
        } catch (e) {
            errs.push(errors.invalidParamErr('networks', e.message));
        }
    } else {
        errs.push(errors.missingParamErr('networks'));
    }

    // max_swap
    if (params.max_swap) {
        if (!validNumber(params.max_swap)) {
            errs.push(errors.invalidParamErr('max_swap', 'Not a valid number'));
        } else if (params.max_swap < MIN_SWAP) {
            errs.push(errors.invalidParamErr('max_swap',
                'Cannot be less than ' + MIN_SWAP));
        }
    }

    // cpu_shares
    if (params.cpu_shares && !validNumber(params.cpu_shares)) {
        errs.push(errors.invalidParamErr('cpu_shares', 'Not a valid number'));
    }

    // quota
    if (params.quota && !validNumber(params.quota, true)) {
        errs.push(errors.invalidParamErr('quota', 'Not a valid number'));
    }

    // tmpfs
    if (params.tmpfs && !validNumber(params.tmpfs, true)) {
        errs.push(errors.invalidParamErr('tmpfs', 'Not a valid number'));
    }

    // customer_metadata
    if (params.customer_metadata &&
        (typeof (params.customer_metadata) === 'string')) {
        try {
            params.customer_metadata = JSON.parse(params.customer_metadata);
            validMetadata('customer_metadata', params.customer_metadata);
        } catch (e) {
            if (e.body && e.body.errors) {
                errs.push(e.body.errors[0]);
            } else {
                errs.push(errors.invalidParamErr('customer_metadata'));
            }
        }
    }

    // internal_metadata
    if (params.internal_metadata &&
        (typeof (params.internal_metadata) === 'string')) {
        try {
            params.internal_metadata = JSON.parse(params.internal_metadata);
            validMetadata('internal_metadata', params.internal_metadata);
        } catch (e) {
            if (e.body && e.body.errors) {
                errs.push(e.body.errors[0]);
            } else {
                errs.push(errors.invalidParamErr('internal_metadata'));
            }
        }
    }

    // tags
    if (params.tags && (typeof (params.tags) === 'string')) {
        try {
            params.tags = JSON.parse(params.tags);
            validMetadata('tags', params.tags);
        } catch (e) {
            if (e.body && e.body.errors) {
                errs.push(e.body.errors[0]);
            } else {
                errs.push(errors.invalidParamErr('tags'));
            }
        }
    }

    // firewall_enabled
    if (params.hasOwnProperty('firewall_enabled') &&
        (typeof (params.firewall_enabled) !== 'boolean')) {
        errs.push(errors.invalidParamErr('firewall_enabled'));
    }

    // maintain_resolvers
    if (params.hasOwnProperty('maintain_resolvers') &&
        (typeof (params.maintain_resolvers) !== 'boolean')) {
        errs.push(errors.invalidParamErr('maintain_resolvers'));
    }

    // alias
    if (params.alias && !validAlias(params.alias)) {
        errs.push(errors.invalidParamErr('alias'));
    }

    validateBrandParams(params, errs);

    // Async validations
    var asyncFns = [];
    if (params.alias) {
        asyncFns.push(validateAlias);
    }
    if (params['server_uuid']) {
        asyncFns.push(validateServer);
    }

    function validateAlias(next) {
        validateUniqueAlias(vmapi.moray, params, function (err, errorObj) {
            if (err) {
                return next(err);
            } else if (errorObj) {
                errs.push(errorObj);
            }
            return next();
        });
    }

    function validateServer(next) {
        vmapi.cnapi.getServer(params['server_uuid'], function (err) {
            if (err) {
                if (err.name === 'ResourceNotFoundError') {
                    errs.push({
                        field: 'server_uuid',
                        code: 'Invalid',
                        message: err.message
                    });
                } else {
                    return next(err);<commit_msg>ZAPI-484: VMAPI shouldn't accept creation of a new VM with the same UUID as an existing VM ever<commit_after>/*
 * Copyright (c) 2012, Joyent, Inc. All rights reserved.
 *
 * A brief overview of this source file: what is its purpose.
 */


var assert = require('assert');
var restify = require('restify');
var async = require('async');
var format = require('util').format;

var errors = require('../errors');
var common = require('./vm-common');


var UUID_RE = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
/*JSSTYLED*/
var ALIAS_RE = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
/*JSSTYLED*/
var IP_RE = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/;
var PW_SUFFIX = /^(.*)_pw$/;

var VALID_VM_BRANDS = [
    'joyent-minimal',
    'joyent',
    'kvm',
    'sngl'
];

var DEFAULT_QUOTA = 10; // GiB
var MIN_SWAP = 256;     // MiB


/*
 * Validates if a string is a UUID
 */
function validUUID(uuid) {
    return UUID_RE.test(uuid);
}

exports.validUUID = validUUID;



/*
 * Validates if an array contains strings
 */
function validStrings(object) {
    var array;
    var string;

    if (Array.isArray(object)) {
        array = object;
    } else if (typeof (object) === 'string') {
        array = object.split(',');
    } else {
        return false;
    }

    for (var i = 0; i < array.length; i++) {
        string = array[i];
        if (typeof (string) !== 'string') {
            return false;
        }
    }

    return true;
}

exports.validStrings = validStrings;



/*
 * Validates if an array contains UUIDs
 */
function validUUIDs(object) {
    var array;
    var uuid;

    if (Array.isArray(object)) {
        array = object;
    } else if (typeof (object) === 'string') {
        array = object.split(',');
    } else {
        return false;
    }

    for (var i = 0; i < array.length; i++) {
        uuid = array[i];
        if (!UUID_RE.test(uuid)) {
            return false;
        }
    }

    return true;
}

exports.validUUIDs = validUUIDs;


// Just make sure it works in case someone decides to send 'false'
function isPrimary(prm) {
    return (prm === undefined ? false : (prm === true || prm === 'true'));
}


/*
 * Validates if a comma separated string contains UUIDs
 * If isProvision is true then it will assume a new VM that has no NICs yet,
 * therefore marking the first NIC as primary if not explicitly done for others.
 * If isProvision is false it means that we can't deafult a NIC as primary
 */
function validNetworks(object, isProvision) {
    var networks = [];
    var primaries = 0;
    var array, obj, uuid;

    if (Array.isArray(object)) {
        array = object;
    } else if (typeof (object) === 'string') {
        array = object.split(',');
    } else {
        throw new Error('Malformed networks object');
    }

    for (var i = 0; i < array.length; i++) {
        obj = array[i];

        // Legacy: [ uuid1, uuid2 ]
        // New: [ { uuid: uuid1, primary: true }, { uuid: uuid2, ip: ip2 } ]
        if (typeof (obj) == 'string') {
            uuid = obj;
            obj = { uuid: uuid };
        } else if (isPrimary(obj.primary)) {
            primaries++;
        }

        if (obj.uuid && !UUID_RE.test(obj.uuid)) {
            throw new Error(format('Invalid uuid %s', obj.uuid));
        } else if (!obj.uuid && !obj.name) {
            throw new Error('Network object must specify a UUID or a name');
        }
        networks.push(obj);
    }

    // Two primaries were specified
    if (primaries > 1) {
        throw new Error('Cannot specify more than one primary network');
    } else if (isProvision === true && primaries === 0) {
        networks[0].primary = true;
    } // else just one primary which is fine

    return networks;
}

exports.validNetworks = validNetworks;



/*
 * Two validations at the moment:
 * - reordering: if you provide 'interface' for one nic, all others must have
 *      the same attribute as well
 * - reassigning primary: only one nic can be primary
 */
function validNics(vm, object) {
    var nics = [];
    var primaries = 0;
    var interfaces = 0;
    var array, obj;

    if (Array.isArray(object)) {
        array = object;
    } else if (typeof (object) === 'string') {
        array = object.split(',');
    } else {
        throw new Error('Malformed NICs object');
    }
    if (array.length === 0) {
        throw new Error('At least one NIC must be updated');
    }

    var nic;
    for (var i = 0; i < array.length; i++) {
        nic = {};
        obj = array[i];

        if (obj.mac === undefined) {
            throw new Error('All NICs must have a `mac` attribute');
        }
        nic.mac = obj.mac;

        if (isPrimary(obj.primary)) {
            nic.primary = obj.primary;
            primaries++;
        }
        if (obj['interface'] !== undefined) {
            nic['interface'] = obj['interface'];
            interfaces++;
        }

        var antiSpoofParams = ['allow_dhcp_spoofing', 'allow_ip_spoofing',
            'allow_mac_spoofing', 'allow_restricted_traffic'];
        antiSpoofParams.forEach(function (spoofParam) {
            if (obj.hasOwnProperty(spoofParam)) {
                nic[spoofParam] = obj[spoofParam];
            }
        });

        nics.push(nic);
    }

    // Two primaries were specified
    if (primaries > 1) {
        throw new Error('Cannot specify more than one primary NIC');
    } else if (interfaces > 0 && interfaces !== vm.nics.length) {
        throw new Error('If reordering, must specify a new `interface` ' +
            'for every NIC that the VM currently has');
    }

    return nics;
}

exports.validNics = validNics;



/*
 * Validates if an alias is url compatible
 */
function validAlias(alias) {
    return ALIAS_RE.test(alias);
}

exports.validAlias = validAlias;



/*
 * Validates if a vm brand is either joyent or kvm
 */
function validBrand(brand) {
    return VALID_VM_BRANDS.indexOf(brand) != -1;
}

exports.validBrand = validBrand;



/*
 * Validates if a param is a valid number. If gezero (greater or equal than
 * zero) is true, then >= will be used instead of >
 */
function validNumber(param, gezero) {
    var number = parseInt(param, 10);
    if (gezero === true) {
        return (number >= 0 ? true : false);
    } else {
        return (number > 0 ? true : false);
    }
}

exports.validNumber = validNumber;



/*
 * Validates if a vm is owned by the owner_uuid in the params
 */
function validOwner(vm, params) {
    var owner_uuid = params.owner_uuid;

    if (owner_uuid) {
        if (!validUUID(owner_uuid)) {
            var error = [ errors.invalidUuidErr('owner_uuid') ];
            throw new errors.ValidationFailedError('Invalid Parameters', error);
        }

        if (owner_uuid != vm.owner_uuid) {
            throw new restify.ResourceNotFoundError('VM not found');
        }
    }

    return true;
}

exports.validOwner = validOwner;



/*
 * Validates if a metadata object contains only strings, numbers or booleans
 */
function validMetadata(name, obj) {
    var types = ['string', 'boolean', 'number'];
    var error;

    for (var key in obj) {
        if (types.indexOf(typeof (obj[key])) == -1) {
            error = [ errors.invalidParamErr(name + '.' + key,
                'Forbidden Data Type') ];
            throw new errors.ValidationFailedError(
                'Invalid Parameter', error);
        } else if (name === 'customer_metadata' && PW_SUFFIX.test(key)) {
            error = [ errors.invalidParamErr(name + '.' + key,
                'Forbidden Metadata Key') ];
            throw new errors.ValidationFailedError(
                'Invalid Parameter', error);
        }
    }

    return true;
}

exports.validMetadata = validMetadata;



/*
 * Validates that the disks for the KVM VM have a valid format
 */
function validDisks(disks, errs) {
    var i;
    var ndisks = disks.length;
    var disk0 = disks[0];

    if (disk0['image_uuid'] === undefined) {
        errs.push(errors.missingParamErr('disks.0.image_uuid'));
    } else if (!validUUID(disk0['image_uuid'])) {
        errs.push(errors.invalidUuidErr('disks.0.image_uuid'));
    }

    if (disk0.size !== undefined) {
        errs.push(errors.invalidParamErr('disks.0.size', 'Not Allowed'));
    }


    for (i = 1; i < ndisks; i++) {
        var disk = disks[i];

        if (disk['image_uuid'] !== undefined) {
            errs.push(errors.invalidParamErr('disks.' + i + '.image_uuid',
                'Not Allowed'));
        }

        if (disk.size === undefined) {
            errs.push(errors.missingParamErr('disks.' + i + '.size'));
        }
    }

    return true;
}



/*
 * Does additional validation depending on the VM brand. This function only
 * populates the errors array that was passed in case some fields are not valid,
 * and later on the main validation code will throw an exception if needed
 */
function validateBrandParams(params, errs) {
    if (params.brand === 'kvm') {
        // Skip disks validation if billing_id was passed. The disks will be
        // created from the package definition
        if (params.billing_id && !params.disks) {
            return;
        } else if (!params.disks) {
            errs.push(errors.missingParamErr('disks'));
            return;
        } else if (typeof (params.disks) === 'string') {
            try {
                params.disks = JSON.parse(params.disks);
            } catch (e) {
                errs.push(errors.invalidParamErr('disks', 'Malformed JSON'));
                return;
            }
        }

        if (!Array.isArray(params.disks) || (params.disks.length < 1)) {
            errs.push(errors.invalidParamErr('disks'));
        } else {
            validDisks(params.disks, errs);
        }

    } else {
        // Only non-kvm vms require image_uuid
        if (params['image_uuid'] === undefined) {
            errs.push(errors.missingParamErr('image_uuid'));
        } else if (!validUUID(params['image_uuid'])) {
            errs.push(errors.invalidUuidErr('image_uuid'));
        }
    }
}



/*
 * Validates and creates metadata object to be used for vmadm update
 *
 * - vm: VM to update
 * - mdataKey: any of customer_metadata, internal_metadata or tags
 * - params: params to be sent to WFAPI
 * - obj: input object from the request
 */
function createMetadataObject(vm, mdataKey, params, obj) {
    var metadata;
    var error;

    if (typeof (obj) === 'object') {
        metadata = obj;

    } else if (typeof (obj) === 'string') {
        try {
            metadata = JSON.parse(obj);
        } catch (e) {
            error = [ errors.invalidParamErr(mdataKey, 'Malformed JSON') ];
            throw new errors.ValidationFailedError('Invalid Parameters', error);
        }
    } else {
        error = [ errors.invalidParamErr(mdataKey, 'Invalid data type') ];
        throw new errors.ValidationFailedError('Invalid Parameters', error);
    }


    validMetadata(mdataKey, metadata);
    var updateObject = common.setMetadata(vm, mdataKey, metadata);

    if (updateObject['set_' + mdataKey]) {
        params['set_' + mdataKey] = updateObject['set_' + mdataKey];
    }

    if (updateObject['remove_' + mdataKey]) {
        params['remove_' + mdataKey] = updateObject['remove_' + mdataKey];
    }

    return true;
}


/*
 * Validates a vm representation given a set of request parameters
 */
exports.validateVm = function (vmapi, params, callback) {
    return validateParams(vmapi, params, callback);
};



/*
 * Validates a vm representation given a set of request parameters
 */
function validateParams(vmapi, params, callback) {
    var message;
    var errs = [];

    // uuid
    if (params.uuid && !validUUID(params.uuid)) {
        errs.push(errors.invalidUuidErr('uuid'));
    }

    // owner_uuid
    if (!params.owner_uuid) {
        errs.push(errors.missingParamErr('owner_uuid'));
    } else if (!validUUID(params.owner_uuid)) {
        errs.push(errors.invalidUuidErr('owner_uuid'));
    }

    // server_uuid
    if (params.server_uuid && !validUUID(params.server_uuid)) {
        errs.push(errors.invalidUuidErr('server_uuid'));
    }

    // billing_id
    if (params.billing_id && !validUUID(params.billing_id)) {
        errs.push(errors.invalidUuidErr('billing_id'));
    }

    // brand
    if (!params.brand) {
        errs.push(errors.missingParamErr('brand'));
    } else if (!validBrand(params.brand)) {
        message = 'Must be one of: ' + VALID_VM_BRANDS.join(', ');
        errs.push(errors.invalidParamErr('brand', message));
    }

    // when no package is passed, we want to validate presence of ram,
    // max_physical_memory and disks (when kvm) at least
    if (!params.billing_id) {
        if (params.brand === 'kvm' && !params.ram) {
            errs.push(errors.missingParamErr('ram', 'Is required for KVM'));
        } else if (!params.max_physical_memory && !params.ram) {
            errs.push(errors.missingParamErr('ram'));
        }
    }

    if (params.ram && !validNumber(params.ram)) {
        errs.push(errors.invalidParamErr('ram', 'Not a valid number'));
    }

    if (params.max_physical_memory &&
        !validNumber(params.max_physical_memory)) {
        errs.push(errors.invalidParamErr('max_physical_memory',
            'Not a valid number'));
    }

    // networks
    if (params.networks) {
        try {
            params.networks = validNetworks(params.networks, true);
        } catch (e) {
            errs.push(errors.invalidParamErr('networks', e.message));
        }
    } else {
        errs.push(errors.missingParamErr('networks'));
    }

    // max_swap
    if (params.max_swap) {
        if (!validNumber(params.max_swap)) {
            errs.push(errors.invalidParamErr('max_swap', 'Not a valid number'));
        } else if (params.max_swap < MIN_SWAP) {
            errs.push(errors.invalidParamErr('max_swap',
                'Cannot be less than ' + MIN_SWAP));
        }
    }

    // cpu_shares
    if (params.cpu_shares && !validNumber(params.cpu_shares)) {
        errs.push(errors.invalidParamErr('cpu_shares', 'Not a valid number'));
    }

    // quota
    if (params.quota && !validNumber(params.quota, true)) {
        errs.push(errors.invalidParamErr('quota', 'Not a valid number'));
    }

    // tmpfs
    if (params.tmpfs && !validNumber(params.tmpfs, true)) {
        errs.push(errors.invalidParamErr('tmpfs', 'Not a valid number'));
    }

    // customer_metadata
    if (params.customer_metadata &&
        (typeof (params.customer_metadata) === 'string')) {
        try {
            params.customer_metadata = JSON.parse(params.customer_metadata);
            validMetadata('customer_metadata', params.customer_metadata);
        } catch (e) {
            if (e.body && e.body.errors) {
                errs.push(e.body.errors[0]);
            } else {
                errs.push(errors.invalidParamErr('customer_metadata'));
            }
        }
    }

    // internal_metadata
    if (params.internal_metadata &&
        (typeof (params.internal_metadata) === 'string')) {
        try {
            params.internal_metadata = JSON.parse(params.internal_metadata);
            validMetadata('internal_metadata', params.internal_metadata);
        } catch (e) {
            if (e.body && e.body.errors) {
                errs.push(e.body.errors[0]);
            } else {
                errs.push(errors.invalidParamErr('internal_metadata'));
            }
        }
    }

    // tags
    if (params.tags && (typeof (params.tags) === 'string')) {
        try {
            params.tags = JSON.parse(params.tags);
            validMetadata('tags', params.tags);
        } catch (e) {
            if (e.body && e.body.errors) {
                errs.push(e.body.errors[0]);
            } else {
                errs.push(errors.invalidParamErr('tags'));
            }
        }
    }

    // firewall_enabled
    if (params.hasOwnProperty('firewall_enabled') &&
        (typeof (params.firewall_enabled) !== 'boolean')) {
        errs.push(errors.invalidParamErr('firewall_enabled'));
    }

    // maintain_resolvers
    if (params.hasOwnProperty('maintain_resolvers') &&
        (typeof (params.maintain_resolvers) !== 'boolean')) {
        errs.push(errors.invalidParamErr('maintain_resolvers'));
    }

    // alias
    if (params.alias && !validAlias(params.alias)) {
        errs.push(errors.invalidParamErr('alias'));
    }

    validateBrandParams(params, errs);

    // Async validations
    var asyncFns = [];
    if (params.uuid) {
        asyncFns.push(validateUniqueUuid);
    }
    if (params.alias) {
        asyncFns.push(validateAlias);
    }
    if (params['server_uuid']) {
        asyncFns.push(validateServer);
    }

    function validateUniqueUuid(next) {
        vmapi.moray.getVm({ uuid: params.uuid }, function onGetVm(err, vm) {
            if (err) {
                return next(err);
            }

            if (vm) {
                errs.push(errors.duplicateParamErr('uuid'));
            }
            return next();
        });
    }

    function validateAlias(next) {
        validateUniqueAlias(vmapi.moray, params, function (err, errorObj) {
            if (err) {
                return next(err);
            } else if (errorObj) {
                errs.push(errorObj);
            }
            return next();
        });
    }

    function validateServer(next) {
        vmapi.cnapi.getServer(params['server_uuid'], function (err) {
            if (err) {
                if (err.name === 'ResourceNotFoundError') {
                    errs.push({
                        field: 'server_uuid',
                        code: 'Invalid',
                        message: err.message
                    });
                } else {
                    return next(err);
{'fraction_non_alphanumeric': 0.1410699122558732, 'fraction_numerical': 0.006708180016982734, 'mean_word_length': 2.2017217942908927, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4004597', 'n_tokens_mistral': 11213, 'n_tokens_neox': 10857, 'n_words': 2624}