HOAB

History of a bug

Migration Courier-imap/qmail vers Postfix/dovecot

Rédigé par gorki Aucun commentaire

Le problème :

Je veux migrer en douceur et de manière transparente un serveur de courier d'un serveur A vers un serveur B.
Le serveur A est un qmail/vpopmail/courier-imap.
Le serveur B est un postfix/dovecot
Go !

Solution :

Pour palier au temps de propagation des DNS qui peut être rapide ou pas, l'idée est donc :

Serveur B : préparation

<domaine.com> mail._domainkey.<domaine.com>
  1. créer dans postfixadmin sur le serveur B le nom de domaine à gérer
  2. générer la clé DKIM pour ce nouveau domaine (cf configuration DKIM multi-domaine)
  3. vérifier le domaine (cf post 10/10)
  4. créer les boites aux lettres, j'en ai peu, mais il y a des scripts de migration à partir des vpasswd a priori (ou ça en fait)
  5. via thunderbird portable, et mes configurations, je créé des comptes et je teste l'envoi et la réception
IMAP
ip du serveur (le DNS n'est pas migré), port 143, STARTTLS, mot de passe non chiffré

SMTP
ip du serveur (le DNS n'est pas migré), port 587, STARTTLS, mot de passe non chiffré

Serveur A : relay

Le serveur B étant prêt à gérer les mails, on redirige le trafic SMTP du serveur A vers le B pour ce domaine.

Un guide ici.

  1. Vérifier la présence de <domaine.com> dans /var/qmail/control/rcpthosts
  2. Supprimer la référence à <domaine.com> dans /var/qmail/control/smtproutes
  3. Ajouter la redirection dans : /var/qmail/control/smtproutes
<domaine.com>:<IP serveur B>
  1. redémarrer qmail
  2. Tester l'envoi d'un email en utilisant le DNS mail.<domaine.com>, il passe par serveur A et est forwardé au serveur B

DNS : bascule

Modifier les DNS

Serveur A/B : recopier les boites aux lettres

Via un rsync plusieurs jours avant si les boites sont volumineuses, c'est la copie qui est longue, la conversion est rapide (quelques fichiers à modifier).

Le script de migration est ici : http://wiki.dovecot.org/Migration/Courier

Donc :

  1. recopier les boites courier-map
# Serveur A
cd /home/vpopmail/domains
tar zcf <domaine.com>.tgz <domaine.com>
scp <domaine.com>.tgz <Serveur B>:/home/vmail/<domaine.com>/tmp_migrate

# OU rsynch
cd /home/vpopmail/domains
rsync -razh --delete root@<serveur A>:/home/vpopmail/domains/<domaine.com> .

 

  1. les extraire
# Serveur B
cd /home/vmail/<domaine.com>/tmp_migrate
tar zxf <domaine.com>.tgz
  1. les convertir
 ./courier-dovecot-migrate.pl <domaine.com> --recursive 
# si ok
 ./courier-dovecot-migrate.pl <domaine.com> --recursive --convert
  1. les transférer dans la nouvelle boite
chown -R <user dovecot:user dovecot> *
cp -r <domaine.com>/* /home/vmail/<domaine.com>/

synchronisation via SELECT FOR UPDATE et données non mises à jour !

Rédigé par gorki Aucun commentaire

Le problème :

Un problème tout simple ou presque :)

Deux threads qui discutent avec la base de données, synchronisés par un "SELECT ... FOR UPDATE" sur le même objet.

Le deuxième thread n'a pas toujours le résultat du travail du premier thread !

Solution :

Alors j'écarte toutes les hypothèses qui m'ont été faites :

  • une seule transaction par thread de la prise de lock au commit
  • transaction en REQUIRED_NEW
  • le lock via le SELECT FOR UPDATE est la première instruction des threads
  • pas d'instruction DDL pendant les threads
  • le SELECT FOR UPDATE fonctionne correctement

La réponse : Mysql utilise par défaut le mode d'isolation READ REPEATABLE alors que la plupart des autres bases utilisent READ COMMITED.

Comme j'imagine que ce n'est pas évident, quelques explications :

Thread n°1 Thread n°2 Mysql Mode : REPEATABLE READ Mysql Mode : READ COMMITED
SELECT tableLock FOR UPDATE WHERE id = 1      
UPDATE user.status = 4      
start thread n°2      
  SELECT tableLock FOR UPDATE WHERE id = 1 Snapshot de la base pour avoir des données constantes pendant la transaction Pas de snapshot, ce sont les dernières données en base
Commit      
  Obtention du lock APRES le commit    
  select user.status status est null / non mis à jour status = 4 !

Remarques :

  • Le thread n°2 déclenché par le thread n°1 se met en attente du lock. En faisant un SELECT !. C'est là le problème. Le mode REPEATABLE READ fait un snapshot valable sur la base entière au premier select.
  • ce point (snapshot sur la base entière) n'est pas très explicite dans les documentations

Solutions :

  • passer MYSQL en mode READ COMMITED (un peu performant en plus...)
  • mettre les mises à jour des données autres dans des sous-transactions
  • envisager un autre mode de synchronisation hors transaction (cache distribué...)

 

RestAssured Junit et tests Session

Rédigé par gorki Aucun commentaire

Le problème :

RestAssured est un framework de tests pour les services Rest avec un langage assez simple.

Le seul problème est qu'il demande à ce qu'un serveur soit démarré, ce qui est toujours un peu lent pour les tests unitaires.

Heureusement, est inclu RestAssuredMockMvc qui permet de mocker le serveur, faire "comme si" on avait un serveur, très bien !

Problème :

  • Dans un de mes tests j'utilisent des informations stockées en session

Autant RestAssured a prévu le coup et a des méthodes pour ça (cf SessionFilter) côté client, autant la partie RestassuredMockMvc n'a rien de tout ça dans la partie serveur simulé.

Solution :

J'ai pas mal tatonné/recherché, mais finalement une solution assez simple et non intrusive :

// Création de la requête de test avec serveur simulé (le given() de RestassuredMockMvc)
MockMvcRequestSpecification request = given().standaloneSetup(controller);

// Création d'un session vide
final MockHttpSession session = new MockHttpSession();

// On ajoute un interceptor pour indiquer lors de la construction de la requête d'utiliser notre session
request.interceptor(new MockHttpServletRequestBuilderInterceptor() {
            @Override
            public void intercept(MockHttpServletRequestBuilder requestBuilder) {
                requestBuilder.session(session);
            }
});

// on peut ensuite enchainer les appels, la session sera la même

request.queryParam(...).when(...)...

request.queryParam(...).when(...)...

Donc côté serveur simulé, on intercepte le constructeur pour lui donner une session vide (mais non null). On pourrait imaginer un système plus complexe pour gérer le fait qu'au premier appel la session devrait être null, mais on va s'arrêter là pour l'instant.

 

 

PHP Soapclient et connexion HTTPS via un proxy

Rédigé par gorki Aucun commentaire

Le problème :

En passant via mon proxy entreprise ou local (SQUID) mes requêtes SOAP recevaient les erreurs :

  • Parsing WSDL: Couldn't load from 'https://host/service?wsdl' : failed to load external entity 'https://host/service?wsdl'
  • Could not connect to host

Solution :

Trouver l'origine du problème, activer les traces :

$client = new SoapClient("http://www.webservicex.net/ConverPower.asmx?WSDL", array('trace' => 1));
echo "====== REQUEST HEADERS =====" . PHP_EOL;
var_dump($client->__getLastRequestHeaders());
echo "========= REQUEST ==========" . PHP_EOL;
var_dump($client->__getLastRequest());
echo "========= RESPONSE =========" . PHP_EOL;
var_dump($response);
array
(
    'trace' => 1
    'proxy_host' => 127.0.0.1
    'proxy_port' => 3128
)
// Attention ! vérifier que ces directives sont bien appelées (par exemple pas dans un fichier classe... sifflotements...)
ini_set('soap.wsdl_cache_enabled',0);
ini_set('soap.wsdl_cache_ttl',0)
$context = stream_context_create(array(
    'ssl' => array(
        'verify_peer' => false,
        'verify_peer_name' => false,
        'allow_self_signed' => true
    )
));

$client  = new SoapClient(null, array( 
    'location' => 'https://...',
    'uri' => '...', 
    'stream_context' => $context
));

 

 

Configuration OpenDKIM en multi-domaines

Rédigé par gorki Aucun commentaire

Le problème :

En complément à cet article http://hoab.fr/postfix-et-10-10-aux-tests-antispam

Solution :

C'est tout simple, c'est pour tracer.

Par rapport aux tutoriels référencés dans l'article précédent, la configuration /etc/opendkim.conf :

#Domain & Selector & KeyFile ignore avec Keytable

#Domain			origames.fr
#Selector		mail
#KeyFile		/etc/dkim/mail.private

KeyTable		/etc/dkim/KeyTable
InternalHosts		/etc/dkim/TrustedHosts
SigningTable		/etc/dkim/SigningTable

On demande à opendkim d'aller chercher les clés dans un fichier plutôt que d'avoir une clé unique.

  1. ajout de la ligne du domaine dans : /etc/dkim/SigningTable
<domaine.com> mail._domainkey.<domaine.com>
  1. ajout de la ligne du domaine dans : /etc/dkim/TrustedHosts
<domaine.com>
  1. ajout de la ligne du domaine dans : /etc/dkim/KeyTable
mail._domainkey.<domaine.com> <domine.com>:mail:/etc/dkim/keys/<domaine.com>/mail.private
  1. générer la clé DKIM
cd /etc/dkim/keys/<domaine.com>/
opendkim-genkey -t -s mail -d <domaine.com>

Et voilà, ça roule.

 

Fil RSS des articles