Files
one/src/vm/VirtualMachineParser.cc
T

1018 lines
25 KiB
C++

/* -------------------------------------------------------------------------- */
/* Copyright 2002-2022, 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 "VirtualMachine.h"
#include "VirtualNetworkPool.h"
#include "DatastorePool.h"
#include "ImagePool.h"
#include "NebulaLog.h"
#include "NebulaUtil.h"
#include "Snapshots.h"
#include "HostShare.h"
#include "Nebula.h"
#include "vm_file_var_syntax.h"
#include "vm_var_syntax.h"
#include "vm_var_parser.h"
#include <sstream>
using namespace std;
/* -------------------------------------------------------------------------- */
/* Parser constanta */
/* -------------------------------------------------------------------------- */
const char * VirtualMachine::NO_NIC_DEFAULTS[] = {"NETWORK_ID", "NETWORK",
"NETWORK_UID", "NETWORK_UNAME"};
const int VirtualMachine::NUM_NO_NIC_DEFAULTS = 4;
const char* VirtualMachine::VROUTER_ATTRIBUTES[] = {
"VROUTER_ID",
"VROUTER_KEEPALIVED_ID",
"VROUTER_KEEPALIVED_PASSWORD"};
const int VirtualMachine::NUM_VROUTER_ATTRIBUTES = 3;
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/**
* Generates image attributes (DS_ID, TM_MAD, SOURCE...) for KERNEL and
* INITRD files.
* @param os attribute of the VM template
* @param base_name of the attribute "KERNEL", or "INITRD"
* @param base_type of the image attribute KERNEL, RAMDISK
* @param error_str Returns the error reason, if any
* @return 0 on succes
*/
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();
int img_id;
Image::ImageType type;
Image::ImageState state;
DatastorePool * ds_pool = nd.get_dspool();
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";
string type_str;
attr = os->vector_value(base_name_ds);
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_id = img_ids.back();
auto img = ipool->get_ro(img_id);
if ( img == nullptr )
{
error_str = "Image no longer exists in attribute: " + attr;
return -1;
}
state = img->get_state();
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.reset();
type_str = Image::type_to_str(type);
if ( type != base_type )
{
ostringstream oss;
oss << base_name << " needs an image of type "
<< Image::type_to_str(base_type) << " and not "
<< type_str;
error_str = oss.str();
return -1;
}
if ( state != Image::READY )
{
ostringstream oss;
oss << type_str << " Image '" << img_id << " 'not in READY state.";
error_str = oss.str();
return -1;
}
auto ds = ds_pool->get_ro(ds_id);
if ( ds == nullptr )
{
error_str = "Associated datastore for image does not exist";
return -1;
}
os->replace(base_name_tm, ds->get_tm_mad());
const set<int>& cluster_ids = ds->get_cluster_ids();
if (!cluster_ids.empty())
{
os->replace(base_name_cluster, one_util::join(cluster_ids, ','));
}
return 0;
}
/* -------------------------------------------------------------------------- */
int VirtualMachine::parse_os(string& error_str)
{
int num;
int rc;
vector<Attribute *> os_attr;
VectorAttribute * os;
num = user_obj_template->remove("OS", os_attr);
for (auto attr : os_attr)
{
obj_template->set(attr);
}
if ( num == 0 )
{
os = new VectorAttribute("OS");
obj_template->set(os);
}
else if ( num > 1 )
{
error_str = "Only one OS attribute can be defined.";
return -1;
}
else
{
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);
string uuid = os->vector_value("UUID");
if (uuid.empty())
{
uuid = one_util::uuid();
os->replace("UUID", uuid);
}
if ( rc != 0 )
{
return -1;
}
return 0;
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
int VirtualMachine::parse_defaults(string& error_str, Template * tmpl)
{
int num;
vector<Attribute *> attr;
VectorAttribute* vatt = 0;
num = tmpl->remove("NIC_DEFAULT", attr);
if ( num == 0 )
{
return 0;
}
if ( num > 1 )
{
error_str = "Only one NIC_DEFAULT attribute can be defined.";
goto error_cleanup;
}
vatt = dynamic_cast<VectorAttribute *>(attr[0]);
if ( vatt == 0 )
{
error_str = "Wrong format for NIC_DEFAULT attribute.";
goto error_cleanup;
}
for (int i=0; i < NUM_NO_NIC_DEFAULTS; i++)
{
if(vatt->vector_value(NO_NIC_DEFAULTS[i]) != "")
{
ostringstream oss;
oss << "Attribute " << NO_NIC_DEFAULTS[i]
<< " is not allowed inside NIC_DEFAULT.";
error_str = oss.str();
return -1;
}
}
obj_template->set(vatt);
return 0;
error_cleanup:
for (int i = 0; i < num ; i++)
{
delete attr[i];
}
return -1;
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
int VirtualMachine::parse_vrouter(string& error_str, Template * tmpl)
{
string st;
for (int i = 0; i < NUM_VROUTER_ATTRIBUTES; i++)
{
tmpl->get(VROUTER_ATTRIBUTES[i], st);
if (!st.empty())
{
obj_template->replace(VROUTER_ATTRIBUTES[i], st);
}
tmpl->erase(VROUTER_ATTRIBUTES[i]);
}
return 0;
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
static int check_pci_attributes(VectorAttribute * pci, const string& default_bus,
string& error_str)
{
static string attrs[] = {"VENDOR", "DEVICE", "CLASS"};
static int num_attrs = 3;
string bus;
bool found = false;
for (int i = 0; i < num_attrs; i++)
{
unsigned int val;
int rc = HostSharePCI::get_pci_value(attrs[i].c_str(), pci, val);
if (rc == -1)
{
error_str = "Wrong Hex value for PCI attribute " + attrs[i];
return -1;
}
else if ( rc != 0 )
{
found = true;
}
}
if (!found)
{
error_str = "DEVICE, VENDOR or CLASS must be defined for PCI.";
return -1;
}
if ( HostSharePCI::set_pci_address(pci, default_bus) != 0 )
{
error_str = "Wrong BUS in PCI attribute";
return -1;
}
return 0;
}
int VirtualMachine::parse_pci(string& error_str, Template * tmpl)
{
vector<VectorAttribute *> array_pci;
int pci_id = 0;
tmpl->remove("PCI", array_pci);
for (auto it = array_pci.begin(); it !=array_pci.end(); ++it, ++pci_id)
{
(*it)->replace("PCI_ID", pci_id);
obj_template->set(*it);
}
Nebula& nd = Nebula::instance();
string default_bus;
nd.get_configuration_attribute("PCI_PASSTHROUGH_BUS", default_bus);
for (auto attr : array_pci)
{
if ( check_pci_attributes(attr, default_bus, error_str) != 0 )
{
return -1;
}
}
return 0;
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
int VirtualMachine::parse_graphics(string& error_str, Template * tmpl)
{
VectorAttribute * user_graphics = tmpl->get("GRAPHICS");
if ( user_graphics == 0 )
{
return 0;
}
VectorAttribute * graphics = new VectorAttribute(user_graphics);
tmpl->erase("GRAPHICS");
obj_template->erase("GRAPHICS");
obj_template->set(graphics);
if ( !graphics->vector_value("PORT").empty() )
{
unsigned int port;
int rc = graphics->vector_value("PORT", port);
if (rc == -1 || port > 65535 )
{
error_str = "Wrong PORT number in GRAPHICS attribute";
return -1;
}
}
bool random_passwd;
graphics->vector_value("RANDOM_PASSWD", random_passwd);
string password = graphics->vector_value("PASSWD");
if ( random_passwd && password.empty() )
{
password = one_util::random_password();
if ( graphics->vector_value("TYPE") == "SPICE" )
{
// Spice password must be 60 characters maximum
graphics->replace("PASSWD", password.substr(0, 59));
}
else
{
graphics->replace("PASSWD", password);
}
}
return 0;
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
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++)
{
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
* CLONING_TEMPLATE_ID
* TOPOLOGY
* NUMA_NODE
*/
std::vector<std::string> names = {"INPUT", "FEATURES", "RAW",
"CLONING_TEMPLATE_ID", "TOPOLOGY", "NUMA_NODE", "HYPERV_OPTIONS",
"SPICE_OPTIONS"};
for (auto it = names.begin(); it != names.end() ; ++it)
{
vector<Attribute *> v_attr;
user_obj_template->remove(*it, v_attr);
for (auto jt=v_attr.begin(); jt != v_attr.end(); jt++)
{
obj_template->set(*jt);
}
}
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* VirtualMachine Lex & YACC parser functions */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
int VirtualMachine::parse_template_attribute(const string& attribute,
string& parsed,
string& error_str)
{
const char * str;
int rc;
ostringstream oss_parsed;
char * error_msg = 0;
YY_BUFFER_STATE str_buffer = 0;
yyscan_t scanner = 0;
vm_var_lex_init(&scanner);
str = attribute.c_str();
str_buffer = vm_var__scan_string(str, scanner);
if (str_buffer == 0)
{
log("VM",Log::ERROR,"Error setting scan buffer");
return -1;
}
rc = vm_var_parse(this, &oss_parsed, &error_msg, scanner);
vm_var__delete_buffer(str_buffer, scanner);
vm_var_lex_destroy(scanner);
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;
}
/* -------------------------------------------------------------------------- */
int VirtualMachine::parse_file_attribute(string attribute,
vector<int>& img_ids,
string& error)
{
const char * str;
int rc;
ostringstream oss_parsed;
char * error_msg = 0;
size_t non_blank_pos;
YY_BUFFER_STATE str_buffer = 0;
yyscan_t scanner = 0;
//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);
}
vm_var_lex_init(&scanner);
str = attribute.c_str();
str_buffer = vm_var__scan_string(str, scanner);
if (str_buffer == 0)
{
log("VM",Log::ERROR,"Error setting scan buffer");
return -1;
}
rc = vm_file_var_parse(this, &img_ids, &error_msg, scanner);
vm_var__delete_buffer(str_buffer, scanner);
vm_var_lex_destroy(scanner);
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;
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
int VirtualMachine::parse_public_clouds(const char * pname, string& error)
{
vector<VectorAttribute *> attrs;
string p_vatt;
int rc = 0;
int num = user_obj_template->remove(pname, attrs);
for (auto attr : attrs)
{
string str = attr->marshall();
if ( str.empty() )
{
ostringstream oss;
oss << "Internal error processing " << pname;
error = oss.str();
rc = -1;
break;
}
rc = parse_template_attribute(str, p_vatt, error);
if ( rc != 0 )
{
rc = -1;
break;
}
VectorAttribute * nvatt = new VectorAttribute(pname);
nvatt->unmarshall(p_vatt);
user_obj_template->set(nvatt);
}
for (int i = 0; i < num ; i++)
{
delete attrs[i];
}
return rc;
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
int VirtualMachine::parse_cpu_model(Template * tmpl)
{
vector<VectorAttribute *> cm_attr;
int num = tmpl->remove("CPU_MODEL", cm_attr);
if ( num == 0 )
{
return 0;
}
auto it = cm_attr.begin();
obj_template->set(*it);
for ( ++it; it != cm_attr.end(); ++it)
{
delete *it;
}
return 0;
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
int VirtualMachine::parse_topology(Template * tmpl, std::string &error)
{
/**
* TOPOLOGY
* - NUMA_NODES: number of numa nodes
* - PIN_POLICY: CORE, THREAD, SHARED, NONE
* - THREADS
* - CORES
* - SOCKETS
*/
std::vector<VectorAttribute *> numa_nodes;
std::vector<VectorAttribute *> vtopol_a;
VectorAttribute * vtopol = 0;
tmpl->remove("TOPOLOGY", vtopol_a);
if ( !vtopol_a.empty() )
{
auto it = vtopol_a.begin();
vtopol = *it;
for ( ++it; it != vtopol_a.end(); ++it)
{
delete *it;
}
}
tmpl->get("NUMA_NODE", numa_nodes);
if ( vtopol == 0 && numa_nodes.empty() )
{
return 0;
}
if ( vtopol == 0 )
{
vtopol = new VectorAttribute("TOPOLOGY");
}
tmpl->set(vtopol);
std::string pp_s = vtopol->vector_value("PIN_POLICY");
HostShare::PinPolicy pp = HostShare::str_to_pin_policy(pp_s);
/* ---------------------------------------------------------------------- */
/* Set MEMORY, HUGEPAGE_SIZE, vCPU & update CPU for pinned VMS */
/* ---------------------------------------------------------------------- */
long long memory;
unsigned int vcpu = 0;
unsigned int vcpu_max = 0;
if (!tmpl->get("MEMORY", memory))
{
error = "VM has not MEMORY set";
return -1;
}
if (!tmpl->get("VCPU", vcpu))
{
vcpu = 1;
tmpl->replace("VCPU", 1);
}
if ( pp != HostShare::PP_NONE )
{
tmpl->replace("CPU", vcpu);
}
if (tmpl->get("VCPU_MAX", vcpu_max))
{
error = "VM with TOPOLOGY does not support CPU hotplug (VCPU_MAX)";
return -1;
}
/* ---------------------------------------------------------------------- */
/* Check topology for non pinned & pinned VMs */
/* - non-pinned VM needs to set SOCKETS, CORES and THREADS */
/* - pinned VM */
/* 1. Set sockets to number of NUMA_NODE or 1 if not given */
/* 2. core and thread given. Check consistency */
/* 3. core given. Compute threads & check power of 2 */
/* 4. other combinations are set by the scheduler */
/* ---------------------------------------------------------------------- */
unsigned int s, c, t;
s = c = t = 0;
vtopol->vector_value("SOCKETS", s);
vtopol->vector_value("CORES", c);
vtopol->vector_value("THREADS", t);
if ( pp == HostShare::PP_NONE )
{
if ( c == 0 || t == 0 || s == 0 )
{
error = "Non-pinned VMs with a virtual topology needs to set "
" SOCKETS, CORES and THREADS numbers.";
return -1;
}
else if ((s * c * t) != vcpu)
{
error = "Total threads per core and socket needs to match VCPU";
return -1;
}
vtopol->replace("PIN_POLICY", "NONE");
tmpl->erase("NUMA_NODE");
return 0;
}
if ( s == 0 )
{
if ( numa_nodes.empty() )
{
s = 1;
}
else
{
s = numa_nodes.size();
}
vtopol->replace("SOCKETS", s);
}
if ( c != 0 && t != 0 && (s * c * t) != vcpu)
{
error = "Total threads per core and socket needs to match VCPU";
return -1;
}
if ( t == 0 && c != 0 )
{
if ( vcpu%(c * s) != 0 )
{
error = "VCPU is not multiple of the total number of cores";
return -1;
}
t = vcpu/(c * s);
if ((t & (t - 1)) != 0 )
{
error = "Computed number of threads is not power of 2";
return -1;
}
vtopol->replace("THREADS", t);
}
/* ---------------------------------------------------------------------- */
/* Build NUMA_NODE stanzas for the given topology */
/* ---------------------------------------------------------------------- */
if (numa_nodes.empty()) // Automatic Homogenous Topology
{
if ( vcpu % s != 0 )
{
error = "VCPU is not multiple of the number of NUMA nodes";
return -1;
}
if ( memory % s != 0 )
{
error = "MEMORY is not multiple of the number of NUMA nodes";
return -1;
}
long long mem_node = memory / s;
unsigned int cpu_node = vcpu / s;
for (unsigned int i = 0 ; i < s ; ++i)
{
VectorAttribute * node = new VectorAttribute("NUMA_NODE");
node->replace("TOTAL_CPUS", cpu_node);
node->replace("MEMORY", mem_node * 1024);
tmpl->set(node);
}
}
else // Manual/Asymmetric Topology, NUMA_NODE array
{
long long node_mem = 0;
unsigned int node_cpu = 0;
long long nmem = 0;
unsigned int ncpu = 0;
std::vector<VectorAttribute *> new_nodes;
for (auto it = numa_nodes.begin() ; it != numa_nodes.end() ; ++it)
{
ncpu = nmem = 0;
(*it)->vector_value("TOTAL_CPUS", ncpu);
(*it)->vector_value("MEMORY", nmem);
if ( ncpu <= 0 || nmem <= 0)
{
break;
}
VectorAttribute * node = new VectorAttribute("NUMA_NODE");
node->replace("TOTAL_CPUS", ncpu);
node->replace("MEMORY", nmem * 1024);
new_nodes.push_back(node);
node_cpu += ncpu;
node_mem += nmem;
}
tmpl->erase("NUMA_NODE");
if (node_cpu != vcpu || node_mem != memory ||
ncpu <= 0 || nmem <= 0)
{
for (auto it = new_nodes.begin(); it != new_nodes.end(); ++it)
{
delete *it;
}
}
if (ncpu <= 0)
{
error = "A NUMA_NODE must have TOTAL_CPUS greater than 0";
return -1;
}
if (nmem <= 0)
{
error = " A NUMA_NODE must have MEMORY greater than 0";
return -1;
}
if (node_cpu != vcpu)
{
error = "Total CPUS of NUMA nodes is different from VM VCPU";
return -1;
}
if (node_mem != memory)
{
error = "Total MEMORY of NUMA nodes is different from VM MEMORY";
return -1;
}
tmpl->set(new_nodes);
}
return 0;
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
bool VirtualMachine::is_pinned() const
{
VectorAttribute * topology = obj_template->get("TOPOLOGY");
if ( topology == 0 )
{
return false;
}
std::string pp_s = topology->vector_value("PIN_POLICY");
HostShare::PinPolicy pp = HostShare::str_to_pin_policy(pp_s);
return pp != HostShare::PP_NONE;
}