/* ------------------------------------------------------------------------ */ /* Copyright 2002-2018, OpenNebula Project, OpenNebula Systems */ /* */ /* 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 #include #include #include #include #include #include "Image.h" #include "ImagePool.h" #include "AuthManager.h" #include "UserPool.h" #include "NebulaUtil.h" #include "LifeCycleManager.h" #include "Nebula.h" #define TO_UPPER(S) transform(S.begin(),S.end(),S.begin(),(int(*)(int))toupper) /* ************************************************************************ */ /* Image :: Constructor/Destructor */ /* ************************************************************************ */ Image::Image(int _uid, int _gid, const string& _uname, const string& _gname, int _umask, ImageTemplate * _image_template): PoolObjectSQL(-1,IMAGE,"",_uid,_gid,_uname,_gname,table), type(OS), disk_type(FILE), regtime(time(0)), source(""), path(""), fs_type(""), size_mb(0), state(INIT), running_vms(0), cloning_ops(0), cloning_id(-1), ds_id(-1), ds_name(""), vm_collection("VMS"), img_clone_collection("CLONES"), app_clone_collection("APP_CLONES"), snapshots(-1, false), target_snapshot(-1) { if (_image_template != 0) { obj_template = _image_template; } else { obj_template = new ImageTemplate; } set_umask(_umask); } Image::~Image(){}; /* ************************************************************************ */ /* Image :: Database Access Functions */ /* ************************************************************************ */ const char * Image::table = "image_pool"; const char * Image::db_names = "oid, name, body, uid, gid, owner_u, group_u, other_u"; const char * Image::db_bootstrap = "CREATE TABLE IF NOT EXISTS image_pool (" "oid INTEGER PRIMARY KEY, name VARCHAR(128), body MEDIUMTEXT, uid INTEGER, " "gid INTEGER, owner_u INTEGER, group_u INTEGER, other_u INTEGER, " "UNIQUE(name,uid) )"; /* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */ int Image::insert(SqlDB *db, string& error_str) { int rc; string type_attr; string persistent_attr; string dev_prefix; string size_attr; string tm_mad; istringstream iss; ostringstream oss; // --------------------------------------------------------------------- // Check default image attributes // --------------------------------------------------------------------- // ------------ NAME -------------------- erase_template_attribute("NAME", name); // ------------ TYPE -------------------- erase_template_attribute("TYPE", type_attr); if ( type_attr.empty() == true ) { type_attr = ImagePool::default_type(); } if (set_type(type_attr, error_str) != 0) { goto error_common; } // ------------ PERSISTENT & PREFIX -------------------- erase_template_attribute("PERSISTENT", persistent_attr); switch (type) { case OS: case DATABLOCK: case CDROM: TO_UPPER(persistent_attr); persistent_img = (persistent_attr == "YES"); get_template_attribute("DEV_PREFIX", dev_prefix); if( dev_prefix.empty() ) { if (type == CDROM) { dev_prefix = ImagePool::default_cdrom_dev_prefix(); } else { dev_prefix = ImagePool::default_dev_prefix(); } SingleAttribute * dev_att = new SingleAttribute("DEV_PREFIX", dev_prefix); obj_template->set(dev_att); } break; case KERNEL: // Files are always non-persistent with no dev_prefix case RAMDISK: case CONTEXT: persistent_img = false; erase_template_attribute("DEV_PREFIX", dev_prefix); break; } // ------------ SIZE -------------------- erase_template_attribute("SIZE", size_attr); iss.str(size_attr); iss >> size_mb; // ------------ PATH & SOURCE -------------------- erase_template_attribute("PATH", path); erase_template_attribute("SOURCE", source); erase_template_attribute("FSTYPE", fs_type); erase_template_attribute("TM_MAD", tm_mad); if (!is_saving()) { if ( source.empty() && path.empty() && type != DATABLOCK && type != OS) { goto error_no_path; } else if ( !source.empty() && !path.empty() ) { goto error_path_and_source; } if ( path.empty() && (type == Image::DATABLOCK || type == Image::OS)) { if ( fs_type.empty() ) { string driver; fs_type = "raw"; //Default get_template_attribute("DRIVER", driver); one_util::tolower(driver); if (driver == "qcow2" || (driver.empty() && tm_mad == "qcow2")) { fs_type = "qcow2"; } } } else { fs_type = ""; } } else { fs_type = "save_as"; } state = LOCKED; //LOCKED till the ImageManager copies it to the Repository lock_db(-1,-1, PoolObjectSQL::LockStates::ST_USE); //-------------------------------------------------------------------------- // Insert the Image //-------------------------------------------------------------------------- rc = insert_replace(db, false, error_str); return rc; error_no_path: error_str = "No PATH nor SOURCE in template."; goto error_common; error_path_and_source: error_str = "Template malformed, PATH and SOURCE are mutually exclusive."; goto error_common; error_common: NebulaLog::log("IMG", Log::ERROR, error_str); return -1; } /* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */ int Image::update(SqlDB *db) { string error_str; return insert_replace(db, true, error_str); } /* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */ int Image::insert_replace(SqlDB *db, bool replace, string& error_str) { ostringstream oss; int rc; string xml_body; char * sql_name; char * sql_xml; // Update the Image sql_name = db->escape_str(name.c_str()); if ( sql_name == 0 ) { goto error_name; } 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"; } // Construct the SQL statement to Insert or Replace oss <<" INTO "<< table <<" ("<< db_names <<") VALUES (" << oid << "," << "'" << sql_name << "'," << "'" << sql_xml << "'," << uid << "," << gid << "," << owner_u << "," << group_u << "," << other_u << ")"; rc = db->exec_wr(oss); db->free_str(sql_name); db->free_str(sql_xml); return rc; error_xml: db->free_str(sql_name); db->free_str(sql_xml); error_str = "Error transforming the Image to XML."; goto error_common; error_body: db->free_str(sql_name); goto error_generic; error_name: goto error_generic; error_generic: error_str = "Error inserting Image in DB."; error_common: return -1; } /* ************************************************************************ */ /* Image :: Misc */ /* ************************************************************************ */ string& Image::to_xml(string& xml) const { string template_xml; string perms_xml; ostringstream oss; string vm_collection_xml; string clone_collection_xml; string app_clone_collection_xml; string snapshots_xml; string lock_str; oss << "" << "" << oid << "" << "" << uid << "" << "" << gid << "" << "" << uname << "" << "" << gname << "" << "" << name << "" << lock_db_to_xml(lock_str) << perms_to_xml(perms_xml) << "" << type << "" << "" << disk_type << "" << "" << persistent_img << "" << "" << regtime << "" << "" << one_util::escape_xml(source) << "" << "" << one_util::escape_xml(path) << "" << "" << one_util::escape_xml(fs_type)<< "" << "" << size_mb << "" << "" << state << "" << "" << running_vms << "" << "" << cloning_ops << "" << "" << cloning_id << "" << ""<< target_snapshot << ""<< "" << ds_id << ""<< "" << ds_name << "" << vm_collection.to_xml(vm_collection_xml) << img_clone_collection.to_xml(clone_collection_xml) << app_clone_collection.to_xml(app_clone_collection_xml) << obj_template->to_xml(template_xml) << snapshots.to_xml(snapshots_xml) << ""; xml = oss.str(); return xml; } /* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */ int Image::from_xml(const string& xml) { vector content; int int_state; int int_type; int int_disk_type; int rc = 0; // Initialize the internal XML object update_from_str(xml); // Get class base attributes rc += xpath(oid, "/IMAGE/ID", -1); rc += xpath(uid, "/IMAGE/UID", -1); rc += xpath(gid, "/IMAGE/GID", -1); rc += xpath(uname, "/IMAGE/UNAME", "not_found"); rc += xpath(gname, "/IMAGE/GNAME", "not_found"); rc += xpath(name, "/IMAGE/NAME", "not_found"); rc += xpath(int_type, "/IMAGE/TYPE", 0); rc += xpath(int_disk_type, "/IMAGE/DISK_TYPE", 0); rc += xpath(persistent_img, "/IMAGE/PERSISTENT",0); rc += xpath(regtime,"/IMAGE/REGTIME", 0); rc += xpath(size_mb, "/IMAGE/SIZE", 0); rc += xpath(source, "/IMAGE/SOURCE", "not_found"); rc += xpath(int_state, "/IMAGE/STATE", 0); rc += xpath(running_vms,"/IMAGE/RUNNING_VMS",-1); rc += xpath(cloning_ops,"/IMAGE/CLONING_OPS",-1); rc += xpath(cloning_id, "/IMAGE/CLONING_ID", -1); rc += xpath(target_snapshot, "/IMAGE/TARGET_SNAPSHOT", -1); rc += xpath(ds_id, "/IMAGE/DATASTORE_ID", -1); rc += xpath(ds_name, "/IMAGE/DATASTORE", "not_found"); rc += lock_db_from_xml(); // Permissions rc += perms_from_xml(); //Optional image attributes xpath(path,"/IMAGE/PATH", ""); xpath(fs_type,"/IMAGE/FSTYPE",""); type = static_cast(int_type); disk_type = static_cast(int_disk_type); state = static_cast(int_state); // Get associated classes ObjectXML::get_nodes("/IMAGE/TEMPLATE", content); if (content.empty()) { return -1; } rc += obj_template->from_xml_node(content[0]); ObjectXML::free_nodes(content); content.clear(); rc += vm_collection.from_xml(this, "/IMAGE/"); rc += img_clone_collection.from_xml(this, "/IMAGE/"); rc += app_clone_collection.from_xml(this, "/IMAGE/"); ObjectXML::get_nodes("/IMAGE/SNAPSHOTS", content); if (!content.empty()) { rc += snapshots.from_xml_node(content[0]); ObjectXML::free_nodes(content); content.clear(); } if (rc != 0) { return -1; } return 0; } /* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */ void Image::disk_attribute(VirtualMachineDisk * disk, ImageType& img_type, string& dev_prefix, const vector& inherit_attrs) { string target; string driver; string disk_attr_type; string inherit_val; bool ro; vector::const_iterator it; img_type = type; target = disk->vector_value("TARGET"); driver = disk->vector_value("DRIVER"); dev_prefix = disk->vector_value("DEV_PREFIX"); long long snap_size; string template_target; string template_driver; string template_ptype; string template_size; get_template_attribute("TARGET", template_target); get_template_attribute("DRIVER", template_driver); get_template_attribute("PERSISTENT_TYPE", template_ptype); TO_UPPER(template_ptype); //--------------------------------------------------------------------------- // DEV_PREFIX ATTRIBUTE //--------------------------------------------------------------------------- if ( dev_prefix.empty() ) //DEV_PEFIX not in DISK, check for it in IMAGE { get_template_attribute("DEV_PREFIX", dev_prefix); if (dev_prefix.empty())//Removed from image template, get it again { if ( type == CDROM ) { dev_prefix = ImagePool::default_cdrom_dev_prefix(); } else { dev_prefix = ImagePool::default_dev_prefix(); } } disk->replace("DEV_PREFIX", dev_prefix); } //-------------------------------------------------------------------------- // BASE DISK ATTRIBUTES //-------------------------------------------------------------------------- disk->replace("IMAGE", name); disk->replace("IMAGE_ID", oid); disk->replace("SOURCE", source); disk->replace("ORIGINAL_SIZE", size_mb); if ( disk->vector_value("SIZE").empty() ) { disk->replace("SIZE", size_mb); } snap_size = snapshots.get_total_size(); disk->replace("DISK_SNAPSHOT_TOTAL_SIZE", snap_size); if (driver.empty() && !template_driver.empty())//DRIVER in Image,not in DISK { disk->replace("DRIVER",template_driver); } disk->replace("IMAGE_STATE", state); //-------------------------------------------------------------------------- // READONLY, CLONE & SAVE attributes //-------------------------------------------------------------------------- if ( type == CDROM || template_ptype == "IMMUTABLE" ) { disk->replace("SAVE", "NO"); disk->replace("CLONE", "NO"); disk->replace("READONLY", "YES"); } else { if ( disk->vector_value("READONLY", ro) != 0 ) { if ( get_template_attribute("READONLY", ro) ) { disk->replace("READONLY", ro); } else { disk->replace("READONLY", false); } } if ( persistent_img ) { disk->replace("PERSISTENT", "YES"); disk->replace("CLONE", "NO"); disk->replace("SAVE", "YES"); } else { disk->replace("CLONE", "YES"); disk->replace("SAVE", "NO"); } } //Image is being copied/cloned if ( state == Image::LOCKED_USED || state == Image::LOCKED_USED_PERS || state == Image::LOCKED ) { disk->replace("CLONING", "YES"); } else { disk->remove("CLONING"); } //-------------------------------------------------------------------------- // TYPE attribute //-------------------------------------------------------------------------- switch(type) { case OS: case DATABLOCK: disk_attr_type = disk_type_to_str(disk_type); break; case CDROM: //Always use CDROM type for these ones DiskType new_disk_type; switch(disk_type) { case RBD: new_disk_type = RBD_CDROM; break; case SHEEPDOG: new_disk_type = SHEEPDOG_CDROM; break; case GLUSTER: new_disk_type = GLUSTER_CDROM; break; default: new_disk_type = CD_ROM; } disk_attr_type = disk_type_to_str(new_disk_type); break; default: //Other file types should not be never a DISK break; } disk->replace("TYPE", disk_attr_type); //-------------------------------------------------------------------------- // TARGET attribute //-------------------------------------------------------------------------- // TARGET defined in the Image template, but not in the DISK attribute if ( target.empty() && !template_target.empty() ) { disk->replace("TARGET", template_target); } for (it = inherit_attrs.begin(); it != inherit_attrs.end(); it++) { get_template_attribute((*it).c_str(), inherit_val); if (!inherit_val.empty()) { disk->replace(*it, inherit_val); } } } /* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */ int Image::set_type(string& _type, string& error) { int rc = 0; TO_UPPER(_type); if ((_type != "OS" && _type != "DATABLOCK") && (snapshots.size() > 0)) { error = "Image with snapshots can be only of type OS or DATABLOCK"; return -1; } if ( _type == "OS" ) { type = OS; } else if ( _type == "CDROM" ) { type = CDROM; } else if ( _type == "DATABLOCK" ) { type = DATABLOCK; } else if ( _type == "KERNEL" ) { type = KERNEL; } else if ( _type == "RAMDISK" ) { type = RAMDISK; } else if ( _type == "CONTEXT" ) { type = CONTEXT; } else { error = "Unknown type " + type; rc = -1; } return rc; } /* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */ ImageTemplate * Image::clone_template(const string& new_name) const { ImageTemplate * tmpl = new ImageTemplate( *(static_cast(obj_template))); tmpl->replace("NAME", new_name); tmpl->replace("TYPE", type_to_str(type)); tmpl->replace("PATH", source); tmpl->replace("FSTYPE", fs_type); tmpl->replace("SIZE", size_mb); if ( is_persistent() ) { tmpl->replace("PERSISTENT", "YES"); } else { tmpl->replace("PERSISTENT", "NO"); } return tmpl; } /* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */ Image::ImageType Image::str_to_type(string& str_type) { Image::ImageType it = OS; if (str_type.empty()) { str_type = ImagePool::default_type(); } TO_UPPER(str_type); if ( str_type == "OS" ) { it = OS; } else if ( str_type == "CDROM" ) { it = CDROM; } else if ( str_type == "DATABLOCK" ) { it = DATABLOCK; } else if ( str_type == "KERNEL" ) { it = KERNEL; } else if ( str_type == "RAMDISK" ) { it = RAMDISK; } else if ( str_type == "CONTEXT" ) { it = CONTEXT; } return it; } /* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */ Image::DiskType Image::str_to_disk_type(string& s_disk_type) { Image::DiskType type = NONE; one_util::toupper(s_disk_type); if (s_disk_type == "FILE") { type = Image::FILE; } else if (s_disk_type == "BLOCK") { type = Image::BLOCK; } else if (s_disk_type == "ISCSI") { type = Image::ISCSI; } else if (s_disk_type == "CDROM") { type = Image::CD_ROM; } else if (s_disk_type == "RBD") { type = Image::RBD; } else if (s_disk_type == "SHEEPDOG") { type = Image::SHEEPDOG; } else if (s_disk_type == "GLUSTER") { type = Image::GLUSTER; } return type; } /* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */ void Image::set_state(ImageState _state) { if (_state == ERROR && (state == LOCKED_USED || state == LOCKED_USED_PERS)) { LifeCycleManager* lcm = Nebula::instance().get_lcm(); const set& vms = vm_collection.get_collection(); for(set::iterator i = vms.begin(); i != vms.end(); i++) { lcm->trigger(LCMAction::DISK_LOCK_FAILURE, *i); } } else if( _state == LOCKED) { lock_db(-1,-1, PoolObjectSQL::LockStates::ST_USE); } if (_state != LOCKED ) { unlock_db(-1,-1); } state = _state; } /* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */ void Image::set_state_unlock() { LifeCycleManager* lcm = Nebula::instance().get_lcm(); bool vms_notify = false; switch (state) { case LOCKED: unlock_db(-1,-1); set_state(READY); break; case LOCKED_USED: set_state(USED); vms_notify = true; break; case Image::LOCKED_USED_PERS: set_state(USED_PERS); vms_notify = true; break; case Image::ERROR: if (running_vms == 0) { set_state(READY); } else { if(is_persistent()) { set_state(USED_PERS); } else { set_state(USED); } vms_notify = true; } break; default: break; } if ( vms_notify ) { const set& vms = vm_collection.get_collection(); for(set::iterator i = vms.begin(); i != vms.end(); i++) { lcm->trigger(LCMAction::DISK_LOCK_SUCCESS, *i); } } } /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ bool Image::test_set_persistent(Template * image_template, int uid, int gid, bool is_allocate) { Nebula& nd = Nebula::instance(); string per_oned; string conf_name; bool persistent = false; bool tmpl_persis = false; if ( is_allocate ) { conf_name = "DEFAULT_IMAGE_PERSISTENT_NEW"; } else { conf_name = "DEFAULT_IMAGE_PERSISTENT"; } bool has_persistent = image_template->get("PERSISTENT", tmpl_persis); // Get default persistent value from user, group or oned.conf // If no default value found the use PERSISTENT from image template int rc = nd.get_configuration_attribute(uid, gid, conf_name, per_oned); if ( rc == 0 ) { if ( per_oned.empty() ) { persistent = tmpl_persis; } else if (one_util::toupper(per_oned) == "YES") { persistent = true; } } else { persistent = tmpl_persis; } // Honor template persistent value for ne.image.allocate if (is_allocate && has_persistent) { persistent = tmpl_persis; } image_template->replace("PERSISTENT", persistent); return persistent; }