def _vm_apply_new_disk_size( self, vm: VM, offline: bool, offline_transport: str, transaction: Transaction, disk_size: int = 0, ): """ If the new VM disk size is set, checks if it's correct and sufficient and commit the new size. Rolls it back on the interrupted migration :param VM vm: The migrating VM :param str offline_transport: offline migration transport :param Transaction transaction: The transaction to rollback :param int disk_size: the new disk_size_gib attribute """ size = self.vm_new_disk_size(vm, offline, offline_transport, disk_size) if size == vm.dataset_obj['disk_size_gib']: return old_size = vm.dataset_obj['disk_size_gib'] vm.dataset_obj['disk_size_gib'] = size vm.dataset_obj.commit() if transaction: def restore_size(): vm.dataset_obj['disk_size_gib'] = old_size vm.dataset_obj.commit() transaction.on_rollback('reset_disk_size', restore_size)
def change_address( vm_hostname, new_address, offline=False, migrate=False, allow_reserved_hv=False, offline_transport='drbd', ): """Change VMs IP address This is done by changing data in Serveradmin, running Puppet in VM and rebooting it. """ if not offline: raise IGVMError('IP address change can be only performed offline') with _get_vm(vm_hostname) as vm: new_address = ip_address(new_address) if vm.dataset_obj['intern_ip'] == new_address: raise ConfigError('New IP address is the same as the old one!') if not vm.hypervisor.get_vlan_network(new_address) and not migrate: err = 'Current hypervisor does not support new subnet!' raise ConfigError(err) new_network = Query( { 'servertype': 'route_network', 'state': 'online', 'network_type': 'internal', 'intern_ip': Contains(new_address), } ).get()['hostname'] vm_was_running = vm.is_running() with Transaction() as transaction: if vm_was_running: vm.shutdown( transaction=transaction, check_vm_up_on_transaction=False, ) vm.change_address( new_address, new_network, transaction=transaction, ) if migrate: vm_migrate( vm_object=vm, run_puppet=True, offline=True, no_shutdown=True, allow_reserved_hv=allow_reserved_hv, offline_transport=offline_transport, ) else: vm.hypervisor.mount_vm_storage(vm, transaction=transaction) vm.run_puppet() vm.hypervisor.redefine_vm(vm) vm.hypervisor.umount_vm_storage(vm) if vm_was_running: vm.start()
def rename(self, new_hostname): """Rename the VM""" self.dataset_obj['hostname'] = new_hostname self.check_serveradmin_config() fd = BytesIO() fd.write(bytes(new_hostname, 'utf-8')) self.put('/etc/hostname', fd) self.put('/etc/mailname', fd) hosts_file = [ line for line in self.run('cat /etc/hosts').splitlines() if not line.startswith(str(self.dataset_obj['intern_ip'])) ] hosts_file.append('{0}\t{1}'.format(self.dataset_obj['intern_ip'], new_hostname)) self.run("echo '{0}' > /etc/hosts".format('\n'.join(hosts_file))) with Transaction() as transaction: self.shutdown(transaction=transaction) self.hypervisor.redefine_vm(self, new_fqdn=new_hostname) self.dataset_obj.commit() self.start(transaction=transaction)
def build(self, run_puppet=True, debug_puppet=False, postboot=None, cleanup_cert=False): """Builds a VM.""" hypervisor = self.hypervisor self.check_serveradmin_config() image = self.dataset_obj['os'] + '-base.tar.gz' # Can VM run on given hypervisor? self.hypervisor.check_vm(self, offline=True) if not run_puppet or self.dataset_obj['puppet_disabled']: log.warn( 'Puppet is disabled on the VM. It will not receive network ' 'configuration. Expect things to go south.') with Transaction() as transaction: # Clean up the certificate if the build fails for any reason transaction.on_rollback('Clean cert', clean_cert, self.dataset_obj) # Perform operations on the hypervisor self.hypervisor.create_vm_storage(self, transaction) mount_path = self.hypervisor.format_vm_storage(self, transaction) self.hypervisor.download_and_extract_image(image, mount_path) self.prepare_vm() if run_puppet: self.run_puppet(clear_cert=cleanup_cert, debug=debug_puppet) if postboot is not None: self.copy_postboot_script(postboot) self.hypervisor.umount_vm_storage(self) hypervisor.define_vm(self, transaction) # We are updating the information on the Serveradmin, before # starting the VM, because the VM would still be on the hypervisor # even if it fails to start. self.dataset_obj.commit() self.start() # Perform operations on Virtual Machine if postboot is not None: self.run('/buildvm-postboot') self.run('rm /buildvm-postboot') log.info('"{}" is successfully built.'.format(self.fqdn))
def change_address(vm_hostname, new_address, offline=False): """Change VMs IP address This is done by changing data in Serveradmin, running Puppet in VM and rebooting it. """ if not offline: raise IGVMError('IP address change can be only performed offline') with _get_vm(vm_hostname) as vm: if vm.dataset_obj['igvm_operation_mode'] != 'kvm': raise NotImplementedError( 'This operation is not yet supported for {}'.format( vm.dataset_obj['igvm_operation_mode'])) old_address = vm.dataset_obj['intern_ip'] new_address = ip_address(new_address) if old_address == new_address: raise ConfigError('New IP address is the same as the old one!') vm_was_running = vm.is_running() vm.dataset_obj['intern_ip'] = new_address vm.dataset_obj.commit() if vm_was_running: vm.shutdown() try: with Transaction() as transaction: vm.hypervisor.mount_vm_storage(vm, transaction) vm.run_puppet() vm.hypervisor.redefine_vm(vm) vm.hypervisor.umount_vm_storage(vm) if vm_was_running: vm.start() except BaseException: vm.dataset_obj['intern_ip'] = old_address vm.dataset_obj.commit() raise
def rename(self, new_hostname): """Rename the VM""" with Transaction() as transaction: self.set_hostname(new_hostname, transaction=transaction) self.check_serveradmin_config() self.shutdown(transaction=transaction) self.hypervisor.redefine_vm(self, new_fqdn=new_hostname) log.warning( 'Domain redefinition cannot be rolled back properly. Your ' 'domain is still defined with the new name.') self.hypervisor.mount_vm_storage(self, transaction=transaction) self.run_puppet() self.hypervisor.umount_vm_storage(self) self.start(transaction=transaction)
def vm_migrate( vm_hostname: str = None, vm_object=None, hypervisor_hostname: Optional[str] = None, run_puppet: bool = False, debug_puppet: bool = False, offline: bool = False, offline_transport: str = 'drbd', allow_reserved_hv: bool = False, no_shutdown: bool = False, enforce_vm_env: bool = False, disk_size: Optional[int] = None, soft_preferences: bool = False, ): """Migrate a VM to a new hypervisor.""" if not (bool(vm_hostname) ^ bool(vm_object)): raise IGVMError( 'Only one of vm_hostname or vm_object can be given!' ) with ExitStack() as es: if vm_object: # VM given as object and hopefully already locked _vm = vm_object else: _vm = es.enter_context( _get_vm(vm_hostname, allow_retired=True) ) # We have to check migration settings before searching for a HV, # because the new disk size must be checked and set current_size_gib = _vm.dataset_obj['disk_size_gib'] _vm.dataset_obj['disk_size_gib'] = _vm.hypervisor.vm_new_disk_size( _vm, offline, offline_transport, disk_size ) if hypervisor_hostname: hypervisor = es.enter_context(_get_hypervisor( hypervisor_hostname, allow_reserved=allow_reserved_hv )) if _vm.hypervisor.fqdn == hypervisor.fqdn: raise IGVMError( 'Source and destination Hypervisor is the same!' ) else: hypervisor = es.enter_context(_get_best_hypervisor( _vm, ['online', 'online_reserved'] if allow_reserved_hv else ['online'], offline, enforce_vm_env, soft_preferences, )) # After the HV is chosen, disk_size_gib must be restored # to pass _check_attributes(_vm) _vm.dataset_obj['disk_size_gib'] = current_size_gib was_running = _vm.is_running() # There is no point of online migration, if the VM is already shutdown. if not was_running: offline = True if not offline and run_puppet: raise IGVMError('Online migration cannot run Puppet.') # Validate destination hypervisor can run the VM (needs to happen after # setting new IP!) hypervisor.check_vm(_vm, offline) # Require VM to be in sync with serveradmin _check_attributes(_vm) _vm.check_serveradmin_config() with Transaction() as transaction: _vm.hypervisor.migrate_vm( _vm, hypervisor, offline, offline_transport, transaction, no_shutdown, disk_size, ) previous_hypervisor = _vm.hypervisor _vm.hypervisor = hypervisor def _reset_hypervisor(): _vm.hypervisor = previous_hypervisor transaction.on_rollback('reset hypervisor', _reset_hypervisor) if run_puppet: hypervisor.mount_vm_storage(_vm, transaction) _vm.run_puppet(debug=debug_puppet) hypervisor.umount_vm_storage(_vm) if offline and was_running: _vm.start(transaction=transaction) _vm.reset_state() # Add migration log entries to hypervisor and previous_hypervisor hypervisor.log_migration(_vm, '+') transaction.on_rollback( 'reset hypervisor log', hypervisor.log_migration, _vm, '-', ) previous_hypervisor.log_migration(_vm, '-') transaction.on_rollback( 'reset previous hypervisor log', previous_hypervisor.log_migration, _vm, '+', ) # Update Serveradmin _vm.dataset_obj['hypervisor'] = hypervisor.dataset_obj['hostname'] _vm.dataset_obj.commit() # If removing the existing VM fails we shouldn't risk undoing the newly # migrated one. previous_hypervisor.undefine_vm(_vm)
def vm_migrate(vm_hostname, hypervisor_hostname=None, run_puppet=False, debug_puppet=False, offline=False, offline_transport='drbd', allow_reserved_hv=False, no_shutdown=False): """Migrate a VM to a new hypervisor.""" with ExitStack() as es: vm = es.enter_context(_get_vm(vm_hostname, allow_retired=True)) if vm.dataset_obj['igvm_operation_mode'] != 'kvm': raise NotImplementedError( 'This operation is not yet supported for {}'.format( vm.dataset_obj['igvm_operation_mode'])) if hypervisor_hostname: hypervisor = es.enter_context( _get_hypervisor(hypervisor_hostname, allow_reserved=allow_reserved_hv)) if vm.hypervisor.fqdn == hypervisor.fqdn: raise IGVMError( 'Source and destination Hypervisor is the same!') else: hypervisor = es.enter_context( _get_best_hypervisor( vm, ['online', 'online_reserved'] if allow_reserved_hv else ['online'], offline, )) was_running = vm.is_running() # There is no point of online migration, if the VM is already shutdown. if not was_running: offline = True if not offline and run_puppet: raise IGVMError('Online migration cannot run Puppet.') # Validate destination hypervisor can run the VM (needs to happen after # setting new IP!) hypervisor.check_vm(vm, offline) # Require VM to be in sync with serveradmin _check_attributes(vm) vm.check_serveradmin_config() with Transaction() as transaction: vm.hypervisor.migrate_vm( vm, hypervisor, offline, offline_transport, transaction, no_shutdown, ) previous_hypervisor = vm.hypervisor vm.hypervisor = hypervisor def _reset_hypervisor(): vm.hypervisor = previous_hypervisor transaction.on_rollback('reset hypervisor', _reset_hypervisor) if run_puppet: hypervisor.mount_vm_storage(vm, transaction) vm.run_puppet(debug=debug_puppet) hypervisor.umount_vm_storage(vm) if offline and was_running: vm.start(transaction=transaction) vm.reset_state() # Update Serveradmin vm.dataset_obj['hypervisor'] = hypervisor.dataset_obj['hostname'] vm.dataset_obj.commit() # If removing the existing VM fails we shouldn't risk undoing the newly # migrated one. previous_hypervisor.undefine_vm(vm)