Source code for mcvirt.virtual_machine.factory

# Copyright (c) 2016 - I.T. Dev Ltd
#
# This file is part of MCVirt.
#
# MCVirt is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# MCVirt is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with MCVirt.  If not, see <http://www.gnu.org/licenses/>

import Pyro4
from texttable import Texttable
import re
from os.path import exists as os_path_exists
from os import makedirs

from mcvirt.virtual_machine.virtual_machine import VirtualMachine
from mcvirt.virtual_machine.virtual_machine_config import VirtualMachineConfig
from mcvirt.mcvirt_config import MCVirtConfig
from mcvirt.auth.permissions import PERMISSIONS
from mcvirt.exceptions import (InvalidNodesException, DrbdNotEnabledOnNode,
                               InvalidVirtualMachineNameException, VmAlreadyExistsException,
                               ClusterNotInitialisedException, NodeDoesNotExistException,
                               VmDirectoryAlreadyExistsException)
from mcvirt.rpc.lock import locking_method
from mcvirt.rpc.pyro_object import PyroObject
from mcvirt.utils import get_hostname
from mcvirt.argument_validator import ArgumentValidator
from mcvirt.virtual_machine.hard_drive.base import Driver as HardDriveDriver


[docs]class Factory(PyroObject): """Class for obtaining virtual machine objects""" OBJECT_TYPE = 'virtual machine' VIRTUAL_MACHINE_CLASS = VirtualMachine @Pyro4.expose()
[docs] def getVirtualMachineByName(self, vm_name): """Obtain a VM object, based on VM name""" ArgumentValidator.validate_hostname(vm_name) vm_object = VirtualMachine(self, vm_name) self._register_object(vm_object) return vm_object
@Pyro4.expose()
[docs] def getAllVirtualMachines(self): """Return objects for all virtual machines""" return [self.getVirtualMachineByName(vm_name) for vm_name in self.getAllVmNames()]
@Pyro4.expose()
[docs] def getAllVmNames(self, node=None): """Returns a list of all VMs within the cluster or those registered on a specific node""" if node is not None: ArgumentValidator.validate_hostname(node) # If no node was defined, check the local configuration for all VMs if (node is None): return MCVirtConfig().get_config()['virtual_machines'] elif node == get_hostname(): # Obtain array of all domains from libvirt all_domains = self._get_registered_object( 'libvirt_connector').get_connection().listAllDomains() return [vm.name() for vm in all_domains] else: # Return list of VMs registered on remote node cluster = self._get_registered_object('cluster') def remote_command(node_connection): virtual_machine_factory = node_connection.get_connection('virtual_machine_factory') return virtual_machine_factory.getAllVmNames(node=node) return cluster.run_remote_command(callback_method=remote_command, nodes=[node])[node]
@Pyro4.expose() @locking_method() def listVms(self): """Lists the VMs that are currently on the host""" table = Texttable() table.set_deco(Texttable.HEADER | Texttable.VLINES) table.header(('VM Name', 'State', 'Node')) for vm_object in self.getAllVirtualMachines(): table.add_row((vm_object.get_name(), vm_object._getPowerState().name, vm_object.getNode() or 'Unregistered')) table_output = table.draw() return table_output @Pyro4.expose()
[docs] def check_exists(self, vm_name): """Determines if a VM exists, given a name""" try: ArgumentValidator.validate_hostname(vm_name) except (TypeError, InvalidVirtualMachineNameException): return False return (vm_name in self.getAllVmNames())
@Pyro4.expose()
[docs] def checkName(self, name, ignore_exists=False): try: ArgumentValidator.validate_hostname(name) except TypeError: raise InvalidVirtualMachineNameException( 'Error: Invalid VM Name - VM Name can only contain 0-9 a-Z and dashes' ) if len(name) < 3: raise InvalidVirtualMachineNameException('VM Name must be at least 3 characters long') if self.check_exists(name) and not ignore_exists: raise VmAlreadyExistsException('VM already exists') return True
@Pyro4.expose() @locking_method(instance_method=True) def create(self, *args, **kwargs): """Exposed method for creating a VM, that performs a permission check""" self._get_registered_object('auth').assert_permission(PERMISSIONS.CREATE_VM) return self._create(*args, **kwargs) @locking_method(instance_method=True) def _create(self, name, cpu_cores, memory_allocation, hard_drives=[], network_interfaces=[], node=None, available_nodes=[], storage_type=None, hard_drive_driver=None): """Creates a VM and returns the virtual_machine object for it""" self.checkName(name) ArgumentValidator.validate_positive_integer(cpu_cores) ArgumentValidator.validate_positive_integer(memory_allocation) for hard_drive in hard_drives: ArgumentValidator.validate_positive_integer(hard_drive) if network_interfaces: for network_interface in network_interfaces: ArgumentValidator.validate_network_name(network_interface) if node is not None: ArgumentValidator.validate_hostname(node) for available_node in available_nodes: ArgumentValidator.validate_hostname(available_node) assert storage_type in [None] + [ storage_type_itx.__name__ for storage_type_itx in self._get_registered_object( 'hard_drive_factory').STORAGE_TYPES ] if hard_drive_driver is not None: HardDriveDriver[hard_drive_driver] # Ensure the cluster has not been ignored, as VMs cannot be created with MCVirt running # in this state if self._cluster_disabled: raise ClusterNotInitialisedException('VM cannot be created whilst the cluster' + ' is not initialised') # Determine if VM already exists if self.check_exists(name): raise VmAlreadyExistsException('Error: VM already exists') # If a node has not been specified, assume the local node if node is None: node = get_hostname() # If Drbd has been chosen as a storage type, ensure it is enabled on the node node_drbd = self._get_registered_object('node_drbd') if storage_type == 'Drbd' and not node_drbd.is_enabled(): raise DrbdNotEnabledOnNode('Drbd is not enabled on this node') # Create directory for VM on the local and remote nodes if os_path_exists(VirtualMachine._get_vm_dir(name)): raise VmDirectoryAlreadyExistsException('Error: VM directory already exists') # If available nodes has not been passed, assume the local machine is the only # available node if local storage is being used. Use the machines in the cluster # if Drbd is being used cluster_object = self._get_registered_object('cluster') all_nodes = cluster_object.get_nodes(return_all=True) all_nodes.append(get_hostname()) if len(available_nodes) == 0: if storage_type == 'Drbd': # If the available nodes are not specified, use the # nodes in the cluster available_nodes = all_nodes else: # For local VMs, only use the local node as the available nodes available_nodes = [get_hostname()] # If there are more than the maximum number of Drbd machines in the cluster, # add an option that forces the user to specify the nodes for the Drbd VM # to be added to if storage_type == 'Drbd' and len(available_nodes) != node_drbd.CLUSTER_SIZE: raise InvalidNodesException('Exactly two nodes must be specified') for check_node in available_nodes: if check_node not in all_nodes: raise NodeDoesNotExistException('Node \'%s\' does not exist' % check_node) if get_hostname() not in available_nodes and self._is_cluster_master: raise InvalidNodesException('One of the nodes must be the local node') # Create directory for VM makedirs(VirtualMachine._get_vm_dir(name)) # Add VM to MCVirt configuration def updateMCVirtConfig(config): config['virtual_machines'].append(name) MCVirtConfig().update_config( updateMCVirtConfig, 'Adding new VM \'%s\' to global MCVirt configuration' % name) # Create VM configuration file VirtualMachineConfig.create(name, available_nodes, cpu_cores, memory_allocation) # Add VM to remote nodes if self._is_cluster_master: def remote_command(remote_connection): virtual_machine_factory = remote_connection.get_connection( 'virtual_machine_factory' ) virtual_machine_factory.create( name=name, memory_allocation=memory_allocation, cpu_cores=cpu_cores, node=node, available_nodes=available_nodes ) cluster_object.run_remote_command(callback_method=remote_command) # Obtain an object for the new VM, to use to create disks/network interfaces vm_object = self.getVirtualMachineByName(name) vm_object.get_config_object().gitAdd('Created VM \'%s\'' % vm_object.get_name()) if node == get_hostname(): # Register VM with LibVirt. If MCVirt has not been initialised on this node, # do not set the node in the VM configuration, as the change can't be # replicated to remote nodes vm_object._register(set_node=self._is_cluster_master) elif self._is_cluster_master: # If MCVirt has been initialised on this node and the local machine is # not the node that the VM will be registered on, set the node on the VM vm_object._setNode(node) if self._is_cluster_master: # Create disk images hard_drive_factory = self._get_registered_object('hard_drive_factory') for hard_drive_size in hard_drives: hard_drive_factory.create(vm_object=vm_object, size=hard_drive_size, storage_type=storage_type, driver=hard_drive_driver) # If any have been specified, add a network configuration for each of the # network interfaces to the domain XML network_adapter_factory = self._get_registered_object('network_adapter_factory') network_factory = self._get_registered_object('network_factory') if network_interfaces is not None: for network in network_interfaces: network_object = network_factory.get_network_by_name(network) network_adapter_factory.create(vm_object, network_object) return vm_object