def connectToDatabases(self, mysqlRootsPassword, statusDBName, commandsDBName, statusdbSQLFilePath, commandsDBSQLFilePath, websiteUser, websiteUserPassword, endpointUser, endpointUserPassword, minCommandInterval): """ Establece la conexión con la base de datos de estado y con la base de datos de comandos. Argumentos: mysqlRootsPassword: la contraseña de root de MySQL statusDBName: el nombre de la base de datos de estado statusdbSQLFilePath: la ruta del script que crea la base de datos de estado websiteUser: nombre de usuario que usará la web para manipular las bases de datos websiteUserPassword: contraseña del usuario de la web endpointUser: usuario que utilizará en eldpoint para manipular las bases de datos de estado. Será el único que puede escribir en la base de datos de estado. endpointUserPassword: contraseña del usuario del endpoint """ # Crear las bases de datos self.__rootsPassword = mysqlRootsPassword self.__statusDatabaseName = statusDBName self.__commandsDatabaseName = commandsDBName configurator = DBConfigurator(mysqlRootsPassword) configurator.runSQLScript(statusDBName, statusdbSQLFilePath) configurator.runSQLScript(commandsDBName, commandsDBSQLFilePath) # Registrar en ellas los usuarios configurator.addUser(websiteUser, websiteUserPassword, statusDBName, False) configurator.addUser(endpointUser, endpointUserPassword, statusDBName, True) configurator.addUser(websiteUser, websiteUserPassword, commandsDBName, True) configurator.addUser(endpointUser, endpointUserPassword, commandsDBName, True) # Crear los conectores self.__commandsDBConnector = CommandsDatabaseConnector(endpointUser, endpointUserPassword, commandsDBName, minCommandInterval) self.__endpointDBConnector = ClusterEndpointDBConnector(endpointUser, endpointUserPassword, statusDBName)
def connectToDatabases(self, statusDBName, commandsDBName, databaseUser, databasePassword): """ Establece una conexión con las bases de datos de estado y comandos. Argumentos: statusDBName: el nombre de la base de datos de estado commandsDBName: el nombre de la base de datos de comandos databaseUser: el nombre de usuario con el que se accederá a las dos bases de datos databasePassword: la contraseña para acceder a las bases de datos Devuelve: Nada """ self.__endpointDBConnector = ClusterEndpointDBConnector( databaseUser, databasePassword, statusDBName) self.__commandsDBConnector = CommandsDatabaseConnector( databaseUser, databasePassword, commandsDBName, 1)
def connectToDatabases(self, statusDBName, commandsDBName, databaseUser, databasePassword): """ Establece una conexión con las bases de datos de estado y comandos. Argumentos: statusDBName: el nombre de la base de datos de estado commandsDBName: el nombre de la base de datos de comandos databaseUser: el nombre de usuario con el que se accederá a las dos bases de datos databasePassword: la contraseña para acceder a las bases de datos Devuelve: Nada """ self.__endpointDBConnector = ClusterEndpointDBConnector(databaseUser, databasePassword, statusDBName) self.__commandsDBConnector = CommandsDatabaseConnector(databaseUser, databasePassword, commandsDBName, 1)
def connectToDatabases(self, mysqlRootsPassword, statusDBName, commandsDBName, statusdbSQLFilePath, commandsDBSQLFilePath, websiteUser, websiteUserPassword, endpointUser, endpointUserPassword, minCommandInterval): """ Establece la conexión con la base de datos de estado y con la base de datos de comandos. Argumentos: mysqlRootsPassword: la contraseña de root de MySQL statusDBName: el nombre de la base de datos de estado statusdbSQLFilePath: la ruta del script que crea la base de datos de estado websiteUser: nombre de usuario que usará la web para manipular las bases de datos websiteUserPassword: contraseña del usuario de la web endpointUser: usuario que utilizará en eldpoint para manipular las bases de datos de estado. Será el único que puede escribir en la base de datos de estado. endpointUserPassword: contraseña del usuario del endpoint """ # Crear las bases de datos self.__rootsPassword = mysqlRootsPassword self.__statusDatabaseName = statusDBName self.__commandsDatabaseName = commandsDBName configurator = DBConfigurator(mysqlRootsPassword) configurator.runSQLScript(statusDBName, statusdbSQLFilePath) configurator.runSQLScript(commandsDBName, commandsDBSQLFilePath) # Registrar en ellas los usuarios configurator.addUser(websiteUser, websiteUserPassword, statusDBName, False) configurator.addUser(endpointUser, endpointUserPassword, statusDBName, True) configurator.addUser(websiteUser, websiteUserPassword, commandsDBName, True) configurator.addUser(endpointUser, endpointUserPassword, commandsDBName, True) # Crear los conectores self.__commandsDBConnector = CommandsDatabaseConnector( endpointUser, endpointUserPassword, commandsDBName, minCommandInterval) self.__endpointDBConnector = ClusterEndpointDBConnector( endpointUser, endpointUserPassword, statusDBName)
class ClusterServerConnector(object): """ Estos objetos comunican la web y el endpoint a través de memoria compartida. """ def __init__(self, userID): """ Inicializa el estado del conector Argumentos: userID: el identificador del usuario que accede al sistema. Se trata de un entero. """ self.__userID = userID def connectToDatabases(self, statusDBName, commandsDBName, databaseUser, databasePassword): """ Establece una conexión con las bases de datos de estado y comandos. Argumentos: statusDBName: el nombre de la base de datos de estado commandsDBName: el nombre de la base de datos de comandos databaseUser: el nombre de usuario con el que se accederá a las dos bases de datos databasePassword: la contraseña para acceder a las bases de datos Devuelve: Nada """ self.__endpointDBConnector = ClusterEndpointDBConnector(databaseUser, databasePassword, statusDBName) self.__commandsDBConnector = CommandsDatabaseConnector(databaseUser, databasePassword, commandsDBName, 1) def dispose(self): """ Cierra las conexiones con las bases de datos Argumentos: Ninguno Devuelve: Nada """ pass def getActiveVMsData(self, showAllVMs=False): """ Devuelve los datos de las máquinas virtuales activas Argumentos: showAllVMs: si es True, se muestran los datos de todas las máquinas activas; si es False, sólo las del usuario registrado en el conector Devuelve: una lista de diccionarios con los datos de las máquinas virtuales activas """ if not showAllVMs : userID = self.__userID else : userID = None return self.__endpointDBConnector.getActiveVMsData(userID) def getVMDistributionData(self): """ Devuelve los datos de distribución de las imágenes Argumentos: Ninguno Devuelve: una lista de diccionarios con los datos de distribución de las imágenes """ return self.__endpointDBConnector.getVMDistributionData() def getVMServersData(self): """ Devuelve los datos básicos de los servidores de máquinas virtuales Argumentos: Ninguno Devuelve: una lista de diccionarios con los datos básicos de los servidores de máquinas virtuales """ return self.__endpointDBConnector.getVMServersData() def registerVMServer(self, vmServerIP, vmServerPort, vmServerName, isVanillaServer): """ Registra un servidor de máquinas virtuales Argumentos: vmServerIP: la IP del servidor de máquinas virtuales vmServerPort: el puerto en el que escucha vmServerName: el nombre del servidor de máquinas virtuales isVanillaServer: indica si el servidor de máquinas virtuales se usará preferentemente para editar imágenes vanilla o no. Devuelve: El identificador único del comando. @attention: La representación del identificador único del comando puede cambiar sin previo aviso. """ (commandType, commandArgs) = CommandsHandler.createVMServerRegistrationCommand(vmServerIP, vmServerPort, vmServerName, isVanillaServer) return self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def unregisterVMServer(self, vmServerNameOrIP, isShutDown): """ Borra un servidor de máquinas virtuales Argumentos: vmServerNameOrIP: el nombre o la IP del servidor a borrar isShutDown: si es True, el servidor se apagará inmediatamente. Si es False, esperará a que los usuarios apaguen sus máquinas virtuales. Devuelve: El identificador único del comando. @attention: La representación del identificador único del comando puede cambiar sin previo aviso. """ (commandType, commandArgs) = CommandsHandler.createVMServerUnregistrationOrShutdownCommand(True, vmServerNameOrIP, isShutDown) return self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def shutdownVMServer(self, vmServerNameOrIP, isShutDown): """ Apaga un servidor de máquinas virtuales Argumentos: vmServerNameOrIP: el nombre o la IP del servidor a borrar isShutDown: si es True, el servidor se apagará inmediatamente. Si es False, esperará a que los usuarios apaguen sus máquinas virtuales. Devuelve: El identificador único del comando. @attention: La representación del identificador único del comando puede cambiar sin previo aviso. """ (commandType, commandArgs) = CommandsHandler.createVMServerUnregistrationOrShutdownCommand(False, vmServerNameOrIP, isShutDown) return self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def bootUpVMServer(self, vmServerNameOrIP): """ Arranca un servidor de máquinas virtuales y lo añade a la infraestructura Argumentos: vmServerNameOrIP: el nombre o la IP del servidor a arrancar Devuelve: El identificador único del comando. @attention: La representación del identificador único del comando puede cambiar sin previo aviso. """ (commandType, commandArgs) = CommandsHandler.createVMServerBootCommand(vmServerNameOrIP) return self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def bootUpVM(self, imageID): """ Solicita a la infraestructura el arranque de una máquina virtual Argumentos: imageID: el identificador único de la imagen a arrancar Devuelve: El identificador único del comando. @attention: La representación del identificador único del comando puede cambiar sin previo aviso. """ (commandType, commandArgs) = CommandsHandler.createVMBootCommand(imageID, self.__userID) return self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def isShutDown(self, haltVMServers): """ Apaga toda la infraestructura Argumentos: haltVMServers: si es True, el servidor se apagará inmediatamente. Si es False, esperará a que los usuarios apaguen sus máquinas virtuales. Devuelve: Nada """ (commandType, commandArgs) = CommandsHandler.createHaltCommand(haltVMServers) self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def destroyDomain(self, domainID): """ Destruye una máquina virtual Argumentos: domainID: el identificador único de la máquina virtual a destruir Devuelve: Nada """ (commandType, commandArgs) = CommandsHandler.createDomainDestructionCommand(domainID) return self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def changeVMServerConfiguration(self, serverNameOrIPAddress, newName, newIPAddress, newPort, newVanillaImageEditionBehavior): (commandType, commandArgs) = CommandsHandler.createVMServerConfigurationChangeCommand(serverNameOrIPAddress, newName, newIPAddress, newPort, newVanillaImageEditionBehavior) return self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def getCommandOutput(self, commandID): """ Devuelve la salida de un comando Argumentos: commandID: el identificador único del comando Devuelve: - Si el comando todavía se está ejecutando, se devuelve una tupla vacía. - Si el comando se ha terminado de ejecutar, se devuelve un diccionario con los datos de su salida. """ if (self.__commandsDBConnector.isRunning(commandID)) : return () else : result = self.__commandsDBConnector.getCommandOutput(commandID) if (result != None) : (outputType, outputContent) = result result = CommandsHandler.deserializeCommandOutput(outputType, outputContent) return result def waitForCommandOutput(self, commandID): """ Espera a que un comando termine, devolviendo su salida en caso de que la haya. Argumentos: commandID: el identificador único del comando Devuelve: - None si el comando no tiene salida - Un diccionario con los datos de su salida en caso contrario @attention: Este método es bloqueante. Si se desea un comportamiento no bloqueante, es necesario utilizar el método getCommandOutput. """ while (self.__commandsDBConnector.isRunning(commandID)) : sleep(0.1) result = self.__commandsDBConnector.getCommandOutput(commandID) if result == None : return None else : return CommandsHandler.deserializeCommandOutput(result[0], result[1]) def getImageBasicData(self, imageID): return self.__endpointDBConnector.getImageBasicData(imageID) def getBootableImagesData(self, imageIDs): return self.__endpointDBConnector.getBootableImagesData(imageIDs) def getBaseImagesData(self): return self.__endpointDBConnector.getBaseImagesData() def getEditedImages(self, userID): return self.__endpointDBConnector.getEditedImages(userID) def getVanillaImageFamilyID(self, imageID): return self.__endpointDBConnector.getVanillaImageFamilyID(imageID) def getVanillaImageFamiliyData(self, vanillaImageFamilyID): return self.__endpointDBConnector.getVanillaImageFamiliyData(vanillaImageFamilyID)
class WebServerEndpoint(object): """ Estos objetos comunican un servidor de cluster con la web """ def __init__(self): """ Inicializa el estado del endpoint Argumentos: Ninguno """ self.__stopped = False def connectToDatabases(self, mysqlRootsPassword, statusDBName, commandsDBName, statusdbSQLFilePath, commandsDBSQLFilePath, websiteUser, websiteUserPassword, endpointUser, endpointUserPassword, minCommandInterval): """ Establece la conexión con la base de datos de estado y con la base de datos de comandos. Argumentos: mysqlRootsPassword: la contraseña de root de MySQL statusDBName: el nombre de la base de datos de estado statusdbSQLFilePath: la ruta del script que crea la base de datos de estado websiteUser: nombre de usuario que usará la web para manipular las bases de datos websiteUserPassword: contraseña del usuario de la web endpointUser: usuario que utilizará en eldpoint para manipular las bases de datos de estado. Será el único que puede escribir en la base de datos de estado. endpointUserPassword: contraseña del usuario del endpoint """ # Crear las bases de datos self.__rootsPassword = mysqlRootsPassword self.__statusDatabaseName = statusDBName self.__commandsDatabaseName = commandsDBName configurator = DBConfigurator(mysqlRootsPassword) configurator.runSQLScript(statusDBName, statusdbSQLFilePath) configurator.runSQLScript(commandsDBName, commandsDBSQLFilePath) # Registrar en ellas los usuarios configurator.addUser(websiteUser, websiteUserPassword, statusDBName, False) configurator.addUser(endpointUser, endpointUserPassword, statusDBName, True) configurator.addUser(websiteUser, websiteUserPassword, commandsDBName, True) configurator.addUser(endpointUser, endpointUserPassword, commandsDBName, True) # Crear los conectores self.__commandsDBConnector = CommandsDatabaseConnector(endpointUser, endpointUserPassword, commandsDBName, minCommandInterval) self.__endpointDBConnector = ClusterEndpointDBConnector(endpointUser, endpointUserPassword, statusDBName) def connectToClusterServer(self, certificatePath, clusterServerIP, clusterServerListenningPort, statusDBUpdateInterval, commandTimeout, commandTimeoutCheckInterval): """ Establece la conexión con el servidor de cluster Argumentos: certificatePath: la ruta del directorio con los ficheros server.crt y server.key. clusterServerIP: la IP del servidor de cluster clusterServerListenningPort: el puerto en el que escucha el servidor de cluster statusDBUpdateInterval: el periodo de actualización de la base de datos (en segundos) Devuelve: Nada Lanza: EnpointException: se lanza cuando no se puede establecer una conexión con el servidor web """ self.__manager = NetworkManager(certificatePath) self.__manager.startNetworkService() callback = _ClusterServerEndpointCallback(self) # Establecer la conexión self.__clusterServerIP = clusterServerIP self.__clusterServerPort = clusterServerListenningPort try : self.__manager.connectTo(clusterServerIP, clusterServerListenningPort, 5, callback, True) while (not self.__manager.isConnectionReady(clusterServerIP, clusterServerListenningPort)) : sleep(0.1) # TODO: si esto falla, terminar. # Preparar la recepción de paquetes y la actualización automática de la base de datos de estado self.__repositoryPacketHandler = ClusterServerPacketHandler(self.__manager) self.__updateRequestThread = VMServerMonitoringThread(_ClusterServerEndpointUpdateHandler(self), statusDBUpdateInterval) self.__updateRequestThread.start() self.__commandMonitoringThread = CommandMonitoringThread(self.__commandsDBConnector, commandTimeout, commandTimeoutCheckInterval) self.__commandMonitoringThread.start() except NetworkManagerException as e : raise EndpointException(e.message) def disconnectFromClusterServer(self): """ Cierra la conexión con el servidor de cluster y borra las bases de datos de estado Argumentos: Ninguno Devuelve: Nada @attention: Este método debe llamarse desde el hilo principal para evitar cuelgues """ # Apagar el servidor de cluster p = self.__repositoryPacketHandler.createHaltPacket(self.__haltVMServers) errorMessage = self.__manager.sendPacket(self.__clusterServerIP, self.__clusterServerPort, p) NetworkManager.printConnectionWarningIfNecessary(self.__clusterServerIP, self.__clusterServerPort, "Cluster server halt", errorMessage) # Dejar de actualizar las bases de datos self.__updateRequestThread.stop() # Dejar de monitorizar los comandos self.__commandMonitoringThread.stop() # Cerrar las conexiones con las bases de datos self.closeNetworkAndDBConnections() def closeNetworkAndDBConnections(self): """ Cierra las conexiones con las bases de datos Argumentos: Ninguno Devuelve: Nada """ # Detener el servicio de red self.__manager.stopNetworkService() # Borrar las bases de datos de comandos y de estado dbConfigurator = DBConfigurator(self.__rootsPassword) dbConfigurator.dropDatabase(self.__commandsDatabaseName) def _processIncomingPacket(self, packet): """ Procesa un paquete enviado desde el servidor de cluster Argumentos: packet: el paquete a procesar Devuelve: Nada """ if (self.__stopped) : return data = self.__repositoryPacketHandler.readPacket(packet) if (data["packet_type"] == PACKET_T.VM_SERVERS_STATUS_DATA) : self.__endpointDBConnector.processVMServerSegment(data["Segment"], data["SequenceSize"], data["Data"]) elif (data["packet_type"] == PACKET_T.VM_DISTRIBUTION_DATA) : self.__endpointDBConnector.processVMDistributionSegment(data["Segment"], data["SequenceSize"], data["Data"]) elif (data["packet_type"] == PACKET_T.ACTIVE_VM_DATA) : self.__endpointDBConnector.processActiveVMSegment(data["Segment"], data["SequenceSize"], data["VMServerIP"], data["Data"]) else : l = data["CommandID"].split("|") commandID = (int(l[0]), float(l[1])) if (data["packet_type"] == PACKET_T.COMMAND_EXECUTED) : self.__commandsDBConnector.removeExecutedCommand(commandID) else : # El resto de paquetes contienen el resultado de ejecutar comandos => los serializamos y los añadimos # a la base de datos de comandos para que los conectores se enteren if (data["packet_type"] == PACKET_T.VM_SERVER_BOOTUP_ERROR or data["packet_type"] == PACKET_T.VM_SERVER_UNREGISTRATION_ERROR or data["packet_type"] == PACKET_T.VM_SERVER_SHUTDOWN_ERROR) : (outputType, outputContent) = CommandsHandler.createVMServerGenericErrorOutput( data["packet_type"], data["ServerNameOrIPAddress"], data["ErrorMessage"]) elif (data["packet_type"] == PACKET_T.VM_SERVER_REGISTRATION_ERROR) : (outputType, outputContent) = CommandsHandler.createVMServerRegistrationErrorOutput( data["VMServerIP"], data["VMServerPort"], data["VMServerName"], data["ErrorMessage"]) elif (data["packet_type"] == PACKET_T.VM_BOOT_FAILURE) : (outputType, outputContent) = CommandsHandler.createVMBootFailureErrorOutput( data["VMID"], data["ErrorMessage"]) elif (data["packet_type"] == PACKET_T.VM_CONNECTION_DATA) : (outputType, outputContent) = CommandsHandler.createVMConnectionDataOutput( data["VNCServerIPAddress"], data["VNCServerPort"], data["VNCServerPassword"]) elif (data["packet_type"] == PACKET_T.DOMAIN_DESTRUCTION_ERROR): (outputType, outputContent) = CommandsHandler.createDomainDestructionErrorOutput(data["ErrorMessage"]) elif (data["packet_type"] == PACKET_T.VM_SERVER_CONFIGURATION_CHANGE_ERROR) : (outputType, outputContent) = CommandsHandler.createVMServerConfigurationChangeErrorOutput(data["ErrorMessage"]) self.__commandsDBConnector.addCommandOutput(commandID, outputType, outputContent) def processCommands(self): """ Procesa los comandos enviados desde los conectores Argumentos: Ninguno Devuelve: Nada """ while not self.__stopped : commandData = self.__commandsDBConnector.popCommand() if (commandData == None) : sleep(0.1) else : (commandID, commandType, commandArgs) = commandData parsedArgs = CommandsHandler.deserializeCommandArgs(commandType, commandArgs) if (commandType != COMMAND_TYPE.HALT) : serializedCommandID = "{0}|{1}".format(commandID[0], commandID[1]) if (commandType == COMMAND_TYPE.BOOTUP_VM_SERVER) : packet = self.__repositoryPacketHandler.createVMServerBootUpPacket(parsedArgs["VMServerNameOrIP"], serializedCommandID) elif (commandType == COMMAND_TYPE.REGISTER_VM_SERVER) : packet = self.__repositoryPacketHandler.createVMServerRegistrationPacket(parsedArgs["VMServerIP"], parsedArgs["VMServerPort"], parsedArgs["VMServerName"], parsedArgs["IsVanillaServer"], serializedCommandID) elif (commandType == COMMAND_TYPE.UNREGISTER_OR_SHUTDOWN_VM_SERVER) : packet = self.__repositoryPacketHandler.createVMServerUnregistrationOrShutdownPacket(parsedArgs["VMServerNameOrIP"], parsedArgs["Halt"], parsedArgs["Unregister"], serializedCommandID) elif (commandType == COMMAND_TYPE.VM_BOOT_REQUEST) : packet = self.__repositoryPacketHandler.createVMBootRequestPacket(parsedArgs["VMID"], parsedArgs["UserID"], serializedCommandID) elif (commandType == COMMAND_TYPE.DESTROY_DOMAIN): packet = self.__repositoryPacketHandler.createDomainDestructionPacket(parsedArgs["DomainID"], serializedCommandID) elif (commandType == COMMAND_TYPE.VM_SERVER_CONFIGURATION_CHANGE) : packet = self.__repositoryPacketHandler.createVMServerConfigurationChangePacket(parsedArgs["VMServerNameOrIPAddress"], parsedArgs["NewServerName"], parsedArgs["NewServerIPAddress"], parsedArgs["NewServerPort"], parsedArgs["NewVanillaImageEditionBehavior"], serializedCommandID) errorMessage = self.__manager.sendPacket(self.__clusterServerIP, self.__clusterServerPort, packet) if (errorMessage != None) : # Error al enviar el paquete => el comando no se podrá ejecutar => avisar a la web (outputType, outputContent) = CommandsHandler.createConnectionErrorOutput() self.__commandsDBConnector.addCommandOutput(commandID, outputType, outputContent) else : self.__stopped = True self.__haltVMServers = parsedArgs["HaltVMServers"] def _sendUpdateRequestPackets(self): """ Solicita información de estado al serividor de cluster Argumentos: Ninguno Devuelve: Nada """ if (self.__stopped) : return # Enviamos paquetes para obtener los tres tipos de información que necesitamos para actualizar la base de datos de estado p = self.__repositoryPacketHandler.createDataRequestPacket(PACKET_T.QUERY_VM_SERVERS_STATUS) errorMessage = self.__manager.sendPacket(self.__clusterServerIP, self.__clusterServerPort, p) NetworkManager.printConnectionWarningIfNecessary(self.__clusterServerIP, self.__clusterServerPort, "Virtual machine servers status", errorMessage) p = self.__repositoryPacketHandler.createDataRequestPacket(PACKET_T.QUERY_VM_DISTRIBUTION) errorMessage = self.__manager.sendPacket(self.__clusterServerIP, self.__clusterServerPort, p) NetworkManager.printConnectionWarningIfNecessary(self.__clusterServerIP, self.__clusterServerPort, "Virtual machine distribution", errorMessage) p = self.__repositoryPacketHandler.createDataRequestPacket(PACKET_T.QUERY_ACTIVE_VM_DATA) errorMessage = self.__manager.sendPacket(self.__clusterServerIP, self.__clusterServerPort, p) NetworkManager.printConnectionWarningIfNecessary(self.__clusterServerIP, self.__clusterServerPort, "Active virtual machines data", errorMessage)
class ClusterServerConnector(object): """ Estos objetos comunican la web y el endpoint a través de memoria compartida. """ def __init__(self, userID): """ Inicializa el estado del conector Argumentos: userID: el identificador del usuario que accede al sistema. Se trata de un entero. """ self.__userID = userID def connectToDatabases(self, statusDBName, commandsDBName, databaseUser, databasePassword): """ Establece una conexión con las bases de datos de estado y comandos. Argumentos: statusDBName: el nombre de la base de datos de estado commandsDBName: el nombre de la base de datos de comandos databaseUser: el nombre de usuario con el que se accederá a las dos bases de datos databasePassword: la contraseña para acceder a las bases de datos Devuelve: Nada """ self.__endpointDBConnector = ClusterEndpointDBConnector( databaseUser, databasePassword, statusDBName) self.__commandsDBConnector = CommandsDatabaseConnector( databaseUser, databasePassword, commandsDBName, 1) def dispose(self): """ Cierra las conexiones con las bases de datos Argumentos: Ninguno Devuelve: Nada """ pass def getActiveVMsData(self, showAllVMs=False): """ Devuelve los datos de las máquinas virtuales activas Argumentos: showAllVMs: si es True, se muestran los datos de todas las máquinas activas; si es False, sólo las del usuario registrado en el conector Devuelve: una lista de diccionarios con los datos de las máquinas virtuales activas """ if not showAllVMs: userID = self.__userID else: userID = None return self.__endpointDBConnector.getActiveVMsData(userID) def getVMDistributionData(self): """ Devuelve los datos de distribución de las imágenes Argumentos: Ninguno Devuelve: una lista de diccionarios con los datos de distribución de las imágenes """ return self.__endpointDBConnector.getVMDistributionData() def getVMServersData(self): """ Devuelve los datos básicos de los servidores de máquinas virtuales Argumentos: Ninguno Devuelve: una lista de diccionarios con los datos básicos de los servidores de máquinas virtuales """ return self.__endpointDBConnector.getVMServersData() def registerVMServer(self, vmServerIP, vmServerPort, vmServerName, isVanillaServer): """ Registra un servidor de máquinas virtuales Argumentos: vmServerIP: la IP del servidor de máquinas virtuales vmServerPort: el puerto en el que escucha vmServerName: el nombre del servidor de máquinas virtuales isVanillaServer: indica si el servidor de máquinas virtuales se usará preferentemente para editar imágenes vanilla o no. Devuelve: El identificador único del comando. @attention: La representación del identificador único del comando puede cambiar sin previo aviso. """ (commandType, commandArgs) = CommandsHandler.createVMServerRegistrationCommand( vmServerIP, vmServerPort, vmServerName, isVanillaServer) return self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def unregisterVMServer(self, vmServerNameOrIP, isShutDown): """ Borra un servidor de máquinas virtuales Argumentos: vmServerNameOrIP: el nombre o la IP del servidor a borrar isShutDown: si es True, el servidor se apagará inmediatamente. Si es False, esperará a que los usuarios apaguen sus máquinas virtuales. Devuelve: El identificador único del comando. @attention: La representación del identificador único del comando puede cambiar sin previo aviso. """ (commandType, commandArgs ) = CommandsHandler.createVMServerUnregistrationOrShutdownCommand( True, vmServerNameOrIP, isShutDown) return self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def shutdownVMServer(self, vmServerNameOrIP, isShutDown): """ Apaga un servidor de máquinas virtuales Argumentos: vmServerNameOrIP: el nombre o la IP del servidor a borrar isShutDown: si es True, el servidor se apagará inmediatamente. Si es False, esperará a que los usuarios apaguen sus máquinas virtuales. Devuelve: El identificador único del comando. @attention: La representación del identificador único del comando puede cambiar sin previo aviso. """ (commandType, commandArgs ) = CommandsHandler.createVMServerUnregistrationOrShutdownCommand( False, vmServerNameOrIP, isShutDown) return self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def bootUpVMServer(self, vmServerNameOrIP): """ Arranca un servidor de máquinas virtuales y lo añade a la infraestructura Argumentos: vmServerNameOrIP: el nombre o la IP del servidor a arrancar Devuelve: El identificador único del comando. @attention: La representación del identificador único del comando puede cambiar sin previo aviso. """ (commandType, commandArgs ) = CommandsHandler.createVMServerBootCommand(vmServerNameOrIP) return self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def bootUpVM(self, imageID): """ Solicita a la infraestructura el arranque de una máquina virtual Argumentos: imageID: el identificador único de la imagen a arrancar Devuelve: El identificador único del comando. @attention: La representación del identificador único del comando puede cambiar sin previo aviso. """ (commandType, commandArgs) = CommandsHandler.createVMBootCommand( imageID, self.__userID) return self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def isShutDown(self, haltVMServers): """ Apaga toda la infraestructura Argumentos: haltVMServers: si es True, el servidor se apagará inmediatamente. Si es False, esperará a que los usuarios apaguen sus máquinas virtuales. Devuelve: Nada """ (commandType, commandArgs) = CommandsHandler.createHaltCommand(haltVMServers) self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def destroyDomain(self, domainID): """ Destruye una máquina virtual Argumentos: domainID: el identificador único de la máquina virtual a destruir Devuelve: Nada """ (commandType, commandArgs ) = CommandsHandler.createDomainDestructionCommand(domainID) return self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def changeVMServerConfiguration(self, serverNameOrIPAddress, newName, newIPAddress, newPort, newVanillaImageEditionBehavior): (commandType, commandArgs ) = CommandsHandler.createVMServerConfigurationChangeCommand( serverNameOrIPAddress, newName, newIPAddress, newPort, newVanillaImageEditionBehavior) return self.__commandsDBConnector.addCommand(self.__userID, commandType, commandArgs) def getCommandOutput(self, commandID): """ Devuelve la salida de un comando Argumentos: commandID: el identificador único del comando Devuelve: - Si el comando todavía se está ejecutando, se devuelve una tupla vacía. - Si el comando se ha terminado de ejecutar, se devuelve un diccionario con los datos de su salida. """ if (self.__commandsDBConnector.isRunning(commandID)): return () else: result = self.__commandsDBConnector.getCommandOutput(commandID) if (result != None): (outputType, outputContent) = result result = CommandsHandler.deserializeCommandOutput( outputType, outputContent) return result def waitForCommandOutput(self, commandID): """ Espera a que un comando termine, devolviendo su salida en caso de que la haya. Argumentos: commandID: el identificador único del comando Devuelve: - None si el comando no tiene salida - Un diccionario con los datos de su salida en caso contrario @attention: Este método es bloqueante. Si se desea un comportamiento no bloqueante, es necesario utilizar el método getCommandOutput. """ while (self.__commandsDBConnector.isRunning(commandID)): sleep(0.1) result = self.__commandsDBConnector.getCommandOutput(commandID) if result == None: return None else: return CommandsHandler.deserializeCommandOutput( result[0], result[1]) def getImageBasicData(self, imageID): return self.__endpointDBConnector.getImageBasicData(imageID) def getBootableImagesData(self, imageIDs): return self.__endpointDBConnector.getBootableImagesData(imageIDs) def getBaseImagesData(self): return self.__endpointDBConnector.getBaseImagesData() def getEditedImages(self, userID): return self.__endpointDBConnector.getEditedImages(userID) def getVanillaImageFamilyID(self, imageID): return self.__endpointDBConnector.getVanillaImageFamilyID(imageID) def getVanillaImageFamiliyData(self, vanillaImageFamilyID): return self.__endpointDBConnector.getVanillaImageFamiliyData( vanillaImageFamilyID)
class WebServerEndpoint(object): """ Estos objetos comunican un servidor de cluster con la web """ def __init__(self): """ Inicializa el estado del endpoint Argumentos: Ninguno """ self.__stopped = False def connectToDatabases(self, mysqlRootsPassword, statusDBName, commandsDBName, statusdbSQLFilePath, commandsDBSQLFilePath, websiteUser, websiteUserPassword, endpointUser, endpointUserPassword, minCommandInterval): """ Establece la conexión con la base de datos de estado y con la base de datos de comandos. Argumentos: mysqlRootsPassword: la contraseña de root de MySQL statusDBName: el nombre de la base de datos de estado statusdbSQLFilePath: la ruta del script que crea la base de datos de estado websiteUser: nombre de usuario que usará la web para manipular las bases de datos websiteUserPassword: contraseña del usuario de la web endpointUser: usuario que utilizará en eldpoint para manipular las bases de datos de estado. Será el único que puede escribir en la base de datos de estado. endpointUserPassword: contraseña del usuario del endpoint """ # Crear las bases de datos self.__rootsPassword = mysqlRootsPassword self.__statusDatabaseName = statusDBName self.__commandsDatabaseName = commandsDBName configurator = DBConfigurator(mysqlRootsPassword) configurator.runSQLScript(statusDBName, statusdbSQLFilePath) configurator.runSQLScript(commandsDBName, commandsDBSQLFilePath) # Registrar en ellas los usuarios configurator.addUser(websiteUser, websiteUserPassword, statusDBName, False) configurator.addUser(endpointUser, endpointUserPassword, statusDBName, True) configurator.addUser(websiteUser, websiteUserPassword, commandsDBName, True) configurator.addUser(endpointUser, endpointUserPassword, commandsDBName, True) # Crear los conectores self.__commandsDBConnector = CommandsDatabaseConnector( endpointUser, endpointUserPassword, commandsDBName, minCommandInterval) self.__endpointDBConnector = ClusterEndpointDBConnector( endpointUser, endpointUserPassword, statusDBName) def connectToClusterServer(self, certificatePath, clusterServerIP, clusterServerListenningPort, statusDBUpdateInterval, commandTimeout, commandTimeoutCheckInterval): """ Establece la conexión con el servidor de cluster Argumentos: certificatePath: la ruta del directorio con los ficheros server.crt y server.key. clusterServerIP: la IP del servidor de cluster clusterServerListenningPort: el puerto en el que escucha el servidor de cluster statusDBUpdateInterval: el periodo de actualización de la base de datos (en segundos) Devuelve: Nada Lanza: EnpointException: se lanza cuando no se puede establecer una conexión con el servidor web """ self.__manager = NetworkManager(certificatePath) self.__manager.startNetworkService() callback = _ClusterServerEndpointCallback(self) # Establecer la conexión self.__clusterServerIP = clusterServerIP self.__clusterServerPort = clusterServerListenningPort try: self.__manager.connectTo(clusterServerIP, clusterServerListenningPort, 5, callback, True) while (not self.__manager.isConnectionReady( clusterServerIP, clusterServerListenningPort)): sleep(0.1) # TODO: si esto falla, terminar. # Preparar la recepción de paquetes y la actualización automática de la base de datos de estado self.__repositoryPacketHandler = ClusterServerPacketHandler( self.__manager) self.__updateRequestThread = VMServerMonitoringThread( _ClusterServerEndpointUpdateHandler(self), statusDBUpdateInterval) self.__updateRequestThread.start() self.__commandMonitoringThread = CommandMonitoringThread( self.__commandsDBConnector, commandTimeout, commandTimeoutCheckInterval) self.__commandMonitoringThread.start() except NetworkManagerException as e: raise EndpointException(e.message) def disconnectFromClusterServer(self): """ Cierra la conexión con el servidor de cluster y borra las bases de datos de estado Argumentos: Ninguno Devuelve: Nada @attention: Este método debe llamarse desde el hilo principal para evitar cuelgues """ # Apagar el servidor de cluster p = self.__repositoryPacketHandler.createHaltPacket( self.__haltVMServers) errorMessage = self.__manager.sendPacket(self.__clusterServerIP, self.__clusterServerPort, p) NetworkManager.printConnectionWarningIfNecessary( self.__clusterServerIP, self.__clusterServerPort, "Cluster server halt", errorMessage) # Dejar de actualizar las bases de datos self.__updateRequestThread.stop() # Dejar de monitorizar los comandos self.__commandMonitoringThread.stop() # Cerrar las conexiones con las bases de datos self.closeNetworkAndDBConnections() def closeNetworkAndDBConnections(self): """ Cierra las conexiones con las bases de datos Argumentos: Ninguno Devuelve: Nada """ # Detener el servicio de red self.__manager.stopNetworkService() # Borrar las bases de datos de comandos y de estado dbConfigurator = DBConfigurator(self.__rootsPassword) dbConfigurator.dropDatabase(self.__commandsDatabaseName) def _processIncomingPacket(self, packet): """ Procesa un paquete enviado desde el servidor de cluster Argumentos: packet: el paquete a procesar Devuelve: Nada """ if (self.__stopped): return data = self.__repositoryPacketHandler.readPacket(packet) if (data["packet_type"] == PACKET_T.VM_SERVERS_STATUS_DATA): self.__endpointDBConnector.processVMServerSegment( data["Segment"], data["SequenceSize"], data["Data"]) elif (data["packet_type"] == PACKET_T.VM_DISTRIBUTION_DATA): self.__endpointDBConnector.processVMDistributionSegment( data["Segment"], data["SequenceSize"], data["Data"]) elif (data["packet_type"] == PACKET_T.ACTIVE_VM_DATA): self.__endpointDBConnector.processActiveVMSegment( data["Segment"], data["SequenceSize"], data["VMServerIP"], data["Data"]) else: l = data["CommandID"].split("|") commandID = (int(l[0]), float(l[1])) if (data["packet_type"] == PACKET_T.COMMAND_EXECUTED): self.__commandsDBConnector.removeExecutedCommand(commandID) else: # El resto de paquetes contienen el resultado de ejecutar comandos => los serializamos y los añadimos # a la base de datos de comandos para que los conectores se enteren if (data["packet_type"] == PACKET_T.VM_SERVER_BOOTUP_ERROR or data["packet_type"] == PACKET_T.VM_SERVER_UNREGISTRATION_ERROR or data["packet_type"] == PACKET_T.VM_SERVER_SHUTDOWN_ERROR): (outputType, outputContent ) = CommandsHandler.createVMServerGenericErrorOutput( data["packet_type"], data["ServerNameOrIPAddress"], data["ErrorMessage"]) elif (data["packet_type"] == PACKET_T.VM_SERVER_REGISTRATION_ERROR): (outputType, outputContent ) = CommandsHandler.createVMServerRegistrationErrorOutput( data["VMServerIP"], data["VMServerPort"], data["VMServerName"], data["ErrorMessage"]) elif (data["packet_type"] == PACKET_T.VM_BOOT_FAILURE): (outputType, outputContent ) = CommandsHandler.createVMBootFailureErrorOutput( data["VMID"], data["ErrorMessage"]) elif (data["packet_type"] == PACKET_T.VM_CONNECTION_DATA): (outputType, outputContent ) = CommandsHandler.createVMConnectionDataOutput( data["VNCServerIPAddress"], data["VNCServerPort"], data["VNCServerPassword"]) elif (data["packet_type"] == PACKET_T.DOMAIN_DESTRUCTION_ERROR ): (outputType, outputContent ) = CommandsHandler.createDomainDestructionErrorOutput( data["ErrorMessage"]) elif (data["packet_type"] == PACKET_T.VM_SERVER_CONFIGURATION_CHANGE_ERROR): ( outputType, outputContent ) = CommandsHandler.createVMServerConfigurationChangeErrorOutput( data["ErrorMessage"]) self.__commandsDBConnector.addCommandOutput( commandID, outputType, outputContent) def processCommands(self): """ Procesa los comandos enviados desde los conectores Argumentos: Ninguno Devuelve: Nada """ while not self.__stopped: commandData = self.__commandsDBConnector.popCommand() if (commandData == None): sleep(0.1) else: (commandID, commandType, commandArgs) = commandData parsedArgs = CommandsHandler.deserializeCommandArgs( commandType, commandArgs) if (commandType != COMMAND_TYPE.HALT): serializedCommandID = "{0}|{1}".format( commandID[0], commandID[1]) if (commandType == COMMAND_TYPE.BOOTUP_VM_SERVER): packet = self.__repositoryPacketHandler.createVMServerBootUpPacket( parsedArgs["VMServerNameOrIP"], serializedCommandID) elif (commandType == COMMAND_TYPE.REGISTER_VM_SERVER): packet = self.__repositoryPacketHandler.createVMServerRegistrationPacket( parsedArgs["VMServerIP"], parsedArgs["VMServerPort"], parsedArgs["VMServerName"], parsedArgs["IsVanillaServer"], serializedCommandID) elif (commandType == COMMAND_TYPE.UNREGISTER_OR_SHUTDOWN_VM_SERVER): packet = self.__repositoryPacketHandler.createVMServerUnregistrationOrShutdownPacket( parsedArgs["VMServerNameOrIP"], parsedArgs["Halt"], parsedArgs["Unregister"], serializedCommandID) elif (commandType == COMMAND_TYPE.VM_BOOT_REQUEST): packet = self.__repositoryPacketHandler.createVMBootRequestPacket( parsedArgs["VMID"], parsedArgs["UserID"], serializedCommandID) elif (commandType == COMMAND_TYPE.DESTROY_DOMAIN): packet = self.__repositoryPacketHandler.createDomainDestructionPacket( parsedArgs["DomainID"], serializedCommandID) elif (commandType == COMMAND_TYPE.VM_SERVER_CONFIGURATION_CHANGE): packet = self.__repositoryPacketHandler.createVMServerConfigurationChangePacket( parsedArgs["VMServerNameOrIPAddress"], parsedArgs["NewServerName"], parsedArgs["NewServerIPAddress"], parsedArgs["NewServerPort"], parsedArgs["NewVanillaImageEditionBehavior"], serializedCommandID) errorMessage = self.__manager.sendPacket( self.__clusterServerIP, self.__clusterServerPort, packet) if (errorMessage != None): # Error al enviar el paquete => el comando no se podrá ejecutar => avisar a la web (outputType, outputContent ) = CommandsHandler.createConnectionErrorOutput() self.__commandsDBConnector.addCommandOutput( commandID, outputType, outputContent) else: self.__stopped = True self.__haltVMServers = parsedArgs["HaltVMServers"] def _sendUpdateRequestPackets(self): """ Solicita información de estado al serividor de cluster Argumentos: Ninguno Devuelve: Nada """ if (self.__stopped): return # Enviamos paquetes para obtener los tres tipos de información que necesitamos para actualizar la base de datos de estado p = self.__repositoryPacketHandler.createDataRequestPacket( PACKET_T.QUERY_VM_SERVERS_STATUS) errorMessage = self.__manager.sendPacket(self.__clusterServerIP, self.__clusterServerPort, p) NetworkManager.printConnectionWarningIfNecessary( self.__clusterServerIP, self.__clusterServerPort, "Virtual machine servers status", errorMessage) p = self.__repositoryPacketHandler.createDataRequestPacket( PACKET_T.QUERY_VM_DISTRIBUTION) errorMessage = self.__manager.sendPacket(self.__clusterServerIP, self.__clusterServerPort, p) NetworkManager.printConnectionWarningIfNecessary( self.__clusterServerIP, self.__clusterServerPort, "Virtual machine distribution", errorMessage) p = self.__repositoryPacketHandler.createDataRequestPacket( PACKET_T.QUERY_ACTIVE_VM_DATA) errorMessage = self.__manager.sendPacket(self.__clusterServerIP, self.__clusterServerPort, p) NetworkManager.printConnectionWarningIfNecessary( self.__clusterServerIP, self.__clusterServerPort, "Active virtual machines data", errorMessage)