lindev.fr

Aller au contenu | Aller au menu | Aller à la recherche

27 mar. 2014

OS X 10.9.2, smb, et les autres ...

Petite mise en situation, j'ai un parc doté de nombreuses machines Windows, Linux et Mac de versions différentes, et un des partages réseau est placé sur une machine Windows 7 bien intégrée au domaine ...

Jusque là tout baigne, mais voilà qu'un étrange phénomène fait son apparition...

En effet dés qu'un utilisateur sur Mac OS X 10.9.x dépose un fichier sur ce fameux partage ( sous Windows je le rappelle ) tous les autres utilisateurs connectés également à ce partage se retrouvent dehors, et dans l’incapacité de s'y reconnecter !

C'est quoi ce bor*** truc

C'est un peu ce que je me suis dit au départ ... après avoir testé le partage depuis d'autres postes, vérifié les règles FW du windows, l'anti-virus etc etc .. bref sur le moment je sèche ..

Soudain une explication apparaît

J'ai vu quelque part, une note indiquant que la version d'OS X 10.9.x utilisait la version 2 du protocole samba par défaut, ce qui ne pose aucun problème à Windows, il "switch" le protocole du partage smb2 et point barre !!
Oui mais le problème est justement là !

Il "switch" dans un seul sens !!! Donc mes autres postes en une version antérieure ou ne possédant simplement pas le protocole samba dans sa version 2 se retrouvent dehors, à poil !

Les solutions

  1. Changer la version du protocole samba par défaut au niveau du client ( Mac 10.9.x )
  2. Utiliser le protocole cifs en lieu et place de smb , toujours au niveau client

Changer la version sur le mac

Une manip simple, mais qui a l'inconvénient de forcer l'utilisation du protocole dans son ancienne version pour l’ensemble des partages.

Ouvrez un terminal et entrez cette commande :

echo "[default]" >> ~/Library/Preferences/nsmb.conf; echo "smb_neg=smb1_only" >> ~/Library/Preferences/nsmb.conf

Pour revenir à l'état d'origine .. il suffit de supprimer le fichier de config

rm ~/Library/Preferences/nsmb.conf

Utilisez cifs

cifs est en fait l'autre nom qu'avait donné Microsoft pour le protocole smb dans sa version 1, en y intégrant quelques améliorations ..
bref c'est presque un alias de smb v1 !

Le fait donc de se connecter en spécifiant cifs://<le partage> va forcer l'utilisation du protocole samba dans sa version antérieure !

cifs://user:password@x.x.x.x/sharePath

C'est cette dernière solution que j'ai retenu pour son coté pratique et sans modification du comportement du client par défaut.

Conclusion

Je n'aime toujours pas Windows ! ;)

Ch.

07 mar. 2014

Wheezy & fsck

Le saviez-vous

Mon poste de travail, sous débian Wheezy redémarre régulièrement comme tout poste de travail et pourtant ... jamais il ne ma fait de check disque fsck au démarrage !

Et bien figurez-vous que c'est voulu !

En effet d’après ce que j'ai trouvé sur le web à ce sujet, les développeurs de debian estiment que dans le monde professionnel lorsque qu'une machine ( surtout un serveur ) lance son fsck au démarrage, la plupart du temps, c'est au mauvais moment, et provoque plus de frustrations des admin. sys. qu'autre chose .

Il est vrai que le fait de se baser uniquement sur un facteur temps, ou un nombre de montages des volumes pour exécuter cette commande rend sa réelle utilité très aléatoire.
Ils estiment donc que les Admin. Sys. sons assez "grands" pour planifier au moment opportun le fsck des disques .

Le réactiver par défaut à la création de partition

Lors de la création d'une nouvelle partition, pour réactiver par défaut le fsck sur le nombre de montages et l'intervalle de temps, il vous faut changer le paramètre suivant ( en le passant à 1 ):

enable_periodic_fsck = 0

Dans le fichier : /etc/mke2fs.conf

Le réactiver sur un partition existante

Pour réactiver le fsck tout les 50 montages :

sudo tune2fs -c 50 /dev/sdx

Pour réactiver le fsck si le dernier date de plus de 6 mois :

