def validate(self, attrs): target_hostname_or_uuid = attrs.get('target_hostname_or_uuid', None) target_disk_id = attrs.get('target_disk_id', None) if target_hostname_or_uuid and not target_disk_id: err_msg = _( 'This field is required when target_hostname_or_uuid is specified.' ) self._errors['target_disk_id'] = s.ErrorList([err_msg]) return attrs elif not target_hostname_or_uuid and target_disk_id: err_msg = _( 'This field is required when target_disk_id is specified.') self._errors['target_hostname_or_uuid'] = s.ErrorList([err_msg]) return attrs elif target_hostname_or_uuid and target_disk_id: try: self.target_vm = get_vm(self.request, target_hostname_or_uuid, exists_ok=True, noexists_fail=True, check_node_status=None) except ObjectNotFound as exc: self._errors['target_hostname_or_uuid'] = s.ErrorList( [exc.detail]) else: try: self.target_vm_disk_id, self.target_vm_real_disk_id, self.target_vm_disk_zfs_filesystem = \ get_disk_id(self.request, self.target_vm, disk_id=target_disk_id) except InvalidInput as exc: self._errors['target_disk_id'] = s.ErrorList([exc.detail]) return attrs
def validate(self, attrs): if attrs.get('show_all', False): attrs['dc_bound'] = False request = self.request since = attrs.get('since', None) until = attrs.get('until', None) if until and not since: self._errors['since'] = s.ErrorList([_('Missing value.')]) return attrs if since and not until: attrs['until'] = s.TimeStampField.now() dc_bound = attrs['dc_bound'] vm_uuids = attrs.get('vm_uuids', None) vm_hostnames = attrs.get('vm_hostnames', None) node_uuids = attrs.get('node_uuids', None) node_hostnames = attrs.get('node_hostnames', None) if vm_hostnames is not None or vm_uuids is not None: vms_qs = Vm.objects.exclude(status=Vm.NOTCREATED).filter(slavevm__isnull=True)\ .filter(Q(hostname__in=vm_hostnames or ()) | Q(uuid__in=vm_uuids or ())) else: vms_qs = None if dc_bound: if node_uuids is not None: self._errors['node_uuids'] = s.ErrorList([PERMISSION_DENIED]) return attrs if node_hostnames is not None: self._errors['node_hostnames'] = s.ErrorList( [PERMISSION_DENIED]) return attrs if vms_qs: vms_qs = vms_qs.filter(dc=request.dc) else: if node_hostnames is not None or node_uuids is not None: qs = Node.objects.filter( Q(hostname__in=node_hostnames or ()) | Q(uuid__in=node_uuids or ())) self.nodes = map( str, qs.order_by('uuid').values_list('uuid', flat=True)) if vms_qs: self.vms = map( str, vms_qs.order_by('uuid').values_list('uuid', flat=True)) return attrs
def validate(self, attrs): if attrs.get('EMAIL_USE_TLS', None) and attrs.get('EMAIL_USE_SSL', None): self._errors['EMAIL_USE_TLS'] = self._errors['EMAIL_USE_SSL'] = s.ErrorList([ _('Cannot enable EMAIL_USE_TLS and EMAIL_USE_SSL together.') ]) # Do not allow SMS registration without the SMS module if (attrs.get('SMS_REGISTRATION_ENABLED', None) and not attrs.get('SMS_ENABLED', self.request.dc.settings.SMS_ENABLED)): self._errors['SMS_REGISTRATION_ENABLED'] = s.ErrorList([_('SMS support must be enabled first.')]) return super(DefaultDcSettingsSerializer, self).validate(attrs)
def validate(self, attrs): db_only_manifest_keys = {'dc_bound', 'dc_bound_bool', 'owner'} if db_only_manifest_keys.issuperset(attrs.keys()): self.update_manifest = False try: alias = attrs['alias'] except KeyError: alias = self.object.alias try: version = attrs['version'] except KeyError: version = self.object.version qs = Image.objects if self.object.pk: qs = qs.exclude(pk=self.object.pk) if qs.filter(alias__iexact=alias, version=version).exists(): self._errors['alias'] = s.ErrorList([_('This alias is already in use. ' 'Please supply a different alias or version.')]) if self.request.method == 'POST' and self._dc_bound: limit = self._dc_bound.settings.VMS_IMAGE_LIMIT if limit is not None: if Image.objects.filter(dc_bound=self._dc_bound).count() >= int(limit): raise s.ValidationError(_('Maximum number of server disk images reached')) return super(ImageSerializer, self).validate(attrs)
def validate(self, attrs): # User is or will be bound to this DC dc = self._dc_bound if attrs.get('dc_bound_bool', self.object.dc_bound_bool) and attrs.get( 'is_staff', self.object.is_staff): self._errors['dc_bound'] = _( 'A SuperAdmin user cannot be DC-bound.') if dc: # User is or will be member of these groups try: groups = attrs['roles_api'] except KeyError: if self.object.pk: groups = self.object.roles.all() else: groups = () # A DC-bound user cannot be a member of a group that is assigned to another DC other than user.dc_bound if Dc.objects.filter(roles__in=groups).exclude(id=dc.id).exists(): self._errors['dc_bound'] = s.ErrorList([ _("User's group(s) are attached into another datacenter(s)." ) ]) return attrs
def validate(self, attrs): object_type = attrs.get('content_type', None) object_name = attrs.get('object_name', None) # object_name depends on object_type if object_name: if not object_type: self._errors['object_type'] = s.ErrorList([ _('object_type attribute is required when ' 'filtering by object_name.') ]) return attrs self._content_type = content_type = ContentType.objects.get( model=object_type) model_class = content_type.model_class() lookup_kwargs = model_class.get_log_name_lookup_kwargs(object_name) filter_kwargs = { key + '__icontains': val for key, val in lookup_kwargs.items() } self._object_pks = list( model_class.objects.filter(**filter_kwargs).values_list( 'pk', flat=True)) return attrs
def validate(self, attrs): try: zpool = attrs['zpool'] except KeyError: zpool = self.object.zpool try: node = attrs['node'] except KeyError: node = self.object.node try: attrs['zpool'] = get_zpools(self.request).get(node=node, zpool=zpool) except NodeStorage.DoesNotExist: self._errors['zpool'] = s.ErrorList( [_('Zpool does not exist on node.')]) # Check total number of existing backup definitions - Issue #chili-447 if self.request.method == 'POST': limit = self.request.dc.settings.VMS_VM_BACKUP_DEFINE_LIMIT if limit is not None: total = self._model_.objects.filter(vm=self.object.vm).count() if int(limit) <= total: raise s.ValidationError( _('Maximum number of backup definitions reached.')) return attrs
def validate(self, attrs): # Check if it is possible to override a boolean setting for source, value in attrs.items(): if source in self._override_disabled_ and not getattr(settings, source, False) and value: self._errors[source] = s.ErrorList([_('Cannot override global setting.')]) del attrs[source] return attrs
def validate(self, attrs): if attrs.get('EMAIL_USE_TLS', None) and attrs.get( 'EMAIL_USE_SSL', None): self._errors['EMAIL_USE_TLS'] = self._errors[ 'EMAIL_USE_SSL'] = s.ErrorList([ _('Cannot enable EMAIL_USE_TLS and EMAIL_USE_SSL together.' ) ]) return attrs
def validate_dummy_serializer(serializer, value): ser = serializer(data=value) ser.is_valid() for i in ser.init_data: if i not in ser.fields: # noinspection PyProtectedMember ser._errors[i] = s.ErrorList([_('Invalid field.')]) if ser.errors: raise s.NestedValidationError(ser.errors)
def validate(self, attrs): try: network = attrs['network'] except KeyError: network = self.object.network try: netmask = attrs['netmask'] except KeyError: netmask = self.object.netmask try: ip_network = Subnet.get_ip_network(network, netmask) if ip_network.is_reserved: raise ValueError except ValueError: self._errors['network'] = self._errors['netmask'] = \ s.ErrorList([_('Enter a valid IPv4 network and netmask.')]) if self.request.method == 'POST' and self._dc_bound: limit = self._dc_bound.settings.VMS_NET_LIMIT if limit is not None: if Subnet.objects.filter( dc_bound=self._dc_bound).count() >= int(limit): raise s.ValidationError( _('Maximum number of networks reached')) if self._dc_bound: try: vlan_id = attrs['vlan_id'] except KeyError: vlan_id = self.object.vlan_id dc_settings = self._dc_bound.settings if dc_settings.VMS_NET_VLAN_RESTRICT and vlan_id not in dc_settings.VMS_NET_VLAN_ALLOWED: self._errors['vlan_id'] = s.ErrorList( [_('VLAN ID is not available in datacenter.')]) return attrs
def validate(self, attrs): strategy = int(attrs.get('strategy', self.object.strategy)) if strategy == DcNode.RESERVED: cpu = int(attrs.get('cpu', self.object.cpu)) ram = int(attrs.get('ram', self.object.ram)) disk = int(attrs.get('disk', self.object.disk)) cpu_nf, ram_nf, disk_nf = self.object.get_nonreserved_free_resources( exclude_this_dc=True) if cpu > cpu_nf: self._errors['cpu'] = s.ErrorList( [_('Not enough free CPUs on node.')]) if ram > ram_nf: self._errors['ram'] = s.ErrorList( [_('Not enough free RAM on node.')]) if disk > disk_nf: self._errors['disk'] = s.ErrorList( [_('Not enough free disk space on node.')]) return attrs
def validate(self, attrs): self.img_manifest_url = attrs['manifest_url'] file_url = attrs.get('file_url', None) if not file_url: file_url = self.img_manifest_url.strip('/') + '/file' self.img_file_url = file_url try: req_file = HttpClient(file_url) req_file.get(timeout=5, max_size=32) except TooLarge: pass except RequestException as e: self._errors['file_url'] = s.ErrorList([_('Image file URL is unreachable (%s).') % e]) return attrs
def validate(self, attrs): # noqa: R701 try: network = attrs['network'] except KeyError: network = self.object.network try: netmask = attrs['netmask'] except KeyError: netmask = self.object.netmask try: vxlan_id = attrs['vxlan_id'] except KeyError: vxlan_id = self.object.vxlan_id try: mtu = attrs['mtu'] except KeyError: mtu = self.object.mtu try: nic_tag = attrs['nic_tag'] except KeyError: nic_tag = self.object.nic_tag try: ip_network = Subnet.get_ip_network(network, netmask) if ip_network.is_reserved: raise ValueError except ValueError: self._errors['network'] = self._errors['netmask'] = \ s.ErrorList([_('Enter a valid IPv4 network and netmask.')]) if self.request.method == 'POST' and self._dc_bound: limit = self._dc_bound.settings.VMS_NET_LIMIT if limit is not None: if Subnet.objects.filter( dc_bound=self._dc_bound).count() >= int(limit): raise s.ValidationError( _('Maximum number of networks reached.')) nic_tag_type = Node.all_nictags()[nic_tag] # retrieve all available nictags and see what is the type of the current nic tag # if type is overlay then vxlan is mandatory argument if nic_tag_type == 'overlay rule': if not vxlan_id: self._errors['vxlan_id'] = s.ErrorList([ _('VXLAN ID is required when an ' 'overlay NIC tag is selected.') ]) else: attrs['vxlan_id'] = None # validate MTU for overlays and etherstubs, and physical nics if nic_tag_type == 'overlay rule': # if MTU was not set for the overlay if not mtu: attrs['mtu'] = 1400 if mtu > 8900: self._errors['mtu'] = s.ErrorList([ s.IntegerField.default_error_messages['max_value'] % { 'limit_value': 8900 } ]) if nic_tag_type in ('normal', 'aggr') and mtu and mtu < 1500: self._errors['mtu'] = s.ErrorList([ s.IntegerField.default_error_messages['min_value'] % { 'limit_value': 1500 } ]) if self._dc_bound: try: vlan_id = attrs['vlan_id'] except KeyError: vlan_id = self.object.vlan_id dc_settings = self._dc_bound.settings if dc_settings.VMS_NET_VLAN_RESTRICT and vlan_id not in dc_settings.VMS_NET_VLAN_ALLOWED: self._errors['vlan_id'] = s.ErrorList( [_('VLAN ID is not available in datacenter.')]) if dc_settings.VMS_NET_VXLAN_RESTRICT and vxlan_id not in dc_settings.VMS_NET_VXLAN_ALLOWED: self._errors['vxlan_id'] = s.ErrorList( [_('VXLAN ID is not available in datacenter.')]) return super(NetworkSerializer, self).validate(attrs)
def update_errors(self, fields, err_msg): errors = {} for i in fields: errors[i] = s.ErrorList([err_msg]) return errors
class NodeStorageSerializer(s.InstanceSerializer): """ vms.models.NodeStorage """ error_negative_resources = s.ErrorList([_('Value is too low because of existing virtual machines.')]) _model_ = NodeStorage _default_fields_ = ('alias', 'owner', 'size_coef', 'zpool') node = s.Field(source='node.hostname') zpool = s.ChoiceField(source='zpool') alias = s.SafeCharField(source='storage.alias', max_length=32) owner = s.SlugRelatedField(source='storage.owner', slug_field='username', queryset=User.objects, required=False) access = s.IntegerChoiceField(source='storage.access', choices=Storage.ACCESS, default=Storage.PRIVATE) type = s.IntegerChoiceField(source='storage.type', choices=Storage.TYPE, default=Storage.LOCAL) size = s.IntegerField(source='storage.size_total', read_only=True) size_coef = s.DecimalField(source='storage.size_coef', min_value=0, max_digits=4, decimal_places=2) size_free = s.IntegerField(source='storage.size_free', read_only=True) created = s.DateTimeField(source='storage.created', read_only=True, required=False) desc = s.SafeCharField(source='storage.desc', max_length=128, required=False) def __init__(self, request, instance, *args, **kwargs): self._update_fields_ = ['alias', 'owner', 'access', 'desc', 'type', 'size_coef'] super(NodeStorageSerializer, self).__init__(request, instance, *args, **kwargs) if not kwargs.get('many', False): self._size_coef = instance.storage.size_coef self.fields['owner'].queryset = get_owners(request) if request.method == 'POST': self.fields['zpool'].choices = [(i, i) for i in instance.node.zpools.keys()] self._update_fields_.append('zpool') else: self.fields['zpool'].read_only = True def validate_owner(self, attrs, source): """Cannot change owner while pending tasks exist""" validate_owner(self.object, attrs.get(source, None), _('Storage')) return attrs def validate_alias(self, attrs, source): try: value = attrs[source] except KeyError: pass else: validate_alias(self.object, value, field_comparison='storage__alias__iexact') return attrs def validate(self, attrs): # Default owner is request.user, but setting this in __init__ does not work if 'storage.owner' in attrs and attrs['storage.owner'] is None: if self.object.pk: del attrs['storage.owner'] else: attrs['storage.owner'] = self.request.user return attrs @property def update_storage_resources(self): """True if size_coef changed""" return not(self.object.storage.size_coef == self._size_coef)
class NodeDefineSerializer(s.InstanceSerializer): """ vms.models.Node """ error_negative_resources = s.ErrorList( [_('Value is too low because of existing virtual machines.')]) _model_ = Node _update_fields_ = ('status', 'owner', 'is_compute', 'is_backup', 'cpu_coef', 'ram_coef', 'monitoring_hostgroups', 'monitoring_templates') hostname = s.CharField(read_only=True) uuid = s.CharField(read_only=True) address = s.CharField(read_only=True) status = s.IntegerChoiceField(choices=Node.STATUS_DB) node_status = s.DisplayChoiceField(source='status', choices=Node.STATUS_DB, read_only=True) owner = s.SlugRelatedField(slug_field='username', queryset=User.objects, read_only=False) is_head = s.BooleanField(read_only=True) is_compute = s.BooleanField() is_backup = s.BooleanField() cpu = s.IntegerField(source='cpu_total', read_only=True) ram = s.IntegerField(source='ram_total', read_only=True) cpu_coef = s.DecimalField(min_value=0, max_digits=4, decimal_places=2) ram_coef = s.DecimalField(min_value=0, max_value=1, max_digits=4, decimal_places=2) cpu_free = s.IntegerField(read_only=True) ram_free = s.IntegerField(read_only=True) ram_kvm_overhead = s.IntegerField(read_only=True) sysinfo = s.Field( source='api_sysinfo') # Field is read_only=True by default monitoring_hostgroups = s.ArrayField(max_items=16, default=[]) monitoring_templates = s.ArrayField(max_items=32, default=[]) created = s.DateTimeField(read_only=True, required=False) def __init__(self, request, instance, *args, **kwargs): super(NodeDefineSerializer, self).__init__(request, instance, *args, **kwargs) self.clear_cache = False self.status_changed = False self.monitoring_changed = False if not kwargs.get('many', False): # Used for update_node_resources() self._cpu_coef = instance.cpu_coef self._ram_coef = instance.ram_coef # Only active users self.fields['owner'].queryset = get_owners(request) def validate_owner(self, attrs, source): """Cannot change owner while pending tasks exist""" validate_owner(self.object, attrs.get(source, None), _('Compute node')) return attrs def validate_status(self, attrs, source): """Mark the status change -> used for triggering the signal. Do not allow a manual status change from unlicensed status.""" try: value = attrs[source] except KeyError: return attrs if self.object.status != value: node = self.object if node.is_unlicensed(): raise s.ValidationError( _('Cannot change status. Please add a valid license first.' )) if node.is_unreachable() or node.is_offline( ): # Manual switch from unreachable and offline state if settings.DEBUG: logger.warning( 'DEBUG mode on => skipping status checking of node %s', self.object) elif not node_ping(self.object, all_workers=False ): # requires that node is really online raise s.ValidationError( _('Cannot change status. Compute node is down.')) self.clear_cache = True self.status_changed = value return attrs def validate_is_compute(self, attrs, source): """Search for defined VMs when turning compute capability off""" if source in attrs and self.object.is_compute != attrs[source]: if self.object.vm_set.exists(): raise s.ValidationError(_('Found existing VMs on node.')) self.clear_cache = True return attrs def validate_is_backup(self, attrs, source): """Search for existing backup definitions, which are using this node""" if source in attrs and self.object.is_backup != attrs[source]: if self.object.backupdefine_set.exists(): raise s.ValidationError( _('Found existing VM backup definitions.')) self.clear_cache = True # Check existing backups when removing node if self.request.method == 'DELETE': if self.object.backup_set.exists(): raise s.ValidationError(_('Found existing VM backups.')) self.clear_cache = True return attrs def validate_monitoring_hostgroups(self, attrs, source): """Mark the monitoring change -> used for triggering the signal""" if source in attrs and self.object.monitoring_hostgroups != attrs[ source]: self.monitoring_changed = True return attrs def validate_monitoring_templates(self, attrs, source): """Mark the monitoring change -> used for triggering the signal""" if source in attrs and self.object.monitoring_templates != attrs[ source]: self.monitoring_changed = True return attrs @property def update_node_resources(self): """True if cpu_coef or ram_coef changed""" return not (self.object.cpu_coef == self._cpu_coef and self.object.ram_coef == self._ram_coef)
class NodeDefineSerializer(s.InstanceSerializer): """ vms.models.Node """ error_negative_resources = s.ErrorList( [_('Value is too low because of existing virtual machines.')]) _model_ = Node _update_fields_ = ('status', 'owner', 'address', 'is_compute', 'is_backup', 'note', 'cpu_coef', 'ram_coef', 'monitoring_hostgroups', 'monitoring_templates') hostname = s.CharField(read_only=True) uuid = s.CharField(read_only=True) address = s.ChoiceField() status = s.IntegerChoiceField(choices=Node.STATUS_DB) node_status = s.DisplayChoiceField(source='status', choices=Node.STATUS_DB, read_only=True) owner = s.SlugRelatedField(slug_field='username', queryset=User.objects, read_only=False) is_head = s.BooleanField(read_only=True) is_compute = s.BooleanField() is_backup = s.BooleanField() note = s.CharField(required=False) cpu = s.IntegerField(source='cpu_total', read_only=True) ram = s.IntegerField(source='ram_total', read_only=True) cpu_coef = s.DecimalField(min_value=0, max_digits=4, decimal_places=2) ram_coef = s.DecimalField(min_value=0, max_value=1, max_digits=4, decimal_places=2) cpu_free = s.IntegerField(read_only=True) ram_free = s.IntegerField(read_only=True) ram_kvm_overhead = s.IntegerField(read_only=True) sysinfo = s.Field( source='api_sysinfo') # Field is read_only=True by default monitoring_hostgroups = s.ArrayField( max_items=16, default=[], validators=(RegexValidator( regex=MonitoringBackend.RE_MONITORING_HOSTGROUPS), )) monitoring_templates = s.ArrayField(max_items=32, default=[]) created = s.DateTimeField(read_only=True, required=False) def __init__(self, request, instance, *args, **kwargs): super(NodeDefineSerializer, self).__init__(request, instance, *args, **kwargs) self.clear_cache = False self.status_changed = False self.address_changed = False self.old_ip_address = None self.monitoring_changed = False if not kwargs.get('many', False): # Valid node IP addresses self.fields['address'].choices = [(ip, ip) for ip in instance.ips] # Used for update_node_resources() self._cpu_coef = instance.cpu_coef self._ram_coef = instance.ram_coef # Only active users self.fields['owner'].queryset = get_owners(request) def validate_owner(self, attrs, source): """Cannot change owner while pending tasks exist""" validate_owner(self.object, attrs.get(source, None), _('Compute node')) return attrs def validate_address(self, attrs, source): """Mark that node IP address is going to change""" new_address = attrs.get(source, None) if new_address and self.object.address != new_address: self.address_changed = True try: self.old_ip_address = self.object.ip_address except ObjectDoesNotExist: self.old_ip_address = None return attrs def validate_status(self, attrs, source): """Mark the status change -> used for triggering the signal. Do not allow a manual status change from unlicensed status.""" try: value = attrs[source] except KeyError: return attrs if self.object.status != value: node = self.object if node.is_unlicensed(): raise s.ValidationError( _('Cannot change status. Please add a valid license first.' )) if node.is_unreachable() or node.is_offline( ): # Manual switch from unreachable and offline state if settings.DEBUG: logger.warning( 'DEBUG mode on => skipping status checking of node %s', self.object) elif not node_ping(self.object, all_workers=False ): # requires that node is really online raise s.ValidationError( _('Cannot change status. Compute node is down.')) self.clear_cache = True self.status_changed = value return attrs def validate_is_compute(self, attrs, source): """Search for defined VMs when turning compute capability off""" if source in attrs and self.object.is_compute != attrs[source]: if self.object.vm_set.exists(): raise s.ValidationError(_('Found existing VMs on node.')) self.clear_cache = True return attrs def validate_is_backup(self, attrs, source): """Search for existing backup definitions, which are using this node""" if source in attrs and self.object.is_backup != attrs[source]: if self.object.backupdefine_set.exists(): raise s.ValidationError( _('Found existing VM backup definitions.')) self.clear_cache = True # Check existing backups when removing node if self.request.method == 'DELETE': if self.object.backup_set.exists(): raise s.ValidationError(_('Found existing VM backups.')) self.clear_cache = True return attrs def validate_monitoring_hostgroups(self, attrs, source): """Mark the monitoring change -> used for triggering the signal""" if source in attrs and self.object.monitoring_hostgroups != attrs[ source]: self.monitoring_changed = True return attrs def validate_monitoring_templates(self, attrs, source): """Mark the monitoring change -> used for triggering the signal""" if source in attrs and self.object.monitoring_templates != attrs[ source]: self.monitoring_changed = True return attrs @property def update_node_resources(self): """True if cpu_coef or ram_coef changed""" return not (self.object.cpu_coef == self._cpu_coef and self.object.ram_coef == self._ram_coef) def save(self): """Update compute node attributes in database""" node = self.object # NOTE: # Changing cpu or disk coefficients can lead to negative numbers in node.cpu/ram_free or dc_node.cpu/ram_free try: with transaction.atomic(): node.save(update_resources=self.update_node_resources, clear_cache=self.clear_cache) if self.update_node_resources: if node.cpu_free < 0 or node.dcnode_set.filter( cpu_free__lt=0).exists(): raise IntegrityError('cpu_check') if node.ram_free < 0 or node.dcnode_set.filter( ram_free__lt=0).exists(): raise IntegrityError('ram_check') except IntegrityError as exc: errors = {} exc_error = str(exc) # ram or cpu constraint was violated on vms_dcnode (can happen when DcNode strategy is set to RESERVED) # OR a an exception was raised above if 'ram_check' in exc_error: errors['ram_coef'] = self.error_negative_resources if 'cpu_check' in exc_error: errors['cpu_coef'] = self.error_negative_resources if not errors: raise exc return errors if self.update_node_resources: # cpu_free or ram_free changed self.reload() return None
def validate(self, attrs): vm = self.vm node = attrs.get('node', vm.node) changing_node = attrs.get('node', vm.node) != vm.node if self._live: if not changing_node: self._errors['live'] = s.ErrorList( [_('Live migration cannot be performed locally.')]) return attrs if (node.platform_version_short < MIN_PLATFORM_VERSION_LIVE_MIGRATION or vm.node.platform_version_short < MIN_PLATFORM_VERSION_LIVE_MIGRATION): self._errors['live'] = s.ErrorList([ _('Source and/or target node platform does not ' 'support live migration.') ]) return attrs # Ghost VM is a copy of a VM used to take up place in DB. # When node is changing we have to have all disks in a ghost VM. # When changing only disk pools, only the changed disks have to be in a ghost VM. ghost_vm = SlaveVm(_master_vm=vm) ghost_vm.reserve_resources = changing_node ghost_vm.set_migration_hostname() ghost_vm.node = node ghost_vm_define = SlaveVmDefine(ghost_vm) # Validate root_zpool (we can do this after we know the new node) root_zpool = attrs.get('root_zpool', None) # Every pool must be validated when changing node try: root_zpool = ghost_vm_define.save_root_zpool( root_zpool, save_same_zpool=changing_node) except APIValidationError as exc: self._errors['node'] = exc.api_errors return attrs # Validate disk_zpools (we can do this after we know the new node) if ghost_vm.vm.is_kvm(): disk_zpools = attrs.get('disk_zpools', {}) try: disk_zpools = ghost_vm_define.save_disk_zpools( disk_zpools, save_same_zpool=changing_node) except APIValidationError as exc: self._errors['node'] = exc.api_errors return attrs else: disk_zpools = {} # Nothing changed, he? if not changing_node and not (root_zpool or disk_zpools): raise s.ValidationError(_('Nothing to do.')) # Validate dc_node resources try: ghost_vm_define.validate_node_resources( ignore_cpu_ram=not changing_node) except APIValidationError as exc: self._errors['node'] = exc.api_errors return attrs # Validate storage resources try: ghost_vm_define.validate_storage_resources() except APIValidationError as exc: self._errors['node'] = exc.api_errors return attrs # Validate images self.img_required = ghost_vm_define.check_required_images() # Save params # noinspection PyAttributeOutsideInit self._root_zpool = root_zpool # noinspection PyAttributeOutsideInit self._disk_zpools = disk_zpools # noinspection PyAttributeOutsideInit self.ghost_vm_define = ghost_vm_define # noinspection PyAttributeOutsideInit self.changing_node = changing_node return attrs