Перенос базы jabberd (xml файлы) на openfire сервер (база po

Проблемы с установкой, настройкой и работой системных и сетевых программ.

Модераторы: GRooVE, alexco

Правила форума
Убедительная просьба юзать теги [code] при оформлении листингов.
Сообщения не оформленные должным образом имеют все шансы быть незамеченными.
fr33man
сержант
Сообщения: 218
Зарегистрирован: 2006-09-04 17:41:27
Откуда: Москва
Контактная информация:

Перенос базы jabberd (xml файлы) на openfire сервер (база po

Непрочитанное сообщение fr33man » 2011-03-01 17:41:56

Предвведение: как и обещал - выкладываю свои мысли по поводу переноса всей этой хероты..

0. Введение.

Наверняка многие из Вас сталкивались с ситуацией, когда приходя на новую работу, обнаруживаешь там зоопарк из серверов, которые постоянно падают и требуют к себе слишком много внимания. Я не стал исключением из правила, и в один прекрасный день встал вопрос о переносе корпоративного Jabber сервера на новую платформу, в связи со старой версией сервиса (jabberd 1.4) и не очень удобной админкой - данные о пользователях хранились в обычных текстовых файлах. Проблема заключалась в том, что перенести базу нужно было абсолютно прозрачно для конечных пользователей, иначе мог случиться апокалипсис.

1. Изучаем старую базу данных.

Jabberd сервер был настроен на использование xml файлов, в качестве базы данных пользователей. С одной стороны это не есть хорошо, ввиду того, что с большим количеством файлов работать достаточно неудобно. Но с другой стороны не надо забывать, что файлики эти не простые, а xml'евые, что существенно упрощает задачу по их разбору. Итак, перейдем непосредственно к анализу существующей БД.

Первым делом посмотрим кол-во пользователей и заглянем в нутро одного из них:

Код: Выделить всё

mail spool # ls -la *.xml | wc -l
610
mail spool # cat testtest.xml
<xdb><password xmlns='jabber:iq:auth' xdbns='jabber:iq:auth'>test12</password><query xmlns='jabber:iq:register' xdbns='jabber:iq:register'><username>TestTest</username><x xmlns='jabber:x:delay' stamp='20100311T09:40:28'>registered</x></query><zerok xmlns='jabber:iq:auth:0k' xdbns='jabber:iq:auth:0k'><hash>395094b9bda14fccaa2308c147e76b8699f15572</hash><token>4B98BA93</token><sequence>500</sequence></zerok><foo xmlns='jabber:x:offline' xdbns='jabber:x:offline'/><query xmlns='jabber:iq:last' last='1268300442' xdbns='jabber:iq:last'>Disconnected</query></xdb>
mail spool #
Отлично! 610 пользователей, вся инфа о которых хранится в xml файлах. Но ничего, мы же не собираемся вручную переносить все это добро. Посмотрев на структуру можно понять, где хранится пароль и имя пользователя. Прошу заметить, что нам очень повезло, что пароль хранится в открытом виде, поскольку если бы он был зашифрован, то пришлось бы как-то совмещать OpenFire шифрование с шифрованием Jabberd. Или, как вариант, поменять пароли всем пользователям, но данная возможность не рассматривается, ввиду возникшего бы потом огромного потока звонков от пользователей системы. Итак, возвращаемся к нашим баранам, то есть к пользователям. Для анализа все равно придется писать какой-никакой скриптик, поэтому предлагаю прямо сейчас накидать небольшую дебаг версию. Использовать будем perl с извращенным стилем написания кода. Скриптики будем складывать в папочку scripts.

scripts/user.pl:

Код: Выделить всё

#!/usr/bin/perl

# Подключаем модуль XML
use XML::Simple;
# Подключаем модуль для вывода XML на экран
use Data::Dumper;

# Считываем имя файла
$file = $ARGV[0];

# Создаем XML объект
$xml = new XML::Simple;

# Обрезаем \n
chomp $file;

# Считываем XML файл
$data = $xml->XMLin($file);

# Парсим и выводим на экран
print Dumper($data);

Для запуска скрипта понадобится модули, указанные в начале. Для их установки можно использовать cpan или встроенный менеджер пакетов системы. Я предпочел второй вариант и с помощью emerge установил необходимые модули. Скрипт принимает на вход имя файла, парсит его и выдает его структурный вид. Ну что же - пробуем:

mail spool # perl scripts/user.pl testtest.xml
$VAR1 = {
          'password' => {
                        'xmlns' => 'jabber:iq:auth',
                        'xdbns' => 'jabber:iq:auth',
                        'content' => 'test12'
                      },
          'query' => [
                     {
                       'xmlns' => 'jabber:iq:register',
                       'xdbns' => 'jabber:iq:register',
                       'x' => {
                              'xmlns' => 'jabber:x:delay',
                              'stamp' => '20100311T09:40:28',
                              'content' => 'registered'
                            },
                       'username' => 'TestTest'
                     },
                     {
                       'xmlns' => 'jabber:iq:last',
                       'xdbns' => 'jabber:iq:last',
                       'content' => 'Disconnected',
                       'last' => '1268300442'
                     }
                   ],
          'foo' => {
                   'xmlns' => 'jabber:x:offline',
                   'xdbns' => 'jabber:x:offline'
                 },
          'zerok' => {
                     'hash' => '395094b9bda14fccaa2308c147e76b8699f15572',
                     'xmlns' => 'jabber:iq:auth:0k',
                     'xdbns' => 'jabber:iq:auth:0k',
                     'sequence' => '500',
                     'token' => '4B98BA93'
                   }
        };
mail spool #
Замечательно - мы уже можем вполне себе нормально читать xml файлики. Давайте попробуем выгрести какую-нибудь инфу о пользователе. Тут я сделаю небольшое отступление и скажу, что совершенно случайно я выбрал поле last, содержащие время последнего входа. Этот случайный выбор реально сократил мне количество работы, но об этом чуть-чуть попозже. Итак, накатываем скриптик такого вида, для выводы даты последнего логина каждого пользователя:

Код: Выделить всё

#!/usr/bin/perl

# Подключаем модуль XML
use XML::Simple;

# Задаем переменную для времени последнего входа
my $lastlogindate;

# Составляем список файлов для обработки
@files = `ls *.xml`;

# Создаем XML объект
$xml = new XML::Simple;

# Перебираем все файлики
foreach $file (@files)
{
	# Обрезаем \n
    chomp $file;
    # Считываем XML файл
    $data = $xml->XMLin($file);

	# Выводим на экран имя файла, который рассматриваем
    print $file.":";

    # Разгребаем тэг <query>
    foreach $each (@{$data->{'query'}})
    {
        # Получаем инфу о последнем логине пользователя
        if ($each->{'xmlns'} eq 'jabber:iq:last')
        {
            ($year, $month, $day) = (localtime($each->{'last'}))[5,4,3];
            $date = sprintf ("%04d-%02d-%02d", $year+1900, $month+1, $day);
            $lastlogindate = $date;
        }

    }
	# Выводим инфу на экран
    print "Last login: ".$lastlogindate."\n";

}
Я не буду приводить тут полный вывод команды, поскольку тогда придется разбивать статью еще на несколько частей. Приведу только для одного файлика:

Код: Выделить всё

mail spool # perl scripts/parse.pl  | grep testtest
testtest.xml:Last login: 2010-03-11
mail spool #
Итак, даты отображаются, все зашибись. Тут надо бы заметить одну небольшую деталь, которая существенно облегчит нам жизнь. Посмотрите на дату последнего входа пользователя в систему на предыдущем выводе: 11 марта 2010 года. А на календаре у нас 16 февраля 2011. Логично рассудив, что пользователь не пользуется джаббером достаточно давно, мне стало интересно, сколько еще пользователей изредка посещают столь замечательный сервис:

Код: Выделить всё

mail spool # perl scripts/parse.pl  | grep 2011  | wc -l
234
mail spool # perl scripts/parse.pl  | grep -v 2011  | wc -l
376
mail spool #
Вот оно! В 2011 году в систему не заходило 376 человек, которых мы сможем с чистой совестью удалить, предварительно забэкапив. Тут логика проста: если человек не появлялся больше месяца в сети, то наверное он давно уже не работает в компании, либо джабер ему задаром не впился. Но в любом случае мы всегда сможем восстановить любой аккаунт за каких-то 10 секунд. Для переноса пользователей надо дополнить вышеприведенный скрипт несколькими строчками. Я приводить их здесь не буду, так как более банальных вещей и не придумать.

Идем дальше. А дальше нам надо выгрести всю информацию о пользователях:

1. Логин
2. Пароль
3. Контакты
- группы
- контакты
4. vCard
5. Offline сообщения (посланные пользователю, когда он был отключен).

Так как задача стояла сделать все прозрачно для пользователей (чтобы вообще не заметили переезда), то необходимо скопировать всю информацию об их контактах, включая группы. Начнем с простого - логин и пароль, ну и до кучи - последняя дата входа. Стругаем скриптик:

Код: Выделить всё

#!/usr/bin/perl

# Подключаем модуль XML
use XML::Simple;

# Объявляем необходимые переменные
my $password;
my $username;
my $lastlogindate;

# Составляем список файлов для обработки
@files = `ls *.xml`;
# Создаем XML объект
$xml = new XML::Simple;

# Перебираем файлы
foreach $file (@files)
{
	# Обрезаем \n
    chomp $file;
    # Считываем xml файл
    $data = $xml->XMLin($file);
	
	# Выводим имя файла, которое обрабатываем
    print "########".$file.":\n";

    # Получаем пароль
    $password = $data->{'password'}->{'content'};

    # Разбираем тэг <query>
    foreach $each (@{$data->{'query'}})
    {
        # Получаем имя пользователя
        if ($each->{'xmlns'} eq 'jabber:iq:register')
        {
            $username = lc($each->{'username'});
        }

        # Получаем инфу о времени последнего входа в систему
        if ($each->{'xmlns'} eq 'jabber:iq:last')
        {
            ($year, $month, $day) = (localtime($each->{'last'}))[5,4,3];
            $date = sprintf ("%04d-%02d-%02d", $year+1900, $month+1, $day);
            $lastlogindate = $date;
        }

    }

    # Выводим полученные нелегким трудом данные
    print "Username: ".$username."\n";
    print "Password: ".$password."\n";
    print "Last login: ".$lastlogindate."\n";

}
Заметьте, что логин переводится в нижний регистр, ввиду того, что в базе openfire он используется как ключ. Следовательно все должно быть в одном регистре (я выбрал нижний), иначе у вас могут не подключиться, например, контакты (если в ofuser будет TestTest, а в ofroster testtest). Так, логины и пароли у нас есть. Остается сделать что-нибудь с контактами. Тут я провозился достаточно долго, поскольку simplexml по-разному выводил контакты пользователей. Ниже представлены два варианта вывода контактов (позже я буду на них ссылаться):

Вариант 1:

{
'xmlns' => 'jabber:iq:roster',
'xdbns' => 'jabber:iq:roster',
'item' => [
{
'group' => 'test'
'jid' => 'user4@domen.ru',
'subscription' => 'none'
},
{
'name' => 'user something'
'jid' => 'user3@domen.ru',
'subscription' => 'both'
},
}

Вариант 2:
{
'xmlns' => 'jabber:iq:roster',
'xdbns' => 'jabber:iq:roster',
'item' => {
'user1' => {
'group' => "group1",
'jid' => 'user1@domen.ru',
'subscription' => 'both'
},
'user2' => {
'jid' => 'user2@domen.ru',
'subscription' => 'both'
},
}
}

В первом случае при обращении к элементу item ($data->{query}->[x]->{item}) мы получаем хэш, а во втором случае - массив... Поэтому надо как-то определять, какой вид хранения контактов используется. Убив полдня на поиски красивого любого решения, я остановил свой взгляд на функции ref, которая выдает тип данных, на который указывает ссылка. Ниже приведен кусок кода, позволяющий выцепить контакты пользователя, вне зависимости от того, как они сформированы в файле xml. Надеюсь, что вы сами догадаетесь, куда скопировать данный участок текста.

Код: Выделить всё

		# Если попали в секцию контактов
        if ($each->{'xmlns'} eq 'jabber:iq:roster')
        {
			# проверяем тип поля item, если array, то это вариант 1
            if(ref($each->{'item'}) eq "ARRAY")
            {
                foreach $contact (@{$each->{'item'}})
                {
                    $contactname = $contact->{'name'};
                    $contactgroup = $contact->{'group'};
                    $contactjid = $contact->{'jid'};
                    $contactsubscription = $contact->{'subscription'};

					# В данном случае (в отличии от HASH) 
					# имя контакта может быть пустое.
					# если оно пустое, то просто делаем его равным jid
                    if (length($contactname) == 0)
                    {
                        $contactname = $contactjid;
                    }

                    if (length($contactgroup) == 0)
                    {
                        $contactgroup = "DEFAULT";
                    }
					# Здесь делаем что-то с этими данными
                }
            }

			# если тип Hash, то это вариант 2
            if(ref($each->{'item'}) eq "HASH")
            {
                @names = keys %{$each->{'item'}};

                foreach $name (@names)
                {

                    $contactname = $name;
                    $contactgroup = $each->{'item'}->{$name}->{'group'};
                    $contactjid = $each->{'item'}->{$name}->{'jid'};
                    $contactsubscription = $each->{'item'}->{$name}->{'subscription'};
					
					# Данная проверка выявила двух пользователей
					# со странно сгенерированными xml файлами... но об этом ниже
                    if (length($contactname) == 0)
                    {
                        $contactname = $contactjid;
                        system("echo \"$file\" >> /tmp/smth-strange");
                    }

                    if (length($contactgroup) == 0)
                    {
                        $contactgroup = "DEFAULT";
                    }
					# Делаем что-то с данными
                }
			}
			

Вот такой вот скрипт позволяет разобрать контакты пользователей и сделать с ними что-то полезное. Теперь о грустном. Как я указал в комментариях к коду - имя контакта может быть пустое и это нормальное поведение SimpleXML при варианте 1, который указан выше. При втором же варианте имя контакта пустым быть не может, ибо это ключ хэша. Небольшой тест показал, что имеется два файла, у которых как раз при втором варианте было пустое имя контакта, в следствии чего контакты этих пользователей не парсились. Я свалил данный баг (или фичу) на плечи SimpleXML. Единственное, что стоит отметить это то, что пользоватли этих аккаунтов работали на маках. Так что аккуратнее - старайтесь проводить хотя бы поверхностные проверки, для определения того, что скрипт отрабатывает правильно, иначе можно сильно напороться. Из-за своей большой лени я решил перенести этих пользователей потом вручную, чтобы не писать дополнительные условия в скрипте. Ну в принципе главной цели в данном разделе мы достигли - научились выгребать информацию о пользователях и обрабатывать ее. Поэтому предлагаю идти дальше.

2. Изучения новой БД.

Это очень важный момент во всей этой истории, поскольку если ошибиться с вводом данных, то сервер может не совсем коректно обрабатывать пользователей. Поэтому предлагаю проделывать действия описанные в этом пункте очень внимательно, дабы ничего не упустить. Сейчас нам предстоит понять, как хранится база пользователей OpenFire. Основные моменты, которые необходимо уяснить:

1. Где хранится Логин.
2. Где хранится пароль и в каком виде (шифрованный или нешифрованный)
3. Где хранятся контакты пользователя
4. Где хранятся группы для контактов конкретного пользователя.
5. Где хранится vcard пользователя.

На самом деле нужно ставить вопрос не "Где хранится", а "как правильно добавить". Обратите внимание на слово правильно - оно тут не случайно. Предлагаю действовать таким способом. Ставим OpenFire на любую машинку (я на виртуалку загнал) и цепляем к чистой базе. Дальше создаем нового пользователя, логинимся под ним и создаем левые контакты, группы и т.д., а потом смотрим в каком виде это все записалось в базу. За отсутствием других идей я так и поступил. Итак, представим что у нас уже поднят OpenFire сервер. Смотрим таблички в БД до добавления пользователя и сравниваем с тем, что получилось потом. Для оптимизации данного процесся я заюзал pg_dump, который просто дампил мне нужную базу данных до и после добавления пользователя:

До:

Код: Выделить всё

mail ~ # mkdir dump
mail ~ # cd dump/
mail dump # pg_dump -Upostgres -h 127.0.0.1 --exclude-table=ofrrds OpenFire > before.sql
mail dump #
Я исключил табличку ofrrds, в связи с огромным количеством ненужных данных. Эта таблица используется для записи состояний сервера (мониторинг по-русски). Вообщем все норм, теперь добавляем пользователя elisium, после чего делаем второй дамп:

Код: Выделить всё

mail dump # pg_dump -Upostgres -h 127.0.0.1 --exclude-table=ofrrds OpenFire > after1.sql
Смотрим diff'ы:

Код: Выделить всё

mail dump # diff -Naur before.sql after1.sql
--- before.sql  2011-02-16 11:05:43.000000000 +0300
+++ after1.sql  2011-02-16 11:11:02.000000000 +0300
@@ -1348,6 +1348,7 @@
 23     2
 21     2
 20     2
+25     2
 \.


@@ -1608,6 +1609,7 @@
 COPY ofuser (username, plainpassword, encryptedpassword, name, email, creationdate, modificationdate) FROM stdin;
 admin  \N      b2bc0ad012178eeb11367f243a8b9433801c26570cad1818f75ba46bc2d4c207        Administrator   root@domen.ru    001297828650125 0
 demo   \N      ac732afe8c0eceae8871ae34c8cd6fee228ca464bb6e608f        Fastpath Demo Account   demo@fastpath.com       001297828674916 001297828674916
+elisium        \N      340f1e99b0217570e50f83ed3bca4cba0c3b9a5e06214d458cedb5e50798fd3d        Vasiliy fr33m2n@gmail.com       001297833054376 001297833054376
 \.


mail dump #
Итак, со вторым изменением более или меннее все понятно - просто пользователь в базу попал, а вот с первым немного непонятно... Лезем в гугл и довольно быстро находим инфу по таблице ofid (имя таблицы взял из файлика after1.sql на строке 1348). Взято отсюда [1]: ofID (used for unique ID sequence generation). Ну вообщем понятно - для генерации уникальных ID'шников (чтобы не зависить от autoincrement'а базы). Здесь [2] и здесь [3] описаны основные idtype. У нас изменился idtype 25 следовательно (судя по табличке) должна была добавиться запись в securityauditlog... А ее нет... Вообщем не совсем понятно... Короче я решил особенно сильно по этому поводу не заморачиваться, а просто добавить всех юзеров и все их контакты и после этого поменять значение id для определенного idtype. Идем дальше. Теперь добавляем любые группы и контакты и смотрим изменения:

Код: Выделить всё

mail dump # pg_dump -Upostgres -h 127.0.0.1 --exclude-table=ofrrds OpenFire > after2.sql
mail dump # diff -Naur after1.sql after2.sql
--- after1.sql  2011-02-16 11:11:02.000000000 +0300
+++ after2.sql  2011-02-16 11:58:18.000000000 +0300
@@ -1341,7 +1341,6 @@
 --

 COPY ofid (idtype, id) FROM stdin;
-18     1
 19     1
 26     2
 24     2
@@ -1349,6 +1348,7 @@
 21     2
 20     2
 25     2
+18     6
 \.


@@ -1457,6 +1457,7 @@
 --

 COPY ofprivate (username, name, namespace, privatedata) FROM stdin;
+elisium        roster  roster:delimiter        <roster xmlns="roster:delimiter">\\</roster>
 \.


@@ -1559,6 +1560,8 @@
 --

 COPY ofroster (rosterid, username, jid, sub, ask, recv, nick) FROM stdin;
+1      elisium ozerovvasiliy@jabber.domen.ru    3       -1      -1      ozerovvasiliy
+2      elisium test@jabber.org 0       0       -1      test
 \.


@@ -1567,6 +1570,8 @@
 --

 COPY ofrostergroups (rosterid, rank, groupname) FROM stdin;
+1      0       testgroup2\\subgrouptest21
+2      0       testgroup\\subgrouptest
 \.


@@ -1634,6 +1639,7 @@
 --

 COPY ofvcard (username, vcard) FROM stdin;
+elisium        <vCard xmlns="vcard-temp" version="2.0" prodid="-//HandGen//NONSGML vGen v1.0//EN">\n<FN>ФИО</FN>\n<NICKNAME>ник</NICKNAME>\n<TEL>\n<HOME/>\n<VOICE/>\n<NUMBER>309</NUMBER>\n</TEL>\n<URL>доменру</URL>\n</vCard>
 \.


mail dump #
Тяяяк. В табличку ofprivate вносится инфа о разделителе для групп/подгрупп. Эта инфа в xml файлах имеется не у всех пользователей - только у тех, у кого есть группы. В табличку ofroster вносится инфа о контактах пользователя. Инфа вся стандартная, единственное что - здесь subscription задается тремя полями: sub, ask, resv. Небольшая табличка соответствия именованиям в старой базе и в новой ([4] - здесь описаны подписки, регламентированные RFC 3921):

Код: Выделить всё

name		sub		ask		recv
both		3		-1		-1
none		0		-1		-1
from		2		-1		-1
to			1		-1		-1
[5] -- здесь описаны поля sub, ask, recv. Давайте не будем забивать себе голову ненужной информацией и поля ask, recv оставим в значении -1. В принципе контактов с подпиской, отличной от both очень мало (в моей БД по крайней мере), поэтому глобальных проблем не предвидется. Идем дальше, ofrostergroups содержит информацию о созданных группах. Если в группе стоит разделитель \\ (один слэш экраннирование, а второй - просто слэш), то это уже подгруппы. Если разделителя нет, то это просто группа. rosterid связан с контактом из ofroster. У любого контакта должна быть своя группа. Если в таблице ofrostergroups нет записи, где ofrostergroups.rosterid = osroster.rosterid, то тогда контакт будет отображаться вне групп. Ну немного с группами разобрались, основной принцип добавления, надеюсь понятен. Табличка vcard ничего сложного из себя не представляет - имя пользователя и связанная с ним карточка.

Вроде с базой более или менее решили. Переходим на следующий уровень.

3. Подготовка к миграции.

Основной задачей на данном этапе мне виделось написание скрипта, который генерил бы SQL запросы на добавления пользователя, его контактов, групп и vcard'а. Ну что же, давайте тогда накидаем сначала запросы вручную для каждой из операций, а потом сделаем скриптик.

- добавление пользователя:

Код: Выделить всё

INSERT INTO ofuser (username, plainpassword, encryptedpassword, name, email, creationdate, modificationdate) VALUES ('tested', 'password', NULL, 'Nickname', 'email', 001297833054376, 0);
Небольшое замечание - openfire шифрует пароль на основе поля passwordtype из своего конфига. По умолчанию это вроде бы blowfish, с котором разбираться было решительно лениво. Вобщем используем пока открытые пароли, а потом можно будет как-нить сменить. По поводу даты создания я тоже особо не задумывался - просто взял дату создания пользователя admin и усе.

- Добавление контакта:

Код: Выделить всё

INSERT INTO ofroster (rosterid, username, jid, sub, ask, recv, nick) VALUES (4, 'tested', 'user@jabber.org', 3, -1, -1, 'name');
Тут совсем все совсем элементарно. rosterid - уникальный ID, далее имя пользователя (причем точно такое же как и в ofuser), jid контакта. Подписка задается в соответствии с таблицой, какую я приводил выше. И последний столбец - имя контакта.

- Добавление разделителя имени группы:

Код: Выделить всё

INSERT INTO ofprivate (username, name, namespace, privatedata) VALUES ('tested', 'roster', 'roster:delimiter', '<roster xmlns="roster:delimiter">\\</roster>');
Тут даже комментировать ничего не буду.

- Добавление группы для контакта:

Код: Выделить всё

INSERT INTO ofrostergroups (rosterid, rank, groupname) VALUES (3, 0, 'group1');
rosterid, напомню, должен совпадать с rosterid из таблицы ofroster. Что такое rank я так и не понял, так что благополучно забиваем на это поле.

- Добавление карточки пользователя:

Код: Выделить всё

INSERT INTO ofvcard (username, vcard) VALUES ('tested', '<vCard xmlns="vcard-temp" version="2.0" prodid="-//HandGen//NONSGML vGen v1.0//EN">\n<FN>ФИО</FN>\n<NICKNAME>ник</NICKNAME>\n<TEL>\n<HOME/>\n<VOICE/>\n<NUMBER>309</NUMBER>\n</TEL>\n<URL>доменру</URL>\n</vCard>');
Ничего сложного.

Ну вроде как с запросами определились. Теперь вперед - ваять скрипт.

Код: Выделить всё

#!/usr/bin/perl

# Подключаем модуль XML
use XML::Simple;

# Задаем необходимые переменные
my $password;
my $username;
my $lastlogindate;
# Задаем начальный rosterid
my $rosterid = 4;

# Считываем список файлов, которые будем обрабатывать
@files = `ls *.xml`;
# Создаем объект XML
$xml = new XML::Simple;

# Открываем файл, куда будем записывать SQL запросы
open (OUT, ">/root/users-jabber.sql");

# Перебираем все файлы
foreach $file (@files)
{
	# Обрезаем \n
    chomp $file;
    # Считываем xml файл
    $data = $xml->XMLin($file);

	# выводим на экран имя файла, которое парсим
    print "########".$file."\n";

    # Получаем пароль пользователя
    $password = $data->{'password'}->{'content'};

    # Разбираем тэг <query>
    foreach $each (@{$data->{'query'}})
    {
        # Получаем имя
        if ($each->{'xmlns'} eq 'jabber:iq:register')
        {
            $username = lc($each->{'username'});
        }

        # Получаем инфу о последнем логине
        if ($each->{'xmlns'} eq 'jabber:iq:last')
        {
            ($year, $month, $day) = (localtime($each->{'last'}))[5,4,3];
            $date = sprintf ("%04d-%02d-%02d", $year+1900, $month+1, $day);
            $lastlogindate = $date;
        }

        # Получаем конктакты
        if ($each->{'xmlns'} eq 'jabber:iq:roster')
        {
			# Вариант 1 хранения пользовательских контактов
            if(ref($each->{'item'}) eq "ARRAY")
            {

                foreach $contact (@{$each->{'item'}})
                {
					# Получаем данные контакта
					$contactname = $contact->{'name'};
                    $contactgroup = $contact->{'group'};
                    $contactjid = $contact->{'jid'};
                    $contactsubscription = $contact->{'subscription'};

					# Если имя не задано (нормальная ситуация), то используем jid
                    if (length($contactname) == 0)
                    {
                        $contactname = $contactjid;
                    }

					# В зависимости от типа подписки генерируем запрос
                    if ($contactsubscription eq 'both')
                        { print OUT "INSERT INTO ofroster (rosterid, username, jid, sub, ask, recv, nick) VALUES ($rosterid, '$username', '$contactjid', 3, -1, -1, '$contactname');\n"; };
                    if ($contactsubscription eq 'none')
                        { print OUT "INSERT INTO ofroster (rosterid, username, jid, sub, ask, recv, nick) VALUES ($rosterid, '$username', '$contactjid', 0, -1, -1, '$contactname');\n"; };
                    if ($contactsubscription eq 'from')
                        { print OUT "INSERT INTO ofroster (rosterid, username, jid, sub, ask, recv, nick) VALUES ($rosterid, '$username', '$contactjid', 2, -1, -1, '$contactname');\n"; };
                    if ($contactsubscription eq 'to')
                        { print OUT "INSERT INTO ofroster (rosterid, username, jid, sub, ask, recv, nick) VALUES ($rosterid, '$username', '$contactjid', 1, -1, -1, '$contactname');\n"; };
					# Если группа задана, то генерим запрос на ее добавление
					if (length($contactgroup) != 0)
                    {
                        print OUT "INSERT INTO ofrostergroups (rosterid, rank, groupname) VALUES ($rosterid, 0, '$contactgroup');\n";
                    }
					# Увеличиваем счетчик id
                    $rosterid++;
                }
            }
			# Вариант 2 хранения пользовательских контактов
            if(ref($each->{'item'}) eq "HASH")
            {
				# Получаем имена контактов
                @names = keys %{$each->{'item'}};

                foreach $name (@names)
                {
					# Получаем данные о контакте
                    $contactname = $name;
                    $contactgroup = $each->{'item'}->{$name}->{'group'};
                    $contactjid = $each->{'item'}->{$name}->{'jid'};
                    $contactsubscription = $each->{'item'}->{$name}->{'subscription'};

					# В зависимости от типа подписки генерируем запрос
                    if ($contactsubscription eq 'both')
                        { print OUT "INSERT INTO ofroster (rosterid, username, jid, sub, ask, recv, nick) VALUES ($rosterid, '$username', '$contactjid', 3, -1, -1, '$contactname');\n"; };
                    if ($contactsubscription eq 'none')
                        { print OUT "INSERT INTO ofroster (rosterid, username, jid, sub, ask, recv, nick) VALUES ($rosterid, '$username', '$contactjid', 0, -1, -1, '$contactname');\n"; };
                    if ($contactsubscription eq 'from')
                        { print OUT "INSERT INTO ofroster (rosterid, username, jid, sub, ask, recv, nick) VALUES ($rosterid, '$username', '$contactjid', 2, -1, -1, '$contactname');\n"; };
                    if ($contactsubscription eq 'to')
                        { print OUT "INSERT INTO ofroster (rosterid, username, jid, sub, ask, recv, nick) VALUES ($rosterid, '$username', '$contactjid', 1, -1, -1, '$contactname');\n"; };
					# Если группа задана, то генерим запрос на ее добавление
					if (length($contactgroup) != 0)
                    {
                        print OUT "INSERT INTO ofrostergroups (rosterid, rank, groupname) VALUES ($rosterid, 0, '$contactgroup');\n";
                    }
					# Увеличиваем счетчик id
                    $rosterid++;

                }
            }

        }

    }
	# Генерим запрос на добавление пользователя
    print OUT "INSERT INTO ofuser (username, plainpassword, encryptedpassword, name, email, creationdate, modificationdate) VALUES ('$username', '$password', NULL, '$username', NULL, 001297833054376, 0);\n";
	# Генерим запрос на добавления разделителя для групп
    print OUT "INSERT INTO ofprivate (username, name, namespace, privatedata) VALUES ('$username', 'roster', 'roster:delimiter', '\<roster xmlns=\"roster:delimiter\"\>\\\</roster\>');\n";

}
# Закрываем файл с SQL
close OUT;
Скрипт достаточно (на мой взгляд) хорошо прокомментирован, так что я не буду подробно на нем останавливаться. Единственно что скажу - хорошо бы просмотреть полученный файлик /root/users-jabber.sql, поскольку у меня получилась ситуация, что имя одного из контактов заканчивалось на \, что соответственно вызвало бы ошибку при SQL запросе. Хорошо, что я просматривал файлик в mc и увидел различие в подсветке до этой строки и после. Ну что, скриптик написан, запущен. Результат оформлен в виде /root/users-jabber.sql. Осталось только импортировать данные в postgresql:

Код: Выделить всё

mail ~ # psql -Upostgres -g 127.0.0.1 OpenFire < /root/users-jabber.sql
Так же надо бы не забыть сделать две вещи:

1. Обработать тех двух пользователей (которые под маками работают) вручную
2. Увеличить значение счетчиков в ofid таблице

Да, чуть не забыл - VCard данные я в этом скриптике не [классический секс].. Что-то у меня так и не получилось заставить SimpleXML выдать xml output (впадлу гуглить было), поэтому я наваял второй скриптик для выдерания vcard из файла пользователя:

Код: Выделить всё

#!/usr/bin/perl

use utf8;
use XML::Simple;

my $vcard = "";

@files = `ls *.xml`;
$xml = new XML::Simple;
open (OUT, ">/root/users-vcard.sql");

foreach $user_file (@files)
{
    chomp $user_file;
    $vcard = "";

    $data = $xml->XMLin($user_file);
    foreach $each (@{$data->{'query'}})
    {
            # Getting username
        if ($each->{'xmlns'} eq 'jabber:iq:register')
        {
            $username = lc($each->{'username'});
        }
    }

    open(IN, "<$user_file");
    while(<IN>)
    {
        chomp $_;
        print "\$_: ".$_."\n";
        if ($_ =~ m/\<vCard/g)
        {
            ($a, $b) = split('<vCard', $_);
            ($c, $d) = split('</vCard', $b);
        }
    }

    close(IN);
    if (!($c eq ''))
    {
        $vcard = "<vCard ".$c."</vCard>";
        $vcard =~ s/\'/\"/g;
    } else
    {
        $vcard = "<vCard></vCard>";
    }
        print OUT "INSERT INTO ofvcard (username, vcard) VALUES (lower('$username'), '$vcard');\n";
}
close OUT;
Получившийся файл (/root/users-vcard.sql) так же импортируем в postgres. После этого данные о пользователях можно считать перенесенными, за исключением, разве что, offline messages, которых у меня оказалось всего лишь 2. Вообщем я не стал заморачиваться и забил на это. Если понадобится - сами допилите скриптик на предмет добавления offline messages в базу данных.

4. Подведение итогов.

Небольшое резюме:

- Было проигнорировано достаточное количество полей, как в старой базе, так и в новой, поэтому считать перенос базы полным, как мне кажется, не совсем разумно.
- Назвать перенос автоматическим нельзя, скорее он прошел в полуавтоматическом режиме. Но это неважно - главное перенесено большое число пользователей автоматически.
- Самое главное - это то, что пользователи не заметят переноса базы вообще. Это огромный плюс.
- Перевод базы занял где-то один день, но это все равно явно быстрее, чем при переносе вручную.

Ну вообщем я надеюсь, что данное мини исследование кому-нибудь сможет помочь.

5. Используемая литература.

[1] http://www.igniterealtime.org/builds/op ... .html#ofID
[2] http://community.igniterealtime.org/docs/DOC-1832
[3] http://community.igniterealtime.org/thread/30368
[4] http://xmpp.org/extensions/xep-0162.html
[5] http://community.igniterealtime.org/docs/DOC-1674
WBR Озеров Василий aka fr33man

Хостинговая компания Host-Food.ru
Хостинг HostFood.ru
 

Услуги хостинговой компании Host-Food.ru

Хостинг HostFood.ru

Тарифы на хостинг в России, от 12 рублей: https://www.host-food.ru/tariffs/hosting/
Тарифы на виртуальные сервера (VPS/VDS/KVM) в РФ, от 189 руб.: https://www.host-food.ru/tariffs/virtualny-server-vps/
Выделенные сервера, Россия, Москва, от 2000 рублей (HP Proliant G5, Intel Xeon E5430 (2.66GHz, Quad-Core, 12Mb), 8Gb RAM, 2x300Gb SAS HDD, P400i, 512Mb, BBU):
https://www.host-food.ru/tariffs/vydelennyi-server-ds/
Недорогие домены в популярных зонах: https://www.host-food.ru/domains/

Аватара пользователя
Alex Keda
стреляли...
Сообщения: 35439
Зарегистрирован: 2004-10-18 14:25:19
Откуда: Made in USSR
Контактная информация:

Re: Перенос базы jabberd (xml файлы) на openfire сервер (баз

Непрочитанное сообщение Alex Keda » 2011-03-05 8:53:52

зачот.
Убей их всех! Бог потом рассортирует...

fr33man
сержант
Сообщения: 218
Зарегистрирован: 2006-09-04 17:41:27
Откуда: Москва
Контактная информация:

Re: Перенос базы jabberd (xml файлы) на openfire сервер (баз

Непрочитанное сообщение fr33man » 2011-03-05 14:05:42

Сейчас хуже будет -- надо три базы в одной совместить. Jabber, Почта, и AD.. Ну и еще инфу с внутренних ресурсов. Я в AD уже подобавлял новые атрибуты и админки написал. Админки встроены в Active drectory users and computers (смотри скрин). Миранда - это джабер. Вот на след. неделе буду совмещать базы :)))
ad.png
ADUC
ad.png (11.27 КБ) 3542 просмотра
WBR Озеров Василий aka fr33man