sudo tune2fs -i 6m /dev/sdx

Désactiver le fsck automatique

Si au contraire vous souhaiter tout désactiver,

tune2fs -c -1 -i 0 /dev/sde1

Forcer un fsck

C'est bien beau de tout désactiver, mais il peut être intéressant de lancer un check des disques de temps en temps quand même !
Pour lancer fsck au démarrage, 2 méthodes

Avec la commande shutdown

shutdown -r -F now

-r Redémarrer la machine après l'arrêt du système.
-F Forcer l'utilisation de fsck lors du redémarrage.

Avec le fichier forcefsck

Le simple fait de créer un fichier nommé forcefsck à la racine du système, va l'obliger à lancer un check fsck au démarrage . Ce fichier sera alors supprimé automatiquement .

sudo touch /forcefsck

Ch.

13 fév. 2014

iSCSI - target et utilisation sous Proxmox

Pour rappel, iSCSI est un protocole de stockage en réseau basé sur le protocole IP . Bref cela permet de présenter au(x) clients un disque SCSI via le réseau . Le système client le voit comme un disque SCSI local et peut donc le monter et l'utiliser de la même manière.

ça sert à quoi ?

Un cas d'utilisation ( qui sera présenter ici ), vous avez un environnement de serveurs virtualisés, répartis sur deux nœuds ( serveurs hôtes ).
Sans baie de stockage ou autre système d’espace disque partagé , il est impossible de mettre en place une "haute dispo" (HA), les disques des VM se trouvant sur l'un ou l'autre des nœuds, si l'un des deux tombe, les disques des VM ne sont plus accessibles, aucune migration n'est alors possible = coupure de service

Mettre en place un disque iSCSI sur un troisième serveur est donc un moyen de combler ce manque à moindre cout ! ( pour ce qui est des performances, cela va grandement dépendre du réseau local en place, l'idéal étant d'avoir des liens directs entre le serveur iSCSI et les nœuds qui doivent y accéder, ainsi que la gestion du stockage de ce troisième nœud: disque(s) SAS SATA IDE .. en Raid 0,1,5 ... etc ... ).

Le serveur iSCSI

Pour les tests, mon serveur ( serveur "Cible" ou encore "Traget" dans le jargon iSCSI ) est une simple machine sous debian Wheezy avec un disque de 1To, identifié par /dev/sdf.
commençons par installer les outils nécessaire pour configurer la cible

apt-get install iscsitarget-dkms

iSCSI

Le fichier de configuration se trouve à l'endroit suivant /etc/iet/ietd.conf .
Commençons par en faire une copie .

cp /etc/iet/ietd.conf /etc/iet/ietd.conf.orig

Nous pouvons donc maintenant modifier sereinement le fichier d'origine pour déclarer le disque iSCSI

vim /etc/iet/ietd.conf

Nous pouvons présenter plusieurs types de "volumes", un périphérique de type block ( ce sera notre cas ici ), un volume LVM , un volume RAID.

Nous allons donc dans notre fichier ietd.conf définir notre cible (target).
Ajoutons donc les lignes suivantes à la fin du fichier de configuration

Target iqn.2014-02.lindev.fr:storage.lun0
        Lun 0 Path=/dev/sdf,Type=fileio
        Alias Lun0

ATTENTION : le volume paramétré (ici /dev/sdf) ne doit pas être monté !

Les paramètres utilisés :

  • Target Nom unique de la cible iSCSI normalisé iqn.yyyy-mm.<reversed domain name>:identifier
  • Lun X "X étant le numéro de Lun, la numérotation DOIT commencer à 0"
  • Alias "Facultatif ... ai-je vraiment besoin d'expliquer ce qu'est un Alias ? "


Il ne reste plus qu'à redémarrer le service

service iscsitarget restart

Le client

Avant de nous attaquer au cas d'utilisation proxmox, voyons comment utiliser le volume iSCSI sur un client standard .. ( une autre machine linux )

Commençons par installer les utilitaires nécessaires

apt-get install open-iscsi

Nous allons maintenant scanner le serveur ( ici 10.0.0.213 ) pour savoir quel(s) volume(s) sont disponibles

