/* * * Copyright (c) 2009-2017 NetApp, Inc. * All rights reserved. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #else #include #endif // CURL is a C library for fetching URLs, already used by DOT #include #include #include #include #include #ifdef NETAPP #include #include #else // Allow linux development #define EINVALIDINPUTERROR 1 #define EINTERNALERROR 2 #define EHOSTNOTFOUND 3 #define EONTAPI_ECONNREFUSED 4 #define EAPIAUTHENTICATION 5 #define EMS_vsa_vsphere_unreachable(d) do {} while (0) #define EMS_vsa_vsphere_noauth(d) do {} while (0) #define EMS_vsa_vsphere_connected(u, s) do {} while (0) #endif using namespace std; /* Debug aids - see --dump/trace options */ static int dumpRawResponse, dumpRpcStats, dumpXmlResponse; static int traceRpc; /* Namespace URIs */ #define soapenvNsURI "http://schemas.xmlsoap.org/soap/envelope/" #define xsdNsURI "http://www.w3.org/2001/XMLSchema" #define xsiNsURI "http://www.w3.org/2001/XMLSchema-instance" #define vimNsURI "urn:vim25" #ifdef NETAPP #define CONFIG_DIR "/var/etc" #else #define CONFIG_DIR "." #endif #define CONFIG_FILE "vsa_vsphere_config" #define CACHE_FILE "/tmp/vmhostinfo.cache" struct EsxPhysicalAdapterTargetLun { string key; string lun; string canonicalName; string uuid; string blockSize; string blocks; string vendor; string model; }; struct EsxPhysicalAdapterTarget { string key; string target; string iScsiName; string address; list > luns; }; struct EsxPhysicalAdapter { string adapter; string key; string xsiType; string pci; string device; string model; string iScsiName; string driver; list > targets; }; struct EsxBackingStore { string name; string xsiType; string type; string size; string nasHost; string nasPath; vector extents; vector extentParts; }; struct EsxVirtualDisk { string label; string fileName; string datastore; string uuid; string xsiType; string lunUuid; string unitNumber; long capacityInKB; long controllerKey; set ontapDisks; }; /* * The following structure keeps datastore and backing device * details for a single disk in one place. */ struct OntapDiskBackingInfo { string name; string type; string serialNumber; string capacity; string hypervisorAreaName; string hypervisorFileName; string datastoreType; string datastoreCapacity; string datastoreNasPath; string backingAdapterPci; string backingAdapterDevice; string backingAdapterModel; string backingAdapterDriverName; string backingTargetIscsiName; string backingTargetIscsiAddress; string backingTargetLunId; string backingLunCanonicalName; string backingLunPartition; string backingLunCapacity; string backingLunVendorName; string backingLunModel; }; static void dumpResponse(xmlDocPtr doc, string to); static void xmlAddChild(xmlNodePtr node, string key, string value); static xmlNodePtr xmlSectionStart(xmlNodePtr, string key); static ostream &debug = cerr; // Ugh. Put errs on cout since csh can't redirect stderr to stdout // when this gets invoked from D-Blade (adminapi). static ostream &err = cout; class VIMFatalErr : public runtime_error { public: int zapiErrno; VIMFatalErr(string msg, int zapiErrno) : runtime_error(msg) { this->zapiErrno = zapiErrno; } }; class VIMResponse { protected: static map refs; xmlXPathContextPtr xpathCtxt; string method, queryPfx; void cleanup() { if (xpathCtxt != NULL && --refs[xpathCtxt] == 0) { xmlDocPtr doc = xpathCtxt->doc; xmlXPathFreeContext(xpathCtxt); xmlFreeDoc(doc); } } void init(string method, xmlXPathContextPtr xpathCtxt, string queryPfx = "") { this->xpathCtxt = xpathCtxt; if (xpathCtxt != NULL) { if (++refs[xpathCtxt] == 1) { /* Register namespaces */ registerNs("soapenv", soapenvNsURI); registerNs("xsd", xsdNsURI); registerNs("xsi", xsiNsURI); registerNs("vim", vimNsURI); } } this->method = method; this->queryPfx = queryPfx; } void init(string method, xmlDocPtr doc, string queryPfx = "") { /* Create xpath evaluation context */ xmlXPathContextPtr xpathCtxt = xmlXPathNewContext(doc); if (xpathCtxt == NULL) { throw runtime_error("unable to create new XPath context."); } init(method, xpathCtxt, queryPfx); } void checkXPathCtxt(string op) { if (xpathCtxt == NULL) { throw runtime_error("cannot invoke VIMResponse." + op + " with NULL xpathCtxt."); } } string fixupXPath(string inXPath); public: VIMResponse() { xpathCtxt = NULL; } VIMResponse(string method, xmlXPathContextPtr xpathCtxt, string queryPfx = "") { init(method, xpathCtxt, queryPfx); } VIMResponse(const VIMResponse &other) { init(other.method, other.xpathCtxt, other.queryPfx); } VIMResponse(const VIMResponse &other, string queryPfx) { init(other.method, other.xpathCtxt, queryPfx); } VIMResponse(string method, xmlDocPtr doc, string queryPfx = "") { init(method, doc, queryPfx); } VIMResponse(string method, string response, string queryPfx = "") { /* Parse method response */ xmlDocPtr doc = xmlParseMemory(response.c_str(), (int) response.length()); if (doc == NULL) { throw VIMFatalErr("error parsing XML response to " + method, EONTAPI_ECONNREFUSED); } if (dumpXmlResponse) { dumpResponse(doc, method); } try { init(method, doc, queryPfx); } catch (...) { xmlFreeDoc(doc); throw; } } ~VIMResponse() { cleanup(); } VIMResponse &operator=(const VIMResponse &other) { cleanup(); init(other.method, other.xpathCtxt, other.queryPfx); return *this; } void registerNs(const char *ns, const char *nsURI) { checkXPathCtxt("registerNs"); if (xmlXPathRegisterNs(xpathCtxt, (const xmlChar *)ns, (const xmlChar *)nsURI)) { throw runtime_error("unable to register XML namespace " + string(ns) + " with XPath context."); } } vector queryV(string xpath); string query(string xpath, int required = 0); void queryInquiryV(string inXPath, list &serialNums, list &deviceIds); }; map VIMResponse::refs; static size_t handleCurlData(void *p, size_t size, size_t nmemb, void *stream); static size_t handleCurlHdr(void *p, size_t size, size_t nmemb, void *stream); static void check(CURLcode code); class VIMContext { protected: string server, url; CURL *curl; struct curl_slist *headers; CURLcode curlStat; bool outOfMemory; /* stats */ unsigned int numRpcs; unsigned long sentBytes, recdBytes, maxResponseLen; /* initialized by login() */ string sessMgr, searchIndex, propertyCollector, loginKey; void cleanup() { if (headers) { curl_slist_free_all(headers); } curl_easy_cleanup(curl); } public: VIMContext(string server) { curl = curl_easy_init(); if (curl == NULL) { throw runtime_error("curl_easy_init failed."); } this->server = server; url = "https://" + server + "/sdk"; try { /* Setup CURL session options */ headers = curl_slist_append(NULL, "Content-Type: text/xml"); if (headers == NULL) { throw bad_alloc(); } check(curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2)); check(curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0)); check(curl_easy_setopt(curl, CURLOPT_POST, 1)); check(curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers)); check(curl_easy_setopt(curl, CURLOPT_URL, url.c_str())); check(curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, handleCurlHdr)); check(curl_easy_setopt(curl, CURLOPT_HEADERDATA, this)); check(curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handleCurlData)); } catch (...) { cleanup(); throw; } curlStat = CURLE_OK; outOfMemory = false; numRpcs = 0; sentBytes = recdBytes = maxResponseLen = 0; } VIMContext(const VIMContext &other) { throw runtime_error("VIMContext copying not currently allowed."); } VIMContext &operator=(const VIMContext &other) { throw runtime_error("VIMContext assignment not currently allowed."); } ~VIMContext() { try { if (loginKey != "") { logout(); } } catch (...) { // ignore any problems during logout } curl_slist_free_all(headers); curl_easy_cleanup(curl); } VIMResponse call(string objType, string obj, string method, string args); void login(string username, string password) { VIMResponse resp; try { try { resp = call("ServiceInstance", "ServiceInstance", "RetrieveServiceContent", ""); } catch (const VIMFatalErr& exc) { // Interpret protocol error here as "not vSphere server" if (exc.zapiErrno == EONTAPI_ECONNREFUSED) { throw VIMFatalErr(this->server + " not a vSphere server.", EONTAPI_ECONNREFUSED); } else { throw; } } } catch (const exception& exc) { EMS_vsa_vsphere_unreachable(exc.what()); throw; } sessMgr = resp.query("sessionManager", 1); propertyCollector = resp.query("propertyCollector", 1); searchIndex = resp.query("searchIndex", 1); string args = "" + username + "" "" + password + ""; loginKey = call("SessionManager", sessMgr, "Login", args).query("key"); if (loginKey == "") { string msg = "vSphere login to " + server + " for user " + username + " failed."; EMS_vsa_vsphere_noauth(msg.c_str()); throw VIMFatalErr(msg, EAPIAUTHENTICATION); } EMS_vsa_vsphere_connected(username.c_str(), server.c_str()); } void logout() { if (loginKey == "") { throw runtime_error("cannot logout() before successful login."); } call("SessionManager", sessMgr, "Logout", ""); } bool addHeader(string hdr) { struct curl_slist *newHdrs = curl_slist_append(headers, hdr.c_str()); outOfMemory = newHdrs == NULL; if (outOfMemory) { return false; } headers = newHdrs; curlStat = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); return curlStat == CURLE_OK; } string findByUuid(string uuid, bool vmSearch = false) { string args = "" + uuid + "" "" + (vmSearch ? "1" : "0") + ""; return call("SearchIndex", searchIndex, "FindByUuid", args).query(""); } VIMResponse getProperty(string objType, string obj, string property) { if (propertyCollector == "") { throw runtime_error("cannot getProperty() before successful login."); } string args = "" "" "" + objType + "" "" + property + "" "" "" "" + obj + "" "" ""; return VIMResponse(call("PropertyCollector", propertyCollector, "RetrieveProperties", args), "propSet/val/"); } VIMResponse getHostProperty(string host, string property) { return getProperty("HostSystem", host, property); } VIMResponse getVMProperty(string vm, string property) { return getProperty("VirtualMachine", vm, property); } string getHostProp(string host, string property, int required = 0) { return getHostProperty(host, property).query("", required); } string getVMProp(string vm, string property, int required = 0) { return getVMProperty(vm, property).query("", required); } void outputRpcStats() { debug << "** TOTAL: " << numRpcs << " rpcs " << sentBytes << " sent " << recdBytes << " recd " << maxResponseLen << " max resp len **" << endl; } void outputHostInfo(const string &host, bool xmlOut, xmlNodePtr zRoot); void getEsxVirtualDiskInfo(const string &vm, const multimap diskMap, // output list > &esxDisks, map > &rdmUuids, set &datastores); int outputEsxVirtualDiskInfo(const list > &esxDisks, map > &storeInfoMap, bool xmlOut, xmlNodePtr zRoot, // output map > &diskBackingInfoMap); void getEsxDatastoreInfo(const set &datastores, // output list > &esxBackingStores, set &physDisks, map > &storeInfoMap); void outputEsxDatastoreInfo(const list > &esxBackingStores); void getEsxPhysDiskInfo(const string &host, const multimap &diskMapSerialNum, const multimap &diskMapDeviceId, const map > &rdmUuids, // output list > &esxPhysDiskInfo); void outputEsxPhysDiskInfo(const set &physDisks, bool listAll, const map > &rdmUuids, const list > &esxPhysDiskInfo, const map > &storeInfoMap, // output map > &diskBackingInfoMap); void createDiskRecord(OntapDiskBackingInfo *diskInfoBlock, map > &storeInfoMap, map > &diskBackingInfoMap); void addDiskBackingInfoToXml(xmlNodePtr zRoot, map > &diskBackingInfoMap); }; /* * Convenience function to check a CURLcode and throw an appropriate * runtime_error if it's not CURLE_OK. */ static void check(CURLcode code) { const char *msg = curl_easy_strerror(code); switch (code) { case CURLE_OK: return; case CURLE_COULDNT_RESOLVE_HOST: throw VIMFatalErr(msg, EHOSTNOTFOUND); case CURLE_OUT_OF_MEMORY: throw bad_alloc(); default: // This covers a lot of protocol errors, as well // as the connection refused case. Perhaps we // should distinguish between the two? throw VIMFatalErr(msg, EONTAPI_ECONNREFUSED); } } /* * Invoke a VMware "VIM" method via the SOAP protocol. */ VIMResponse VIMContext::call(string objType, string obj, string method, string args) { string msg("" "" "<" + method + " xmlns='" vimNsURI "'>" + "<_this type='" + objType + "'>" + obj + "" + args + "" + "" + ""); string response; if (traceRpc) { debug << "RPC: calling (" << objType << ")" << obj << "." << method << "(" << args << ")" << endl; if (traceRpc > 1) { debug << "RPC: " << msg << endl; } } /* Pass a pointer to to handleCurlData(), when it is * invoked by curl_easy_perform(). handleCurlData() accumulates * the raw RPC response into . */ check(curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response)); check(curl_easy_setopt(curl, CURLOPT_POSTFIELDS, msg.c_str())); CURLcode curlStat = curl_easy_perform(curl); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL); /* First check for errs from addHeader via handleCurlHdr */ if (outOfMemory) { outOfMemory = false; throw bad_alloc(); } if (this->curlStat != CURLE_OK) { CURLcode s = this->curlStat; this->curlStat = CURLE_OK; check(s); } /* Now check for general errs signalled directly by curl_easy_perform */ check(curlStat); if (traceRpc > 1) { debug << "RPC: response (" << response << ")" << endl; } /* Update & maybe dump stats */ numRpcs++; sentBytes += msg.length(); recdBytes += response.length(); if (response.length() > maxResponseLen) { maxResponseLen = response.length(); } if (dumpRpcStats > 1) { debug << "** for " << objType << "." << method << " " << msg.length() << " sent " << response.length() << " recd **" << endl; } /* Parse response */ VIMResponse resp(method, response); /* Throw exception if response is fault */ string faultPfx = "/soapenv:Envelope/soapenv:Body/soapenv:Fault/"; string fault = resp.query(faultPfx + "faultcode/text()"); if (fault.length()) { if (traceRpc) { debug << "RPC: fault " << fault << endl; } throw runtime_error(fault + ": " + resp.query(faultPfx + "detail/*")); } /* Otherwise return parsed response */ if (traceRpc) { debug << "RPC: response ok (" << response.length() << " bytes)" << endl; } return resp; } /* * Returns the string representation of a terminal XML . */ static string unparseNode(xmlNodePtr node) { switch (node->type) { case XML_TEXT_NODE: return string((const char *)node->content); case XML_ATTRIBUTE_NODE: return unparseNode(node->children); case XML_ELEMENT_NODE: { ostringstream ostr; xmlAttrPtr attr; xmlNodePtr child; ostr << "<" << node->name; for (attr = node->properties; attr; attr = attr->next) { ostr << " " << attr->name << "='" << unparseNode(attr->children) << "'"; } if (node->children) { ostr << ">"; for (child = node->children; child; child = child->next) { ostr << unparseNode(child); } ostr << "name << ">"; } else { // Use short format for terminal elements, e.g. ostr << "/>"; } return ostr.str(); } default: stringstream typeSS; typeSS << node->type; throw runtime_error("got unexpected XML node type " + typeSS.str()); } } /* Full xpaths to the various VIM response elements get very long and tedious. * This function allow us to specify *much* shorter xpaths by performing some * standard "fixups" to paths that don't start with a slash (/). Xpaths * starting with a slash (/) are returned "as is". * * The fixups performed on all other xpaths are: * (1) is prefixed with boilerplate to get into return value * and with * (2) every non-attribute element is prefixed with "vim:" * (3) finally, "/text()" is appended unless the last xpath element is an * attribute reference */ string VIMResponse::fixupXPath(string xpath) { unsigned int isAttr = 0, i; ostringstream fullXPath; if (xpath.length() > 0 && xpath[0] == '/') { return xpath; } fullXPath << "/soapenv:Envelope/soapenv:Body/vim:" << method << "Response/vim:returnval/"; xpath = queryPfx + xpath; i = 0; while (i < xpath.length()) { isAttr = xpath[i] == '@'; if (! isAttr) { fullXPath << "vim:"; } do { fullXPath << xpath[i++]; } while (i < xpath.length() && xpath[i-1] != '/'); } if (! isAttr) { fullXPath << "/text()"; } return fullXPath.str(); } void VIMResponse::queryInquiryV(string inXPath, list &serialNums, list &deviceIds) { checkXPathCtxt("queryInquiryV"); string xpath = "/soapenv:Envelope/soapenv:Body/" "vim:RetrievePropertiesResponse/vim:returnval/vim:propSet/" "vim:val/vim:" + inXPath; xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression((const xmlChar *)xpath.c_str(), xpathCtxt); if (xpathObj == NULL) { throw runtime_error("unable to evaluate xpath expr " + xpath); } xmlNodeSetPtr nodeset = xpathObj->nodesetval; for (int n = 0; nodeset && n < nodeset->nodeNr; n++) { xmlNodePtr node = nodeset->nodeTab[n]; xmlNodePtr child = node->children; vector data; char *namesp = NULL; while (child) { if (child->type == XML_ELEMENT_NODE) { const char *eltName = (const char *)child->name; if (strcmp(eltName, "namespace") == 0) { namesp = (char *)xmlNodeGetContent(child); if (strcmp(namesp, "GENERIC_VPD") == 0) { // we don't care about generic vpd namesp = NULL; break; } } else if (strcmp(eltName, "data") == 0) { int val = atoi((char *)xmlNodeGetContent(child)); data.push_back((char)val); } } child = child->next; } if (namesp == NULL) { } else if (strcmp(namesp, "SERIALNUM") == 0) { // convert directly to string string serial(data.begin(), data.end()); serialNums.push_back(serial); } else { // convert to hex string (device id) string deviceId; for (size_t i = 0; i < data.size(); ++i) { char byte[3] = {0}; sprintf(byte, "%02x", (unsigned char)data[i]); deviceId += string(byte); } deviceIds.push_back(deviceId); } } xmlFree(xpathObj); } vector VIMResponse::queryV(string inXPath) { checkXPathCtxt("queryV"); string xpath = fixupXPath(inXPath); xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression((const xmlChar *)xpath.c_str(), xpathCtxt); if (xpathObj == NULL) { throw runtime_error("unable to evaluate xpath expr " + xpath); } xmlNodeSetPtr nodeset = xpathObj->nodesetval; vector result; if (nodeset) { try { int i; for (i = 0; i < nodeset->nodeNr; i++) { result.push_back(unparseNode(nodeset->nodeTab[i])); } } catch (...) { xmlFree(xpathObj); throw; } } xmlFree(xpathObj); return result; } string VIMResponse::query(string inXPath, int required) { string xpath = fixupXPath(inXPath); vector results = queryV(xpath); if (results.size() < 1) { if (required) { dumpResponse(xpathCtxt->doc, inXPath); throw runtime_error("no result for xpath query " + xpath); } return string(); } if (results.size() > 1) { throw runtime_error("multiple results for xpath query " + xpath); } return results[0]; } static void dumpResponse(xmlDocPtr doc, string to) { xmlChar *mem; int size; xmlDocDumpFormatMemory(doc, &mem, &size, 2); debug << "XML RESPONSE to '" << to << "': " << string((char *)mem, size) << endl; xmlFree(mem); } static const char *units[] = { "Bytes", "KB", "MB", "GB", "TB", "PB" }; static string disksizeStr(long capacity) { int u = sizeof(units) / sizeof(units[0]) - 1; long unitSize = 1L << (10 * u); ostringstream oss; while (capacity < unitSize && --u >= 0) { unitSize >>= 10; } if (capacity >= unitSize && unitSize > 0) { if (capacity % unitSize == 0) { oss << capacity / unitSize; } else { oss << (double)capacity / unitSize; } oss << " " << units[u]; } return oss.str(); } static string checkString(const char *src, int *serr, string *serrm) { // Assumption: src is null terminated, from argv or fgets *serr = 0; string retstring; if( src == NULL){ *serr = EINVALIDINPUTERROR; *serrm = " unexpected NULL sent to checkString "; } else { retstring.reserve(strlen(src)+1); // Scrub (un-taint) for valid ASCII 32-126(' '-'~') or \n , \r, \t for(size_t i=0; src[i] != '\0'; i++){ if((src[i] >= 32 && src[i] <= 126) || (src[i] == '\n') || (src[i] == '\r') || (src[i] == '\t')) { retstring += src[i]; } else { char str[100]; snprintf(str, sizeof(str), " invalid character [%zu] #%u ", i, src[i]); *serrm = str; *serr = EINVALIDINPUTERROR; break; } } } return retstring; } #define ZSMCLI "/usr/sbin/zsmcli" /* * Returns the mapping of virtual SCSI disk serial numbers to ONTAP * data disk names. */ static int getOntapDiskMap(multimap &diskMapSerialNum, multimap &diskMapDeviceId) { int status = 1; string cmd = ZSMCLI " storage-disk-get-iter"; FILE *cmdOut = popen(cmd.c_str(), "r"); if (cmdOut == NULL) { throw VIMFatalErr("could not execute " ZSMCLI, EINTERNALERROR); } string response; char buf[256]; while (fgets(buf, sizeof(buf), cmdOut) != NULL) { int serr = 0; string serrm; response += checkString(buf, &serr, &serrm); if(serr != 0) { throw VIMFatalErr("invalid output " + serrm + ZSMCLI, EINVALIDINPUTERROR); } } pclose(cmdOut); xmlDocPtr doc; if ( response.length() <= INT_MAX) { doc = xmlParseMemory(response.c_str(), (int) response.length()); } else { throw VIMFatalErr("response too long " ZSMCLI, EINVALIDINPUTERROR); } if (doc == NULL) { string msg = string("invalid XML response to '") + cmd + "':\n\t"; throw VIMFatalErr(msg + response, EINTERNALERROR); } if (dumpXmlResponse) { dumpResponse(doc, cmd); } xmlXPathContextPtr xpathCtxt = xmlXPathNewContext(doc); if (xpathCtxt == NULL) { xmlFreeDoc(doc); throw runtime_error("unable to create new XPath context."); } string xpath = "/results/attributes-list/storage-disk-info"; xmlXPathObjectPtr obj = xmlXPathEvalExpression((const xmlChar *)xpath.c_str(), xpathCtxt); if (obj == NULL) { xmlFreeDoc(doc); xmlXPathFreeContext(xpathCtxt); throw VIMFatalErr("unable to evaluate xpath expr " + xpath, EINTERNALERROR); } xmlNodeSetPtr nodeset = obj->nodesetval; for (int n = 0; nodeset && n < nodeset->nodeNr; n++) { xmlNodePtr node = nodeset->nodeTab[n]; char *name = NULL; char *serialnum = NULL; char *backingSerialnum = NULL; char *backingDeviceId = NULL; /* xpath: "/results/attributes-list/storage-disk-info/.*" */ for (xmlNodePtr child = node->children; child; child = child->next) { if (child->type != XML_ELEMENT_NODE) { continue; } const char *eltName = (const char *)child->name; if (name == NULL && strcmp(eltName, "disk-name") == 0) { name = (char *)xmlNodeGetContent(child); } else if (strcmp(eltName, "disk-inventory-info") == 0) { /* xpath: ".../storage-disk-info/disk-inventory-info/.*" */ for (xmlNodePtr invChild = child->children; invChild; invChild = invChild->next) { if (invChild->type != XML_ELEMENT_NODE) { continue; } const char *invEltName = (const char *)invChild->name; if (serialnum == NULL && strcmp(invEltName, "serial-number") == 0) { serialnum = (char *)xmlNodeGetContent(invChild); } else if (strcmp(invEltName, "vmdisk-backing-info") == 0) { /* xpath: ".../disk-inventory-info/vmdisk-backing-info/.*" */ for (xmlNodePtr backingChild = invChild->children; backingChild; backingChild = backingChild->next) { if (backingChild->type != XML_ELEMENT_NODE) { continue; } const char *sliceEltName = (const char *)backingChild->name; if (backingSerialnum == NULL && strcmp(sliceEltName, "vmdisk-backing-serial-number") == 0) { backingSerialnum = (char *)xmlNodeGetContent(backingChild); } else if (backingDeviceId == NULL && strcmp(sliceEltName, "vmdisk-backing-device-id") == 0) { backingDeviceId = (char *)xmlNodeGetContent(backingChild); } } } } } } if (name) { string finalSerialnum; if (backingSerialnum) { finalSerialnum = string(backingSerialnum); } else if (backingDeviceId) { /* serialnum is invalid, do not assign it. */ } else if (serialnum) { finalSerialnum = string(serialnum); } if (!finalSerialnum.empty()) { status = 0; diskMapSerialNum.insert(pair(finalSerialnum, name)); } if (backingDeviceId) { status = 0; diskMapDeviceId.insert(pair(backingDeviceId, name)); } } if (name) { xmlFree(name); } if (serialnum) { xmlFree(serialnum); } if (backingSerialnum) { xmlFree(backingSerialnum); } if (backingDeviceId) { xmlFree(backingDeviceId); } } xmlFree(obj); xmlFreeDoc(doc); xmlXPathFreeContext(xpathCtxt); return status; } static string sysctl_get(string name) { #ifdef __FreeBSD__ char buf[128]; size_t buflen = sizeof(buf); if (sysctlbyname(name.c_str(), buf, &buflen, NULL, 0) >= 0) { return string(buf); } #endif // Allows testing without smbios change, and linux development too replace(name.begin(), name.end(), '.', '_'); char *val = getenv(name.c_str()); return string(val ? val : ""); } #define SET_COOKIE_HDR "Set-Cookie:" /* * Looks for Set-Cookie: header in server response, then include cookie in * future outgoing headers. * * This function's signature and return semantics are dictated by libcurl, * so it can't (for example) throw exceptions. */ static size_t handleCurlHdr(void *p, size_t size, size_t nmemb, void *stream) { VIMContext *vim = (VIMContext *)stream; const char *cp = (const char *)p; size_t len = size * nmemb; size_t SET_COOKIE_HDR_len = sizeof(SET_COOKIE_HDR) - 1; if (len > SET_COOKIE_HDR_len && strncasecmp(cp, SET_COOKIE_HDR, SET_COOKIE_HDR_len) == 0) { const char *start = cp + 4; const char *end = start; while (*end && *end != ';') { end++; } if (!vim->addHeader(string(start, end - start))) { return 0; } } return len; } /* * Copy server response data to a buffer. This can be called an arbitrary * number of times while a CURL request is being performed. * * This function's signature and return semantics are dictated by libcurl, * so it can't (for example) throw exceptions. */ static size_t handleCurlData(void *p, size_t size, size_t nmemb, void *stream) { string *response = (string *)stream; size_t len = size * nmemb; string newData = string((const char *)p, len); if (dumpRawResponse) { debug << "RAW RESPONSE: " << newData << endl; } *response += newData; return len; } const string indent = " "; static string outPrefix; static unsigned outLevel; static void outSectionStart(string name) { cout << outPrefix; for (unsigned i = 0; i < outLevel; i++) { cout << indent; } cout << name << ":" << endl; ++outLevel; } static void outSectionEnd() { if (outLevel == 0) { throw runtime_error("outSectionEnd w/o matching outSectionStart."); } --outLevel; } static void out(string label, string val) { size_t pre = indent.length() * outLevel; const size_t valCol = 24; if (label != "") { label += ": "; } cout << outPrefix; for (unsigned i = 0; i < outLevel; i++) { cout << indent; } if (pre < valCol) { cout << setw((int) (valCol - pre)) << left; } cout << label << val << endl; cout.unsetf(ios::adjustfield); } static void xmlAddChild(xmlNodePtr node, string key, string value) { xmlNewTextChild(node, NULL, (xmlChar*)key.c_str(), (xmlChar*)value.c_str()); } static xmlNodePtr xmlSectionStart(xmlNodePtr node, string key) { return xmlNewChild(node, NULL, (xmlChar*)key.c_str(), NULL); } static char lowercase(char inp) { return tolower(inp); } /* List all virtual disks and their backing stores */ void VIMContext::getEsxVirtualDiskInfo(const string &vm, const multimap diskMap, // output list > &esxDisks, map > &rdmUuids, set &datastores) { vector disks; try { // vSphere docs say use 'layoutEx' instead of 'layout' for vSphere // >= 4.0, but it seems that layoutEx doesn't exist (at least in ESX // 4.1). We'll go ahead and try to do the right thing (use layoutEx // here), but we expect that will fail and so we'll fallback and query // for 'layout' instead. disks = getVMProperty(vm, "layoutEx.disk").queryV("*/key"); } catch (...) { disks = getVMProperty(vm, "layout.disk").queryV("*/key"); } for (unsigned i = 0; i < disks.size(); i++) { string prop = "config.hardware.device[" + disks[i] + "]"; VIMResponse resp = getVMProperty(vm, prop); std::shared_ptr disk(new EsxVirtualDisk); disk->label = resp.query("deviceInfo/label"); disk->fileName = resp.query("backing/fileName"); disk->datastore = resp.query("backing/datastore"); disk->uuid = resp.query("backing/uuid"); disk->xsiType = resp.query("backing/@xsi:type", 1); disk->lunUuid = resp.query("backing/lunUuid"); disk->unitNumber = resp.query("unitNumber"); disk->capacityInKB = atol(resp.query("capacityInKB").c_str()); disk->controllerKey = atol(resp.query("controllerKey").c_str()); // fix up uuid to match disk serial number string::iterator ustart = disk->uuid.begin(); string::iterator uend = remove(ustart, disk->uuid.end(), '-'); disk->uuid.resize(uend - ustart); transform(ustart, uend, ustart, lowercase); multimap::const_iterator dit; for (dit = diskMap.lower_bound(disk->uuid); dit != diskMap.upper_bound(disk->uuid); ++dit) { disk->ontapDisks.insert(dit->second); } esxDisks.push_back(disk); if (disk->xsiType == "VirtualDiskRawDiskMappingVer1BackingInfo" && disk->lunUuid != "") { rdmUuids.insert(pair >(disk->lunUuid, disk)); } if (disk->datastore != "") { datastores.insert(disk->datastore); } } } /* List all virtual disks and their backing stores */ int VIMContext::outputEsxVirtualDiskInfo(const list > &esxDisks, map > &storeInfoMap, bool xmlOut, xmlNodePtr zRoot, // output map > &diskBackingInfoMap) { /* Variable declaration */ int rc = 0; ostringstream oss; OntapDiskBackingInfo localDiskInfoBlock; outSectionStart("Virtual Disks"); list >::const_iterator it; for (it = esxDisks.begin(); it != esxDisks.end(); ++it) { string unitNum = it->get()->unitNumber; bool ide = (it->get()->controllerKey == 200 || it->get()->controllerKey == 201); if (ide) { unitNum[0] += 2 * (it->get()->controllerKey - 200); } /* * CLI output */ string diskinfo; if (ide) { localDiskInfoBlock.name = "ad" + unitNum; localDiskInfoBlock.serialNumber = it->get()->uuid; diskinfo = "sys disk" + localDiskInfoBlock.name; } else { int ndisks = 0; for (set::const_iterator dit = it->get()->ontapDisks.begin(); dit != it->get()->ontapDisks.end(); ++dit) { ++ndisks; if (ndisks == 1) { diskinfo = *dit; } else { diskinfo += ", " + *dit; } } if (ndisks == 0 || ndisks == 1) { /* Get the disk uuid */ localDiskInfoBlock.serialNumber = it->get()->uuid; if (!localDiskInfoBlock.serialNumber.empty()) { diskinfo = "disk [" + diskinfo + " w/serial num " + localDiskInfoBlock.serialNumber + "]"; } else { diskinfo = "disk [unknown]"; localDiskInfoBlock.serialNumber = "Unknown"; rc = 1; } } else { diskinfo = "disks [" + diskinfo + "]"; } } long capacity = it->get()->capacityInKB; if (capacity > 0) { oss.str(""); capacity *= 1024; oss << capacity; localDiskInfoBlock.capacity = oss.str(); diskinfo += " " + disksizeStr(capacity); } if (it->get()->label != "") { diskinfo += " (" + it->get()->label + ")"; } out(diskinfo + " backed by", ""); out("", it->get()->fileName); if (xmlOut) { /* * xml output */ // Parse "fileName" size_t areaStart = it->get()->fileName.find('[') + 1; size_t areaLen = it->get()->fileName.find(']') - areaStart; size_t nameStart = areaStart + areaLen + 2; localDiskInfoBlock.hypervisorAreaName = it->get()->fileName.substr(areaStart, areaLen); localDiskInfoBlock.hypervisorFileName = it->get()->fileName.substr(nameStart); // output disk info if (ide) { switch(unitNum[0]) { case '0': xmlAddChild(zRoot, "vm-bootdisk-area-name", localDiskInfoBlock.hypervisorAreaName); xmlAddChild(zRoot, "vm-bootdisk-file-name", localDiskInfoBlock.hypervisorFileName); localDiskInfoBlock.type = "boot"; break; case '1': xmlAddChild(zRoot, "vm-coredisk-area-name", localDiskInfoBlock.hypervisorAreaName); xmlAddChild(zRoot, "vm-coredisk-file-name", localDiskInfoBlock.hypervisorFileName); localDiskInfoBlock.type = "core"; break; case '2': xmlAddChild(zRoot, "vm-logdisk-area-name", localDiskInfoBlock.hypervisorAreaName); xmlAddChild(zRoot, "vm-logdisk-file-name", localDiskInfoBlock.hypervisorFileName); localDiskInfoBlock.type = "log"; break; } /* * Create a record for this ONTAP disk in the hashmap. */ createDiskRecord(&localDiskInfoBlock, storeInfoMap, diskBackingInfoMap); } else { for (set::const_iterator dit = it->get()->ontapDisks.begin(); dit != it->get()->ontapDisks.end(); ++dit) { /* * For VHAv2, multiple disks (slices) can reside on a single ESX disk. * So attempt a create a separate record for each slice. */ localDiskInfoBlock.name = *dit; localDiskInfoBlock.type = "Data"; createDiskRecord(&localDiskInfoBlock, storeInfoMap, diskBackingInfoMap); } } } } outSectionEnd(); return rc; } /* * Create a different record for each ONTAP disk if it's not already. * Update the record with the latest values in diskInfoBlock and storeInfoMap. */ void VIMContext::createDiskRecord(OntapDiskBackingInfo *diskInfoBlock, map > &storeInfoMap, map > &diskBackingInfoMap) { ostringstream oss; /* Check if disk name already exists in the hash. */ map >::const_iterator diskInfoIt; diskInfoIt = diskBackingInfoMap.find(diskInfoBlock->name); bool diskRecordFound = (diskInfoIt != diskBackingInfoMap.end()); /* If not, create a new element with this name and initiate an iterator. */ if (!diskRecordFound) { std::shared_ptr diskPtr(new OntapDiskBackingInfo); diskPtr->name = diskInfoBlock->name; diskBackingInfoMap.insert(pair >(diskInfoBlock->name, diskPtr)); diskInfoIt = diskBackingInfoMap.find(diskInfoBlock->name); diskRecordFound = (diskInfoIt != diskBackingInfoMap.end()); } if (diskRecordFound) { /* * Entry found in hash! Now update the struct elements * with the virtual disk and datastore details. */ diskInfoIt->second->name = diskInfoBlock->name; diskInfoIt->second->type = diskInfoBlock->type; diskInfoIt->second->serialNumber = diskInfoBlock->serialNumber; diskInfoIt->second->capacity = diskInfoBlock->capacity; diskInfoIt->second->hypervisorAreaName = diskInfoBlock->hypervisorAreaName; diskInfoIt->second->hypervisorFileName = diskInfoBlock->hypervisorFileName; /* * Check if the datastore name for this virtual disk extists in the hash. * If yes, update the fields accordingly. */ map >::const_iterator storeInfoIt; storeInfoIt = storeInfoMap.find(diskInfoBlock->hypervisorAreaName); if (storeInfoIt != storeInfoMap.end()) { string nasHost = storeInfoIt->second->nasHost; string nasPath = storeInfoIt->second->nasPath; diskInfoBlock->datastoreNasPath = nasHost; if (!nasPath.empty()) { diskInfoBlock->datastoreNasPath = diskInfoBlock->datastoreNasPath + ":" + nasPath; } diskInfoIt->second->datastoreNasPath = diskInfoBlock->datastoreNasPath; long datastore_capacity_in_bytes = atol(storeInfoIt->second->size.c_str()); if (datastore_capacity_in_bytes > 0) { oss.str(""); oss << datastore_capacity_in_bytes; diskInfoBlock->datastoreCapacity = oss.str(); } else { diskInfoBlock->datastoreCapacity.clear(); } diskInfoIt->second->datastoreCapacity = diskInfoBlock->datastoreCapacity; diskInfoBlock->datastoreType = storeInfoIt->second->type; diskInfoIt->second->datastoreType = diskInfoBlock->datastoreType; } } } /* List datastores backing VSA disks */ void VIMContext::getEsxDatastoreInfo(const set &datastores, // output list > &esxBackingStores, set &physDisks, map > &storeInfoMap) { set::iterator ssit = datastores.begin(); while (ssit != datastores.end()) { string datastore = *ssit++; VIMResponse resp = getProperty("Datastore", datastore, "info"); std::shared_ptr storePtr(new EsxBackingStore); storePtr->name = resp.query("name", 1); storePtr->xsiType = resp.query("@xsi:type", 1); storePtr->type = resp.query("*/type"); storePtr->size = resp.query("*/capacity"); storePtr->nasHost = resp.query("nas/remoteHost"); storePtr->nasPath = resp.query("nas/remotePath"); storePtr->extents = resp.queryV("*/extent/diskName"); storePtr->extentParts = resp.queryV("*/extent/partition"); esxBackingStores.push_back(storePtr); for (unsigned i = 0; i < storePtr->extents.size(); i++) { physDisks.insert(storePtr->extents[i]); } /* Add the pointer to the EsxBackingStore structure to hash with store name as a key. */ storeInfoMap.insert(pair >(storePtr->name, storePtr)); } } /* List datastores backing VSA disks */ void VIMContext::outputEsxDatastoreInfo(const list > &esxBackingStores) { outSectionStart("Virtual Disk Backing Stores"); for (list >::const_iterator it = esxBackingStores.begin(); it != esxBackingStores.end(); ++it) { long bytes = atol(it->get()->size.c_str()); string size = bytes > 0 ? (" " + disksizeStr(bytes)) : ""; /* Dump datastore info according to type */ if (it->get()->xsiType == "NasDatastoreInfo") { out(it->get()->name, "(" + it->get()->type + ")" + size + " " + it->get()->nasHost + ":" + it->get()->nasPath); } else if (it->get()->xsiType == "VmfsDatastoreInfo") { out(it->get()->name, "(" + it->get()->type + ")" + size); } else { out(it->get()->name, "(" + it->get()->xsiType + ")" + size); } ++outLevel; for (unsigned i = 0; i < it->get()->extents.size(); i++) { ostringstream oss; oss << "ext " << i; out(oss.str(), it->get()->extents[i] + " partition " + it->get()->extentParts[i]); } --outLevel; } outSectionEnd(); } /* List phys disks & controllers backing datastores backing VSA disks */ void VIMContext::getEsxPhysDiskInfo(const string &host, const multimap &diskMapSerialNum, const multimap &diskMapDeviceId, const map > &rdmUuids, // output list > &esxPhysDiskInfo) { string strgDev = "config.storageDevice"; string scsiTopology = strgDev + ".scsiTopology"; VIMResponse resp = getHostProperty(host, scsiTopology + ".adapter"); vector adapters = resp.queryV("*/adapter"); vector adapterKeys = resp.queryV("*/key"); for (unsigned a = 0; a < adapters.size(); a++) { string prop = strgDev + ".hostBusAdapter[\"" + adapters[a] + "\"]"; resp = getHostProperty(host, prop); std::shared_ptr adapter(new EsxPhysicalAdapter); adapter->adapter = adapters[a]; adapter->key = adapterKeys[a]; adapter->xsiType = resp.query("@xsi:type", 1); adapter->pci = resp.query("pci"); adapter->device = resp.query("device"); adapter->model = resp.query("model"); adapter->iScsiName = resp.query("iScsiName"); adapter->driver = resp.query("driver"); string adapterProp = scsiTopology + ".adapter[\"" + adapterKeys[a] + "\"]"; resp = getHostProperty(host, adapterProp); vector tgtKeys = resp.queryV("target/key"); for (unsigned t = 0; t < tgtKeys.size(); t++) { string tgtProp = adapterProp + ".target[\"" + tgtKeys[t] + "\"]"; resp = getHostProperty(host, tgtProp); std::shared_ptr target(new EsxPhysicalAdapterTarget); target->key = tgtKeys[t]; target->target = resp.query("target"); target->iScsiName = resp.query("transport/iScsiName"); target->address = resp.query("transport/address"); vector lunKeys = resp.queryV("lun/scsiLun"); vector luns = resp.queryV("lun/lun"); for (unsigned l = 0; l < lunKeys.size(); l++) { /* * THE FOLLOWING PROPERTY QUERY IS VERY EXPENSIVE (roughly * 30Kb per SCSI LUN!), and we have to get the info for EVERY * visible host LUN (because this is the only place listing * LUN canonicalName that we can correlate with physDisks). * * Maybe it's better to do it in one big query? The total * amount of data isn't reduced much, but maybe it's more * a matter of server overhead? * * Update: Goes MUCH faster talking to vCenter server. */ prop = strgDev + ".scsiLun[\"" + lunKeys[l] + "\"]"; resp = getHostProperty(host, prop); std::shared_ptr lun(new EsxPhysicalAdapterTargetLun); lun->key = lunKeys[l]; lun->lun = luns[l]; lun->canonicalName = resp.query("canonicalName"); lun->uuid = resp.query("uuid"); lun->blockSize = resp.query("capacity/blockSize"); lun->blocks = resp.query("capacity/block"); lun->vendor = resp.query("vendor"); lun->model = resp.query("model"); /* Look for this uuid in the rdmUuid map */ map >::const_iterator rdmIt; rdmIt = rdmUuids.find(lun->uuid); if (rdmIt != rdmUuids.end()) { /* Get list of serial numbers and device-ids */ list serialNums; list deviceIds; resp.queryInquiryV("durableName", serialNums, deviceIds); resp.queryInquiryV("alternateName", serialNums, deviceIds); /* Iterate over this device's serial numbers (probably 1) */ list::const_iterator sit; for (sit = serialNums.begin(); sit != serialNums.end(); ++sit) { /* Look for serial number in serial number map */ multimap::const_iterator dit; for (dit = diskMapSerialNum.lower_bound(*sit); dit != diskMapSerialNum.upper_bound(*sit); ++dit) { rdmIt->second->ontapDisks.insert(dit->second); } } /* Iterate over the list of this device's device-ids */ for (sit = deviceIds.begin(); sit != deviceIds.end(); ++sit) { /* Look for device-id in device-id map */ multimap::const_iterator dit; for (dit = diskMapDeviceId.lower_bound(*sit); dit != diskMapDeviceId.upper_bound(*sit); ++dit) { rdmIt->second->ontapDisks.insert(dit->second); } } } target->luns.push_back(lun); } adapter->targets.push_back(target); } esxPhysDiskInfo.push_back(adapter); } } /* List phys disks & controllers backing datastores backing VSA disks */ void VIMContext::outputEsxPhysDiskInfo(const set &physDisks, bool listAll, const map > &rdmUuids, const list > &esxPhysDiskInfo, const map > &storeInfoMap, // output map > &diskBackingInfoMap) { outSectionStart(listAll ? "Physical Disks & Controllers" : "Physical Disks & Controllers Used By VM"); list >::const_iterator adIt; for (adIt = esxPhysDiskInfo.begin(); adIt != esxPhysDiskInfo.end(); ++adIt) { /* Variable declaration */ string diskName; string backingAdapterPci; string backingAdapterDevice; string backingAdapterModel; string backingAdapterDriverName; string backingTargetIscsiName; string backingTargetIscsiAddress; string backingTargetLunId; string backingLunCanonicalName; string backingLunPartition; string backingLunCapacity; string backingLunVendorName; string backingLunModel; ostringstream oss; bool iscsi = (adIt->get()->xsiType == "HostInternetScsiHba"); string adapterInfo = ""; if (! iscsi) { backingAdapterPci = adIt->get()->pci; adapterInfo = backingAdapterPci + " "; } backingAdapterDevice = adIt->get()->device; backingAdapterModel = adIt->get()->model; adapterInfo += backingAdapterDevice + " " + backingAdapterModel; if (iscsi) { backingAdapterDriverName = adIt->get()->iScsiName; adapterInfo += " " + backingAdapterDriverName; } else { backingAdapterDriverName = adIt->get()->driver; adapterInfo += " (" + backingAdapterDriverName + ")"; } if (listAll) { outSectionStart(adapterInfo); adapterInfo = ""; } list >::const_iterator tarIt; for (tarIt = adIt->get()->targets.begin(); tarIt != adIt->get()->targets.end(); ++tarIt) { string tgtInfo = ""; string targetId; if (iscsi) { backingTargetIscsiName = tarIt->get()->iScsiName; backingTargetIscsiAddress = tarIt->get()->address; string iscsiAddr = backingTargetIscsiName + ":" + backingTargetIscsiAddress; targetId = tarIt->get()->target; tgtInfo = "target " + backingTargetLunId + " (" + iscsiAddr + ")"; if (listAll) { outSectionStart(tgtInfo); tgtInfo = ""; } } list >::const_iterator lunIt; for (lunIt = tarIt->get()->luns.begin(); lunIt != tarIt->get()->luns.end(); ++lunIt) { map >::const_iterator rdmIt; rdmIt = rdmUuids.find(lunIt->get()->uuid); bool rdm = (rdmIt != rdmUuids.end()); if (listAll || physDisks.find(lunIt->get()->canonicalName) != physDisks.end() || rdm) { long bytes = atol(lunIt->get()->blockSize.c_str()) * atol(lunIt->get()->blocks.c_str()); if (bytes > 0) { oss.str(""); oss << bytes; backingLunCapacity = oss.str(); } else { backingLunCapacity.clear(); } string disksize = bytes ? "(" + disksizeStr(bytes) + ") " : ""; if (adapterInfo != "") { outSectionStart(adapterInfo); adapterInfo = ""; } if (tgtInfo != "") { outSectionStart(tgtInfo); tgtInfo = ""; } string lunId = lunIt->get()->lun; backingTargetLunId = targetId + ":" + lunId; backingLunCanonicalName = lunIt->get()->canonicalName; backingLunVendorName = lunIt->get()->vendor; backingLunModel = lunIt->get()->model; out(backingTargetLunId, backingLunCanonicalName + " " + disksize + backingLunVendorName + " " + backingLunModel); /* For RDMs, print out the ONTAP disk name(s) of this disk*/ if (rdm) { int ndisks = 0; string disklist; set::const_iterator dit; for (dit = rdmIt->second->ontapDisks.begin(); dit != rdmIt->second->ontapDisks.end(); ++dit) { diskName = *dit; /* * For each virtual disk, update the fields related to backing device. * By now, the disk entry must exist in hash. Just ignore if not. */ map >::const_iterator diskInfoIt; diskInfoIt = diskBackingInfoMap.find(diskName); bool diskRecordFound = (diskInfoIt != diskBackingInfoMap.end()); if (diskRecordFound) { /* * Disk record found! Now look for the datastore in storeInfoMap hash. * We need it to locate the exact partition number in the backing disk * when datastore and virtual disk resides. */ map >::const_iterator storeIt; storeIt = storeInfoMap.find(diskInfoIt->second->hypervisorAreaName); bool storeRecordFound = (storeIt != storeInfoMap.end()); if (storeRecordFound) { /* * Store record found! Now retrieve the backing disk partition no. */ for (unsigned i = 0; i < storeIt->second->extents.size(); i++) { if (storeIt->second->extents[i] == backingLunCanonicalName) { backingLunPartition = storeIt->second->extentParts[i]; break; } } } /* * Update the backing device related fields in the diskBackingInfoMap. */ diskInfoIt->second->backingAdapterPci = backingAdapterPci; diskInfoIt->second->backingAdapterDevice = backingAdapterDevice; diskInfoIt->second->backingAdapterModel = backingAdapterModel; diskInfoIt->second->backingAdapterDriverName = backingAdapterDriverName; diskInfoIt->second->backingTargetIscsiName = backingTargetIscsiName; diskInfoIt->second->backingTargetIscsiAddress = backingTargetIscsiAddress; diskInfoIt->second->backingTargetLunId = backingTargetLunId; diskInfoIt->second->backingLunCanonicalName = backingLunCanonicalName; diskInfoIt->second->backingLunPartition = backingLunPartition; diskInfoIt->second->backingLunCapacity = backingLunCapacity; diskInfoIt->second->backingLunVendorName = backingLunVendorName; diskInfoIt->second->backingLunModel = backingLunModel; } ++ndisks; if (ndisks == 1) { disklist = *dit; } else { disklist += ", " + *dit; } } disklist = "[" + disklist + "]"; if (ndisks == 1) { out("", "Data ONTAP Disk: " + disklist); } else { out("", "Data ONTAP Disks" + disklist); } } } } if (iscsi && tgtInfo == "") { outSectionEnd(); } } if (adapterInfo == "") { outSectionEnd(); } } outSectionEnd(); } /* Output host properties */ void VIMContext::outputHostInfo(const string &host, bool xmlOut, xmlNodePtr zRoot) { string processorId = getHostProperty(host, "hardware.cpuFeature"). query("HostCpuIdInfo[2]/eax"); string productLineId = getHostProp(host, "config.product.productLineId"); VIMResponse resp; /* * ESXi does not have a service console and thus the management * network is configured as the first vnic. */ if (productLineId == "embeddedEsx") { resp = getHostProperty(host, "config.network.vnic"); } else { resp = getHostProperty(host, "config.network.consoleVnic"); } string ipAddress = resp.query("HostVirtualNic[1]/spec/ip/ipAddress"); string netMask = resp.query("HostVirtualNic[1]/spec/ip/subnetMask"); out("Hypervisor", getHostProp(host, "config.product.fullName")); out("Host Name", getHostProp(host, "name")); out("Booted", getHostProp(host, "summary.runtime.bootTime")); out("Host UUID", getHostProp(host, "hardware.systemInfo.uuid")); out("BIOS version", getHostProp(host, "hardware.biosInfo.biosVersion")); out("BIOS released", getHostProp(host, "hardware.biosInfo.releaseDate")); out("CPU pkgs", getHostProp(host, "summary.hardware.numCpuPkgs")); out("CPU cores", getHostProp(host, "summary.hardware.numCpuCores")); out("CPU threads", getHostProp(host, "summary.hardware.numCpuThreads")); out("Processor ID", processorId); out("Processor type", getHostProp(host, "summary.hardware.cpuModel")); out("CPU MHz", getHostProp(host, "summary.hardware.cpuMhz")); out("Memory Size", getHostProp(host, "summary.hardware.memorySize")); outSectionStart("IPv4 configuration"); out("IP address", ipAddress); out("Netmask", netMask); out("Gateway", getHostProp(host, "config.network.ipRouteConfig.defaultGateway")); outSectionEnd(); if (xmlOut) { /* Add all hyprvisor related fields to xml. */ xmlAddChild(zRoot, "vmhost-bios-release-date", getHostProp(host, "hardware.biosInfo.releaseDate")); xmlAddChild(zRoot, "vmhost-bios-version", getHostProp(host, "hardware.biosInfo.biosVersion")); xmlAddChild(zRoot, "vmhost-boot-time", getHostProp(host, "summary.runtime.bootTime")); xmlAddChild(zRoot, "vmhost-cpu-clock-rate", getHostProp(host, "summary.hardware.cpuMhz")); xmlAddChild(zRoot, "vmhost-cpu-core-count", getHostProp(host, "summary.hardware.numCpuCores")); xmlAddChild(zRoot, "vmhost-cpu-socket-count", getHostProp(host, "summary.hardware.numCpuPkgs")); xmlAddChild(zRoot, "vmhost-cpu-thread-count", getHostProp(host, "summary.hardware.numCpuThreads")); xmlAddChild(zRoot, "vmhost-hypervisor", getHostProp(host, "config.product.fullName")); xmlAddChild(zRoot, "vmhost-memory", getHostProp(host, "summary.hardware.memorySize")); xmlAddChild(zRoot, "vmhost-name", getHostProp(host, "name")); xmlAddChild(zRoot, "vmhost-processor-id", processorId); xmlAddChild(zRoot, "vmhost-processor-type", getHostProp(host, "summary.hardware.cpuModel")); xmlAddChild(zRoot, "vmhost-uuid", getHostProp(host, "hardware.systemInfo.uuid")); xmlAddChild(zRoot, "vmhost-ip-address", ipAddress); xmlAddChild(zRoot, "vmhost-netmask", netMask); xmlAddChild(zRoot, "vmhost-gateway", getHostProp(host, "config.network.ipRouteConfig.defaultGateway")); } } /* * Create xml node pointer for each virtual disk and dump backing info to it. * New in FS.0 : The hash element contains virtual disk (including system disks) * details and it's backing information (e.g. datastore, backing iSCSI device etc). */ void VIMContext::addDiskBackingInfoToXml(xmlNodePtr zRoot, map > &diskBackingInfoMap) { /* All the disk information goes under tag. */ xmlNodePtr zDisks = xmlSectionStart(zRoot, "virtual-disks"); map >::const_iterator diskIt; /* * Each element in the hash represents the unique ONTAP disk (including system disk). * For each of them, create the tag and add children with the field values. */ for (diskIt = diskBackingInfoMap.begin(); diskIt != diskBackingInfoMap.end(); ++diskIt) { xmlNodePtr disk = xmlSectionStart(zDisks, "disk"); xmlAddChild(disk, "vmdisk-name", diskIt->first); if (!diskIt->second->type.empty()) { xmlAddChild(disk, "vmdisk-type", diskIt->second->type); } if (!diskIt->second->serialNumber.empty()) { xmlAddChild(disk, "vmdisk-serial-number", diskIt->second->serialNumber); } if (!diskIt->second->capacity.empty()) { xmlAddChild(disk, "vmdisk-capacity", diskIt->second->capacity); } if (!diskIt->second->hypervisorAreaName.empty()) { xmlAddChild(disk, "vmdisk-hypervisor-area-name", diskIt->second->hypervisorAreaName); } if (!diskIt->second->hypervisorFileName.empty()) { xmlAddChild(disk, "vmdisk-hypervisor-file-name", diskIt->second->hypervisorFileName); } if (!diskIt->second->datastoreType.empty()) { xmlAddChild(disk, "vmdisk-store-type", diskIt->second->datastoreType); } if (!diskIt->second->datastoreCapacity.empty()) { xmlAddChild(disk, "vmdisk-store-capacity", diskIt->second->datastoreCapacity); } if (!diskIt->second->datastoreNasPath.empty()) { xmlAddChild(disk, "vmdisk-store-nas-path", diskIt->second->datastoreNasPath); } if (!diskIt->second->backingAdapterPci.empty()) { xmlAddChild(disk, "vmdisk-adapter-pci", diskIt->second->backingAdapterPci); } if (!diskIt->second->backingAdapterDevice.empty()) { xmlAddChild(disk, "vmdisk-adapter-device", diskIt->second->backingAdapterDevice); } if (!diskIt->second->backingAdapterModel.empty()) { xmlAddChild(disk, "vmdisk-adapter-model", diskIt->second->backingAdapterModel); } if (!diskIt->second->backingAdapterDriverName.empty()) { xmlAddChild(disk, "vmdisk-adapter-driver", diskIt->second->backingAdapterDriverName); } if (!diskIt->second->backingTargetIscsiName.empty()) { xmlAddChild(disk, "vmdisk-target-iscsi-name", diskIt->second->backingTargetIscsiName); } if (!diskIt->second->backingTargetIscsiAddress.empty()) { xmlAddChild(disk, "vmdisk-target-iscsi-address", diskIt->second->backingTargetIscsiAddress); } if (!diskIt->second->backingTargetLunId.empty()) { xmlAddChild(disk, "vmdisk-target-lun-id", diskIt->second->backingTargetLunId); } if (!diskIt->second->backingLunCanonicalName.empty()) { xmlAddChild(disk, "vmdisk-lun-canonical-name", diskIt->second->backingLunCanonicalName); } if (!diskIt->second->backingLunPartition.empty()) { xmlAddChild(disk, "vmdisk-lun-partition", diskIt->second->backingLunPartition); } if (!diskIt->second->backingLunCapacity.empty()) { xmlAddChild(disk, "vmdisk-lun-capacity", diskIt->second->backingLunCapacity); } if (!diskIt->second->backingLunVendorName.empty()) { xmlAddChild(disk, "vmdisk-lun-vendor-name", diskIt->second->backingLunVendorName); } if (!diskIt->second->backingLunModel.empty()) { xmlAddChild(disk, "vmdisk-lun-model", diskIt->second->backingLunModel); } } } /* DESkey is NOT an arbitrary value. * It must be exactly eight bytes long. * It must exhibit DES parity; should not be changed by des_setparity(DESkey). */ static char DESkey[] = "bgab4ky#"; static string passwordEncrypt(string passwd) { unsigned len = (unsigned) passwd.length(); unsigned paddedLen = ((len + 7) / 8) * 8; char buf[paddedLen]; char printable[paddedLen * 2 + 1]; unsigned i; /* * Identify empty passwords which need not be * encrypted and will be stored as an empty string. */ if (len == 0) { return string(""); } strncpy(buf, passwd.c_str(), len); while (len < paddedLen) { buf[len++] = '\0'; } if (DES_FAILED(ecb_crypt(DESkey, buf, paddedLen, DES_ENCRYPT))) { throw runtime_error("ecb_crypt failed."); } for (i = 0; i < paddedLen; i++) { sprintf(&printable[i * 2], "%02x", (unsigned char)buf[i]); } return string(printable); } static string passwordDecrypt(string encPasswd) { unsigned encLen = (unsigned) encPasswd.length(); unsigned len = encLen / 2; char buf[len+1]; unsigned i; for (i = 0; i < len; i += 1) { char hexByte[3] = { encPasswd[i*2], encPasswd[i*2+1], '\0' }; buf[i] = (char)strtoul(hexByte, NULL, 16); } buf[i] = '\0'; if (DES_FAILED(ecb_crypt(DESkey, buf, len, DES_DECRYPT))) { throw runtime_error("ecb_crypt failed."); } return string(buf); } static map readConfig(string configDir) { string cfgFile = configDir + "/" + CONFIG_FILE; ifstream cfg(cfgFile.c_str()); map result; if (cfg.fail()) { throw runtime_error("cannot read config file " + cfgFile); } while (cfg.good()) { char line[128]; cfg.getline(line, sizeof(line)); char *it = index(line, '='); if (it) { string key(line, it - line); string val(it+1); result[key] = val; } } return result; } static void writeConfig(string configDir, map cfg) { string cfgFile = configDir + "/" + CONFIG_FILE; ofstream cfgOut(cfgFile.c_str()); if (cfgOut.fail()) { throw runtime_error("cannot write to config file " + cfgFile); } map::iterator it = cfg.begin(); while (it != cfg.end()) { pair key_val = *it++; cfgOut << key_val.first << '=' << key_val.second << endl; } cfgOut.close(); /* * Modify operation was successful. So to force vmhostinfo-cache * to re-validate the credentials and re-generate the cache with * the most recent information, remove the cache file. * vmhostinfo-cache looks for this cache file and if it exists, * it doesn't execute vmhostinfo at all and simply displays * the contents of vmhostinfo.xml. */ (void)remove(CACHE_FILE); } static string getOemName() { string oem = "Unknown"; FILE *cmdOut = popen("/usr/sbin/vsa_license -o", "r"); if (cmdOut != NULL) { char buf[64]; if (fgets(buf, sizeof(buf), cmdOut) != NULL) { oem = string(buf, strlen(buf)-1); } pclose(cmdOut); } return oem; } static void usage(string program, int exitVal, string detail = "") { const int tab = 28; if (detail != "") { err << program << ": " << detail << endl; } err << "Usage: " << program << " [options] [server user passwd]" << endl << " options:" << endl << setw(tab) << left << " -h,--help" << "Display this message" << endl << setw(tab) << left << " -p,--prefix " << "Per-line output prefix" << endl << setw(tab) << left << " -S,--server " << "Set vSphere server" << endl << setw(tab) << left << " -U,--username " << "Set vSphere username" << endl << setw(tab) << left << " -P,--password " << "Set vSphere password" << endl << setw(tab) << left << " -C,--check-auth" << "Verify vSphere authentication" << endl << setw(tab) << left << " -A,--show-auth" << "Display vSphere server & user" << endl << setw(tab) << left << " -b,--brief" << "Don't output physical disk details" << endl << setw(tab) << left << " -a,--all" << "Include ALL host datastores & phys disks" << endl << setw(tab) << left << " -x,--xml " << "Output in xml format to file" << endl << endl << "Displays ESX host info (especially storage-related) relevant to this VM." << endl << endl << " can be either an ESX host or a vCenter server. and " << endl << "need to provide only \"read-only\" vSphere access. Specifying -S/-U/-P sets" << endl << "the appropriate value(s) for future invocations then exits. Specifying" << endl << "server, user, and passwd without options temporarily overrides the values" << endl << "for the current invocation." << endl << endl << "Getting the physical disk details from an ESX server is pretty slow (while" << endl << "getting them from a vCenter server is much faster). The --brief option" << endl << "avoids this potentially-expensive operation and omits physical disk details." << endl; exit(exitVal); } int main(int argc, char **argv) { const struct option longOpts[] = { { "help", no_argument, NULL, 'h' }, { "all", no_argument, NULL, 'a' }, { "brief", no_argument, NULL, 'b' }, { "prefix", required_argument, NULL, 'p' }, { "show-auth", no_argument, NULL, 'A' }, { "server", required_argument, NULL, 'S' }, { "username", required_argument, NULL, 'U' }, { "password", optional_argument, NULL, 'P' }, { "check-auth", no_argument, NULL, 'C' }, { "no-zapi-errno", no_argument, NULL, 'Z' }, { "xml", required_argument, NULL, 'x'}, // The following are intentionally undocumented { "config-dir", required_argument, NULL, 'c' }, { "dump", required_argument, NULL, 'D' }, { "trace", no_argument, NULL, 'T' }, { NULL, no_argument, NULL, 0 } }; const struct { const char *opt; int *var; } dumpOpts[] = { { "stats", &dumpRpcStats }, { "raw", &dumpRawResponse }, { "xml", &dumpXmlResponse } }; const size_t numDumpOpts = sizeof(dumpOpts) / sizeof(dumpOpts[0]); const char *program = argv[0]; string configDir = CONFIG_DIR; string server, username, password; string xmlFileName; ofstream xmlFile; bool brief = false, all = false; bool skipConfig = false; bool checkAuth = false, showAuth = false, noOutput = false; bool noZapiErrno = false, gotPassword = false; int status = 1; int opt; int serr = 0; size_t d; bool xmlOut = false; xmlDocPtr zDoc = NULL; xmlNodePtr zRoot = NULL; string serrm; string vmhostError; /* Process cmdline options */ while ((opt = getopt_long(argc, argv, "habCATx:c:p:D:S:U:P::", longOpts, NULL)) != -1) { switch (opt) { case 'h': usage(program, 0); case 'a': all = true; break; case 'b': brief = true; break; case 'c': configDir = optarg; break; case 'p': outPrefix = optarg; break; case 'C': checkAuth = true; noOutput = true; break; case 'A': showAuth = true; noOutput = true; break; case 'Z': noZapiErrno = true; break; case 'S': server = optarg; noOutput = true; break; case 'T': ++traceRpc; break; case 'U': username = optarg; noOutput = true; break; case 'P': // The option argument really isn't optional, but // we allow the empty string as a legit value, which // getopt sees as a missing arg. if (optarg) { password = optarg; } else if (optind < argc ) { password = checkString(argv[optind], &serr, &serrm); optind++; if (serr != 0){ usage(program, 1, "invalid input for password" + serrm); } } else { password = ""; } gotPassword = true; noOutput = true; break; case 'D': for (d = 0; d < numDumpOpts; d++) { if (strcmp(optarg, dumpOpts[d].opt) == 0) { ++*dumpOpts[d].var; break; } } if (d == numDumpOpts) { err << program << ": invalid dump option" << endl; err << " valid choices are:"; for (d = 0; d < numDumpOpts; d++) { err << ' ' << dumpOpts[d].opt; } err << endl; exit(status); } break; case 'x': xmlOut = true; xmlFileName = optarg; break; default: usage(program, 1); } } /* Redirect stderr to /dev/null unless dumping or tracing */ if (!dumpRpcStats && !dumpXmlResponse && !dumpRawResponse && !traceRpc) { stderr = freopen("/dev/null", "w", stderr); } if (argc - optind == 3) { if (server != "") { usage(program, 1, "server already specified"); } server = checkString(argv[optind], &serr, &serrm); optind++; if (serr != 0) { usage(program, 1, "invalid input for server" + serrm); } if (username != "") { usage(program, 1, "username already specified"); } username = checkString(argv[optind], &serr, &serrm); optind++; if (serr != 0) { usage(program, 1, "invalid input for username" + serrm); } if (password != "") { usage(program, 1, "password already specified"); } password = checkString(argv[optind], &serr, &serrm); optind++; if (serr != 0) { usage(program, 1, "invalid input for password" + serrm); } gotPassword = true; skipConfig = true; } if (optind < argc) { usage(program, 1); } /* libxml initialization */ LIBXML_TEST_VERSION; if (xmlOut) { if (noOutput) { err << "Selected options not allowed with -x." << endl; exit(2); } zDoc = xmlNewDoc((xmlChar*)"1.0"); zRoot = xmlNewNode(NULL, (xmlChar*)"vmhost-info"); xmlDocSetRootElement(zDoc, zRoot); } try { /* Fetch VM UUID from SMBIOS */ string uuid = sysctl_get("hw.smbios.uuid"); #ifndef NETAPP if (uuid == "") uuid = getenv("hw_smbios_uuid"); #endif if (uuid == "") { throw runtime_error("could not read sysctl 'hw.smbios.uuid'."); } /* Print SMBIOS type 1 strings */ if (! noOutput) { string hw_vendor = sysctl_get("hw.smbios.t1_manufacturer"); string model = sysctl_get("hw.smbios.t1_product"); string sw_vendor = getOemName(); out("VM UUID", uuid); out("Hardware Vendor", hw_vendor); out("Model", model); out("Software Vendor", sw_vendor); if (xmlOut) { if (!hw_vendor.empty()) { xmlAddChild(zRoot, "vmhost-hardware-vendor", hw_vendor); } if (!model.empty()) { xmlAddChild(zRoot, "vmhost-model", model); } if (!uuid.empty()) { xmlAddChild(zRoot, "vm-uuid", uuid); } if (!sw_vendor.empty()) { xmlAddChild(zRoot, "vmhost-software-vendor", sw_vendor); } } } /* Read/update/write vsphere auth info from cfg file and cmdline. */ if (! skipConfig) { map cfg; bool needWrite = server != "" || username != "" || gotPassword; try { cfg = readConfig(configDir); } catch (const exception& exc) { err << program << ": warning - " << exc.what() << endl; needWrite = true; } catch (...) { needWrite = true; } if (server == "") { server = cfg["vsphere_server"]; } else { cfg["vsphere_server"] = server; } if (username == "") { username = cfg["vsphere_username"]; } else { cfg["vsphere_username"] = username; } if (!gotPassword) { // Must distinguish between empty password & no password here map::iterator it = cfg.find("vsphere_password"); if (it != cfg.end()) { string encPwd = (*it).second; gotPassword = 1; if (encPwd != "") { password = passwordDecrypt(encPwd); } } } else { cfg["vsphere_password"] = passwordEncrypt(password); } if (needWrite) { writeConfig(configDir, cfg); } } /* Handle --show-auth */ if (showAuth) { if (server != "") { cout << outPrefix << "server=" << server << endl; } if (username != "") { cout << outPrefix << "username=" << username << endl; } } if (noOutput && ! checkAuth) { return 0; } if (server == "") { throw VIMFatalErr("Failed to connnect to the vSphere server." " Reason: Either the server hostname or IP address" " is not set, or there are network issues.", EINVALIDINPUTERROR); } else if (username == "") { throw VIMFatalErr("Access to the vSphere server is failing since the server username is not set.", EINVALIDINPUTERROR); } else if (password == "" && !gotPassword) { throw VIMFatalErr("Access to the vSphere server is failing since the server password is not set.", EINVALIDINPUTERROR); } /* Initialize CURL library */ check(curl_global_init(CURL_GLOBAL_ALL)); /* Login to vSphere server */ VIMContext vim(server); vim.login(username, password); if (checkAuth) { cout << "vmservices: vSphere login succeeded for user " << username << endl; } if (noOutput) { return 0; } /* Find VM from UUID */ string vm = vim.findByUuid(uuid, 1); if (vm == "") { throw runtime_error("VM w/UUID " + uuid + " is not found on vSphere Server. Verify the server hostname or IP address."); } /* Get host from VM */ string host = vim.getVMProp(vm, "runtime.host"); if (host == "") { throw runtime_error("No ESX Host found for VM w/UUID " + uuid + "."); } /* Refresh host storage info */ string storageSys = vim.getHostProperty(host, "configManager").query("storageSystem"); VIMResponse r = vim.call("HostStorageSystem", storageSys, "RefreshStorageSystem", ""); /* Output the host info */ vim.outputHostInfo(host, xmlOut, zRoot); status = 0; multimap diskMapSerialNum; multimap diskMapDeviceId; try { status |= getOntapDiskMap(diskMapSerialNum, diskMapDeviceId); } catch (const exception& exc) { // We can live without the diskmap - we'll just report data disks // by serial number rather than by their ONTAP name (which is // far preferable to dying with an error). But we'll warn, // since this is unexpected. status |= 1; err << "warning: " << exc.what() << endl; } list > esxDisks; map > rdmUuids; set stores; vim.getEsxVirtualDiskInfo(vm, diskMapSerialNum, esxDisks, rdmUuids, stores); if (all) { /* For dev testing: list all host datastores */ vector allStores = vim.getHostProperty(host, "datastore").queryV("*"); stores.insert(allStores.begin(), allStores.end()); } set physDisks; list > esxBackingStores; map > storeInfoMap; vim.getEsxDatastoreInfo(stores, esxBackingStores, physDisks, storeInfoMap); list > esxPhysDiskInfo; if (! brief && physDisks.size() > 0) { vim.getEsxPhysDiskInfo(host, diskMapSerialNum, diskMapDeviceId, rdmUuids, // output esxPhysDiskInfo); } map > diskBackingInfoMap; status |= vim.outputEsxVirtualDiskInfo(esxDisks, storeInfoMap, xmlOut, zRoot, diskBackingInfoMap); vim.outputEsxDatastoreInfo(esxBackingStores); if (! brief && physDisks.size() > 0) { vim.outputEsxPhysDiskInfo(physDisks, all, rdmUuids, esxPhysDiskInfo, storeInfoMap, diskBackingInfoMap); } if (xmlOut) { vim.addDiskBackingInfoToXml(zRoot, diskBackingInfoMap); } /* Dump RPC stats if requested */ if (dumpRpcStats) { vim.outputRpcStats(); } } catch (const VIMFatalErr& exc) { status |= 1; err << "Error: "; if (! noZapiErrno) { err << "[" << exc.zapiErrno << "] "; } err << exc.what() << " Correct the vSphere credentials with the \"system node virtual-machine hypervisor modify-credentials\" command." << endl; vmhostError = vmhostError + exc.what() + " Correct the vSphere credentials with the \"system node virtual-machine hypervisor modify-credentials\" command."; } catch (const bad_alloc& exc) { status |= 1; err << "Error: out of memory" << endl; vmhostError = "Out of Memory."; } catch (const exception& exc) { status |= 1; err << "Error: " << exc.what() << endl; vmhostError = exc.what(); } catch (...) { status |= 1; err << "Error: internal error (unknown exception)" << endl; vmhostError = "Internal Error (Unknown Exception)."; } /* Add tag with the error in case vmhostinfo fails. */ if (!vmhostError.empty()) { xmlAddChild(zRoot, "vmhost-error", vmhostError); } if (xmlOut) { xmlChar *zOutput = NULL; try { int size; xmlDocDumpFormatMemory(zDoc, &zOutput, &size, 0); // convert to string string zOutString((char *)zOutput, size); // remove first line XXX better way? size_t zStart = zOutString.find('\n') + 1; zOutString = zOutString.substr(zStart); // output xmlFile.exceptions(ofstream::failbit | ofstream::badbit); xmlFile.open(xmlFileName.c_str()); xmlFile << zOutString; xmlFile.close(); } catch (const std::out_of_range&) { err << "Error: generate xml failure." << endl; } catch (const ofstream::failure& e) { status |= 1; err << "Error: xml file open/write failure." << endl; } catch (...) { err << "Error: internal error (unknown exception)" << endl; } xmlFree(zOutput); xmlFreeDoc(zDoc); } return status; }