/* -------------------------------------------------------------------------- */
/* Copyright 2002-2013, OpenNebula Project (OpenNebula.org), C12G Labs        */
/*                                                                            */
/* 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.                                             */
/* -------------------------------------------------------------------------- */
#include <limits.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <iostream>
#include <sstream>
#include <queue>

#include "VirtualMachine.h"
#include "VirtualNetworkPool.h"
#include "ImagePool.h"
#include "NebulaLog.h"

#include "Nebula.h"

#include "vm_file_var_syntax.h"
#include "vm_var_syntax.h"

/* ************************************************************************** */
/* Virtual Machine :: Constructor/Destructor                                  */
/* ************************************************************************** */

VirtualMachine::VirtualMachine(int           id,
                               int           _uid,
                               int           _gid,
                               const string& _uname,
                               const string& _gname,
                               int           umask,
                               VirtualMachineTemplate * _vm_template):
        PoolObjectSQL(id,VM,"",_uid,_gid,_uname,_gname,table),
        last_poll(0),
        state(INIT),
        lcm_state(LCM_INIT),
        resched(0),
        stime(time(0)),
        etime(0),
        deploy_id(""),
        memory(0),
        cpu(0),
        net_tx(0),
        net_rx(0),
        history(0),
        previous_history(0),
        _log(0)
{
    if (_vm_template != 0)
    {
        // This is a VM Template, with the root TEMPLATE.
        _vm_template->set_xml_root("USER_TEMPLATE");

        user_obj_template = _vm_template;
    }
    else
    {
        user_obj_template = new VirtualMachineTemplate(false,'=',"USER_TEMPLATE");
    }

    obj_template = new VirtualMachineTemplate;

    set_umask(umask);
}

VirtualMachine::~VirtualMachine()
{
    for (unsigned int i=0 ; i < history_records.size() ; i++)
    {
            delete history_records[i];
    }

    if (_log != 0)
    {
        delete _log;
    }

    if (obj_template != 0)
    {
        delete obj_template;
    }

    if (user_obj_template != 0)
    {
        delete user_obj_template;
    }
}

/* ************************************************************************** */
/* Virtual Machine :: Database Access Functions                               */
/* ************************************************************************** */

const char * VirtualMachine::table = "vm_pool";

const char * VirtualMachine::db_names =
    "oid, name, body, uid, gid, last_poll, state, lcm_state, "
    "owner_u, group_u, other_u";

const char * VirtualMachine::db_bootstrap = "CREATE TABLE IF NOT EXISTS "
    "vm_pool (oid INTEGER PRIMARY KEY, name VARCHAR(128), body MEDIUMTEXT, uid INTEGER, "
    "gid INTEGER, last_poll INTEGER, state INTEGER, lcm_state INTEGER, "
    "owner_u INTEGER, group_u INTEGER, other_u INTEGER)";


const char * VirtualMachine::monit_table = "vm_monitoring";

const char * VirtualMachine::monit_db_names = "vmid, last_poll, body";