iscsiadm -m discovery -t st -p 10.0.0.213

Ce qui nous donne

10.0.0.213:3260,1 iqn.2014-02.lindev.fr:storage.lun0

Pour nous connecter au volume iSCSI nous allons utiliser la commande suivante

iscsiadm -m node --targetname "iqn.2014-02.lindev.fr:storage.lun0" --portal "10.0.0.213:3260" --login

Ce qui donne

Logging in to [iface: default, target: iqn.2014-02.lindev.fr:storage.lun0, portal: 10.0.0.213,3260] (multiple)
Login to [iface: default, target: iqn.2014-02.lindev.fr:storage.lun0, portal: 10.0.0.213,3260] successful.

Pour voir les sessions actives

iscsiadm -m session

resultat :

tcp: [1] 10.0.0.213:3260,1 iqn.2014-02.lindev.fr:storage.lun0

Au moment de la connexion du volume iSCSI, un nouveau "device" a normalement été créé automatiquement , il suffit de regarder dans les log le nom de ce "device"

tail -n 50 /var/syslog

dans mon cas :

Feb 14 09:56:17 debian7-cdsl kernel: [89543.405273] scsi8 : iSCSI Initiator over TCP/IP
Feb 14 09:56:17 debian7-cdsl kernel: [89543.660414] scsi 8:0:0:0: Direct-Access     IET      VIRTUAL-DISK     0    PQ: 0 ANSI: 4
Feb 14 09:56:17 debian7-cdsl kernel: [89543.660739] sd 8:0:0:0: Attached scsi generic sg7 type 0
Feb 14 09:56:17 debian7-cdsl kernel: [89543.660994] sd 8:0:0:0: [sdg] 1953525168 512-byte logical blocks: (1.00 TB/931 GiB)
Feb 14 09:56:17 debian7-cdsl kernel: [89543.661098] sd 8:0:0:0: [sdg] Write Protect is off
Feb 14 09:56:17 debian7-cdsl kernel: [89543.661102] sd 8:0:0:0: [sdg] Mode Sense: 77 00 00 08
Feb 14 09:56:17 debian7-cdsl kernel: [89543.661252] sd 8:0:0:0: [sdg] Write cache: disabled, read cache: enabled, doesn't support DPO or FUA
Feb 14 09:56:17 debian7-cdsl kernel: [89543.677076]  sdg: unknown partition table
Feb 14 09:56:17 debian7-cdsl kernel: [89543.677970] sd 8:0:0:0: [sdg] Attached SCSI disk
Feb 14 09:56:17 debian7-cdsl iscsid: Connection1:0 to [target: iqn.2014-02.lindev.fr:storage.lun0, portal: 10.0.0.213,3260] through [iface: default] is operational now

Le volume iSCSI est donc attaché à /dev/sdg qui est donc utilisable comme n'importe quel disque local !

Monter automatiquement

Pour monter le volume automatiquement au démarrage du système, voici la ligne que je devrais ajouter au fstab

/dev/Lun0/	/mnt/diskLun0	auto	_netdev	0	0

Proxmox

Le but de ce disque ( dans cet article ), n'est pas de l'utiliser sur un poste , mais sur un cluster Proxmox .

A partir de maintenant tout se passe sur l'interface d'administration proxmox !
Je parts du principe ou le cluster proxmox est en place et fonctionnel .

Déclarer le disque iSCSI

Commençons par déclarer au cluster proxmox, le disque iSCSI ... suivez le guide ...

Selection_201.png
Selection_202.png
Selection_203.png
Voilà , à ce moment là, proxmox possède un nouveau disque "local" qui en fait est un disque iSCSI ( on est au même stade que la manip "client" ci-dessus )
Pour le rendre exploitable pour y mettre les disques des VM , il faut exploiter ce disque en y créant un volume LVM dessus .
Encore une fois .. suivez le guide ;)

Selection_204.png
Selection_205.png

Démo

Après avoir créé ou migré vos disques de VM sur ce nouveau volume, la migration à chaud est possible .. voici une démo pour l'occasion .

17 janv. 2014

Recrutement Développeur web Python/PHP

