# 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)