class LAGGInterfaceForm(ModelForm): lagg_interfaces = forms.MultipleChoiceField( widget=forms.SelectMultiple(), label=_('Physical NICs in the LAGG'), ) class Meta: model = models.LAGGInterface exclude = ('lagg_interface', ) widgets = { 'lagg_protocol': forms.RadioSelect(), } def __init__(self, *args, **kwargs): super(LAGGInterfaceForm, self).__init__(*args, **kwargs) self.fields['lagg_interfaces'].choices = list( choices.NICChoices(nolagg=True)) # Remove empty option (e.g. -------) self.fields['lagg_protocol'].choices = ( self.fields['lagg_protocol'].choices[1:]) def save(self, *args, **kwargs): # Search for a available slot for laggX interface interface_names = [ v[0] for v in models.Interfaces.objects.all().values_list( 'int_interface') ] candidate_index = 0 while ("lagg%d" % (candidate_index)) in interface_names: candidate_index += 1 lagg_name = "lagg%d" % candidate_index lagg_protocol = self.cleaned_data['lagg_protocol'] lagg_member_list = self.cleaned_data['lagg_interfaces'] with transaction.atomic(): # Step 1: Create an entry in interface table that # represents the lagg interface lagg_interface = models.Interfaces(int_interface=lagg_name, int_name=lagg_name, int_dhcp=False, int_ipv6auto=False) lagg_interface.save() # Step 2: Write associated lagg attributes lagg_interfacegroup = models.LAGGInterface( lagg_interface=lagg_interface, lagg_protocol=lagg_protocol) lagg_interfacegroup.save() # Step 3: Write lagg's members in the right order order = 0 for interface in lagg_member_list: lagg_member_entry = models.LAGGInterfaceMembers( lagg_interfacegroup=lagg_interfacegroup, lagg_ordernum=order, lagg_physnic=interface, lagg_deviceoptions='up') lagg_member_entry.save() order = order + 1 self.instance = lagg_interfacegroup notifier().start("network") return lagg_interfacegroup
class LAGGInterfaceForm(forms.Form): lagg_protocol = forms.ChoiceField(choices=choices.LAGGType, widget=forms.RadioSelect()) lagg_interfaces = forms.MultipleChoiceField( widget=forms.SelectMultiple(), label=_('Physical NICs in the LAGG') ) def __init__(self, *args, **kwargs): super(LAGGInterfaceForm, self).__init__(*args, **kwargs) self.fields['lagg_interfaces'].choices = list( choices.NICChoices(nolagg=True) )
class LAGGInterfaceForm(ModelForm): lagg_interfaces = forms.MultipleChoiceField( widget=forms.SelectMultiple(), label=_('Physical NICs in the LAGG'), ) class Meta: model = models.LAGGInterface exclude = ('lagg_interface', ) widgets = { 'lagg_protocol': forms.RadioSelect(), } def __init__(self, *args, **kwargs): super(LAGGInterfaceForm, self).__init__(*args, **kwargs) self.fields['lagg_interfaces'].choices = list( choices.NICChoices(nolagg=True)) # For HA we dont want people using failover type # See #25351 if not notifier().is_freenas() and notifier().failover_licensed(): filter_failover_type = True else: filter_failover_type = False # Remove empty option (e.g. -------) lagg_protocol = list(self.fields['lagg_protocol'].choices[1:]) if filter_failover_type: lagg_protocol = filter(lambda x: x[0] != 'failover', lagg_protocol) self.fields['lagg_protocol'].choices = tuple(lagg_protocol) def save(self, *args, **kwargs): # Search for a available slot for laggX interface interface_names = [ v[0] for v in models.Interfaces.objects.all().values_list( 'int_interface') ] candidate_index = 0 while ("lagg%d" % (candidate_index)) in interface_names: candidate_index += 1 lagg_name = "lagg%d" % candidate_index lagg_protocol = self.cleaned_data['lagg_protocol'] lagg_member_list = self.cleaned_data['lagg_interfaces'] with DBSync(): model_objs = [] try: # Step 1: Create an entry in interface table that # represents the lagg interface lagg_interface = models.Interfaces(int_interface=lagg_name, int_name=lagg_name, int_dhcp=False, int_ipv6auto=False) lagg_interface.save() model_objs.append(lagg_interface) # Step 2: Write associated lagg attributes lagg_interfacegroup = models.LAGGInterface( lagg_interface=lagg_interface, lagg_protocol=lagg_protocol) lagg_interfacegroup.save() model_objs.append(lagg_interfacegroup) # Step 3: Write lagg's members in the right order order = 0 for interface in lagg_member_list: lagg_member_entry = models.LAGGInterfaceMembers( lagg_interfacegroup=lagg_interfacegroup, lagg_ordernum=order, lagg_physnic=interface, ) model_objs.append( models.Interfaces.objects.create( int_interface=interface, int_name=f'member of {lagg_name}', )) lagg_member_entry.save() model_objs.append(lagg_member_entry) order = order + 1 except Exception: for obj in reversed(model_objs): obj.delete() raise self.instance = lagg_interfacegroup return lagg_interfacegroup def delete(self, *args, **kwargs): with DBSync(): super(LAGGInterfaceForm, self).delete(*args, **kwargs) notifier().start("network") def done(self, *args, **kwargs): super(LAGGInterfaceForm, self).done(*args, **kwargs) notifier().start("network")
class VolumeWizardForm(forms.Form): volume_name = forms.CharField(max_length=30, label=_('Volume name'), required=False) volume_fstype = forms.ChoiceField( choices=((x, x) for x in ('UFS', 'ZFS')), widget=forms.RadioSelect(attrs=attrs_dict), label=_('Filesystem type')) volume_disks = forms.MultipleChoiceField( choices=(), widget=forms.SelectMultiple(attrs=attrs_dict), label='Member disks', required=False) group_type = forms.ChoiceField(choices=(), widget=forms.RadioSelect(attrs=attrs_dict), required=False) force4khack = forms.BooleanField( required=False, initial=False, help_text=_('Force 4096 bytes sector size')) ufspathen = forms.BooleanField(initial=False, label=_('Specify custom path'), required=False) ufspath = forms.CharField(max_length=1024, label=_('Path'), required=False) def __init__(self, *args, **kwargs): super(VolumeWizardForm, self).__init__(*args, **kwargs) self.fields['volume_disks'].choices = self._populate_disk_choices() qs = models.Volume.objects.filter(vol_fstype='ZFS') if qs.exists(): self.fields['volume_add'] = forms.ChoiceField( label=_('Volume add'), required=False) self.fields['volume_add'].choices = [('', '-----')] + \ [(x.vol_name, x.vol_name) for x in qs] self.fields['volume_add'].widget.attrs['onChange'] = ( 'wizardcheckings(true);') self.fields['volume_fstype'].widget.attrs['onClick'] = ( 'wizardcheckings();') self.fields['ufspathen'].widget.attrs['onClick'] = ( 'toggleGeneric("id_ufspathen", ["id_ufspath"], true);') if not self.data.get("ufspathen", False): self.fields['ufspath'].widget.attrs['disabled'] = 'disabled' self.fields['ufspath'].widget.attrs['promptMessage'] = _( "Leaving this" " blank will give the volume a default path of " "/mnt/${VOLUME_NAME}") grouptype_choices = ( ('mirror', 'mirror'), ('stripe', 'stripe'), ) fstype = self.data.get("volume_fstype", None) if "volume_disks" in self.data: disks = self.data.getlist("volume_disks") else: disks = [] if fstype == "UFS": l = len(disks) - 1 if l >= 2 and (((l - 1) & l) == 0): grouptype_choices += (('raid3', 'RAID-3'), ) elif fstype == "ZFS": if len(disks) >= 3: grouptype_choices += (('raidz', 'RAID-Z'), ) if len(disks) >= 4: grouptype_choices += (('raidz2', 'RAID-Z2'), ) # Not yet #if len(disks) >= 5: # grouptype_choices += ( ('raidz3', 'RAID-Z3'), ) self.fields['group_type'].choices = grouptype_choices def _populate_disk_choices(self): disks = [] # Grab disk list # Root device already ruled out for disk, info in notifier().get_disks().items(): disks.append( Disk(info['devname'], info['capacity'], serial=info.get('ident'))) # Exclude what's already added used_disks = [] for v in models.Volume.objects.all(): used_disks.extend(v.get_disks()) qs = iSCSITargetExtent.objects.filter(iscsi_target_extent_type='Disk') used_disks.extend([i.get_device()[5:] for i in qs]) for d in list(disks): if d.dev in used_disks: disks.remove(d) choices = sorted(disks) choices = [tuple(d) for d in choices] return choices def clean_volume_name(self): vname = self.cleaned_data['volume_name'] if vname and not re.search(r'^[a-z][-_.a-z0-9]*$', vname, re.I): raise forms.ValidationError( _("The volume name must start with " "letters and may include numbers, \"-\", \"_\" and \".\" .")) if models.Volume.objects.filter(vol_name=vname).exists(): raise forms.ValidationError( _("A volume with that name already " "exists.")) return vname def clean_group_type(self): if 'volume_disks' not in self.cleaned_data or \ len(self.cleaned_data['volume_disks']) > 1 and \ self.cleaned_data['group_type'] in (None, ''): raise forms.ValidationError(_("This field is required.")) return self.cleaned_data['group_type'] def clean_ufspath(self): ufspath = self.cleaned_data['ufspath'] if not ufspath: return None if not access(ufspath, 0): raise forms.ValidationError(_("Path does not exist.")) st = stat(ufspath) if not S_ISDIR(st.st_mode): raise forms.ValidationError(_("Path is not a directory.")) return ufspath def clean(self): cleaned_data = self.cleaned_data volume_name = cleaned_data.get("volume_name", "") disks = cleaned_data.get("volume_disks") if volume_name and cleaned_data.get("volume_add"): self._errors['__all__'] = self.error_class([ _("You cannot select an existing ZFS volume and specify a new " "volume name"), ]) elif not (volume_name or cleaned_data.get("volume_add")): self._errors['__all__'] = self.error_class([ _("You must specify a new volume name or select an existing " "ZFS volume to append a virtual device"), ]) elif not volume_name: volume_name = cleaned_data.get("volume_add") if cleaned_data.get("volume_fstype") not in ('ZFS', 'UFS'): msg = _(u"You must select a filesystem") self._errors["volume_fstype"] = self.error_class([msg]) cleaned_data.pop("volume_fstype", None) if len(disks) == 0 and models.Volume.objects.filter( vol_name=volume_name).count() == 0: msg = _(u"This field is required") self._errors["volume_disks"] = self.error_class([msg]) del cleaned_data["volume_disks"] if (cleaned_data.get("volume_fstype") == 'ZFS' and \ models.Volume.objects.filter(vol_name=volume_name).exclude( vol_fstype='ZFS').count() > 0 ) or ( cleaned_data.get("volume_fstype") == 'UFS' and \ models.Volume.objects.filter( vol_name=volume_name).count() > 0 ): msg = _(u"You already have a volume with same name") self._errors["volume_name"] = self.error_class([msg]) del cleaned_data["volume_name"] if cleaned_data.get("volume_fstype", None) == 'ZFS': if volume_name in ('log', ): msg = _(u"\"log\" is a reserved word and thus cannot be used") self._errors["volume_name"] = self.error_class([msg]) cleaned_data.pop("volume_name", None) elif re.search(r'^c[0-9].*', volume_name) or \ re.search(r'^mirror.*', volume_name) or \ re.search(r'^spare.*', volume_name) or \ re.search(r'^raidz.*', volume_name): msg = _(u"The volume name may NOT start with c[0-9], mirror, " "raidz or spare") self._errors["volume_name"] = self.error_class([msg]) cleaned_data.pop("volume_name", None) elif cleaned_data.get("volume_fstype") == 'UFS' and volume_name: if len(volume_name) > 9: msg = _(u"UFS volume names cannot be higher than 9 characters") self._errors["volume_name"] = self.error_class([msg]) cleaned_data.pop("volume_name", None) elif not re.search(r'^[a-z0-9]+$', volume_name, re.I): msg = _(u"UFS volume names can only contain alphanumeric " "characters") self._errors["volume_name"] = self.error_class([msg]) cleaned_data.pop("volume_name", None) return cleaned_data def done(self, request): # Construct and fill forms into database. volume_name = self.cleaned_data.get("volume_name") or \ self.cleaned_data.get("volume_add") volume_fstype = self.cleaned_data['volume_fstype'] disk_list = self.cleaned_data['volume_disks'] force4khack = self.cleaned_data.get("force4khack", False) ufspath = self.cleaned_data['ufspath'] mp_options = "rw" mp_path = None if (len(disk_list) < 2): if volume_fstype == 'ZFS': group_type = 'stripe' else: # UFS middleware expects no group_type for single disk volume group_type = '' else: group_type = self.cleaned_data['group_type'] with transaction.commit_on_success(): vols = models.Volume.objects.filter(vol_name=volume_name, vol_fstype='ZFS') if vols.count() == 1: volume = vols[0] add = True else: add = False volume = models.Volume(vol_name=volume_name, vol_fstype=volume_fstype) volume.save() mp_path = ufspath if ufspath else '/mnt/' + volume_name if volume_fstype == 'UFS': mp_options = 'rw,nfsv4acls' mp = models.MountPoint(mp_volume=volume, mp_path=mp_path, mp_options=mp_options) mp.save() self.volume = volume zpoolfields = re.compile(r'zpool_(.+)') grouped = OrderedDict() grouped['root'] = {'type': group_type, 'disks': disk_list} for i, gtype in request.POST.items(): if zpoolfields.match(i): if gtype == 'none': continue disk = zpoolfields.search(i).group(1) if gtype in grouped: # if this is a log vdev we need to mirror it for safety if gtype == 'log': grouped[gtype]['type'] = 'log mirror' grouped[gtype]['disks'].append(disk) else: grouped[gtype] = { 'type': gtype, 'disks': [ disk, ] } if len(disk_list) > 0 and add: notifier().zfs_volume_attach_group(volume, grouped['root'], force4khack=force4khack) if add: for grp_type in grouped: if grp_type in ('log', 'cache', 'spare'): notifier().zfs_volume_attach_group( volume, grouped.get(grp_type), force4khack=force4khack) else: notifier().init("volume", volume, groups=grouped, force4khack=force4khack, path=ufspath) if volume.vol_fstype == 'ZFS': models.Scrub.objects.create(scrub_volume=volume) try: notifier().restart("cron") except: pass if mp_path in ('/etc', '/var', '/usr'): device = '/dev/ufs/' + volume_name mp = '/mnt/' + volume_name if not access(mp, 0): mkdir(mp, 755) mount(device, mp) popen("/usr/local/bin/rsync -avz '%s/*' '%s/'" % (mp_path, mp)).close() umount(mp) if access(mp, 0): rmdir(mp) else: # This must be outside transaction block to make sure the changes # are committed before the call of mx-fstab notifier().reload("disk")