CDI Développeur PHP/Python Nord - Comines

comines2.png

Lire la suite...

Django, gérer les FormSets dynamiquement

Les formsets sous Django sont très pratiques, notamment pour gérer les formulaires à "champs multiples".

qu'est-ce que j'entends par "champs multiples" ... ceci :

champs_multiples.png

Dans ce formulaire, j'ai donc deux champs uniques

  1. Largeur
  2. Hauteur

Puis vient enfin mon formset qui est composé de trois champs

  1. Désignation
  2. Quantité
  3. Liste : R | RV

J'ai donc au final un formulaire dont je ne connais pas à l'avance la quantité de données à traiter puisque le formset permet d'ajouter dynamiquement ses champs. Et c'est précisément ce que nous allons voir dans ce billet .

  • Comment créer un formset
  • Comment le rendre dynamique pour l'utilisateur ( ajout / suppression )
  • Comment valider les données en une seule ligne ( Merci Django :) )

Créer un Formset

Nous allons reproduire le formulaire présenté ci-dessus , nous aurons donc un formulaire classique , puis un formset de trois champs .

Formulaire classique

class ConfForm(forms.Form):
        """
        Formulaire de configuration du module
        """
        format_x = forms.IntegerField( label="Largeur", required=True, min_value=0, widget=forms.TextInput(attrs={'placeholder':'mm','class':'form-control input-sm'}) )
        format_y = forms.IntegerField( label="Hauteur", required=True, min_value=0, widget=forms.TextInput(attrs={'placeholder':'mm','class':'form-control input-sm'}) )

On ne peut pas plus .. classique pour les détails je vous laisse voir la doc de Django

Le formset

class SorteForm(forms.Form):

    #Champs multiples
    designation = forms.CharField( label="Désignation", required=True, widget=forms.TextInput(attrs={'placeholder':'','class':'form-control input-sm'}) )
    quantite = forms.IntegerField( label="Quantité", required=True, min_value=0, widget=forms.TextInput(attrs={'placeholder':'','class':'form-control input-sm'}) )
    rectoverso = forms.ChoiceField(label='',choices = [(False,'Recto Seul'),(True,'Recto Verso')],required=True,widget=forms.Select( attrs={'class':'form-control input-sm'}))

Vous allez me dire ... "c'est une déclaration de formulaire classique" .. et bien oui rien ne différencie au niveau déclaration un formulaire qui sera utilisé de façon classique, d'un formulaire qui sera utilisé au sein d'un formset .

Tout ceci pour en venir au fait qu'un formset, n'est ni plus, ni moins qu'un mécanisme proposé par Django pour gérer l'affichage, la validation des données et quelques autres petites choses comme les limitations min/max en un minimum d'efforts pour le développeur ...

Ça tombe bien je suis fainéant pour ce qui est de la gestion de formulaires ( pas vous ? ).

Maintenant que nous avons nos deux déclarations de formulaires, voyons comment les implémenter dans une vue.

Dans la vue

Note : Je ne vais pas expliquer ici la mise en forme CSS du formulaire, ce n'est pas l'objectif du billet, sachez juste que j'utilise BootstrapV3 dans la présentation du début.

Commençons avec une vue standard ... vide

def show_form(self, request):
    """ Affichage du formulaire + formset """

    # Formulaire standard à deux champs
    form = ConfForm(auto_id=True)

    #Création du formset avec une seule itération : extra=1
    sorteForm = formset_factory(SorteForm,extra=1)
        
    # Récupération du formulaire géré par le mécanisme formset
    formset = sorteForm()

    # Affichage du template 
    return render_to_response('my_form.html', {'form':form, 'sorteForm':formset}, context_instance=RequestContext(request) )

Le template

D'habitude, pour afficher un formulaire sans mise en forme particulière , la balise form se suffit à elle même.
Pour le formset , c'est la même chose, si ce n'est que nous allons devoir mettre cette balise dans une itération, car rappelez-vous, un formset peut contenir plusieurs formulaires.

Ce qui va donc nous donner : ( sans mise en forme )

<!-- formulaire standard -->
{{ form }}