const char * VirtualMachine::monit_db_bootstrap = "CREATE TABLE IF NOT EXISTS "
    "vm_monitoring (vmid INTEGER, last_poll INTEGER, body MEDIUMTEXT, "
    "PRIMARY KEY(vmid, last_poll))";

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::select(SqlDB * db)
{
    ostringstream   oss;
    ostringstream   ose;

    string system_dir;
    int    rc;
    int    last_seq;

    Nebula& nd = Nebula::instance();

    // Rebuild the VirtualMachine object
    rc = PoolObjectSQL::select(db);

    if( rc != 0 )
    {
        return rc;
    }

    //Get History Records. Current history is built in from_xml() (if any).
    if( hasHistory() )
    {
        last_seq = history->seq - 1;

        for (int i = last_seq; i >= 0; i--)
        {
            History * hp;

            hp = new History(oid, i);
            rc = hp->select(db);

            if ( rc != 0)
            {
                goto error_previous_history;
            }

            history_records[i] = hp;

            if ( i == last_seq )
            {
                previous_history = hp;
            }
        }
    }

    if ( state == DONE ) //Do not recreate dirs. They may be deleted
    {
        _log = 0;

        return 0;
    }

    //--------------------------------------------------------------------------
    //Create support directories for this VM
    //--------------------------------------------------------------------------
    oss.str("");
    oss << nd.get_vms_location() << oid;

    mkdir(oss.str().c_str(), 0700);
    chmod(oss.str().c_str(), 0700);

    //--------------------------------------------------------------------------
    //Create Log support for this VM
    //--------------------------------------------------------------------------
    try
    {
        Log::MessageType   clevel;
        NebulaLog::LogType log_system;

        log_system  = nd.get_log_system();
        clevel      = nd.get_debug_level();

        switch(log_system)
        {
            case NebulaLog::FILE_TS:
            case NebulaLog::FILE:
                _log = new FileLog(nd.get_vm_log_filename(oid), clevel);
                break;

            case NebulaLog::SYSLOG:
                _log = new SysLogResource(oid, obj_type, clevel);
                break;

            case NebulaLog::CERR:
                _log = new CerrLog(clevel);
                break;

            default:
                throw runtime_error("Unknown log system.");
                break;
        }
    }
    catch(exception &e)
    {
        ose << "Error creating log: " << e.what();
        NebulaLog::log("ONE",Log::ERROR, ose);

        _log = 0;
    }

    return 0;

error_previous_history:
    ose << "Cannot get previous history record (seq:" << history->seq
        << ") for VM id: " << oid;

    log("ONE", Log::ERROR, ose);
    return -1;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::insert(SqlDB * db, string& error_str)
{
    int    rc;
    string name;
    string prefix;

    string value;
    int    ivalue;
    float  fvalue;

    ostringstream oss;

    // ------------------------------------------------------------------------
    // Set a name if the VM has not got one and VM_ID
    // ------------------------------------------------------------------------

    oss << oid;
    value = oss.str();

    user_obj_template->erase("VMID");
    obj_template->add("VMID", value);

    user_obj_template->get("TEMPLATE_ID", value);
    user_obj_template->erase("TEMPLATE_ID");

    if (!value.empty())
    {
        obj_template->add("TEMPLATE_ID", value);
    }

    user_obj_template->get("NAME",name);
    user_obj_template->erase("NAME");

    user_obj_template->get("TEMPLATE_NAME", prefix);
    user_obj_template->erase("TEMPLATE_NAME");

    if (prefix.empty())
    {
        prefix = "one";
    }

    if (name.empty() == true)
    {
        oss.str("");
        oss << prefix << "-" << oid;
        name = oss.str();
    }

    if ( !PoolObjectSQL::name_is_valid(name, error_str) )
    {
        goto error_name;
    }

    this->name = name;

    // ------------------------------------------------------------------------
    // Check for CPU, VCPU and MEMORY attributes
    // ------------------------------------------------------------------------

    if ( user_obj_template->get("MEMORY", ivalue) == false || ivalue <= 0 )
    {
        goto error_memory;
    }

    user_obj_template->erase("MEMORY");
    obj_template->add("MEMORY", ivalue);

    if ( user_obj_template->get("CPU", fvalue) == false || fvalue <= 0 )
    {
        goto error_cpu;
    }

    user_obj_template->erase("CPU");
    obj_template->add("CPU", fvalue);

    // VCPU is optional, first check if the attribute exists, then check it is
    // an integer
    user_obj_template->get("VCPU", value);

    if ( value.empty() == false )
    {
        if ( user_obj_template->get("VCPU", ivalue) == false || ivalue <= 0 )
        {
            goto error_vcpu;
        }

        user_obj_template->erase("VCPU");
        obj_template->add("VCPU", ivalue);
    }

    // ------------------------------------------------------------------------
    // Check the OS attribute
    // ------------------------------------------------------------------------

    rc = parse_os(error_str);

    if ( rc != 0 )
    {
        goto error_os;
    }

    // ------------------------------------------------------------------------
    // Get network leases
    // ------------------------------------------------------------------------

    rc = get_network_leases(error_str);

    if ( rc != 0 )
    {
        goto error_leases_rollback;
    }

    // ------------------------------------------------------------------------
    // Get disk images
    // ------------------------------------------------------------------------

    rc = get_disk_images(error_str);

    if ( rc != 0 )
    {
        // The get_disk_images method has an internal rollback for
        // the acquired images, release_disk_images() would release all disks
        goto error_leases_rollback;
    }

    // -------------------------------------------------------------------------
    // Parse the context & requirements
    // -------------------------------------------------------------------------

    rc = parse_context(error_str);

    if ( rc != 0 )
    {
        goto error_context;
    }

    rc = parse_requirements(error_str);

    if ( rc != 0 )
    {
        goto error_requirements;
    }

    rc = automatic_requirements(error_str);

    if ( rc != 0 )
    {
        goto error_requirements;
    }

    parse_graphics();

    parse_well_known_attributes();

    // ------------------------------------------------------------------------
    // Insert the VM
    // ------------------------------------------------------------------------

    rc = insert_replace(db, false, error_str);

    if ( rc != 0 )
    {
        goto error_update;
    }

    return 0;

error_update:
    goto error_rollback;

error_context:
    goto error_rollback;

error_requirements:
    goto error_rollback;

error_rollback:
    release_disk_images();

error_leases_rollback:
    release_network_leases();
    goto error_common;

error_cpu:
    error_str = "CPU attribute must be a positive float or integer value.";
    goto error_common;

error_vcpu:
    error_str = "VCPU attribute must be a positive integer value.";
    goto error_common;

error_memory:
    error_str = "MEMORY attribute must be a positive integer value.";
    goto error_common;

error_os:
error_name:
error_common:
    NebulaLog::log("ONE",Log::ERROR, error_str);

    return -1;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::set_os_file(VectorAttribute *  os,
                                const string&      base_name,
                                Image::ImageType   base_type,
                                string&            error_str)
{
    vector<int>  img_ids;
    Nebula& nd = Nebula::instance();

    ImagePool * ipool = nd.get_ipool();
    Image  *    img   = 0;

    Image::ImageType type;

    DatastorePool * ds_pool = nd.get_dspool();
    Datastore *     ds;
    int             ds_id;

    string attr;
    string base_name_ds     = base_name + "_DS";
    string base_name_id     = base_name + "_DS_ID";
    string base_name_source = base_name + "_DS_SOURCE";
    string base_name_ds_id  = base_name + "_DS_DSID";
    string base_name_tm     = base_name + "_DS_TM";
    string base_name_cluster= base_name + "_DS_CLUSTER_ID";

    attr = os->vector_value(base_name_ds.c_str());

    if ( attr.empty() )
    {
        return 0;
    }

    if ( parse_file_attribute(attr, img_ids, error_str) != 0 )
    {
        return -1;
    }

    if ( img_ids.size() != 1 )
    {
        error_str = "Only one FILE variable can be used in: " + attr;
        return -1;
    }

    img = ipool->get(img_ids.back(), true);

    if ( img == 0 )
    {
        error_str = "Image no longer exists in attribute: " + attr;
        return -1;
    }

    ds_id = img->get_ds_id();
    type  = img->get_type();

    os->remove(base_name);

    os->replace(base_name_id,     img->get_oid());
    os->replace(base_name_source, img->get_source());
    os->replace(base_name_ds_id,  img->get_ds_id());

    img->unlock();

    if ( type != base_type )
    {
        ostringstream oss;

        oss << base_name << " needs an image of type "
            << Image::type_to_str(base_type) << " and not "
            << Image::type_to_str(type);

        error_str = oss.str();
        return -1;
    }

    ds = ds_pool->get(ds_id, true);

    if ( ds == 0 )
    {
        error_str = "Associated datastore for image does not exist";
        return -1;
    }

    os->replace(base_name_tm, ds->get_tm_mad());

    if ( ds->get_cluster_id() != ClusterPool::NONE_CLUSTER_ID )
    {
        os->replace(base_name_cluster, ds->get_cluster_id());
    }

    ds->unlock();

    return 0;
}

/* -------------------------------------------------------------------------- */

int VirtualMachine::parse_os(string& error_str)
{
    int num;
    int rc;

    vector<Attribute *> os_attr;
    VectorAttribute *   os;

    vector<Attribute *>::iterator it;

    num = user_obj_template->remove("OS", os_attr);

    for (it=os_attr.begin(); it != os_attr.end(); it++)
    {
        obj_template->set(*it);
    }

    if ( num == 0 )
    {
        return 0;
    }
    else if ( num > 1 )
    {
        error_str = "Only one OS attribute can be defined.";
        return -1;
    }

    os = dynamic_cast<VectorAttribute *>(os_attr[0]);

    if ( os == 0 )
    {
        error_str = "Internal error parsing OS attribute.";
        return -1;
    }

    rc = set_os_file(os, "KERNEL", Image::KERNEL, error_str);

    if ( rc != 0 )
    {
        return -1;
    }

    rc = set_os_file(os, "INITRD", Image::RAMDISK, error_str);

    if ( rc != 0 )
    {
        return -1;
    }

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::parse_context(string& error_str)
{
    int rc, num;

    vector<Attribute *> array_context;
    VectorAttribute *   context;
    VectorAttribute *   context_parsed;

    string * str;
    string   parsed;
    string   files_ds;
    string   files_ds_parsed;

    ostringstream oss_parsed;

    vector<int>  img_ids;

    num = obj_template->remove("CONTEXT", array_context);

    if ( num == 0 )
    {
        return 0;
    }
    else if ( num > 1 )
    {
        error_str = "Only one CONTEXT attribute can be defined.";
        goto error_cleanup;
    }

    context = dynamic_cast<VectorAttribute *>(array_context[0]);

    if ( context == 0 )
    {
        error_str = "Wrong format for CONTEXT attribute.";
        goto error_cleanup;
    }

    //Backup datastore files to parse them later

    files_ds = context->vector_value("FILES_DS");

    context->remove("FILES_DS");

    // -------------------------------------------------------------------------
    // Parse CONTEXT variables & free vector attributes
    // -------------------------------------------------------------------------

    str = context->marshall(" @^_^@ ");

    if (str == 0)
    {
        error_str = "Cannot marshall CONTEXT";
        goto error_cleanup;
    }

    rc = parse_template_attribute(*str, parsed, error_str);

    delete str;

    if (rc != 0)
    {
        goto error_cleanup;
    }

    for (int i = 0; i < num ; i++)
    {
        if (array_context[i] != 0)
        {
            delete array_context[i];
        }
    }

    // -------------------------------------------------------------------------
    // Parse FILE_DS variables
    // -------------------------------------------------------------------------

    if (!files_ds.empty())
    {
        if ( parse_file_attribute(files_ds, img_ids, error_str) != 0 )
        {
            return -1;
        }

        if ( img_ids.size() > 0 )
        {
            vector<int>::iterator it;

            Nebula& nd = Nebula::instance();

            ImagePool * ipool = nd.get_ipool();
            Image  *    img   = 0;

            Image::ImageType type;

            for ( it=img_ids.begin() ; it < img_ids.end(); it++ )
            {
                img = ipool->get(*it, true);

                if ( img != 0 )
                {
                    oss_parsed << img->get_source() << ":'"
                               << img->get_name() << "' ";

                    type = img->get_type();

                    img->unlock();

                    if (type != Image::CONTEXT)
                    {
                        error_str = "Only images of type CONTEXT can be used in"
                                    " FILE_DS attribute.";
                        return -1;
                    }
                }
            }
        }
    }

    files_ds_parsed = oss_parsed.str();

    context_parsed = new VectorAttribute("CONTEXT");
    context_parsed->unmarshall(parsed," @^_^@ ");

    if ( !files_ds_parsed.empty() )
    {
        context_parsed->replace("FILES_DS", files_ds_parsed);
    }

    obj_template->set(context_parsed);

    return rc;

error_cleanup:
    for (int i = 0; i < num ; i++)
    {
        if (array_context[i] != 0)
        {
            delete array_context[i];
        }
    }

    return -1;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::parse_graphics()
{
    int num;

    vector<Attribute *> array_graphics;
    VectorAttribute *   graphics;

    vector<Attribute *>::iterator it;

    num = user_obj_template->remove("GRAPHICS", array_graphics);

    for (it=array_graphics.begin(); it != array_graphics.end(); it++)
    {
        obj_template->set(*it);
    }

    if ( num == 0 )
    {
        return;
    }

    graphics = dynamic_cast<VectorAttribute * >(array_graphics[0]);

    if ( graphics == 0 )
    {
        return;
    }

    string port = graphics->vector_value("PORT");

    if ( port.empty() )
    {
        Nebula&       nd = Nebula::instance();

        ostringstream oss;
        istringstream iss;

        int           base_port;
        string        base_port_s;

        nd.get_configuration_attribute("VNC_BASE_PORT",base_port_s);
        iss.str(base_port_s);
        iss >> base_port;

        oss << ( base_port + oid );
        graphics->replace("PORT", oss.str());
    }
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::parse_requirements(string& error_str)
{
    int rc, num;

    vector<Attribute *> array_reqs;
    SingleAttribute *   reqs;

    string              parsed;

    num = user_obj_template->remove("SCHED_REQUIREMENTS", array_reqs);

    if ( num == 0 ) // Compatibility with old REQUIREMENTS attribute
    {
        num = user_obj_template->remove("REQUIREMENTS", array_reqs);
    }
    else
    {
        user_obj_template->erase("REQUIREMENTS");
    }

    if ( num == 0 )
    {
        return 0;
    }
    else if ( num > 1 )
    {
        error_str = "Only one SCHED_REQUIREMENTS attribute can be defined.";
        goto error_cleanup;
    }

    reqs = dynamic_cast<SingleAttribute *>(array_reqs[0]);

    if ( reqs == 0 )
    {
        error_str = "Wrong format for SCHED_REQUIREMENTS attribute.";
        goto error_cleanup;
    }

    rc = parse_template_attribute(reqs->value(), parsed, error_str);

    if ( rc == 0 )
    {
        SingleAttribute * reqs_parsed;

        reqs_parsed = new SingleAttribute("SCHED_REQUIREMENTS",parsed);
        user_obj_template->set(reqs_parsed);
    }

    /* --- Delete old requirements attribute --- */

    delete array_reqs[0];

    return rc;

error_cleanup:
    for (int i = 0; i < num ; i++)
    {
        if (array_reqs[i] != 0)
        {
            delete array_reqs[i];
        }
    }

    return -1;
}

/* ------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------ */

void VirtualMachine::parse_well_known_attributes()
{
    /*
     * List of meaningful attributes, used in other places and expected in
     * obj_template:
     *
     * DISK
     * NIC
     * VCPU
     * MEMORY
     * CPU
     * CONTEXT
     * OS
     * GRAPHICS
     *
     * INPUT
     * FEATURES
     * RAW
     */

    vector<Attribute *>             v_attr;
    vector<Attribute *>::iterator   it;

    string names[] = {"INPUT", "FEATURES", "RAW"};

    for (int i=0; i<3; i++)
    {
        v_attr.clear();

        user_obj_template->remove(names[i], v_attr);

        for (it=v_attr.begin(); it != v_attr.end(); it++)
        {
            obj_template->set(*it);
        }
    }
}

/* ------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------ */

static int check_and_set_cluster_id(const char *      id_name,
                                    VectorAttribute * vatt,
                                    string&           cluster_id)
{
    string vatt_cluster_id;

    vatt_cluster_id = vatt->vector_value(id_name);

    if ( !vatt_cluster_id.empty() )
    {
        if ( cluster_id.empty() )
        {
            cluster_id = vatt_cluster_id;
        }
        else if ( cluster_id != vatt_cluster_id )
        {
            return -1;
        }
    }

    return 0;
}

/* ------------------------------------------------------------------------ */

int VirtualMachine::automatic_requirements(string& error_str)
{
    int                   num_vatts;
    vector<Attribute  * > v_attributes;
    VectorAttribute *     vatt;

    ostringstream   oss;
    string          requirements;
    string          cluster_id = "";

    int incomp_id;
    int rc;

    // Get cluster id from all DISK vector attributes (IMAGE Datastore)

    num_vatts = obj_template->get("DISK",v_attributes);

    for(int i=0; i<num_vatts; i++)
    {
        vatt = dynamic_cast<VectorAttribute * >(v_attributes[i]);

        if ( vatt == 0 )
        {
            continue;
        }

        rc = check_and_set_cluster_id("CLUSTER_ID", vatt, cluster_id);

        if ( rc != 0 )
        {
            incomp_id = i;
            goto error_disk;
        }
    }

    // Get cluster id from the KERNEL and INITRD (FILE Datastores)

    v_attributes.clear();
    num_vatts = obj_template->get("OS",v_attributes);

    if ( num_vatts > 0 )
    {
        vatt = dynamic_cast<VectorAttribute * >(v_attributes[0]);

        if ( vatt != 0 )
        {
            rc = check_and_set_cluster_id("KERNEL_CLUSTER_ID", vatt, cluster_id);

            if ( rc != 0 )
            {
                goto error_kernel;
            }

            rc = check_and_set_cluster_id("INITRD_CLUSTER_ID", vatt, cluster_id);

            if ( rc != 0 )
            {
                goto error_initrd;
            }
        }
    }

    // Get cluster id from all NIC vector attributes

    v_attributes.clear();
    num_vatts = obj_template->get("NIC",v_attributes);

    for(int i=0; i<num_vatts; i++)
    {
        vatt = dynamic_cast<VectorAttribute * >(v_attributes[i]);

        if ( vatt == 0 )
        {
            continue;
        }

        rc = check_and_set_cluster_id("CLUSTER_ID", vatt, cluster_id);

        if ( rc != 0 )
        {
            incomp_id = i;
            goto error_nic;
        }
    }

    if ( !cluster_id.empty() )
    {
        oss.str("");
        oss << "CLUSTER_ID = " << cluster_id;

        obj_template->add("AUTOMATIC_REQUIREMENTS", oss.str());
    }

    return 0;

error_disk:
    oss << "Incompatible clusters in DISK. Datastore for DISK "<< incomp_id
        << " is not the same as the one used by other VM elements (cluster "
        << cluster_id << ")";
    goto error_common;

error_kernel:
    oss << "Incompatible cluster in KERNEL datastore, it should be in cluster "
        << cluster_id << ".";
    goto error_common;

error_initrd:
    oss << "Incompatible cluster in INITRD datastore, it should be in cluster "
        << cluster_id << ".";
    goto error_common;

error_nic:
    oss << "Incompatible clusters in NIC. Network for NIC "<< incomp_id
        << " is not the same as the one used by other VM elements (cluster "
        << cluster_id << ")";
    goto error_common;

error_common:
    error_str = oss.str();

    return -1;
}

/* ------------------------------------------------------------------------ */
/* ------------------------------------------------------------------------ */

int VirtualMachine::insert_replace(SqlDB *db, bool replace, string& error_str)
{
    ostringstream   oss;
    int             rc;

    string xml_body;
    char * sql_name;
    char * sql_xml;

    sql_name =  db->escape_str(name.c_str());

    if ( sql_name == 0 )
    {
        goto error_generic;
    }

    sql_xml = db->escape_str(to_xml(xml_body).c_str());

    if ( sql_xml == 0 )
    {
        goto error_body;
    }

    if ( validate_xml(sql_xml) != 0 )
    {
        goto error_xml;
    }

    if(replace)
    {
        oss << "REPLACE";
    }
    else
    {
        oss << "INSERT";
    }

    oss << " INTO " << table << " ("<< db_names <<") VALUES ("
        <<          oid             << ","
        << "'" <<   sql_name        << "',"
        << "'" <<   sql_xml         << "',"
        <<          uid             << ","
        <<          gid             << ","
        <<          last_poll       << ","
        <<          state           << ","
        <<          lcm_state       << ","
        <<          owner_u         << ","
        <<          group_u         << ","
        <<          other_u         << ")";

    db->free_str(sql_name);
    db->free_str(sql_xml);

    rc = db->exec(oss);

    return rc;

error_xml:
    db->free_str(sql_name);
    db->free_str(sql_xml);

    error_str = "Error transforming the VM to XML.";

    goto error_common;

error_body:
    db->free_str(sql_name);
    goto error_generic;

error_generic:
    error_str = "Error inserting VM in DB.";
error_common:
    return -1;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::update_monitoring(SqlDB * db)
{
    ostringstream   oss;
    int             rc;

    string xml_body;
    string error_str;
    char * sql_xml;

    sql_xml = db->escape_str(to_xml(xml_body).c_str());

    if ( sql_xml == 0 )
    {
        goto error_body;
    }

    if ( validate_xml(sql_xml) != 0 )
    {
        goto error_xml;
    }

    oss << "INSERT INTO " << monit_table << " ("<< monit_db_names <<") VALUES ("
        <<          oid             << ","
        <<          last_poll       << ","
        << "'" <<   sql_xml         << "')";

    db->free_str(sql_xml);

    rc = db->exec(oss);

    return rc;

error_xml:
    db->free_str(sql_xml);

    error_str = "could not transform the VM to XML.";

    goto error_common;

error_body:
    error_str = "could not insert the VM in the DB.";

error_common:
    oss.str("");
    oss << "Error updating VM monitoring information, " << error_str;

    NebulaLog::log("ONE",Log::ERROR, oss);

    return -1;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::add_history(
    int   hid,
    const string& hostname,
    const string& vmm_mad,
    const string& vnm_mad,
    const string& tm_mad,
    const string& ds_location,
    int           ds_id)
{
    ostringstream os;
    int           seq;
    string        vm_xml;

    if (history == 0)
    {
        seq = 0;
    }
    else
    {
        seq = history->seq + 1;

        previous_history = history;
    }

    to_xml_extended(vm_xml, 0);

    history = new History(oid,
                          seq,
                          hid,
                          hostname,
                          vmm_mad,
                          vnm_mad,
                          tm_mad,
                          ds_location,
                          ds_id,
                          vm_xml);

    history_records.push_back(history);
};

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::cp_history()
{
    History * htmp;
    string    vm_xml;

    if (history == 0)
    {
        return;
    }

    to_xml_extended(vm_xml, 0);

    htmp = new History(oid,
                       history->seq + 1,
                       history->hid,
                       history->hostname,
                       history->vmm_mad_name,
                       history->vnm_mad_name,
                       history->tm_mad_name,
                       history->ds_location,
                       history->ds_id,
                       vm_xml);

    previous_history = history;
    history          = htmp;

    history_records.push_back(history);
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::cp_previous_history()
{
    History * htmp;
    string    vm_xml;

    if ( previous_history == 0 || history == 0)
    {
        return;
    }

    to_xml_extended(vm_xml, 0);

    htmp = new History(oid,
                       history->seq + 1,
                       previous_history->hid,
                       previous_history->hostname,
                       previous_history->vmm_mad_name,
                       previous_history->vnm_mad_name,
                       previous_history->tm_mad_name,
                       previous_history->ds_location,
                       previous_history->ds_id,
                       vm_xml);

    previous_history = history;
    history          = htmp;

    history_records.push_back(history);
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::get_requirements (int& cpu, int& memory, int& disk)
{
    istringstream   iss;
    float           fcpu;

    if ((get_template_attribute("MEMORY",memory) == false) ||
        (get_template_attribute("CPU",fcpu) == false))
    {
        cpu    = 0;
        memory = 0;
        disk   = 0;

        return;
    }

    cpu    = (int) (fcpu * 100);//now in 100%
    memory = memory * 1024;     //now in Kilobytes
    disk   = 0;

    return;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::check_resize (
        float cpu, int memory, int vcpu, string& error_str)
{
    if (cpu < 0)
    {
        error_str = "CPU must be a positive float or integer value.";
        return -1;
    }

    if (memory < 0)
    {
        error_str = "MEMORY must be a positive integer value.";
        return -1;
    }

    if (vcpu < 0)
    {
        error_str = "VCPU must be a positive integer value.";
        return -1;
    }

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::resize(float cpu, int memory, int vcpu, string& error_str)
{
    ostringstream oss;

    int rc = check_resize(cpu, memory, vcpu, error_str);

    if (rc != 0)
    {
        return rc;
    }

    if (cpu > 0)
    {
        oss << cpu;
        replace_template_attribute("CPU", oss.str());
        oss.str("");
    }

    if (memory > 0)
    {
        oss << memory;
        replace_template_attribute("MEMORY", oss.str());
        oss.str("");
    }

    if (vcpu > 0)
    {
        oss << vcpu;
        replace_template_attribute("VCPU", oss.str());
    }

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

static void assign_disk_targets(queue<pair <string, VectorAttribute *> >& _queue,
                                set<string>& used_targets)
{
    int    index = 0;
    string target;

    pair <string, VectorAttribute *> disk_pair;

    while (_queue.size() > 0 )
    {
        disk_pair = _queue.front();
        index     = 0;

        do
        {
            target = disk_pair.first + static_cast<char>(('a'+ index));
            index++;
        }
        while ( used_targets.count(target) > 0 && index < 26 );

        disk_pair.second->replace("TARGET", target);
        used_targets.insert(target);

        _queue.pop();
    }
}

/* -------------------------------------------------------------------------- */

int VirtualMachine::get_disk_images(string& error_str)
{
    int                  num_disks, num_context, rc;
    vector<Attribute  *> disks;
    vector<Attribute  *> context_disks;
    ImagePool *          ipool;
    VectorAttribute *    disk;
    vector<int>          acquired_images;

    int     image_id;
    string  dev_prefix;
    string  target;

    queue<pair <string, VectorAttribute *> > os_disk;
    queue<pair <string, VectorAttribute *> > cdrom_disks;
    queue<pair <string, VectorAttribute *> > datablock_disks;

    set<string> used_targets;

    ostringstream    oss;
    Image::ImageType img_type;

    Nebula& nd = Nebula::instance();
    ipool      = nd.get_ipool();

    vector<Attribute*>::iterator it;

    // -------------------------------------------------------------------------
    // The context is the first of the cdroms
    // -------------------------------------------------------------------------
    num_context = user_obj_template->remove("CONTEXT", context_disks);
    num_disks   = user_obj_template->remove("DISK", disks);

    for (it=context_disks.begin(); it != context_disks.end(); it++)
    {
        obj_template->set(*it);
    }

    for (it=disks.begin(); it != disks.end(); it++)
    {
        obj_template->set(*it);
    }

    if ( num_disks > 20 )
    {
        goto error_max_disks;
    }

    if ( num_context > 0 )
    {
        disk = dynamic_cast<VectorAttribute * >(context_disks[0]);

        if ( disk != 0 )
        {
            target = disk->vector_value("TARGET");

            if ( !target.empty() )
            {
                used_targets.insert(target);
            }
            else
            {
                cdrom_disks.push(make_pair(ipool->default_dev_prefix(), disk));
            }

            // Disk IDs are 0..num-1, context disk is is num
            disk->replace("DISK_ID", num_disks);
        }
    }

    // -------------------------------------------------------------------------
    // Set DISK attributes & Targets
    // -------------------------------------------------------------------------
    for(int i=0; i<num_disks; i++)
    {
        disk = dynamic_cast<VectorAttribute * >(disks[i]);

        if ( disk == 0 )
        {
            continue;
        }

        rc = ipool->disk_attribute(oid,
                                   disk,
                                   i,
                                   img_type,
                                   dev_prefix,
                                   uid,
                                   image_id,
                                   error_str);
        if (rc == 0 )
        {
            acquired_images.push_back(image_id);

            target = disk->vector_value("TARGET");

            if ( !target.empty() )
            {
                if (  used_targets.insert(target).second == false )
                {
                    goto error_duplicated_target;
                }
            }
            else
            {
                switch(img_type)
                {
                    case Image::OS:
                        // The first OS disk gets the first device (a),
                        // other OS's will be managed as DATABLOCK's
                        if ( os_disk.empty() )
                        {
                            os_disk.push( make_pair(dev_prefix, disk) );
                        }
                        else
                        {
                            datablock_disks.push( make_pair(dev_prefix, disk) );
                        }
                        break;

                    case Image::CDROM:
                        cdrom_disks.push( make_pair(dev_prefix, disk) );
                        break;

                    case Image::DATABLOCK:
                        datablock_disks.push( make_pair(dev_prefix, disk) );
                        break;

                    default:
                        break;
                }
            }
        }
        else
        {
            goto error_common;
        }
    }

    assign_disk_targets(os_disk, used_targets);
    assign_disk_targets(cdrom_disks, used_targets);
    assign_disk_targets(datablock_disks, used_targets);

    return 0;

error_max_disks:
    error_str = "Exceeded the maximum number of disks (20)";
    return -1;

error_duplicated_target:
    oss << "Two disks have defined the same target " << target;
    error_str = oss.str();

error_common:
    ImageManager *  imagem  = nd.get_imagem();

    vector<int>::iterator img_it;

    for ( img_it=acquired_images.begin() ; img_it < acquired_images.end(); img_it++ )
    {
        imagem->release_image(oid, *img_it, false);
    }

    return -1;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::get_disk_info(int&         max_disk_id,
                                   set<string>& used_targets)
{
    vector<Attribute  *> disks;
    VectorAttribute *    disk;

    string target;

    int disk_id;
    int num_disks;

    max_disk_id = -1;

    num_disks = obj_template->get("DISK", disks);

    for(int i=0; i<num_disks; i++)
    {
        disk = dynamic_cast<VectorAttribute * >(disks[i]);

        if ( disk == 0 )
        {
            continue;
        }

        target = disk->vector_value("TARGET");

        if ( !target.empty() )
        {
            used_targets.insert(target);
        }

        disk->vector_value("DISK_ID", disk_id);

        if ( disk_id > max_disk_id )
        {
            max_disk_id = disk_id;
        }
    }

    disks.clear();

    if ( obj_template->get("CONTEXT", disks) > 0 )
    {
        disk = dynamic_cast<VectorAttribute * >(disks[0]);

        if ( disk != 0 )
        {
            target = disk->vector_value("TARGET");

            if ( !target.empty() )
            {
                used_targets.insert(target);
            }

            disk->vector_value("DISK_ID", disk_id);

            if ( disk_id > max_disk_id )
            {
                max_disk_id = disk_id;
            }
        }
    }
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

VectorAttribute * VirtualMachine::set_up_attach_disk(
                int                      vm_id,
                VirtualMachineTemplate * tmpl,
                set<string>&             used_targets,
                int                      max_disk_id,
                int                      uid,
                int&                     image_id,
                string&                  error_str)
{
    vector<Attribute  *> disks;
    VectorAttribute *    new_disk;

    string target;

    Nebula&       nd     = Nebula::instance();
    ImagePool *   ipool  = nd.get_ipool();
    ImageManager* imagem = nd.get_imagem();

    string           dev_prefix;
    Image::ImageType img_type;

    image_id = -1;

    // -------------------------------------------------------------------------
    // Get the DISK attribute from the template
    // -------------------------------------------------------------------------

    if ( tmpl->get("DISK", disks) != 1 )
    {
        error_str = "The template must contain one DISK attribute";
        return 0;
    }

    new_disk = dynamic_cast<VectorAttribute * >(disks[0]);

    if ( new_disk == 0 )
    {
        error_str = "Internal error parsing DISK attribute";
        return 0;
    }

    new_disk = new_disk->clone();

    // -------------------------------------------------------------------------
    // Acquire the new disk image
    // -------------------------------------------------------------------------

    int rc = ipool->disk_attribute(vm_id,
                                   new_disk,
                                   max_disk_id + 1,
                                   img_type,
                                   dev_prefix,
                                   uid,
                                   image_id,
                                   error_str);
    if ( rc != 0 )
    {
        delete new_disk;
        return 0;
    }

    target = new_disk->vector_value("TARGET");

    if ( !target.empty() )
    {
        if (  used_targets.insert(target).second == false )
        {
            ostringstream oss;

            oss << "Target " << target << "is already in use.";
            error_str = oss.str();

            imagem->release_image(vm_id, image_id, false);

            delete new_disk;
            return 0;
        }
    }
    else
    {
        queue<pair <string, VectorAttribute *> > disks_queue;

        disks_queue.push(make_pair(dev_prefix, new_disk));

        assign_disk_targets(disks_queue, used_targets);
    }

    return new_disk;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::set_attach_disk(int disk_id)
{
    VectorAttribute * disk;

    disk = get_disk(disk_id);

    if ( disk != 0 )
    {
        disk->replace("ATTACH", "YES");
        return 0;
    }

    return -1;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

VectorAttribute* VirtualMachine::get_attach_disk()
{
    int                  num_disks;
    vector<Attribute  *> disks;
    VectorAttribute *    disk;

    num_disks = obj_template->get("DISK", disks);

    for(int i=0; i<num_disks; i++)
    {
        disk = dynamic_cast<VectorAttribute * >(disks[i]);

        if ( disk == 0 )
        {
            continue;
        }

        if ( disk->vector_value("ATTACH") == "YES" )
        {
            return disk;
        }
    }

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::clear_attach_disk()
{
    int                  num_disks;
    vector<Attribute  *> disks;
    VectorAttribute *    disk;

    num_disks = obj_template->get("DISK", disks);

    for(int i=0; i<num_disks; i++)
    {
        disk = dynamic_cast<VectorAttribute * >(disks[i]);

        if ( disk == 0 )
        {
            continue;
        }

        if ( disk->vector_value("ATTACH") == "YES" )
        {
            disk->remove("ATTACH");
            return;
        }
    }
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

VectorAttribute * VirtualMachine::delete_attach_disk()
{
    vector<Attribute  *> disks;
    VectorAttribute *    disk;

    int num_disks = obj_template->get("DISK", disks);

    for(int i=0; i<num_disks; i++)
    {
        disk = dynamic_cast<VectorAttribute * >(disks[i]);

        if ( disk == 0 )
        {
            continue;
        }

        if ( disk->vector_value("ATTACH") == "YES" )
        {
            return static_cast<VectorAttribute * >(obj_template->remove(disk));
        }
    }

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::get_nic_info(int& max_nic_id)
{
    vector<Attribute  *> nics;
    VectorAttribute *    nic;

    int nic_id;
    int num_nics;

    max_nic_id = -1;

    num_nics = obj_template->get("NIC", nics);

    for(int i=0; i<num_nics; i++)
    {
        nic = dynamic_cast<VectorAttribute * >(nics[i]);

        if ( nic == 0 )
        {
            continue;
        }

        nic->vector_value("NIC_ID", nic_id);

        if ( nic_id > max_nic_id )
        {
            max_nic_id = nic_id;
        }
    }
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

VectorAttribute * VirtualMachine::set_up_attach_nic(
                        int                      vm_id,
                        VirtualMachineTemplate * tmpl,
                        int                      max_nic_id,
                        int                      uid,
                        int&                     network_id,
                        string&                  error_str)
{
    vector<Attribute  *> nics;
    VectorAttribute *    new_nic;

    Nebula&             nd     = Nebula::instance();
    VirtualNetworkPool* vnpool = nd.get_vnpool();

    network_id = -1;

    // -------------------------------------------------------------------------
    // Get the NIC attribute from the template
    // -------------------------------------------------------------------------

    if ( tmpl->get("NIC", nics) != 1 )
    {
        error_str = "The template must contain one NIC attribute";
        return 0;
    }

    new_nic = dynamic_cast<VectorAttribute * >(nics[0]);

    if ( new_nic == 0 )
    {
        error_str = "Internal error parsing NIC attribute";
        return 0;
    }

    new_nic = new_nic->clone();

    // -------------------------------------------------------------------------
    // Acquire the new network lease
    // -------------------------------------------------------------------------

    int rc = vnpool->nic_attribute(new_nic, max_nic_id+1, uid, vm_id, error_str);

    if ( rc == -1 ) //-2 is not using a pre-defined network
    {
        delete new_nic;
        return 0;
    }

    return new_nic;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::clear_attach_nic()
{
    int                  num_nics;
    vector<Attribute  *> nics;
    VectorAttribute *    nic;

    num_nics = obj_template->get("NIC", nics);

    for(int i=0; i<num_nics; i++)
    {
        nic = dynamic_cast<VectorAttribute * >(nics[i]);

        if ( nic == 0 )
        {
            continue;
        }

        if ( nic->vector_value("ATTACH") == "YES" )
        {
            nic->remove("ATTACH");
            return;
        }
    }
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

VectorAttribute * VirtualMachine::delete_attach_nic()
{
    vector<Attribute  *> nics;
    VectorAttribute *    nic;

    int num_nics = obj_template->get("NIC", nics);

    for(int i=0; i<num_nics; i++)
    {
        nic = dynamic_cast<VectorAttribute * >(nics[i]);

        if ( nic == 0 )
        {
            continue;
        }

        if ( nic->vector_value("ATTACH") == "YES" )
        {
            return static_cast<VectorAttribute * >(obj_template->remove(nic));
        }
    }

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::set_attach_nic(int nic_id)
{
    int num_nics;
    int n_id;

    vector<Attribute  *> nics;
    VectorAttribute *    nic;

    num_nics = obj_template->get("NIC", nics);

    for(int i=0; i<num_nics; i++)
    {
        nic = dynamic_cast<VectorAttribute * >(nics[i]);

        if ( nic == 0 )
        {
            continue;
        }

        nic->vector_value("NIC_ID", n_id);

        if ( n_id == nic_id )
        {
            nic->replace("ATTACH", "YES");
            return 0;
        }
    }

    return -1;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::release_disk_images()
{
    int     iid;
    int     save_as_id;
    int     rc;
    int     num_disks;

    vector<Attribute const  * > disks;
    ImageManager *              imagem;

    string  disk_base_path = "";

    Nebula& nd = Nebula::instance();
    imagem     = nd.get_imagem();

    num_disks  = get_template_attribute("DISK",disks);

    for(int i=0; i<num_disks; i++)
    {
        VectorAttribute const *  disk =
            dynamic_cast<VectorAttribute const * >(disks[i]);

        if ( disk == 0 )
        {
            continue;
        }

        rc = disk->vector_value("IMAGE_ID", iid);

        if ( rc == 0 )
        {
            imagem->release_image(oid, iid, (state == FAILED));
        }

        rc = disk->vector_value("SAVE_AS", save_as_id);

        if ( rc == 0 )
        {
            imagem->release_image(oid, save_as_id, (state == FAILED));
        }
    }
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::new_snapshot(string& name, int& snap_id)
{
    int num_snaps;
    int id;
    int max_id = -1;

    vector<Attribute  *> snaps;
    VectorAttribute *    snap;

    num_snaps = obj_template->get("SNAPSHOT", snaps);

    for(int i=0; i<num_snaps; i++)
    {
        snap = dynamic_cast<VectorAttribute * >(snaps[i]);

        if ( snap == 0 )
        {
            continue;
        }

        snap->vector_value("SNAPSHOT_ID", id);

        if (id > max_id)
        {
            max_id = id;
        }
    }

    snap_id = max_id + 1;

    if (name.empty())
    {
        ostringstream oss;

        oss << "snapshot-" << snap_id;

        name = oss.str();
    }

    snap = new VectorAttribute("SNAPSHOT");
    snap->replace("SNAPSHOT_ID", snap_id);
    snap->replace("NAME", name);
    snap->replace("TIME", (int)time(0));
    snap->replace("HYPERVISOR_ID", "");

    snap->replace("ACTIVE", "YES");

    obj_template->set(snap);

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::set_active_snapshot(int snap_id)
{
    int num_snaps;
    int s_id;

    vector<Attribute  *> snaps;
    VectorAttribute *    snap;

    num_snaps = obj_template->get("SNAPSHOT", snaps);

    for(int i=0; i<num_snaps; i++)
    {
        snap = dynamic_cast<VectorAttribute * >(snaps[i]);

        if ( snap == 0 )
        {
            continue;
        }

        snap->vector_value("SNAPSHOT_ID", s_id);

        if ( s_id == snap_id )
        {
            snap->replace("ACTIVE", "YES");
            return 0;
        }
    }

    return -1;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::update_snapshot_id(string& hypervisor_id)
{
    int                  num_snaps;
    vector<Attribute  *> snaps;
    VectorAttribute *    snap;

    num_snaps = obj_template->get("SNAPSHOT", snaps);

    for(int i=0; i<num_snaps; i++)
    {
        snap = dynamic_cast<VectorAttribute * >(snaps[i]);

        if ( snap == 0 )
        {
            continue;
        }

        if ( snap->vector_value("ACTIVE") == "YES" )
        {
            snap->replace("HYPERVISOR_ID", hypervisor_id);
            break;
        }
    }
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::clear_active_snapshot()
{
    int                  num_snaps;
    vector<Attribute  *> snaps;
    VectorAttribute *    snap;

    num_snaps = obj_template->get("SNAPSHOT", snaps);

    for(int i=0; i<num_snaps; i++)
    {
        snap = dynamic_cast<VectorAttribute * >(snaps[i]);

        if ( snap == 0 )
        {
            continue;
        }

        if ( snap->vector_value("ACTIVE") == "YES" )
        {
            snap->remove("ACTIVE");
            return;
        }
    }
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::delete_active_snapshot()
{
    vector<Attribute  *> snaps;
    VectorAttribute *    snap;

    int num_snaps = obj_template->get("SNAPSHOT", snaps);

    for(int i=0; i<num_snaps; i++)
    {
        snap = dynamic_cast<VectorAttribute * >(snaps[i]);

        if ( snap == 0 )
        {
            continue;
        }

        if ( snap->vector_value("ACTIVE") == "YES" )
        {
            if (obj_template->remove(snap) != 0)
            {
                delete snap;
            }

            return;
        }
    }
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::delete_snapshots()
{
    obj_template->erase("SNAPSHOT");
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::get_network_leases(string& estr)
{
    int                   num_nics, rc;
    vector<Attribute  * > nics;
    VirtualNetworkPool *  vnpool;
    VectorAttribute *     nic;

    Nebula& nd = Nebula::instance();
    vnpool     = nd.get_vnpool();

    num_nics   = user_obj_template->remove("NIC",nics);

    for (vector<Attribute*>::iterator it=nics.begin(); it != nics.end(); it++)
    {
        obj_template->set(*it);
    }

    for(int i=0; i<num_nics; i++)
    {
        nic = dynamic_cast<VectorAttribute * >(nics[i]);

        if ( nic == 0 )
        {
            continue;
        }

        rc = vnpool->nic_attribute(nic, i, uid, oid, estr);

        if (rc == -1)
        {
            return -1;
        }
    }

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::release_network_leases()
{
    string                        vnid;
    string                        ip;
    int                           num_nics;
    vector<Attribute const  * >   nics;

    num_nics = get_template_attribute("NIC",nics);

    for(int i=0; i<num_nics; i++)
    {
        VectorAttribute const *  nic =
            dynamic_cast<VectorAttribute const * >(nics[i]);

        release_network_leases(nic, oid);
    }
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::release_network_leases(VectorAttribute const * nic, int vmid)
{
    VirtualNetworkPool* vnpool = Nebula::instance().get_vnpool();
    VirtualNetwork*     vn;

    int     vnid;
    string  ip;

    if ( nic == 0 )
    {
        return -1;
    }

    if ( nic->vector_value("NETWORK_ID", vnid) != 0 )
    {
        return -1;
    }

    ip = nic->vector_value("IP");

    if ( ip.empty() )
    {
        return -1;
    }

    vn = vnpool->get(vnid, true);

    if ( vn == 0 )
    {
        return -1;
    }

    if (vn->is_owner(ip,vmid))
    {
        vn->release_lease(ip);
        vnpool->update(vn);
    }

    vn->unlock();

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::generate_context(string &files, int &disk_id)
{
    ofstream file;
    string   files_ds;

    vector<const Attribute*> attrs;
    const VectorAttribute *  context;

    map<string, string>::const_iterator it;

    files = "";

    if ( history == 0 )
        return -1;

    if ( get_template_attribute("CONTEXT",attrs) != 1 )
    {
        log("VM", Log::INFO, "Virtual Machine has no context");
        return 0;
    }

    file.open(history->context_file.c_str(),ios::out);

    if (file.fail() == true)
    {
        ostringstream oss;

        oss << "Could not open context file: " << history->context_file;
        log("VM", Log::ERROR, oss);
        return -1;
    }

    context = dynamic_cast<const VectorAttribute *>(attrs[0]);

    if (context == 0)
    {
        file.close();
        return -1;
    }

    files = context->vector_value("FILES");

    files_ds = context->vector_value("FILES_DS");

    if (!files_ds.empty())
    {
        files += " ";
        files += files_ds;
    }

    const map<string, string> values = context->value();

    file << "# Context variables generated by OpenNebula\n";

    for (it=values.begin(); it != values.end(); it++ )
    {
        file << it->first <<"=\""<< it->second << "\"" << endl;
    }

    file.close();

    context->vector_value("DISK_ID", disk_id);

    return 1;
}

/* -------------------------------------------------------------------------- */

int VirtualMachine::get_image_from_disk(int disk_id, bool hot, string& err_str)
{
    int iid = -1;
    int rc;

    VectorAttribute *     disk;

    ostringstream oss;

    disk = get_disk(disk_id);

    if ( disk == 0 )
    {
        goto error_not_found;
    }

    if(!((disk->vector_value("SAVE_AS")).empty()))
    {
        goto error_saved;
    }

    if(!(disk->vector_value("PERSISTENT").empty()) && !hot)
    {
        goto error_persistent;
    }

    rc = disk->vector_value("IMAGE_ID", iid);

    if ( rc != 0 )
    {
        goto error_image_id;
    }

    return iid;

error_persistent:
    oss << "Source image for DISK " << disk_id << " is persistent.";
    goto error_common;

error_saved:
    oss << "The DISK " << disk_id << " is already going to be saved.";
    goto error_common;

error_image_id:
    oss << "The DISK " << disk_id << " does not have a valid IMAGE_ID.";
    goto error_common;

error_not_found:
    oss << "The DISK " << disk_id << " does not exist for VM " << oid << ".";

error_common:
    err_str = oss.str();

    return -1;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::set_saveas_state(int disk_id, bool hot)
{
    VectorAttribute* disk;

    switch (state)
    {
        case ACTIVE:
            switch (lcm_state)
            {
                case RUNNING:
                    lcm_state = HOTPLUG_SAVEAS;
                break;

                default:
                    return -1;
            }
        break;

        case POWEROFF:
            state     = ACTIVE;
            lcm_state = HOTPLUG_SAVEAS_POWEROFF;
        break;

        case SUSPENDED:
            state     = ACTIVE;
            lcm_state = HOTPLUG_SAVEAS_SUSPENDED;
        break;

        default:
            return -1;
    }

    disk = get_disk(disk_id);

    if ( disk != 0 )
    {
        if (hot)
        {
            disk->replace("HOTPLUG_SAVE_AS_ACTIVE", "YES");
        }
        else
        {
            disk->replace("SAVE_AS_ACTIVE", "YES");
        }
    }

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::clear_saveas_state(int disk_id, bool hot)
{
    VectorAttribute * disk;

    disk = get_disk(disk_id);

    if (disk != 0)
    {
        if (hot)
        {
            disk->remove("HOTPLUG_SAVE_AS_ACTIVE");
            disk->remove("HOTPLUG_SAVE_AS");
            disk->remove("HOTPLUG_SAVE_AS_SOURCE");
        }
        else
        {
            disk->remove("SAVE_AS_ACTIVE");
        }
    }

    switch (lcm_state)
    {
        case HOTPLUG_SAVEAS:
            lcm_state = RUNNING;
        break;

        case HOTPLUG_SAVEAS_POWEROFF:
            state     = POWEROFF;
            lcm_state = LCM_INIT;
        break;

        case HOTPLUG_SAVEAS_SUSPENDED:
            state     = SUSPENDED;
            lcm_state = LCM_INIT;
        break;

        default:
            return -1;
    }

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

VectorAttribute* VirtualMachine::get_disk(int disk_id)
{
    int num_disks;
    int tdisk_id;

    vector<Attribute  *> disks;
    VectorAttribute *    disk;

    num_disks = obj_template->get("DISK", disks);

    for(int i=0; i<num_disks; i++)
    {
        disk = dynamic_cast<VectorAttribute * >(disks[i]);

        if ( disk == 0 )
        {
            continue;
        }

        disk->vector_value("DISK_ID", tdisk_id);

        if ( tdisk_id == disk_id )
        {
            return disk;
        }
    }

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::save_disk(int           disk_id,
                              const string& source,
                              int           img_id)
{
    VectorAttribute * disk;

    if (lcm_state != HOTPLUG_SAVEAS && lcm_state != HOTPLUG_SAVEAS_SUSPENDED
        && lcm_state != HOTPLUG_SAVEAS_POWEROFF )
    {
        return -1;
    }

    disk = get_disk(disk_id);

    if ( disk != 0 )
    {
        disk->replace("SAVE_AS_SOURCE", source);

        disk->replace("SAVE_AS", img_id);

        disk->replace("SAVE", "YES");
    }

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::save_disk_hot(int           disk_id,
                                  const string& source,
                                  int           img_id)
{
    VectorAttribute * disk;

    if (lcm_state != HOTPLUG_SAVEAS && lcm_state != HOTPLUG_SAVEAS_SUSPENDED
        && lcm_state != HOTPLUG_SAVEAS_POWEROFF )
    {
        return -1;
    }

    disk = get_disk(disk_id);

    if ( disk != 0 )
    {
        disk->replace("HOTPLUG_SAVE_AS", img_id);
        disk->replace("HOTPLUG_SAVE_AS_SOURCE", source);
    }

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::get_saveas_disk_hot(int& disk_id, string& source, int& image_id)
{
    vector<Attribute  *> disks;
    VectorAttribute *    disk;

    int rc;
    int num_disks;

    num_disks = obj_template->get("DISK", disks);

    for(int i=0; i<num_disks; i++)
    {
        disk = dynamic_cast<VectorAttribute * >(disks[i]);

        if ( disk == 0 )
        {
            continue;
        }

        if ( disk->vector_value("HOTPLUG_SAVE_AS_ACTIVE") == "YES" )
        {
            source = disk->vector_value("HOTPLUG_SAVE_AS_SOURCE");

            rc =  disk->vector_value("HOTPLUG_SAVE_AS", image_id);
            rc += disk->vector_value("DISK_ID",  disk_id);

            if ( rc != 0 || source.empty() )
            {
                return -1;
            }

            return 0;
        }
    }

    return -1;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::cancel_saveas_disk(int& image_id)
{
    vector<Attribute  *> disks;
    VectorAttribute *    disk;

    int num_disks;

    num_disks = obj_template->get("DISK", disks);

    bool active, hot_active;

    image_id = -1;

    for(int i=0; i<num_disks; i++)
    {
        disk = dynamic_cast<VectorAttribute * >(disks[i]);

        if ( disk == 0 )
        {
            continue;
        }

        disk->vector_value("SAVE_AS_ACTIVE", active);
        disk->vector_value("HOTPLUG_SAVE_AS_ACTIVE", hot_active);

        if (active)
        {
            disk->vector_value("SAVE_AS", image_id);

            disk->remove("SAVE_AS_ACTIVE");
            disk->remove("SAVE_AS_SOURCE");
            disk->remove("SAVE_AS");

            disk->replace("SAVE", "NO");

            return 0;
        }

        if (hot_active)
        {
            disk->vector_value("HOTPLUG_SAVE_AS", image_id);

            disk->remove("HOTPLUG_SAVE_AS_ACTIVE");
            disk->remove("HOTPLUG_SAVE_AS");
            disk->remove("HOTPLUG_SAVE_AS_SOURCE");

            return 0;
        }
    }

    return -1;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::set_auth_request(int uid,
                                      AuthRequest& ar,
                                      VirtualMachineTemplate *tmpl)
{
    int                   num;
    vector<Attribute  * > vectors;
    VectorAttribute *     vector;

    Nebula& nd = Nebula::instance();

    ImagePool *           ipool  = nd.get_ipool();
    VirtualNetworkPool *  vnpool = nd.get_vnpool();

    num = tmpl->get("DISK",vectors);

    for(int i=0; i<num; i++)
    {

        vector = dynamic_cast<VectorAttribute * >(vectors[i]);

        if ( vector == 0 )
        {
            continue;
        }

        ipool->authorize_disk(vector,uid,&ar);
    }

    vectors.clear();

    num = tmpl->get("NIC",vectors);

    for(int i=0; i<num; i++)
    {
        vector = dynamic_cast<VectorAttribute * >(vectors[i]);

        if ( vector == 0 )
        {
            continue;
        }

        vnpool->authorize_nic(vector,uid,&ar);
    }
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

pthread_mutex_t VirtualMachine::lex_mutex = PTHREAD_MUTEX_INITIALIZER;

extern "C"
{
    typedef struct yy_buffer_state * YY_BUFFER_STATE;

    int vm_var_parse (VirtualMachine * vm,
                      ostringstream *  parsed,
                      char **          errmsg);

    int vm_file_var_parse (VirtualMachine * vm,
                           vector<int> *    img_ids,
                           char **          errmsg);

    int vm_var_lex_destroy();

    YY_BUFFER_STATE vm_var__scan_string(const char * str);

    void vm_var__delete_buffer(YY_BUFFER_STATE);
}

/* -------------------------------------------------------------------------- */

int VirtualMachine::parse_template_attribute(const string& attribute,
                                             string&       parsed,
                                             string&       error_str)
{
    YY_BUFFER_STATE  str_buffer = 0;
    const char *     str;
    int              rc;
    ostringstream    oss_parsed;
    char *           error_msg = 0;

    pthread_mutex_lock(&lex_mutex);

    str        = attribute.c_str();
    str_buffer = vm_var__scan_string(str);

    if (str_buffer == 0)
    {
        goto error_yy;
    }

    rc = vm_var_parse(this, &oss_parsed, &error_msg);

    vm_var__delete_buffer(str_buffer);

    vm_var_lex_destroy();

    pthread_mutex_unlock(&lex_mutex);

    if ( rc != 0 && error_msg != 0 )
    {
        ostringstream oss;

        oss << "Error parsing: " << attribute << ". " << error_msg;
        log("VM", Log::ERROR, oss);

        error_str = oss.str();

        free(error_msg);
    }

    parsed = oss_parsed.str();

    return rc;

error_yy:
    log("VM",Log::ERROR,"Error setting scan buffer");
    pthread_mutex_unlock(&lex_mutex);
    return -1;
}

/* -------------------------------------------------------------------------- */

int VirtualMachine::parse_file_attribute(string       attribute,
                                         vector<int>& img_ids,
                                         string&      error)
{
    YY_BUFFER_STATE  str_buffer = 0;
    const char *     str;
    int              rc;
    ostringstream    oss_parsed;
    char *           error_msg = 0;

    size_t non_blank_pos;

    //Removes leading blanks from attribute, these are not managed
    //by the parser as it is common to the other VM varibales
    non_blank_pos = attribute.find_first_not_of(" \t\n\v\f\r");

    if ( non_blank_pos != string::npos )
    {
        attribute.erase(0, non_blank_pos);
    }

    pthread_mutex_lock(&lex_mutex);

    str        = attribute.c_str();
    str_buffer = vm_var__scan_string(str);

    if (str_buffer == 0)
    {
        goto error_yy;
    }

    rc = vm_file_var_parse(this, &img_ids, &error_msg);

    vm_var__delete_buffer(str_buffer);

    vm_var_lex_destroy();

    pthread_mutex_unlock(&lex_mutex);

    if ( rc != 0  )
    {
        ostringstream oss;

        if ( error_msg != 0 )
        {
            oss << "Error parsing: " << attribute << ". " << error_msg;
            free(error_msg);
        }
        else
        {
            oss << "Unknown error parsing: " << attribute << ".";
        }

        error = oss.str();
    }

    return rc;

error_yy:
    log("VM",Log::ERROR,"Error setting scan buffer");
    pthread_mutex_unlock(&lex_mutex);
    return -1;
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

string& VirtualMachine::to_xml_extended(string& xml, int n_history) const
{
    string template_xml;
    string user_template_xml;
    string history_xml;
    string perm_xml;
    ostringstream	oss;

    oss << "<VM>"
        << "<ID>"        << oid       << "</ID>"
        << "<UID>"       << uid       << "</UID>"
        << "<GID>"       << gid       << "</GID>"
        << "<UNAME>"     << uname     << "</UNAME>"
        << "<GNAME>"     << gname     << "</GNAME>"
        << "<NAME>"      << name      << "</NAME>"
        << perms_to_xml(perm_xml)
        << "<LAST_POLL>" << last_poll << "</LAST_POLL>"
        << "<STATE>"     << state     << "</STATE>"
        << "<LCM_STATE>" << lcm_state << "</LCM_STATE>"
        << "<RESCHED>"   << resched   << "</RESCHED>"
        << "<STIME>"     << stime     << "</STIME>"
        << "<ETIME>"     << etime     << "</ETIME>"
        << "<DEPLOY_ID>" << deploy_id << "</DEPLOY_ID>"
        << "<MEMORY>"    << memory    << "</MEMORY>"
        << "<CPU>"       << cpu       << "</CPU>"
        << "<NET_TX>"    << net_tx    << "</NET_TX>"
        << "<NET_RX>"    << net_rx    << "</NET_RX>"
        << obj_template->to_xml(template_xml)
        << user_obj_template->to_xml(user_template_xml);

    if ( hasHistory() && n_history > 0 )
    {
        oss << "<HISTORY_RECORDS>";

        if ( n_history == 2 )
        {
            for (unsigned int i=0; i < history_records.size(); i++)
            {
                oss << history_records[i]->to_xml(history_xml);
            }
        }
        else
        {
            oss << history->to_xml(history_xml);
        }

        oss << "</HISTORY_RECORDS>";
    }
    else
    {
        oss << "<HISTORY_RECORDS/>";
    }

    oss << "</VM>";

    xml = oss.str();

    return xml;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::from_xml(const string &xml_str)
{
    vector<xmlNodePtr> content;

    int istate;
    int ilcmstate;
    int rc = 0;

    // Initialize the internal XML object
    update_from_str(xml_str);

    // Get class base attributes
    rc += xpath(oid,       "/VM/ID",    -1);

    rc += xpath(uid,       "/VM/UID",   -1);
    rc += xpath(gid,       "/VM/GID",   -1);

    rc += xpath(uname,     "/VM/UNAME", "not_found");
    rc += xpath(gname,     "/VM/GNAME", "not_found");
    rc += xpath(name,      "/VM/NAME",  "not_found");

    rc += xpath(last_poll, "/VM/LAST_POLL", 0);
    rc += xpath(istate,    "/VM/STATE",     0);
    rc += xpath(ilcmstate, "/VM/LCM_STATE", 0);
    rc += xpath(resched,   "/VM/RESCHED",   0);

    rc += xpath(stime,     "/VM/STIME",    0);
    rc += xpath(etime,     "/VM/ETIME",    0);
    rc += xpath(deploy_id, "/VM/DEPLOY_ID","");

    rc += xpath(memory,    "/VM/MEMORY",   0);
    rc += xpath(cpu,       "/VM/CPU",      0);
    rc += xpath(net_tx,    "/VM/NET_TX",   0);
    rc += xpath(net_rx,    "/VM/NET_RX",   0);

    // Permissions
    rc += perms_from_xml();

    state     = static_cast<VmState>(istate);
    lcm_state = static_cast<LcmState>(ilcmstate);

    // Virtual Machine template
    ObjectXML::get_nodes("/VM/TEMPLATE", content);

    if (content.empty())
    {
        return -1;
    }
    rc += obj_template->from_xml_node(content[0]);

    ObjectXML::free_nodes(content);
    content.clear();

    // Virtual Machine user template

    ObjectXML::get_nodes("/VM/USER_TEMPLATE", content);

    if (content.empty())
    {
        return -1;
    }

    rc += user_obj_template->from_xml_node(content[0]);

    ObjectXML::free_nodes(content);
    content.clear();

    // Last history entry

    ObjectXML::get_nodes("/VM/HISTORY_RECORDS/HISTORY", content);

    if (!content.empty())
    {
        history = new History(oid);
        rc += history->from_xml_node(content[0]);

        history_records.resize(history->seq + 1);
        history_records[history->seq] = history;

        ObjectXML::free_nodes(content);
    }

    if (rc != 0)
    {
        return -1;
    }

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

string VirtualMachine::get_system_dir() const
{
    ostringstream oss;
    Nebula&       nd = Nebula::instance();

    oss << nd.get_ds_location() << history->ds_id << "/"<< oid;

    return oss.str();
};

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::update_info(
    const int _memory,
    const int _cpu,
    const long long _net_tx,
    const long long _net_rx,
    const map<string, string> &custom)
{
    map<string, string>::const_iterator it;

    last_poll = time(0);

    if (_memory != -1)
    {
        memory = _memory;
    }

    if (_cpu != -1)
    {
        cpu    = _cpu;
    }

    if (_net_tx != -1)
    {
        net_tx = _net_tx;
    }

    if (_net_rx != -1)
    {
        net_rx = _net_rx;
    }

    for (it = custom.begin(); it != custom.end(); it++)
    {
        replace_template_attribute(it->first, it->second);
    }

    set_vm_info();

    clear_template_monitor_error();
};

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

int VirtualMachine::replace_template(
        const string&   tmpl_str,
        bool            keep_restricted,
        string&         error)
{
    VirtualMachineTemplate * new_tmpl =
            new VirtualMachineTemplate(false,'=',"USER_TEMPLATE");

    if ( new_tmpl == 0 )
    {
        error = "Cannot allocate a new template";
        return -1;
    }

    if ( new_tmpl->parse_str_or_xml(tmpl_str, error) != 0 )
    {
        delete new_tmpl;
        return -1;
    }

    if (keep_restricted)
    {
        new_tmpl->remove_restricted();

        if (user_obj_template != 0)
        {
            user_obj_template->remove_all_except_restricted();

            string aux_error;
            new_tmpl->merge(user_obj_template, aux_error);
        }
    }

    delete user_obj_template;

    user_obj_template = new_tmpl;

    return 0;
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::set_template_error_message(const string& message)
{
    set_template_error_message("ERROR", message);
}

/* -------------------------------------------------------------------------- */

void VirtualMachine::set_template_error_message(const string& name,
                                               const string& message)
{
    SingleAttribute * attr;
    ostringstream     error_value;

    error_value << one_util::log_time() << " : " << message;

    attr = new SingleAttribute(name, error_value.str());

    user_obj_template->erase(name);
    user_obj_template->set(attr);
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::clear_template_error_message()
{
    user_obj_template->erase("ERROR");
}

/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */

void VirtualMachine::set_template_monitor_error(const string& message)
{
    set_template_error_message("ERROR_MONITOR", message);
}

/* -------------------------------------------------------------------------- */

void VirtualMachine::clear_template_monitor_error()
{
    user_obj_template->erase("ERROR_MONITOR");
}
