| # Copyright 2012 Citrix Systems, Inc. Licensed under the |
| # Apache License, Version 2.0 (the "License"); you may not use this |
| # file except in compliance with the License. Citrix Systems, Inc. |
| # reserves all rights not expressly granted by 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. |
| # |
| # Automatically generated by addcopyright.py at 04/03/2012 |
| |
| |
| |
| from xml.dom.minidom import * |
| import urllib2 |
| import MySQLdb |
| from XenAPI import * |
| import time |
| import datetime |
| import os |
| import sys |
| import paramiko |
| import traceback |
| |
| ### Logging functions |
| |
| def getTimestamp(): |
| return datetime.datetime.now().strftime("%m/%d/%Y | %I:%M:%S %p") |
| |
| def nonExitingLogDecorator(entryMessage): |
| return genDecoratorFn(entryMessage, False, False) |
| |
| def basicLogDecorator(entryMessage): |
| return genDecoratorFn(entryMessage, True, True) |
| |
| def verboseLogDecorator(entryMessage): |
| return genDecoratorFn(entryMessage, False, True) |
| |
| def genDecoratorFn(entryMessage, printToScreen, exitOnError): |
| def wrap(f): |
| def g(*args): |
| writeToLog("", printToScreen) |
| writeToLog(getTimestamp() + " | " + entryMessage, printToScreen) |
| |
| if (len(args) > 0): |
| argString = "" |
| for i in range(len(args)): |
| arg = args[i] |
| argString += str(arg) |
| if (i != len(args) - 1): |
| argString += ", " |
| writeToLog("args: " + argString, False) |
| |
| returnValue = None |
| try: |
| returnValue = f(*args) |
| except SystemExit: |
| sys.exit(1) |
| except Exception, e: |
| if (exitOnError): |
| handleError(str(e), True) |
| traceback.print_exc(file = GLOBALS["LOG_FILE"]) |
| sys.exit(1) |
| else: |
| return False |
| |
| if (returnValue in (None, False)): |
| if (exitOnError): |
| writeToLog(str(f) + " returned " + str(returnValue), False) |
| handleError(None, True) |
| sys.exit(1) |
| else: |
| return False |
| else: |
| return returnValue |
| return g |
| return wrap |
| |
| def handleError(msg, printToScreen): |
| writeToLog(getTimestamp() + " | " + "Failed to complete this step.", printToScreen) |
| if (msg != None): |
| writeToLog("Details: " + msg, printToScreen) |
| |
| def writeToLog(message, printToScreen): |
| logFile = GLOBALS.get("LOG_FILE") |
| if (logFile != None): |
| logFile.write(message) |
| logFile.write("\n") |
| if (printToScreen): |
| print message |
| |
| ### Util classes |
| |
| class System: |
| def __init__(self, managementServerIp, asyncApi, xenServerIp, xenServerPassword, xenServerPasswordMap, dbName, dbLogin, dbPassword, zoneId, templateId, isoId, defaultServiceOfferingId, defaultDiskOfferingId): |
| self.zoneId = zoneId |
| self.templateId = templateId |
| self.isoId = isoId |
| self.defaultServiceOfferingId = defaultServiceOfferingId |
| self.defaultDiskOfferingId = defaultDiskOfferingId |
| self.api = System.API(managementServerIp, asyncApi) |
| if (dbPassword == None): |
| dbPassword = "" |
| self.db = System.DB(managementServerIp, dbName, dbLogin, dbPassword) |
| |
| self.xenServerIp = None |
| self.xenapi = None |
| if (xenServerIp != None or xenServerPasswordMap != None): |
| self.findXenApi(xenServerIp, xenServerPassword, xenServerPasswordMap) |
| self.controlDomainRef = self.findControlDomainRef() |
| self.sshConn = paramiko.SSHClient() |
| self.sshConn.set_missing_host_key_policy(paramiko.AutoAddPolicy()) |
| self.sshConn.connect(self.xenServerIp, username = "root", password = self.xenServerPassword) |
| |
| @verboseLogDecorator("Finding the XenAPI connection...") |
| def findXenApi(self, xenServerIp, xenServerPassword, xenServerPasswordMap): |
| if (xenServerPasswordMap != None): |
| xenServerIp = xenServerPasswordMap.keys()[0] |
| xenServerPassword = xenServerPasswordMap[xenServerIp] |
| |
| masterXenServerIp = xenServerIp |
| masterXenServerPassword = xenServerPassword |
| session = None |
| try: |
| session = Session("http://" + masterXenServerIp + "/var/run/xend/xen-api.sock") |
| session.login_with_password("root", masterXenServerPassword) |
| except Exception, e: |
| if (e.details != None and len(e.details) == 2 and e.details[0] == "HOST_IS_SLAVE"): |
| masterXenServerIp = e.details[1] |
| if (xenServerPasswordMap != None): |
| masterXenServerPassword = xenServerPasswordMap[masterXenServerIp] |
| else: |
| masterXenServerPassword = xenServerPassword |
| session = Session("http://" + masterXenServerIp + "/var/run/xend/xen-api.sock") |
| session.login_with_password("root", masterXenServerPassword) |
| else: |
| raise |
| |
| self.xenServerIp = masterXenServerIp |
| self.xenServerPassword = masterXenServerPassword |
| self.xenapi = session.xenapi |
| return True |
| |
| @verboseLogDecorator("Finding the control domain for the dest system...") |
| def findControlDomainRef(self): |
| # Find the host ref for this system |
| hostRefs = self.xenapi.host.get_all() |
| systemHostRef = None |
| for hostRef in hostRefs: |
| address = self.xenapi.host.get_address(hostRef) |
| if (address == self.xenServerIp): |
| systemHostRef = hostRef |
| break |
| |
| if (systemHostRef == None): |
| raise Exception("Failed to find the XenServer host ref for " + str(self)) |
| |
| # Find the control domain ref that corresponds to this host |
| vmRefs = self.xenapi.VM.get_all() |
| for vmRef in vmRefs: |
| if (self.xenapi.VM.get_is_control_domain(vmRef)): |
| controlDomainHostRef = self.xenapi.VM.get_resident_on(vmRef) |
| if (controlDomainHostRef == systemHostRef): |
| return vmRef |
| return None |
| |
| @nonExitingLogDecorator("Running ssh command...") |
| def runSshCommand(self, command): |
| stdin, stdout, stderr = self.sshConn.exec_command(command) |
| return (stdin.channel.recv_exit_status() == 0) |
| |
| def updateIsoPermissions(self): |
| setParams = {"public":"1"} |
| whereParams = {"id":self.isoId} |
| return self.db.updateDbValues("vm_template", setParams, whereParams) |
| |
| def __str__(self): |
| description = "Management Server: %s" % (self.api.ip) |
| if (self.xenServerIp != None): |
| description += " | XenServer: %s" %(self.xenServerIp) |
| return description |
| |
| class API: |
| # Vars: ip |
| def __init__(self, ip, asyncApi): |
| self.ip = ip |
| self.asyncApi = asyncApi |
| |
| # Runs a synchronous API command and returns the ID of the created object, or success/failure |
| def runSyncApiCommand(self, command, params, objectName): |
| requestURL = self.buildRequestUrl(command, params) |
| xmlText = urllib2.urlopen(requestURL).read() |
| if (objectName == None): |
| responseName = (command + "response").lower() |
| return (System.API.getTagValue(xmlText, responseName, "success") == "true") |
| else: |
| return System.API.getTagValue(xmlText, objectName, "id") |
| |
| # Runs a asynchronous API command and returns the ID of the created object, or success/failure |
| def runAsyncApiCommand(self, command, params, objectName): |
| requestURL = self.buildRequestUrl(command, params) |
| xmlText = urllib2.urlopen(requestURL).read() |
| responseName = (command + "response").lower() |
| jobId = System.API.getTagValue(xmlText, responseName, "jobid") |
| objectId = System.API.getTagValue(xmlText, responseName, objectName + "id") |
| params = dict() |
| params["jobId"] = jobId |
| requestURL = self.buildRequestUrl("queryAsyncJobResult", params) |
| retries = int(GLOBALS["ASYNC_RETRIES"]) |
| jobResult = None |
| while (retries > 0): |
| time.sleep(float(GLOBALS["ASYNC_SLEEP_TIME"])) |
| xmlText = urllib2.urlopen(requestURL).read() |
| if (System.API.getTagValue(xmlText, "queryasyncjobresultresponse", "jobstatus") == "1"): |
| if (objectId != None): |
| return objectId |
| else: |
| return True |
| jobResult = System.API.getTagValue(xmlText, "queryasyncjobresultresponse", "jobresult") |
| retries -= 1 |
| raise Exception(jobResult) |
| |
| def buildRequestUrl(self, command, params): |
| requestURL = "http://" + self.ip + ":8096/client/api/?command=" + command |
| for paramKey in params.keys(): |
| paramVal = params.get(paramKey) |
| requestURL += "&" + str(paramKey) + "=" + str(paramVal) |
| return requestURL |
| |
| @staticmethod |
| def getTagValue(xmlText, objectName, tagName): |
| xmlDoc = parseString(xmlText) |
| response = xmlDoc.getElementsByTagName(objectName)[0] |
| for x in response.childNodes: |
| if (x.tagName == tagName): |
| return " ".join(z.wholeText for z in x.childNodes) |
| return None |
| |
| @staticmethod |
| def printApiValues(listOfAPIObjects): |
| for apiObject in listOfAPIObjects: |
| for key in apiObject.keys(): |
| print key + ":" + apiObject.get(key) |
| print " " |
| |
| class DB: |
| # Vars: conn |
| def __init__(self, ip, dbName, dbLogin, dbPassword): |
| self.conn = MySQLdb.connect(host = ip, user = dbLogin, passwd = dbPassword, db = dbName) |
| self.conn.autocommit(True) |
| |
| def getTable(self, table): |
| cursor = self.conn.cursor() |
| sql = "SELECT * from " + table |
| cursor.execute(sql) |
| for row in cursor.fetchall(): |
| print row |
| cursor.close() |
| |
| # Returns a list of hashtables, each one representing a row and using column names as keys. |
| def getDbValues(self, table, columns, whereParams): |
| values = [] |
| cursor = self.conn.cursor() |
| columnsText = ",".join(columns) |
| sql = "SELECT " + columnsText + " FROM " + table |
| if (len(whereParams) > 0): |
| sql += System.DB.buildSqlWhereClause(whereParams) |
| cursor.execute(sql) |
| rows = cursor.fetchall() |
| cursor.close() |
| for row in rows: |
| value = dict() |
| for i in range(len(columns)): |
| val = str(row[i]) |
| value[columns[i]] = val |
| values.append(value) |
| return values |
| |
| def updateDbValues(self, table, setParams, whereParams): |
| setClause = System.DB.buildSqlSetClause(setParams) |
| sql = "UPDATE " + table + setClause |
| if (len(whereParams) > 0): |
| sql += System.DB.buildSqlWhereClause(whereParams) |
| |
| cursor = self.conn.cursor() |
| cursor.execute(sql) |
| self.conn.commit() |
| cursor.close() |
| return True |
| |
| def insertIntoDb(self, table, setParams): |
| existingRecords = self.getDbValues(table, ["id"], setParams) |
| if (len(existingRecords) > 0): |
| return existingRecords[0]["id"] |
| columns = setParams.keys() |
| values = [] |
| for column in columns: |
| values.append(setParams[column]) |
| sql = "INSERT INTO " + table + System.DB.buildSqlInsertClause(columns, values) |
| cursor = self.conn.cursor() |
| cursor.execute(sql) |
| insertId = self.conn.insert_id() |
| self.conn.commit() |
| cursor.close() |
| return insertId |
| |
| @staticmethod |
| def buildSqlInsertClause(columns, values): |
| columnsSql = " (" |
| valuesSql = " VALUES (" |
| for i in range(len(columns)): |
| if (str(values[i]) == "null"): |
| continue |
| |
| columnsSql += columns[i] |
| valuesSql += "'" + str(values[i]) + "'" |
| if (i != (len(columns) - 1)): |
| columnsSql += ", " |
| valuesSql += ", " |
| else: |
| columnsSql += ")" |
| valuesSql += ")" |
| return columnsSql + valuesSql |
| |
| @staticmethod |
| def buildSqlWhereClause(params): |
| sql = " WHERE " |
| keys = params.keys() |
| for i in range(len(keys)): |
| key = str(keys[i]) |
| val = str(params[key]) |
| if ("like" in val): |
| val = val.split(":")[1] |
| sql += key + " like '" + val + "'" |
| elif ("neq" in val): |
| val = val.split(":")[1] |
| sql += key + " != '" + val + "'" |
| elif (val == "null" or val == "not null"): |
| sql += key + " IS " + val |
| else: |
| sql += key + " = '" + val + "'" |
| if (i != (len(keys) - 1)): |
| sql += " AND " |
| return sql |
| |
| @staticmethod |
| def buildSqlSetClause(params): |
| sql = " SET " |
| keys = params.keys() |
| for i in range(len(keys)): |
| key = keys[i] |
| val = params[key] |
| sql += key + " = " |
| if (val == "null"): |
| sql += "null" |
| else: |
| sql += "'" + val + "'" |
| if (i != (len(keys) - 1)): |
| sql += ", " |
| return sql |
| |
| ### Data classes |
| |
| class User: |
| # Vars: system, id, username, password, accountId, firstname, lastname, email, accountType, accountName, domainId |
| |
| def __init__(self, system, userId, username, password, accountId, firstname, lastname, email, accountType, accountName, domainId): |
| self.system = system |
| self.id = userId |
| self.username = username |
| self.password = password |
| self.accountId = accountId |
| self.firstname = firstname |
| self.lastname = lastname |
| self.email = email |
| self.accountType = accountType |
| self.accountName = accountName |
| self.domainId = domainId |
| |
| def __str__(self): |
| return "(User: %s | %s)" % (self.username, self.system) |
| |
| def alreadyMigrated(self): |
| f = open(GLOBALS["MIGRATED_ACCOUNTS_FILE"], "a+") |
| migratedUsersCsv = f.read() |
| f.close() |
| migratedUsersEntries = migratedUsersCsv.split(",") |
| for migratedUsersEntry in migratedUsersEntries: |
| if (migratedUsersEntry.strip() == self.accountId): |
| return True |
| return False |
| |
| def tagAsMigrated(self): |
| if (not self.alreadyMigrated()): |
| f = open(GLOBALS["MIGRATED_ACCOUNTS_FILE"], "a") |
| f.write(self.accountId + ",") |
| f.close() |
| return True |
| |
| @staticmethod |
| def getByName(system, username): |
| columns = ["id"] |
| users = system.db.getDbValues("user", columns, {"username":username, "removed":"null"}) |
| if (len(users) > 0): |
| return User.get(system, users[0]["id"]) |
| else: |
| return None |
| |
| @staticmethod |
| def get(system, userId): |
| columns = ["id", "username", "password", "account_id", "firstname", "lastname", "email"] |
| users = system.db.getDbValues("user", columns, {"id":userId}) |
| if (len(users) == 0): |
| return None |
| user = users[0] |
| columns = ["type", "account_name", "domain_id"] |
| account = system.db.getDbValues("account", columns, {"id":user["account_id"]})[0] |
| return User(system, userId, user["username"], user["password"], user["account_id"], user["firstname"], user["lastname"], user["email"], account["type"], account["account_name"], account["domain_id"]) |
| |
| @staticmethod |
| def getDomain(system, domainId): |
| columns = ["id", "parent", "name", "owner"] |
| return system.db.getDbValues("domain", columns, {"id":domainId})[0] |
| |
| @staticmethod |
| def getDomainByName(system, domainName): |
| columns = ["id"] |
| domains = system.db.getDbValues("domain", columns, {"name":domainName}) |
| if (len(domains) > 0): |
| return domains[0] |
| else: |
| return None |
| |
| @staticmethod |
| def createDomain(srcSystem, destSystem, srcDomainId): |
| # Get the source domain |
| srcDomain = User.getDomain(srcSystem, srcDomainId) |
| |
| # If a domain with the same name exists in the dest system, return its ID |
| destDomain = User.getDomainByName(destSystem, srcDomain["name"]) |
| if (destDomain != None): |
| return destDomain["id"] |
| else: |
| # Otherwise, create a new domain in the dest system with the same name, and return its ID |
| # If the src domain has parent domains, we need to create these first |
| parentId = None |
| if (srcDomain["parent"] != "null"): |
| parentId = User.createDomain(srcSystem, destSystem, srcDomain["parent"]) |
| params = dict() |
| params["name"] = srcDomain["name"] |
| if (parentId != None): |
| params["parent"] = parentId |
| newDomainId = destSystem.api.runSyncApiCommand("createDomain", params, "domain") |
| if (newDomainId == None): |
| raise Exception("Failed to create domain " + srcDomain["name"]) |
| else: |
| # Set the owner for the new domain |
| srcOwner = User.get(srcSystem, srcDomain["owner"]) |
| destOwner = User.create(destSystem, srcOwner) |
| if (not destSystem.db.updateDbValues("domain", {"owner":destOwner.id}, {"id":newDomainId})): |
| raise Exception("Failed to update the owner for domain " + srcDomain["name"]) |
| return newDomainId |
| |
| @staticmethod |
| @basicLogDecorator("Creating new user...") |
| def create(system, srcUser): |
| user = User.getByName(system, srcUser.username) |
| if (user != None): |
| return user |
| else: |
| # If the user's domain doesn't exist in the system, create it |
| domainId = User.createDomain(srcUser.system, system, srcUser.domainId) |
| |
| params = dict() |
| params["username"] = srcUser.username |
| params["password"] = "temp" |
| params["firstname"] = srcUser.firstname |
| params["lastname"] = srcUser.lastname |
| params["email"] = srcUser.email |
| accountType = srcUser.accountType |
| if (accountType == "2"): |
| accountType = "0" |
| params["accounttype"] = accountType |
| params["account"] = srcUser.accountName |
| params["domainid"] = domainId |
| newUserId = system.api.runSyncApiCommand("createUser", params, "user") |
| if (newUserId != None): |
| if (system.db.updateDbValues("user", {"password":srcUser.password}, {"id":newUserId})): |
| return User.get(system, newUserId) |
| else: |
| return None |
| else: |
| return None |
| |
| class ServiceOffering: |
| # Vars: system, id, numCpus, speed, memory, disk |
| |
| def __init__(self, system, offeringId, numCpus, speed, memory, disk): |
| self.system = system |
| self.id = offeringId |
| self.numCpus = numCpus |
| self.speed = speed |
| self.memory = memory |
| self.disk = disk |
| |
| def __str__(self): |
| return "ServiceOffering: %s | id: %s | numCpus: %s | speed: %s | memory: %s | disk: %s" % (self.system, self.id, self.numCpus, self.speed, self.memory, self.disk) |
| |
| @staticmethod |
| def getCorrespondingServiceOffering(srcServiceOfferingId): |
| srcServiceOffering = ServiceOffering.getSrcSystemServiceOfferingById(srcServiceOfferingId) |
| destServiceOffering = ServiceOffering.getDestSystemServiceOffering(srcServiceOffering.numCpus, srcServiceOffering.speed, srcServiceOffering.memory) |
| return destServiceOffering |
| |
| @staticmethod |
| def getDestSystemServiceOffering(numCpus, speed, memory): |
| serviceOfferings = GLOBALS["DEST_SYSTEM"].db.getDbValues("service_offering", ["id"], {"cpu":numCpus, "speed":speed, "ram_size":memory, "guest_ip_type":"Virtualized"}) |
| if (len(serviceOfferings) > 0): |
| return ServiceOffering(GLOBALS["DEST_SYSTEM"], serviceOfferings[0]["id"], numCpus, speed, memory, None) |
| else: |
| return None |
| |
| @staticmethod |
| def getSrcSystemServiceOfferingByVmId(vmId): |
| serviceOfferingId = GLOBALS["SRC_SYSTEM"].db.getDbValues("user_vm", ["service_offering_id"], {"id":vmId})[0]["service_offering_id"] |
| return getSrcSystemServiceOfferingById(serviceOfferingId) |
| |
| @staticmethod |
| def getSrcSystemServiceOfferingById(serviceOfferingId): |
| columns = ["id", "cpu", "speed", "ram_size", "disk"] |
| serviceOfferings = GLOBALS["SRC_SYSTEM"].db.getDbValues("service_offering", columns, {"id":serviceOfferingId}) |
| if (len(serviceOfferings) > 0): |
| offering = serviceOfferings[0] |
| return ServiceOffering(GLOBALS["SRC_SYSTEM"], offering["id"], offering["cpu"], offering["speed"], offering["ram_size"], offering["disk"]) |
| else: |
| return None |
| |
| @staticmethod |
| def getSrcSystemServiceOfferings(): |
| serviceOfferings = [] |
| columns = ["id", "cpu", "speed", "ram_size", "disk"] |
| srcServiceOfferings = GLOBALS["SRC_SYSTEM"].db.getDbValues("service_offering", columns, {}) |
| for offering in srcServiceOfferings: |
| serviceOfferings.append(ServiceOffering(GLOBALS["SRC_SYSTEM"], offering["id"], offering["cpu"], offering["speed"], offering["ram_size"], offering["disk"])) |
| return serviceOfferings |
| |
| class DiskOffering: |
| # Vars: id, size |
| |
| def __init__(self, diskOfferingId, size): |
| self.id = diskOfferingId |
| self.size = size |
| |
| def __str__(self): |
| return "Disk Offering: size = %s" % (self.size) |
| |
| @staticmethod |
| def getDestDiskOffering(size): |
| columns = ["id"] |
| diskOfferingRows = GLOBALS["DEST_SYSTEM"].db.getDbValues("disk_offering", ["id"], {"disk_size":size, "type":"Disk"}) |
| if (len(diskOfferingRows) > 0): |
| return DiskOffering(diskOfferingRows[0]["id"], size) |
| else: |
| size = ((int(size) / 1024) + 1) * 1024 |
| diskOfferingRows = GLOBALS["DEST_SYSTEM"].db.getDbValues("disk_offering", ["id"], {"disk_size":size, "type":"Disk"}) |
| if (len(diskOfferingRows) > 0): |
| return DiskOffering(diskOfferingRows[0]["id"], size) |
| else: |
| return None |
| |
| @staticmethod |
| def getCorrespondingDiskOffering(srcServiceOfferingId): |
| srcServiceOffering = ServiceOffering.getSrcSystemServiceOfferingById(srcServiceOfferingId) |
| diskOffering = DiskOffering.getDestDiskOffering(srcServiceOffering.disk) |
| return diskOffering |
| |
| |
| class VM: |
| # Vars: id, system, user, serviceOfferingId, name, templateId, guestOsId |
| |
| def __init__(self, vmId, user, serviceOfferingId, guestOsId, guestOsCategoryId): |
| self.id = vmId |
| self.system = user.system |
| self.user = user |
| self.serviceOfferingId = serviceOfferingId |
| self.guestOsId = guestOsId |
| self.guestOsCategoryId = guestOsCategoryId |
| |
| def __str__(self): |
| return "UserVM: id = %s | username = %s | system = %s" % (self.id, self.user.username, self.system) |
| |
| def getName(self): |
| columns = ["name"] |
| return self.system.db.getDbValues("vm_instance", columns, {"id":self.id})[0]["name"] |
| |
| @basicLogDecorator("Deploying a temporary VM...") |
| def deployTemp(self): |
| params = {"account":self.user.accountName, "domainid":self.user.domainId, "zoneId":self.system.zoneId, "serviceofferingid":self.system.defaultServiceOfferingId, "templateid":self.system.templateId} |
| vmId = self.system.api.runAsyncApiCommand("deployVirtualMachine", params, "virtualmachine") |
| if (vmId in (None, False)): |
| return False |
| self.id = vmId |
| self.name = self.getName() + "-temp-vm" |
| success = self.system.db.updateDbValues("vm_instance", {"name":self.name}, {"id":self.id}) |
| return success |
| |
| @basicLogDecorator("Deploying a new VM for the user ...") |
| def deploy(self, srcVm): |
| params = dict() |
| params["account"] = self.user.accountName |
| params["domainid"] = self.user.domainId |
| params["zoneid"] = self.system.zoneId |
| params["serviceofferingid"] = self.serviceOfferingId |
| params["templateid"] = self.system.isoId |
| params["diskofferingid"] = self.system.defaultDiskOfferingId |
| vmId = self.system.api.runAsyncApiCommand("deployVirtualMachine", params, "virtualmachine") |
| if (vmId in (None, False)): |
| return None |
| self.id = vmId |
| self.name = self.getName() + "-" + str(srcVm.id) + " (" + VM.getGuestOsName(GLOBALS["DEST_SYSTEM"], self.guestOsId) + ")" |
| success = self.system.db.updateDbValues("vm_instance", {"name":self.name}, {"id":self.id}) |
| return success |
| |
| @verboseLogDecorator("Updating the guest OS ID for the VM...") |
| def updateGuestOsId(self): |
| setParams = {"guest_os_id":self.guestOsId} |
| whereParams = {"id":self.id} |
| return self.system.db.updateDbValues("vm_instance", setParams, whereParams) |
| |
| @basicLogDecorator("Starting VM...") |
| def start(self): |
| params = {"id":self.id} |
| return self.system.api.runAsyncApiCommand("startVirtualMachine", params, "virtualmachine") |
| |
| @basicLogDecorator("Stopping VM...") |
| def stop(self): |
| params = {"id":self.id} |
| if (self.system.api.asyncApi): |
| return self.system.api.runAsyncApiCommand("stopVirtualMachine", params, "virtualmachine") |
| else: |
| return self.system.api.runSyncApiCommand("stopVirtualMachine", params, None) |
| |
| @basicLogDecorator("Destroying temporary VM...") |
| def destroy(self): |
| params = {"id":self.id} |
| return self.system.api.runAsyncApiCommand("destroyVirtualMachine", params, "virtualmachine") |
| |
| @verboseLogDecorator("Detaching ISO from VM...") |
| def detachIso(self): |
| isoId = self.system.db.getDbValues("vm_instance", ["iso_id"], {"id":self.id})[0]["iso_id"] |
| if (isoId == "None"): |
| return True |
| |
| params = {"virtualmachineid":self.id} |
| return self.system.api.runAsyncApiCommand("detachIso", params, "virtualmachine") |
| |
| def isLinuxVm(self): |
| return (self.guestOsCategoryId != str(GLOBALS["DEST_WINDOWS_GUEST_OS_CATEGORY_ID"])) |
| |
| @staticmethod |
| def getGuestOsName(system, guestOsId): |
| columns = ["id", "display_name"] |
| guestOsList = system.db.getDbValues("guest_os", columns, {"id":guestOsId}) |
| if (len(guestOsList) > 0): |
| return guestOsList[0]["display_name"] |
| else: |
| return None |
| |
| @staticmethod |
| def getGuestOsCategoryId(system, guestOsId): |
| columns = ["category_id"] |
| return system.db.getDbValues("guest_os", columns, {"id":guestOsId})[0]["category_id"] |
| |
| @staticmethod |
| def getVmId(system, accountId, guestIpAddress): |
| userVms = system.db.getDbValues("user_vm", ["id"], {"account_id":accountId, "guest_ip_address":guestIpAddress}) |
| for userVm in userVms: |
| vmInstances = system.db.getDbValues("vm_instance", ["id"], {"id":userVm["id"], "removed":"null", "state":"neq:Destroyed"}) |
| if (len(vmInstances) > 0): |
| return vmInstances[0]["id"] |
| return None |
| |
| @staticmethod |
| def getVms(user): |
| system = user.system |
| vms = [] |
| columns = ["id", "service_offering_id"] |
| userVmRows = system.db.getDbValues("user_vm", columns, {"account_id":user.accountId}) |
| for userVmRow in userVmRows: |
| vmInstanceRow = system.db.getDbValues("vm_instance", ["vm_template_id", "removed"], {"id":userVmRow["id"]})[0] |
| if (vmInstanceRow["removed"] != "None"): |
| continue |
| |
| # Determine the service offering ID |
| serviceOfferingId = userVmRow["service_offering_id"] |
| |
| # Determine the new guest OS id and category id |
| templateId = vmInstanceRow["vm_template_id"] |
| guestOsId = GLOBALS["GUEST_OS_MAP"][templateId] |
| guestOsCategoryId = VM.getGuestOsCategoryId(GLOBALS["DEST_SYSTEM"], guestOsId) |
| |
| vms.append(VM(userVmRow["id"], user, serviceOfferingId, guestOsId, guestOsCategoryId)) |
| return vms |
| |
| @staticmethod |
| def getCorrespondingVm(destUser, srcVm): |
| system = destUser.system |
| columns = ["id", "guest_os_id"] |
| correspondingVms = system.db.getDbValues("vm_instance", columns, {"name":"like:%-" + srcVm.id + " (%"}) |
| if (len(correspondingVms) > 0): |
| correspondingVm = correspondingVms[0] |
| newServiceOffering = ServiceOffering.getCorrespondingServiceOffering(srcVm.serviceOfferingId) |
| vmId = correspondingVm["id"] |
| guestOsId = srcVm.guestOsId |
| guestOsCategoryId = VM.getGuestOsCategoryId(GLOBALS["DEST_SYSTEM"], guestOsId) |
| return VM(vmId, destUser, newServiceOffering.id, guestOsId, guestOsCategoryId) |
| else: |
| return None |
| |
| @staticmethod |
| def getTempVm(user): |
| system = user.system |
| columns = ["id"] |
| tempVms = system.db.getDbValues("vm_instance", columns, {"removed":"null", "state":"neq:Destroyed", "name":"like:%-temp-vm"}) |
| if (len(tempVms) > 0): |
| tempVm = tempVms[0] |
| return VM(tempVm["id"], user, None, None, None) |
| else: |
| return None |
| |
| @staticmethod |
| def getTemplate(system, templateId): |
| columns = ["id", "name", "format"] |
| templates = system.db.getDbValues("vm_template", columns, {"id":templateId}) |
| if (len(templates) > 0): |
| return templates[0] |
| else: |
| return None |
| |
| @staticmethod |
| def getTemplateIds(system): |
| templateIds = [] |
| columns = ["id", "unique_name"] |
| templates = system.db.getDbValues("vm_template", columns, {}) |
| for template in templates: |
| if (template["unique_name"] == "routing"): |
| continue |
| templateIds.append(template["id"]) |
| return templateIds |
| |
| @staticmethod |
| @basicLogDecorator("Migrating the user's VMs...") |
| def migrateVirtualMachines(srcUser, destUser): |
| # Maintain a map of src system VM IDs to dest system VM ids |
| vmIdMap = dict() |
| |
| # Get a list of user VMs for the source user |
| srcVms = VM.getVms(srcUser) |
| |
| for srcVm in srcVms: |
| # Try to find an existing VM in the dest system that corresponds to the VM in the src system |
| destVm = VM.getCorrespondingVm(destUser, srcVm) |
| |
| # If there is no corresponding VM, deploy a new VM in the dest system |
| if (destVm == None): |
| destVm = VM(None, destUser, srcVm.serviceOfferingId, srcVm.guestOsId, srcVm.guestOsCategoryId) |
| destVm.deploy(srcVm) |
| |
| # Add a mapping between the src VM and the dest VM |
| vmIdMap[srcVm.id] = destVm.id |
| |
| # Get a list of volumes for the source VM |
| srcVolumes = Volume.getSrcVolumes(srcUser, srcVm) |
| |
| # If these volumes have already been copied to the dest system, skip migration for this VM |
| vmAlreadyMigrated = True |
| for srcVolume in srcVolumes: |
| destVolume = Volume.getDestVolume(None, destVm, srcVolume.type) |
| if (destVolume == None): |
| vmAlreadyMigrated = False |
| break |
| elif (srcVolume.id != destVolume.name.split("-")[-1]): |
| vmAlreadyMigrated = False |
| break |
| |
| if (vmAlreadyMigrated): |
| writeToLog("\n" + str(srcVm) + " has already been migrated.", True) |
| continue |
| else: |
| writeToLog("\nMigrating volumes for source VM: " + str(srcVm), True) |
| |
| # Stop the dest VM |
| destVm.stop() |
| |
| # Stop the source VM |
| srcVm.stop() |
| |
| for srcVolume in srcVolumes: |
| destVolume = None |
| if (srcVolume.type == "DATADISK"): |
| destVolume = Volume.getDestVolume(None, destVm, "DATADISK") |
| if (destVolume == None): |
| diskOffering = DiskOffering.getCorrespondingDiskOffering(srcVm.serviceOfferingId) |
| destVolume = Volume(GLOBALS["DEST_SYSTEM"], None, str(destVm.id) + "-DATADISK", None, None, None, "DATA", diskOffering.id) |
| destVolume.createAndAttach(destVm) |
| else: |
| destVolume = Volume.getDestVolume(None, destVm, "ROOT") |
| |
| # If the dest volume is already tagged with the source volume's ID, we don't need to do a copy |
| if (srcVolume.id == destVolume.name.split("-")[-1]): |
| writeToLog(str(srcVolume) + " has already been migrated.") |
| continue |
| |
| # If the srcVolume's iSCSI SR isn't created on the XenServer, create it |
| srcHost = Host.getHost(GLOBALS["SRC_SYSTEM"], srcVolume.hostId) |
| srcSR = SR.getExistingSrcSr(srcHost.ip, srcHost.iqn) |
| if (srcSR == None): |
| srcSR = SR(GLOBALS["DEST_SYSTEM"], srcHost.ip, srcHost.iqn, None) |
| srcSR.create() |
| else: |
| writeToLog("Found existing SR: " + str(srcSR), False) |
| |
| # Find the VDI corresponding to the src volume |
| srcVdi = VDI(srcSR, srcVolume, None) |
| |
| # Find the SR corresponding to the dest storage pool |
| destStoragePool = StoragePool.getStoragePool(GLOBALS["DEST_SYSTEM"], destVolume.poolId) |
| destSR = SR(GLOBALS["DEST_SYSTEM"], None, None, destStoragePool.uuid) |
| destSR.find() |
| |
| # Copy the src VDI to the dest SR |
| copiedVdiUuid = srcVdi.copy(destSR) |
| |
| # If this is the rootdisk of a Linux VM, change the disk name |
| destVdi = VDI(destSR, destVolume, copiedVdiUuid) |
| if (destVolume.type == "ROOT" and srcVm.isLinuxVm()): |
| destVdi.changeBootableDeviceName() |
| |
| # Destroy the VM's old VDI |
| oldDestVdi = VDI(destSR, destVolume, destVolume.path) |
| oldDestVdi.destroy() |
| |
| # Update the destVolume's database record to have the UUID of the copied VDI, the virtual size of the copied VDI, and the ID of the source volume |
| destVolume.update(copiedVdiUuid, destVdi.getVirtualSize(), destVolume.name + "-" + srcVolume.id) |
| |
| # Detach the dest VM's ISO |
| destVm.detachIso() |
| |
| # Update the guest OS ID for the VM |
| destVm.updateGuestOsId() |
| |
| # Start the dest VM |
| destVm.start() |
| |
| return vmIdMap |
| |
| class Volume: |
| # vars: system, id, hostId, poolId, path, zoneId, iscsiName, type, diskOfferingId |
| |
| def __init__(self, system, volumeId, name, poolOrHostId, path, iscsiName, volumeType, diskOfferingId): |
| self.system = system |
| self.id = volumeId |
| self.name = name |
| if (iscsiName == None): |
| self.poolId = poolOrHostId |
| self.iscsiName = None |
| else: |
| self.hostId = poolOrHostId |
| self.iscsiName = iscsiName |
| self.path = path |
| self.type = volumeType |
| self.diskOfferingId = diskOfferingId |
| |
| def __str__(self): |
| return "Volume: %s | type: %s | path: %s" % (self.system, self.type, self.path) |
| |
| @basicLogDecorator("Creating a new volume and attaching it to the user's VM...") |
| def createAndAttach(self, destVm): |
| params = dict() |
| params["account"] = destVm.user.accountName |
| params["domainid"] = destVm.user.domainId |
| params["name"] = self.name |
| params["zoneid"] = self.system.zoneId |
| params["diskofferingid"] = self.diskOfferingId |
| volumeId = self.system.api.runAsyncApiCommand("createVolume", params, "volume") |
| if (volumeId in (None, False)): |
| return False |
| self.id = volumeId |
| params = dict() |
| params["id"] = volumeId |
| params["virtualmachineid"] = destVm.id |
| success = self.system.api.runAsyncApiCommand("attachVolume", params, "volume") |
| if (success in (None, False)): |
| return False |
| newVolume = Volume.getDestVolume(volumeId, None, None) |
| self.poolId = newVolume.poolId |
| self.path = newVolume.path |
| return True |
| |
| def update(self, volumeUuid, volumeSize, name): |
| setParams = {"path":volumeUuid, "size":volumeSize, "name":name} |
| whereParams = {"id":self.id} |
| return self.system.db.updateDbValues("volumes", setParams, whereParams) |
| |
| @staticmethod |
| def getSrcVolumes(user, vm): |
| volumes = [] |
| columns = ["id", "name", "host_id", "path", "iscsi_name", "volume_type", "offering_id"] |
| volumeRows = GLOBALS["SRC_SYSTEM"].db.getDbValues("volumes", columns, {"account_id":user.accountId, "instance_id":vm.id, "removed":"null"}) |
| for volumeRow in volumeRows: |
| volumes.append(Volume(GLOBALS["SRC_SYSTEM"], volumeRow["id"], volumeRow["name"], volumeRow["host_id"], volumeRow["path"], volumeRow["iscsi_name"], volumeRow["volume_type"], volumeRow["offering_id"])) |
| return volumes |
| |
| @staticmethod |
| def getDestVolume(volumeId, vm, volumeType): |
| columns = ["id", "name", "pool_id", "path", "disk_offering_id"] |
| whereParams = None |
| if (volumeId != None): |
| whereParams = {"id":volumeId} |
| else: |
| whereParams = {"instance_id":vm.id, "volume_type":volumeType} |
| volumeRows = GLOBALS["DEST_SYSTEM"].db.getDbValues("volumes", columns, whereParams) |
| if (len(volumeRows) > 0): |
| volumeRow = volumeRows[0] |
| return Volume(GLOBALS["DEST_SYSTEM"], volumeRow["id"], volumeRow["name"], volumeRow["pool_id"], volumeRow["path"], None, volumeType, volumeRow["disk_offering_id"]) |
| else: |
| return None |
| |
| class DomainRouter: |
| # Vars: id, system, user |
| |
| def __init__(self, user): |
| self.system = user.system |
| self.user = user |
| self.id = self.getId() |
| |
| def __str__(self): |
| return "DomainRouter: %s" % (self.user) |
| |
| @basicLogDecorator("Stopping user's router...") |
| def stop(self): |
| if (self.id == None): |
| raise Exception("Could not find router for " + str(self.user)) |
| params = {"id":self.id} |
| if (self.system.api.asyncApi): |
| return self.system.api.runAsyncApiCommand("stopRouter", params, "router") |
| else: |
| return self.system.api.runSyncApiCommand("stopRouter", params, None) |
| |
| @basicLogDecorator("Starting user's router...") |
| def start(self): |
| if (self.id == None): |
| return False |
| params = {"id":self.id} |
| if (self.system.api.asyncApi): |
| return self.system.api.runAsyncApiCommand("startRouter", params, "router") |
| else: |
| return self.system.api.runSyncApiCommand("startRouter", params, None) |
| |
| @basicLogDecorator("Rebooting user's router...") |
| def reboot(self): |
| if (self.id == None): |
| return False |
| params = {"id":self.id} |
| return self.system.api.runAsyncApiCommand("rebootRouter", params, "router") |
| |
| def getId(self): |
| routers = self.system.db.getDbValues("domain_router", ["id"], {"account_id":self.user.accountId}) |
| if (len(routers) > 0): |
| return routers[0]["id"] |
| else: |
| return None |
| |
| class PublicIp: |
| # Vars: system, user, address, zoneId, sourceNat, allocated |
| |
| def __init__(self, system, user, address, zoneId, sourceNat, allocated): |
| self.system = system |
| self.user = user |
| self.address = address |
| self.zoneId = zoneId |
| self.sourceNat = sourceNat |
| self.allocated = allocated |
| |
| def __str__(self): |
| return self.address |
| |
| def __repr__(self): |
| return self.address |
| |
| def allocate(self): |
| setParams = {"account_id":self.user.accountId, |
| "domain_id":self.user.domainId, |
| "source_nat":self.sourceNat, |
| "allocated":self.allocated} |
| whereParams = {"public_ip_address":self.address, |
| "data_center_id":self.zoneId} |
| return self.system.db.updateDbValues("user_ip_address", setParams, whereParams) |
| |
| @staticmethod |
| @basicLogDecorator("Clearing existing public IPs...") |
| def clearPublicIps(user): |
| system = user.system |
| setParams = {"account_id":"null", "domain_id":"null", "source_nat":"0", "allocated":"null"} |
| whereParams = {"account_id":user.accountId} |
| return system.db.updateDbValues("user_ip_address", setParams, whereParams) |
| |
| @staticmethod |
| @basicLogDecorator("Migrating allocated public IPs...") |
| def migrateAllocatedPublicIps(srcUser, destUser): |
| # Get a list of public IPs allocated to the source user |
| ips = PublicIp.getAllocatedPublicIps(srcUser) |
| |
| # Allocate each one of these IPs in the dest system |
| for ip in ips: |
| ip.system = GLOBALS["DEST_SYSTEM"] |
| ip.user = destUser |
| if (not ip.allocate()): |
| return None |
| |
| return ips |
| |
| @staticmethod |
| def getAllocatedPublicIps(user): |
| system = user.system |
| ips = [] |
| columns = ["public_ip_address", "data_center_id", "source_nat", "allocated"] |
| ipRows = system.db.getDbValues("user_ip_address", columns, {"account_id":user.accountId}) |
| for ipRow in ipRows: |
| ips.append(PublicIp(system, user, ipRow["public_ip_address"], ipRow["data_center_id"], ipRow["source_nat"], ipRow["allocated"])) |
| return ips |
| |
| @staticmethod |
| def getGuestIpAddress(system, vmId): |
| columns = ["guest_ip_address"] |
| guestIp = system.db.getDbValues("user_vm", columns, {"id":vmId})[0] |
| return guestIp["guest_ip_address"] |
| |
| class ForwardingRule: |
| @staticmethod |
| @basicLogDecorator("Migrating port forwarding and load balancer rules...") |
| def migrateForwardingRules(srcUser, destUser, publicIps, vmIdMap): |
| for publicIp in publicIps: |
| forwardingRules = ForwardingRule.getSrcForwardingRules(srcUser, destUser, publicIp.address, vmIdMap) |
| for forwardingRule in forwardingRules: |
| newRuleId = forwardingRule.createInDestSystem() |
| if (newRuleId == None): |
| return False |
| return True |
| |
| @staticmethod |
| def getSrcForwardingRules(srcUser, destUser, address, vmIdMap): |
| # vmIdMap maps UserVM database IDs in the src system to UserVM database IDs in the dest system |
| columns = ["id", "public_port", "private_ip_address", "private_port", "enabled", "protocol", "forwarding", "algorithm"] |
| ruleRows = GLOBALS["SRC_SYSTEM"].db.getDbValues("ip_forwarding", columns, {"public_ip_address":address}) |
| activeRules = [] |
| for ruleRow in ruleRows: |
| srcVmId = VM.getVmId(GLOBALS["SRC_SYSTEM"], srcUser.accountId, ruleRow["private_ip_address"]) |
| destVmId = vmIdMap.get(srcVmId) |
| if (destVmId == None): |
| continue |
| |
| if (ruleRow["forwarding"] == "1"): |
| activeRules.append(ForwardingRule.PortForwardingRule(address, ruleRow["public_port"], PublicIp.getGuestIpAddress(GLOBALS["DEST_SYSTEM"], destVmId), ruleRow["private_port"], ruleRow["enabled"], ruleRow["protocol"])) |
| else: |
| activeRules.append(ForwardingRule.LoadBalancerRule(destUser.accountId, address, ruleRow["public_port"], ruleRow["private_port"], destVmId, ruleRow["algorithm"])) |
| |
| return activeRules |
| |
| class PortForwardingRule: |
| def __init__(self, publicIp, publicPort, privateIp, privatePort, enabled, protocol): |
| self.publicIp = publicIp |
| self.publicPort = publicPort |
| self.privateIp = privateIp |
| self.privatePort = privatePort |
| self.enabled = enabled |
| self.protocol = protocol |
| |
| def createInDestSystem(self): |
| setParams = dict() |
| setParams["public_ip_address"] = self.publicIp |
| setParams["public_port"] = self.publicPort |
| setParams["private_ip_address"] = self.privateIp |
| setParams["private_port"] = self.privatePort |
| setParams["enabled"] = self.enabled |
| setParams["protocol"] = self.protocol |
| setParams["forwarding"] = "1" |
| setParams["algorithm"] = "null" |
| setParams["group_id"] = "null" |
| return GLOBALS["DEST_SYSTEM"].db.insertIntoDb("ip_forwarding", setParams) |
| |
| class LoadBalancerRule: |
| def __init__(self, accountId, ip, publicPort, privatePort, vmId, algorithm): |
| self.accountId = accountId |
| self.ip = ip |
| self.publicPort = publicPort |
| self.privatePort = privatePort |
| self.vmId = vmId |
| self.algorithm = algorithm |
| |
| def createInDestSystem(self): |
| setParams = dict() |
| setParams["name"] = str(self.publicPort) + "-" + str(self.privatePort) |
| setParams["account_id"] = self.accountId |
| setParams["ip_address"] = self.ip |
| setParams["public_port"] = self.publicPort |
| setParams["private_port"] = self.privatePort |
| setParams["algorithm"] = self.algorithm |
| newLoadBalancerRuleId = GLOBALS["DEST_SYSTEM"].db.insertIntoDb("load_balancer", setParams) |
| if (newLoadBalancerRuleId == None or newLoadBalancerRuleId == "0"): |
| return None |
| setParams = dict() |
| setParams["load_balancer_id"] = newLoadBalancerRuleId |
| setParams["instance_id"] = self.vmId |
| return GLOBALS["DEST_SYSTEM"].db.insertIntoDb("load_balancer_vm_map", setParams) |
| |
| class SR: |
| def __init__(self, system, ip, iqn, uuid): |
| self.system = system |
| self.ip = ip |
| self.iqn = iqn |
| self.uuid = uuid |
| |
| def __str__(self): |
| return "SR: %s | ip: %s | iqn: %s | uuid: %s" % (self.system, self.ip, self.iqn, self.uuid) |
| |
| @verboseLogDecorator("Finding SR...") |
| def find(self): |
| xenapi = self.system.xenapi |
| self.ref = xenapi.SR.get_by_name_label(self.uuid)[0] |
| return True |
| |
| @verboseLogDecorator("Finding source system's iSCSI SR...") |
| def create(self): |
| xenapi = self.system.xenapi |
| host = xenapi.host.get_all()[0] |
| deviceConfig = {'targetIQN': self.iqn, 'target': self.ip} |
| srRef = None |
| name = "1.0 iSCSI pool: " + self.ip + "-" + self.iqn |
| srRef = xenapi.SR.create(host, deviceConfig, "0", name, name, "iscsi", "user", True) |
| if (srRef != None): |
| self.ref = srRef |
| return True |
| else: |
| return False |
| |
| @staticmethod |
| def getExistingSrcSr(ip, iqn): |
| xenapi = GLOBALS["DEST_SYSTEM"].xenapi |
| srRefs = xenapi.SR.get_all() |
| for srRef in srRefs: |
| srNameLabel = xenapi.SR.get_name_label(srRef) |
| if (srNameLabel == "1.0 iSCSI pool: " + ip + "-" + iqn): |
| sr = SR(GLOBALS["DEST_SYSTEM"], ip, iqn, xenapi.SR.get_uuid(srRef)) |
| sr.ref = srRef |
| return sr |
| return None |
| |
| @staticmethod |
| @verboseLogDecorator("Forgetting all src iSCSI SRs...") |
| def forgetAllSrcSrs(): |
| xenapi = GLOBALS["DEST_SYSTEM"].xenapi |
| srRefs = xenapi.SR.get_all() |
| for srRef in srRefs: |
| srNameLabel = xenapi.SR.get_name_label(srRef) |
| if ("1.0 iSCSI pool" in srNameLabel): |
| # Unplug and destroy the SR's PBDs |
| pbdRefs = xenapi.SR.get_PBDs(srRef) |
| for pbdRef in pbdRefs: |
| xenapi.PBD.unplug(pbdRef) |
| xenapi.PBD.destroy(pbdRef) |
| |
| # Forget the SR |
| xenapi.SR.forget(srRef) |
| |
| return True |
| |
| class VDI: |
| def __init__(self, sr, volume, uuid): |
| self.sr = sr |
| self.volume = volume |
| self.uuid = uuid |
| self.find() |
| |
| def __str__(self): |
| return "VDI: %s | uuid: %s" % (self.volume, self.uuid) |
| |
| @verboseLogDecorator("Getting virtual size for VDI...") |
| def getVirtualSize(self): |
| xenapi = self.sr.system.xenapi |
| return xenapi.VDI.get_virtual_size(self.ref) |
| |
| @basicLogDecorator("Copying source system volume to dest system...") |
| def copy(self, destSR): |
| xenapi = self.sr.system.xenapi |
| newVdiRef = xenapi.VDI.copy(self.ref, destSR.ref) |
| return xenapi.VDI.get_uuid(newVdiRef) |
| |
| @basicLogDecorator("Destroying old volume...") |
| def destroy(self): |
| xenapi = self.sr.system.xenapi |
| xenapi.VDI.destroy(self.ref) |
| return True |
| |
| @verboseLogDecorator("Finding VDI in SR...") |
| def find(self): |
| xenapi = self.sr.system.xenapi |
| if (self.uuid == None): |
| # Run an sr-scan |
| xenapi.SR.scan(self.sr.ref) |
| |
| # Get a list of VDIs in the SR |
| vdiRefs = xenapi.SR.get_VDIs(self.sr.ref) |
| |
| # Find the VDI that has the same SCSI ID as the specified volume |
| volumeScsiId = self.volume.iscsiName.split(":")[-1].strip() |
| for vdiRef in vdiRefs: |
| smConfig = xenapi.VDI.get_sm_config(vdiRef) |
| vdiScsiId = smConfig["SCSIid"].strip()[1:] |
| if (vdiScsiId == volumeScsiId): |
| self.ref = vdiRef |
| self.uuid = xenapi.VDI.get_uuid(vdiRef) |
| return True |
| |
| return False |
| else: |
| self.ref = xenapi.VDI.get_by_uuid(self.uuid) |
| return True |
| |
| @basicLogDecorator("Changing disk name for VDI...") |
| def changeBootableDeviceName(self): |
| system = self.volume.system |
| xenapi = system.xenapi |
| controlDomainRef = system.controlDomainRef |
| |
| vbdRef = None |
| try: |
| # Create a VBD for the VDI |
| vbd = {'bootable': True, 'userdevice': '0', 'VDI': self.ref, |
| 'other_config': {}, 'VM': controlDomainRef, |
| 'mode': 'rw', 'qos_algorithm_type': '', 'qos_algorithm_params': {}, |
| 'type': 'Disk', 'empty': False, 'unpluggable': True} |
| vbdRef = xenapi.VBD.create(vbd) |
| |
| # Plug the VBD |
| xenapi.VBD.plug(vbdRef) |
| |
| # Create a temporary directory |
| if (not system.runSshCommand("mkdir -p /root/temp")): |
| raise Exception ("Failed to create directory /root/temp") |
| |
| # Check if /dev/xvda1 exists |
| xvda1Exists = system.runSshCommand("ls /dev/xvda1") |
| |
| # If /dev/xvda1 doesn't exist, work with /dev/xvda |
| if (not xvda1Exists): |
| # Mount /dev/xvda to /root/temp |
| if (not system.runSshCommand("mount /dev/xvda /root/temp")): |
| raise Exception("Failed to mount /dev/xvda to /root/temp") |
| writeToLog("Using /dev/xvda to change bootable device name.", False) |
| else: |
| # Mount /dev/xvda1 to /root/temp |
| if (not system.runSshCommand("mount /dev/xvda1 /root/temp")): |
| raise Exception("Failed to mount /dev/xvda1 to /root/temp") |
| |
| # If the boot directory exists under /root/temp, we can work with xvda1 |
| if (system.runSshCommand("ls /root/temp/boot")): |
| writeToLog("Using /dev/xvda1 to change bootable device name.", False) |
| else: |
| # If the boot directory doesn't exist under /root/temp, we need to work with /dev/xvda2 |
| |
| # Check that /dev/xvda2 exists |
| if (not system.runSshCommand("ls /dev/xvda2")): |
| raise Exception("/dev/xvda1 exists but /dev/xvda2 doesn't exist") |
| |
| # Unmount /dev/xvda1 |
| if (not system.runSshCommand("umount /root/temp")): |
| raise Exception("Failed to unmount /dev/xvda1") |
| |
| # Mount /dev/xvda2 |
| if (not system.runSshCommand("mount /dev/xvda2 /root/temp")): |
| raise Exception("Failed to mount /dev/xvda2") |
| |
| writeToLog("Using /dev/xvda2 to change bootable device name.", False) |
| |
| # Modify fstab, grub.conf, and device.map, if they exist |
| for fileToModify in ["/root/temp/etc/fstab", "/root/temp/boot/grub/grub.conf", "/root/temp/boot/grub/device.map"]: |
| if (system.runSshCommand("ls " + fileToModify)): |
| if (not system.runSshCommand("sed -i 's_/dev/sda_/dev/xvda_' " + fileToModify)): |
| raise Exception("Failed to modify " + fileToModify) |
| finally: |
| # Unmount /root/temp if necessary |
| if (system.runSshCommand("mount | grep '/root/temp'")): |
| if (not system.runSshCommand("umount /root/temp")): |
| raise Exception("Failed to unmount /root/temp") |
| |
| # Delete /root/temp |
| system.runSshCommand("rm -rf /root/temp") |
| |
| if (vbdRef != None): |
| # Unplug the VBD |
| xenapi.VBD.unplug(vbdRef) |
| |
| # Destroy the VBD |
| xenapi.VBD.destroy(vbdRef) |
| |
| return True |
| |
| class StoragePool: |
| # Vars: id, uuid |
| |
| def __init__(self, storagePoolId, uuid): |
| self.id = storagePoolId |
| self.uuid = uuid |
| |
| @staticmethod |
| def getStoragePool(system, storagePoolId): |
| columns = ["id", "uuid"] |
| storagePoolRow = system.db.getDbValues("storage_pool", columns, {"id":storagePoolId})[0] |
| return StoragePool(storagePoolRow["id"], storagePoolRow["uuid"]) |
| |
| class Host: |
| # Vars: id, ip, iqn |
| |
| def __init__(self, hostId, ip, iqn): |
| self.id = hostId |
| self.ip = ip |
| self.iqn = iqn |
| |
| def __str__(self): |
| return "Host: id: %s | ip %s" % (self.id, self.ip) |
| |
| @staticmethod |
| @basicLogDecorator("Sharing LUs...") |
| def shareAllLus(): |
| return Host.shareOrUnshareAllLus(True) |
| |
| @staticmethod |
| @basicLogDecorator("Unsharing LUs...") |
| def unshareAllLus(): |
| return Host.shareOrUnshareAllLus(False) |
| |
| @staticmethod |
| def shareOrUnshareAllLus(share): |
| # Get a map of XenServer IPs -> IQNs |
| xenServerIqns = Host.getXenServerIqns() |
| |
| # Get a map of storage host IPs in the source system -> passwords |
| storageHostPasswords = GLOBALS["STORAGE_HOST_PASSWORDS"] |
| |
| # Copy share_all_lus.sh to each storage host and run with each XenServer IQN |
| for ip in storageHostPasswords.keys(): |
| password = storageHostPasswords[ip] |
| sshConn = paramiko.SSHClient() |
| sshConn.set_missing_host_key_policy(paramiko.AutoAddPolicy()) |
| sshConn.connect(ip, username = "root", password = password) |
| sftpConn = sshConn.open_sftp() |
| sftpConn.put("share_all_lus.sh", "/root/share_all_lus.sh") |
| for xenServerIp in xenServerIqns.keys(): |
| iqn = xenServerIqns[xenServerIp] |
| command = "bash /root/share_all_lus.sh -i " + iqn |
| if (share): |
| command += " -s" |
| else: |
| command += " -u" |
| stdin, stdout, stderr = sshConn.exec_command(command) |
| if (stdin.channel.recv_exit_status() != 0): |
| return False |
| if (not share): |
| sftpConn.remove("/root/share_all_lus.sh") |
| sshConn.close() |
| sftpConn.close() |
| |
| return True |
| |
| @staticmethod |
| def getHost(system, hostId): |
| columns = ["id", "private_ip_address", "iqn"] |
| hostRow = system.db.getDbValues("host", columns, {"id":hostId})[0] |
| return Host(hostRow["id"], hostRow["private_ip_address"], hostRow["iqn"]) |
| |
| @staticmethod |
| def getStorageHostIps(): |
| storageHostIps = [] |
| columns = ["private_ip_address"] |
| hostRows = GLOBALS["SRC_SYSTEM"].db.getDbValues("host", columns, {"type":"Storage"}) |
| for hostRow in hostRows: |
| storageHostIps.append(hostRow["private_ip_address"]) |
| return storageHostIps |
| |
| @staticmethod |
| def getXenServerIqns(): |
| xenServerIqns = dict() |
| columns = ["private_ip_address", "url"] |
| hostRows = GLOBALS["DEST_SYSTEM"].db.getDbValues("host", columns, {"type":"Routing"}) |
| for hostRow in hostRows: |
| xenServerIqns[hostRow["private_ip_address"]] = hostRow["url"] |
| return xenServerIqns |
| |
| |
| ### Runtime |
| |
| GLOBALS = dict() |
| |
| @basicLogDecorator("Reading upgrade.properties...") |
| def readUpgradeProperties(): |
| upgradePropertiesFile = open("upgrade.properties", "r") |
| upgradeProperties = upgradePropertiesFile.read().splitlines() |
| for upgradeProperty in upgradeProperties: |
| if (upgradeProperty.strip() == ""): |
| continue |
| elif (upgradeProperty.startswith("#")): |
| continue |
| else: |
| propList = upgradeProperty.split("=") |
| var = propList[0].strip() |
| val = propList[1].strip() |
| if (val == ""): |
| continue |
| GLOBALS[var] = val |
| |
| # Create the log file |
| logFilePath = GLOBALS["LOG_FILE"] |
| GLOBALS["LOG_FILE"] = open(logFilePath, "a") |
| |
| # Create the guest OS map |
| GLOBALS["GUEST_OS_MAP"] = csvToMap(GLOBALS["GUEST_OS_MAP"]) |
| |
| # Create the XenServer passwords map |
| if (GLOBALS.get("DEST_XENSERVER_PASSWORDS") != None): |
| GLOBALS["DEST_XENSERVER_PASSWORDS"] = csvToMap(GLOBALS.get("DEST_XENSERVER_PASSWORDS")) |
| |
| # Create the Storage Host passwords map |
| if (GLOBALS.get("STORAGE_HOST_PASSWORDS") == None): |
| raise Exception ("Please fill out the variable STORAGE_HOST_PASSWORDS in upgrade.properties.") |
| else: |
| GLOBALS["STORAGE_HOST_PASSWORDS"] = csvToMap(GLOBALS["STORAGE_HOST_PASSWORDS"]) |
| |
| # Create the list of users to upgrade |
| if GLOBALS.has_key("USERS"): |
| GLOBALS["USERS"] = [userId.strip() for userId in GLOBALS["USERS"].split(",")] |
| else: |
| GLOBALS["USERS"] = None |
| |
| return True |
| |
| def csvToMap(csv): |
| entries = csv.split(",") |
| entryMap = dict() |
| for entry in entries: |
| entryList = entry.strip().split(":") |
| key = entryList[0].strip() |
| val = entryList[1].strip() |
| entryMap[key] = val |
| return entryMap |
| |
| @basicLogDecorator("Running diagnostic...") |
| def runDiagnostic(): |
| # Either one XenServer IP and password should be specified, or a mapping between XenServer IPs and passwords should be specified |
| if ((GLOBALS.get("DEST_XENSERVER_IP") == None and (GLOBALS.get("DEST_XENSERVER_PASSWORD") != None or GLOBALS.get("DEST_XENSERVER_PASSWORDS") == None)) |
| or (GLOBALS.get("DEST_XENSERVER_IP") != None and (GLOBALS.get("DEST_XENSERVER_PASSWORD") == None or GLOBALS.get("DEST_XENSERVER_PASSWORDS") != None))): |
| raise Exception("Please specify the IP and root password for one XenServer (if all XenServers have the same root password), or the IPs and root passwords of all XenServers.") |
| |
| GLOBALS["SRC_SYSTEM"] = System(GLOBALS["SRC_MANAGEMENT_SERVER_IP"], False, None, None, None, "vmops", GLOBALS["SRC_DB_LOGIN"], |
| GLOBALS.get("SRC_DB_PASSWORD"), GLOBALS["SRC_ZONE_ID"], None, None, None, None) |
| |
| GLOBALS["DEST_SYSTEM"] = System(GLOBALS["DEST_MANAGEMENT_SERVER_IP"], True, GLOBALS.get("DEST_XENSERVER_IP"), |
| GLOBALS.get("DEST_XENSERVER_PASSWORD"), GLOBALS.get("DEST_XENSERVER_PASSWORDS"), |
| "cloud", GLOBALS["DEST_DB_LOGIN"], GLOBALS.get("DEST_DB_PASSWORD"), GLOBALS["DEST_ZONE_ID"], |
| GLOBALS["DEST_TEMPLATE_ID"], GLOBALS["DEST_ISO_ID"], GLOBALS["DEST_SERVICE_OFFERING_ID"], |
| GLOBALS["DEST_DISK_OFFERING_ID"]) |
| |
| srcSystemServiceOfferings = ServiceOffering.getSrcSystemServiceOfferings() |
| for srcSystemServiceOffering in srcSystemServiceOfferings: |
| # Every service offering in the src system must have a corresponding service offering in the dest system |
| destSystemServiceOffering = ServiceOffering.getCorrespondingServiceOffering(srcSystemServiceOffering.id) |
| if (destSystemServiceOffering == None): |
| raise Exception("No corresponding service offering found for: " + str(srcSystemServiceOffering)) |
| |
| # Every service offering in the src system has a corresponding disk offering in the dest system |
| destSystemDiskOffering = DiskOffering.getCorrespondingDiskOffering(srcSystemServiceOffering.id) |
| if (destSystemDiskOffering == None): |
| raise Exception("No corresponding disk offering found for: " + str(srcSystemServiceOffering)) |
| |
| # Every template ID in the src system has a valid entry in GUEST_OS_MAP |
| srcSystemTemplateIds = VM.getTemplateIds(GLOBALS["SRC_SYSTEM"]) |
| for templateId in srcSystemTemplateIds: |
| if (not GLOBALS["GUEST_OS_MAP"].has_key(templateId)): |
| raise Exception("No corresponding guest OS ID for templateId: " + templateId) |
| else: |
| guestOsId = GLOBALS["GUEST_OS_MAP"][templateId] |
| guestOsName = VM.getGuestOsName(GLOBALS["DEST_SYSTEM"], guestOsId) |
| if (guestOsName == None): |
| raise Exception("The guest OS ID that corresponds to template ID: " + templateId + " is not valid.") |
| |
| # The dest system's ISO id must be valid |
| template = VM.getTemplate(GLOBALS["DEST_SYSTEM"], GLOBALS["DEST_ISO_ID"]) |
| if (template == None or template["format"] != "ISO"): |
| raise Exception("The dest system ISO ID is not valid.") |
| |
| # Verify that all source system storage hosts have a password |
| storageHostIps = Host.getStorageHostIps() |
| for ip in storageHostIps: |
| if (ip not in GLOBALS["STORAGE_HOST_PASSWORDS"].keys()): |
| raise Exception("The storage host IP: " + str(ip) + " has no entry in STORAGE_HOST_PASSWORDS.") |
| |
| return True |
| |
| @basicLogDecorator("Starting CloudStack Migration (1.0 -> 2.1)...") |
| def upgradeUsers(userIds, onlyMigratePublicIps): |
| # Read variables from upgrade.properties |
| readUpgradeProperties() |
| |
| # Run the diagnostic |
| runDiagnostic() |
| |
| if (userIds == None): |
| if (GLOBALS["USERS"] == None): |
| raise Exception("Please specify one or more users to upgrade.") |
| else: |
| userIds = GLOBALS["USERS"] |
| |
| # Make sure all users are valid |
| for userId in userIds: |
| if (User.get(GLOBALS["SRC_SYSTEM"], userId) == None): |
| raise Exception("The user ID: " + str(userId) + " is not valid.") |
| |
| if (not onlyMigratePublicIps): |
| # Share all LUs |
| Host.shareAllLus() |
| |
| try: |
| for userId in userIds: |
| doUpgrade(userId, onlyMigratePublicIps) |
| return True |
| finally: |
| if (not onlyMigratePublicIps): |
| # Forget all iSCSI SRs |
| SR.forgetAllSrcSrs() |
| # Unshare all LUs |
| Host.unshareAllLus() |
| |
| def doUpgrade(userId, onlyMigratePublicIps): |
| # Get the specified user from the source system |
| srcUser = User.get(GLOBALS["SRC_SYSTEM"], userId) |
| |
| writeToLog("\nStarting migration for " + str(srcUser), True) |
| |
| # Create a new user in the destination system with the same attributes as the original user |
| destUser = User.create(GLOBALS["DEST_SYSTEM"], srcUser) |
| |
| if (not srcUser.alreadyMigrated()): |
| # Allocate the src user's public IPs in the dest system |
| allocatedPublicIps = PublicIp.migrateAllocatedPublicIps(srcUser, destUser) |
| |
| if (onlyMigratePublicIps): |
| writeToLog("\nMigrated public IPs for " + str(srcUser), True) |
| return |
| |
| # Stop the source user's DomR |
| srcUserDomR = DomainRouter(srcUser) |
| |
| # Only migrate the user's VMs if there is a DomR |
| if (srcUserDomR.id != None): |
| srcUserDomR.stop() |
| |
| # If the dest user doesn't have a DomR, deploy a temporary VM |
| destUserDomR = DomainRouter(destUser) |
| tempVm = None |
| if (destUserDomR.id == None): |
| tempVm = VM(None, destUser, None, None, None) |
| tempVm.deployTemp() |
| destUserDomR.id = destUserDomR.getId() |
| else: |
| tempVm = VM.getTempVm(destUser) |
| |
| # Migrate the source user's VM's to the dest system |
| vmIdMap = VM.migrateVirtualMachines(srcUser, destUser) |
| |
| # Migrate the source user's port forwarding and load balancer rules |
| ForwardingRule.migrateForwardingRules(srcUser, destUser, allocatedPublicIps, vmIdMap) |
| |
| # Reboot the dest user's router |
| destUserDomR.reboot() |
| |
| # Destroy the temporary VM |
| if (tempVm != None): |
| tempVm.destroy() |
| |
| srcUser.tagAsMigrated() |
| else: |
| writeToLog("\n" + str(srcUser) + " has already been migrated.", True) |
| |
| writeToLog("\nMigration was successful for " + str(srcUser), True) |
| |
| if (len(sys.argv) > 1): |
| if (sys.argv[1].lower() == "publicips"): |
| if (len(sys.argv) > 2): |
| upgradeUsers(sys.argv[2:], True) |
| else: |
| upgradeUsers(None, True) |
| else: |
| upgradeUsers(sys.argv[1:], False) |
| else: |
| upgradeUsers(None, False) |
| |