<!-- Champs utilisés par le mécanisme formset de Django -->
{{ sorteForm.management_form }}

<!-- itération des formulaires du formset -->
{% for form in sorteForm %}
    
    {{ form }}

{% endfor %}

La balise management_form est utilisée par le mécanisme formset pour gérer les éventuels ajouts dynamiques, modifications, etc ... bref .. fonctionner correctement .
Voici son contenu

<!-- nombre de formulaire (s) -->
<input id="id_form-TOTAL_FORMS" type="hidden" value="1" name="form-TOTAL_FORMS">
<!-- nombre de formulaires à l'origine --> 
<input id="id_form-INITIAL_FORMS" type="hidden" value="1" name="form-INITIAL_FORMS">
<!-- nombre max de formulaires gérés si ajout dynamique -->
<input id="id_form-MAX_NUM_FORMS" type="hidden" value="1000" name="form-MAX_NUM_FORMS">

La validation et l'enregistrement

Oui oui .. je sais .. pour le moment il n'y a rien de dynamique, pas d'ajout / suppression ... patience, commençons par boucler la boucle avec la gestion des données POST, nous verrons ensuite pour dynamiser le formulaire .

En effet, l'étape de validation et traitement, ne varie pas selon le nombre de formulaire(s) dans le formset, c'est là toute la force des formset avec Django .

Voici la vue de validation , qui reçoit les données POST

    def save_form(self):
        """  Check & Enregistre les données   """

        # Check si la méthode est bien en POST
        if request.method == 'POST':

            # Instancie et "bound" le formulaire
            form = ConfForm( request.POST, auto_id=True )

            # Création du formset
            sorteForm = formset_factory( SorteForm )
            # "Bound" le formset
            formset = sorteForm( request.POST )

            #Verifie si les données du formulaire simple sont valides
            if form.is_valid() :
                # Vérifie le(s) formulaire(s) du formset 
                if formset.is_valid():

                    #Affiche les données du formulaire simple
                    print form.cleaned_data

                    # Itération du formset pour afficher les données 
                    for fs in formset:
                        print fs.cleaned_data
                        
                else:
                    print = str( formset.errors )
            else:
                print = str( form.errors )

Forcément ici le code ne sert à rien, si ce n'est à expliquer comment valider formulaire + formset pour un traitement des données .. ( enregistrement en base, actions, mails etc ... ou comme ici un simple affichage ;) )

Dynamisme

Nous voilà avec un super formulaire + formset, qui s'affichent correctement, avec les données validées ( peu importe le nombre de formulaire(s) dans le formset ), mais maintenant nous allons voir comment ajouter / supprimer dynamiquement un formulaire dans le formset .

A partir de maintenant tout se passe dans le template , le principe est simple, nous ajoutons un boutton "+" pour ajouter un formulaire au formset et, pour chaque formulaire du formset un boutton "-" qui permettra de retirer le retirer .

Pour ajouter un formulaire au formset, nous allons faire appel à du javascript, qui va cloner un élément du DOM, ré-indexer les formulaires ( du formset ) puis mettre à jour la valeur du champ de management form-TOTAL_FORMS.

Pourquoi ré-indexer

Si l'on regarde le code généré par le formset, les champs sont indexés pour ne pas mélanger les données entres formulaires, vérifier la cohérence des données etc ..

<!-- Form 1 -->
<input id="id_form-0-designation" class="form-control input-sm" type="text" value="V2" placeholder="" name="form-0-designation">
<input id="id_form-0-quantite" class="form-control input-sm" type="text" value="1000" placeholder="" name="form-0-quantite">
....
<!-- Form n -->
<input id="id_form-n-designation" class="form-control input-sm" type="text" value="V2" placeholder="" name="form-n-designation">
<input id="id_form-n-quantite" class="form-control input-sm" type="text" value="1000" placeholder="" name="form-n-quantite">
...

Formulaire vide

Quel formulaire copier dans le DOM .. et puis .. je ne souhaite pas copier le contenu des champs ...ça tombe bien , encore une fois Django nous livre tout sur un plateau d'argent, grâce à l'attribut empty_form.

Nous allons donc dans un div hors de la balise form inclure le code suivant {{ sorteForm.empty_form }} ce qui va donc nous donner


