/* * vz_utils.c: core driver functions for managing * Parallels Cloud Server hosts * * Copyright (C) 2012 Parallels, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; If not, see * . * */ #include #include #include "vircommand.h" #include "virerror.h" #include "viralloc.h" #include "virjson.h" #include "vz_utils.h" #include "vz_sdk.h" #include "virstring.h" #include "datatypes.h" #include "virlog.h" #include "virtime.h" #define VIR_FROM_THIS VIR_FROM_PARALLELS #define PRLSRVCTL "prlsrvctl" VIR_LOG_INIT("parallels.utils"); static virDomainDiskBus vz6DiskBuses[] = {VIR_DOMAIN_DISK_BUS_IDE, VIR_DOMAIN_DISK_BUS_SCSI, VIR_DOMAIN_DISK_BUS_SATA, VIR_DOMAIN_DISK_BUS_LAST}; static virDomainDiskBus vz7DiskBuses[] = {VIR_DOMAIN_DISK_BUS_IDE, VIR_DOMAIN_DISK_BUS_SCSI, VIR_DOMAIN_DISK_BUS_LAST}; static virDomainControllerType vz6ControllerTypes[] = {VIR_DOMAIN_CONTROLLER_TYPE_SCSI, VIR_DOMAIN_CONTROLLER_TYPE_IDE, VIR_DOMAIN_CONTROLLER_TYPE_SATA, VIR_DOMAIN_CONTROLLER_TYPE_LAST}; static virDomainControllerType vz7ControllerTypes[] = {VIR_DOMAIN_CONTROLLER_TYPE_SCSI, VIR_DOMAIN_CONTROLLER_TYPE_IDE, VIR_DOMAIN_CONTROLLER_TYPE_LAST}; /** * vzDomObjFromDomain: * @domain: Domain pointer that has to be looked up * * This function looks up @domain and returns the appropriate virDomainObjPtr * that has to be released by calling virDomainObjEndAPI(). * * Returns the domain object with incremented reference counter which is locked * on success, NULL otherwise. */ virDomainObjPtr vzDomObjFromDomain(virDomainPtr domain) { virDomainObjPtr vm; vzConnPtr privconn = domain->conn->privateData; char uuidstr[VIR_UUID_STRING_BUFLEN]; vzDriverPtr driver = privconn->driver; vm = virDomainObjListFindByUUID(driver->domains, domain->uuid); if (!vm) { virUUIDFormat(domain->uuid, uuidstr); virReportError(VIR_ERR_NO_DOMAIN, _("no domain with matching uuid '%s' (%s)"), uuidstr, domain->name); return NULL; } return vm; } static int vzDoCmdRun(char **outbuf, const char *binary, va_list list) { virCommandPtr cmd = virCommandNewVAList(binary, list); int ret = -1; if (outbuf) virCommandSetOutputBuffer(cmd, outbuf); if (virCommandRun(cmd, NULL) < 0) goto cleanup; ret = 0; cleanup: virCommandFree(cmd); if (ret && outbuf) VIR_FREE(*outbuf); return ret; } /* * Run command and return its output, pointer to * buffer or NULL in case of error. Caller os responsible * for freeing the buffer. */ char * vzGetOutput(const char *binary, ...) { char *outbuf; va_list list; int ret; va_start(list, binary); ret = vzDoCmdRun(&outbuf, binary, list); va_end(list); if (ret) return NULL; return outbuf; } static void vzInitCaps(unsigned long vzVersion, vzCapabilitiesPtr vzCaps) { if (vzVersion < VIRTUOZZO_VER_7) { vzCaps->ctDiskFormat = VIR_STORAGE_FILE_PLOOP; vzCaps->vmDiskFormat = VIR_STORAGE_FILE_PLOOP; vzCaps->diskBuses = vz6DiskBuses; vzCaps->controllerTypes = vz6ControllerTypes; vzCaps->scsiControllerModel = VIR_DOMAIN_CONTROLLER_MODEL_SCSI_BUSLOGIC; } else { vzCaps->ctDiskFormat = VIR_STORAGE_FILE_PLOOP; vzCaps->vmDiskFormat = VIR_STORAGE_FILE_QCOW2; vzCaps->diskBuses = vz7DiskBuses; vzCaps->controllerTypes = vz7ControllerTypes; vzCaps->scsiControllerModel = VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_SCSI; } } int vzInitVersion(vzDriverPtr driver) { char *output, *sVer, *tmp; const char *searchStr = "prlsrvctl version "; int ret = -1; output = vzGetOutput(PRLSRVCTL, "--help", NULL); if (!output) { vzParseError(); goto cleanup; } if (!(sVer = strstr(output, searchStr))) { vzParseError(); goto cleanup; } sVer = sVer + strlen(searchStr); /* parallels server has versions number like 6.0.17977.782218 or 7.0.0, * In libvirt we handle only first two numbers. */ if (!(tmp = strchr(sVer, '.'))) { vzParseError(); goto cleanup; } if (!(tmp = strchr(tmp + 1, '.'))) { vzParseError(); goto cleanup; } tmp[0] = '\0'; if (virParseVersionString(sVer, &(driver->vzVersion), true) < 0) { vzParseError(); goto cleanup; } vzInitCaps(driver->vzVersion, &driver->vzCaps); ret = 0; cleanup: VIR_FREE(output); return ret; } static int vzCheckDiskAddressDriveUnsupportedParams(virDomainDiskDefPtr disk) { virDomainDeviceDriveAddressPtr drive = &disk->info.addr.drive; int devIdx, busIdx; if (drive->controller > 0) { /* We have only one controller of each type */ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid drive address of disk %s, vz driver " "supports only one controller."), disk->dst); return -1; } if (drive->target > 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid drive address of disk %s, vz driver " "supports only target 0."), disk->dst); return -1; } switch (disk->bus) { case VIR_DOMAIN_DISK_BUS_IDE: if (drive->unit > 1) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid drive address of disk %s, vz driver " "supports only units 0-1 for IDE bus."), disk->dst); return -1; } break; case VIR_DOMAIN_DISK_BUS_SCSI: case VIR_DOMAIN_DISK_BUS_SATA: if (drive->bus > 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid drive address of disk %s, vz driver " "supports only bus 0 for SATA and SCSI bus."), disk->dst); return -1; } break; default: virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Specified disk bus is not supported by vz driver.")); return -1; } if (virDiskNameToBusDeviceIndex(disk, &busIdx, &devIdx) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("cannot convert disk '%s' to bus/device index"), disk->dst); return -1; } if (busIdx != drive->bus || devIdx != drive->unit) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid drive address of disk %s, vz driver " "does not support non default name mappings."), disk->dst); return -1; } return 0; } static int vzCheckDiskUnsupportedParams(virDomainDiskDefPtr disk) { if (disk->device != VIR_DOMAIN_DISK_DEVICE_DISK && disk->device != VIR_DOMAIN_DISK_DEVICE_CDROM) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Only hard disks and cdroms are supported " "by vz driver.")); return -1; } if (disk->blockio.logical_block_size || disk->blockio.physical_block_size) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Setting disk block sizes is not " "supported by vz driver.")); return -1; } if (disk->blkdeviotune.total_bytes_sec || disk->blkdeviotune.read_bytes_sec || disk->blkdeviotune.write_bytes_sec || disk->blkdeviotune.total_iops_sec || disk->blkdeviotune.read_iops_sec || disk->blkdeviotune.write_iops_sec) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Setting disk io limits is not " "supported by vz driver yet.")); return -1; } if (disk->serial && disk->device != VIR_DOMAIN_DISK_DEVICE_DISK) { VIR_INFO("%s", _("Setting disk serial number is " "supported only for disk devices.")); } if (disk->wwn) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Setting disk wwn id is not " "supported by vz driver.")); return -1; } if (disk->vendor) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Setting disk vendor is not " "supported by vz driver.")); return -1; } if (disk->product) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Setting disk product id is not " "supported by vz driver.")); return -1; } if (disk->error_policy != VIR_DOMAIN_DISK_ERROR_POLICY_DEFAULT) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Setting disk error policy is not " "supported by vz driver.")); return -1; } if (disk->iomode != VIR_DOMAIN_DISK_IO_DEFAULT && disk->iomode != VIR_DOMAIN_DISK_IO_NATIVE) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Only native iomode is " "supported by vz driver.")); return -1; } if (disk->copy_on_read) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Disk copy_on_read is not " "supported by vz driver.")); return -1; } if (disk->startupPolicy != VIR_DOMAIN_STARTUP_POLICY_DEFAULT) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Setting up disk startup policy is not " "supported by vz driver.")); return -1; } if (disk->transient) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Transient disks are not " "supported by vz driver.")); return -1; } if (disk->discard) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Setting up disk discard parameter is not " "supported by vz driver.")); return -1; } if (disk->iothread) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Setting up disk io thread # is not " "supported by vz driver.")); return -1; } if (disk->src->type != VIR_STORAGE_TYPE_FILE && disk->src->type != VIR_STORAGE_TYPE_BLOCK) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Only disk and block storage types are " "supported by vz driver.")); return -1; } if (vzCheckDiskAddressDriveUnsupportedParams(disk) < 0) return -1; return 0; } int vzCheckUnsupportedDisk(const virDomainDef *def, virDomainDiskDefPtr disk, vzCapabilitiesPtr vzCaps) { size_t i; virStorageFileFormat diskFormat; if (vzCheckDiskUnsupportedParams(disk) < 0) return -1; if (disk->src->type == VIR_STORAGE_TYPE_FILE) { if (disk->device == VIR_DOMAIN_DISK_DEVICE_DISK) { if (IS_CT(def)) diskFormat = vzCaps->ctDiskFormat; else diskFormat = vzCaps->vmDiskFormat; } else { diskFormat = VIR_STORAGE_FILE_RAW; } } else { diskFormat = VIR_STORAGE_FILE_RAW; } if (virDomainDiskGetFormat(disk) != VIR_STORAGE_FILE_NONE && virDomainDiskGetFormat(disk) != diskFormat) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Unsupported format of disk %s"), disk->src->path); return -1; } for (i = 0; vzCaps->diskBuses[i] != VIR_DOMAIN_DISK_BUS_LAST; i++) { if (disk->bus == vzCaps->diskBuses[i]) break; } if (vzCaps->diskBuses[i] == VIR_DOMAIN_DISK_BUS_LAST) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Unsupported disk bus type %s"), virDomainDiskBusTypeToString(disk->bus)); return -1; } return 0; } int vzCheckUnsupportedControllers(const virDomainDef *def, vzCapabilitiesPtr vzCaps) { size_t i, j; virDomainControllerDefPtr controller; for (i = 0; i < def->ncontrollers; i++) { controller = def->controllers[i]; for (j = 0; vzCaps->controllerTypes[j] != VIR_DOMAIN_CONTROLLER_TYPE_LAST; j++) { if (controller->type == vzCaps->controllerTypes[j]) break; } if (vzCaps->controllerTypes[j] == VIR_DOMAIN_CONTROLLER_TYPE_LAST) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Unsupported controller type %s"), virDomainControllerTypeToString(controller->type)); return -1; } if (controller->type == VIR_DOMAIN_CONTROLLER_TYPE_SCSI && controller->model != -1 && controller->model != VIR_DOMAIN_CONTROLLER_MODEL_SCSI_AUTO && controller->model != vzCaps->scsiControllerModel) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Unsupported SCSI controller model %s"), virDomainControllerModelSCSITypeToString(controller->model)); return -1; } } return 0; } int vzGetDefaultSCSIModel(vzDriverPtr driver, PRL_CLUSTERED_DEVICE_SUBTYPE *scsiModel) { switch ((int)driver->vzCaps.scsiControllerModel) { case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_SCSI: *scsiModel = PCD_VIRTIO_SCSI; break; case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_BUSLOGIC: *scsiModel = PCD_BUSLOGIC; break; default: virReportError(VIR_ERR_INTERNAL_ERROR, _("Unknown SCSI controller model %s"), virDomainControllerModelSCSITypeToString( driver->vzCaps.scsiControllerModel)); return -1; } return 0; } int vzCheckUnsupportedGraphics(virDomainGraphicsDefPtr gr) { if (gr->type != VIR_DOMAIN_GRAPHICS_TYPE_VNC) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("vz driver supports only " "VNC graphics.")); return -1; } if (gr->data.vnc.websocket != 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("vz driver doesn't support " "websockets for VNC graphics.")); return -1; } if (gr->data.vnc.keymap != 0 && STRNEQ(gr->data.vnc.keymap, "en-us")) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("vz driver supports only " "\"en-us\" keymap for VNC graphics.")); return -1; } if (gr->data.vnc.sharePolicy == VIR_DOMAIN_GRAPHICS_VNC_SHARE_ALLOW_EXCLUSIVE) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("vz driver doesn't support " "exclusive share policy for VNC graphics.")); return -1; } if (gr->data.vnc.auth.connected == VIR_DOMAIN_GRAPHICS_AUTH_CONNECTED_FAIL || gr->data.vnc.auth.connected == VIR_DOMAIN_GRAPHICS_AUTH_CONNECTED_KEEP) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("vz driver doesn't support " "given action in case of password change.")); return -1; } if (gr->data.vnc.auth.expires) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("vz driver doesn't support " "setting password expire time.")); return -1; } if (gr->nListens > 1) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("vz driver doesn't support more than " "one listening VNC server per domain")); return -1; } if (gr->nListens == 1 && gr->listens[0].type != VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("vz driver supports only address-based VNC listening")); return -1; } return 0; } void* vzDomObjAlloc(void *opaque G_GNUC_UNUSED) { vzDomObjPtr pdom = NULL; if (VIR_ALLOC(pdom) < 0) return NULL; if (virCondInit(&pdom->job.cond) < 0) goto error; pdom->stats = PRL_INVALID_HANDLE; return pdom; error: VIR_FREE(pdom); return NULL; } void vzDomObjFree(void* p) { vzDomObjPtr pdom = p; if (!pdom) return; PrlHandle_Free(pdom->sdkdom); PrlHandle_Free(pdom->stats); virCondDestroy(&pdom->job.cond); VIR_FREE(pdom); }; #define VZ_JOB_WAIT_TIME (1000 * 30) int vzDomainObjBeginJob(virDomainObjPtr dom) { vzDomObjPtr pdom = dom->privateData; unsigned long long now; unsigned long long then; if (virTimeMillisNow(&now) < 0) return -1; then = now + VZ_JOB_WAIT_TIME; while (pdom->job.active) { if (virCondWaitUntil(&pdom->job.cond, &dom->parent.lock, then) < 0) goto error; } if (virTimeMillisNow(&now) < 0) return -1; pdom->job.active = true; pdom->job.started = now; pdom->job.elapsed = 0; pdom->job.progress = 0; pdom->job.hasProgress = false; return 0; error: if (errno == ETIMEDOUT) virReportError(VIR_ERR_OPERATION_TIMEOUT, "%s", _("cannot acquire state change lock")); else virReportSystemError(errno, "%s", _("cannot acquire job mutex")); return -1; } void vzDomainObjEndJob(virDomainObjPtr dom) { vzDomObjPtr pdom = dom->privateData; pdom->job.active = false; pdom->job.cancelled = false; virCondSignal(&pdom->job.cond); } int vzDomainJobUpdateTime(vzDomainJobObjPtr job) { unsigned long long now; if (!job->started) return 0; if (virTimeMillisNow(&now) < 0) return -1; if (now < job->started) { VIR_WARN("Async job starts in the future"); job->started = 0; return 0; } job->elapsed = now - job->started; return 0; }