<div id="my_form">
    <form action="#" name="form_test">
    <!-- Le formulaire standard -->
    {{ form }}

    <button class="btn btn-success btn-sm" id="bt_add_sorte" type="button">+</button>

    {{ sorteForm.management_form }}
        <!-- Les formulaires du formset -->
        <div id="formsetZone">
            {% for form in sorteForm %}
                <div class="nsorte">
                {{ form }}
                </div>
            {% endfor %}
        </div>
    </form>
</div>

<!-- Element à copier pour un ajout au formset -->
<div style="display:none;">
    <div id="eform" class="nsorte" >
        {{ sorteForm.empty_form }}
        <div class='btn btn-warning btn-sm bt_rm_sorte'>
                <i class='glyphicon glyphicon-trash'></i>
        </div>
    </div>
</div>

Voilà à quoi ressemble ce formulaire vide

<label for="id_form-__prefix__-designation">Désignation&nbsp;:</label>
<input class="form-control input-sm" id="id_form-__prefix__-designation" name="form-__prefix__-designation" placeholder="" type="text">
<label for="id_form-__prefix__-quantite">Quantité&nbsp;:</label>
<input class="form-control input-sm" id="id_form-__prefix__-quantite" name="form-__prefix__-quantite" placeholder="" type="text">
<select class="form-control input-sm" id="id_form-__prefix__-rectoverso" name="form-__prefix__-rectoverso">
<option value="False">Recto Seul</option>
<option value="True">Recto Verso</option>
</select>

L'objectif pour l'ajout est donc :

  1. Binder l’événement onclick du bouton avec l'id bt_add_sorte
  2. Cloner le contenu du div avec l'id eform
  3. Ajouter le données clonées à la fin du div id formsetZone
  4. Ré-indexer les formulaires du formset pour remplacer __prefix__ par le bon numéro de formulaire
  5. Incrémenter la valeur du champ de management avec l'id id_form-TOTAL_FORMS

Pour la suppression :

  1. Binder les boutons dont la classe est bt_rm_sorte
  2. Enlever du DOM le formulaire à retirer ( conteneur parent avec la classe nsorte )
  3. Re-indexer les formulaires du Formset
  4. Décrémenter a valeur du champ de management avec l'id id_form-TOTAL_FORMS

vive le javascript

Tout ça en javascript, grâce à JQuery

$(document).ready(function(){

    /**************************************************************************
    *
    *                                      Gesion ADD REMOVE Formset 
    *
    ***************************************************************************/

    index_form = function( fset, index ){

        $(fset).find(':input').each(function() {
            var name = $(this).attr('name').replace( new RegExp('(\_\_prefix\_\_|\\d)') , index );
            var id = 'id_' + name;
            $(this).attr({'name': name, 'id': id});
        });

        $(fset).find('label').each(function() {
            var newFor = $(this).attr('for').replace( new RegExp('(\_\_prefix\_\_|\\d)') , index );
            var id = 'label_' + newFor;
            $(this).attr({'id':id, 'for':newFor});
        });

    }

    reindex_formset = function( formset_zone ){

        var formset = $(formset_zone).find( '.nsorte' );
        for( var cpt=0;cpt<formset.length;cpt++ ){
            index_form( formset[cpt], cpt );
        };

        $("#id_form-TOTAL_FORMS").val( parseInt( cpt ) );

    };



    /**************************************************************************
    *
    *                               Gesion Des evenements formulaire
    *
    ***************************************************************************/


    set_event = function(){
            //Bind le(s) bt delete sorte
            $(".bt_rm_sorte").on('click',function(){
                $(this).parents(".nsorte").remove();
                reindex_formset( "#formsetZone" );
            });
    };

    $("#bt_add_sorte").on('click',function(){

        //Copy eform
        $( "#eform" ).clone(true).appendTo( $("#formsetZone") );

        reindex_formset( "#formsetZone" );

    });

    set_event();


});

Conclusion

Nous avons fait le tour pour gérer les formset dynamiquement, au diable les galères pour gérer ce genre de cas , merci Django .

Ch.

- page 1 de 27