============================
 Процедура миграции доменов
============================


Старт миграции (вьюшка ``MigrationStartView``).
===============================================

По POST она дёргает ``PddDomainMigration.create_migration_task``
и отдаёт её результат, как id миграции.

По GET она отдаёт статус миграции по её id.

При создании задания на миграцию
--------------------------------

1. Выбирается шард для организации.
2. Создаётся запись в таблице migrations со статусом ``check``,
   ``only_check_conditions=False`` и рандомным ``id``.

   При этом, в ``settings`` прописывается словарь с входными параметрами
   метода::

     settings = {
         'admin': admin,
         'domain': domain,
         'deproot': deproot,
         'orgname': orgname,
         'tld': tld,
     }

Дальше в дело вступают воркеры.

Worker condition_check
======================

Этот воркер перебирает все записи из ``migrations``, у которых
``status=='check'``.

1. Для каждого из них он берёт ``lock``.
2. Проверяет, возможна ли миграция. На этом этапе функция проверки
   ``migration_status_and_data`` может вернуть разные ошибки, в том
   числе и ``in_progress``. И этот ``in_progress`` будет означать, что в
   метабазе уже есть запись про организацию с таким же ``label``, как
   для этого домена и ``ready=False``.

   В этом случае воркер ищет уже идущую миграцию, и прописывает её id,
   как в поле ``migration_id`` атрибута ``data`` у текущей миграции.
3. Если миграция разрешена, то её статус переводится в ``setup``.

По итогу, миграция переходит в состояние ``setup``, если всё хорошо,
``in_progress``, если уже идёт миграция этого домена или в какую-то
ошибку, если дальнейшее продолжение миграции невозможно.


Worker setup_migration
======================

Этот воркер обрабатывает все записи из migration, что в состоянии
``setup``.

1. На каждую запись берётся лок.
2. Запускается ``PddDomainMigration.setup_migration``.

   При подготовке миграции проходят следующие шаги:

   1. Сначала проверяется, что свободен аккаунт заданный в переменной
      ``deproot``, если нет, то возвращается исключение
      ``root_maillist_exists``.
   2. Дальше получаем список ящиков и рассылок на домене, а так же
      информацию об админе из паспорта.
   3. Заводим запись про организацию в метабазе и основной базе.
   4. Создаём рассылку для корневого отдела.
   5. Сохраняем алиасы домена из ПДД в Директорию.
   6. Создаём саппортного робота для Ямба.
      
3. Если всё проходит хорошо, то миграция переводится в состояние
   ``migration``
4. Если случилось исключение, то в ``rollback`` с ``code: unknown``.

Есть странный коммент. Я его не понимаю. Зачем табличку migrations обновлять дважды::

  MigrationModel(main_connection).update_one(
      migration_id=task_id,
      status=status,
      data=data
  )
  # запомним ид заготовки организации
  MigrationModel(main_connection).update(
      update_data={'org_id': migrator.org_id},
      filter_data={'id': task_id},
  )

Update. Выяснили с Наилем, что особого смысла в этом нет, можно было бы
одним update обойтись.


Worker migrate_worker
=====================

Этот занимается тем, что мигрирует один ящик.

1. Он достаёт из таблички первую запись, у которой ``migrated is NULL``.
2. Берёт лок на её id
3. Запускает PddDomainMigrationEmail.migrate_email.
4. Тот в свою очередь, достаёт данные по id из таблицы migrate_email и
   создаёт по ним пользователя в Директории.
5. Подписывает пользователя на корневой отдел.
6. Синхронно активирует для него b2b диск.
7. В конце - создаёт в migrate_log две записи, про то, что пользователя
   создали и про то, что подписали его на корневую рассылку.
8. Если в процессе случается PddException, то ошибка логгируется
   в migrate_log.

   .. note:: Прочие ошибки в migrate_log не попадают, однако всё равно
             вызывают откат миграции. **Надо это поправить!**

Воркер migrate_finalizer
========================

Этот воркер выбирает все записи со статусом ``migration``. Потом считает все
migrate_email, относящиеся к этой организации. Если у них у всех поле
``migrated=True``, то воркер создаёт лок по id организации. Дальше:

1. В табличке migrations для миграции проставляется статус
   ``finalization``.
2. Вызывается ``PddDomainMigration.finalize_migration``, который:

   * по ``domain_id`` проставляет в паспорте признак воркспейсности,
     через указание для домена ``organization_name``.
   * дёргает ручку ``ws_update`` в ПДД.

3. Если случается ошибка, то миграция переводится в статус ``rollback``,
   а для всех записей в таблице ``migrate_email``, проставляется флаг
   ``migrated=False``.
4. Если ошибки не было, то:

   * организации в метабазе проставляется признак ``ready=True``;
   * все записи в ``migrate_email``, относящиеся к организации,
     удаляются;
   * генерится действие ``organization_migration``;
   * отправляется письмо админу организации;
   * отправляются письма всем пользователям организации;

.. note:: воркер меняет состояние миграции в секции ``finalize``, уже после
          всех остальных действий. Так что если в ходе ``except`` или
          ``else`` произошла ошибка, то состояние миграции не
          изменится. А поскольку раньше у нас воркеры запускались не в
          транзакции, то здесь могла возникнуть неконсистентность.

          
Воркер migrate_rollback
=======================

Этот воркер вынимает из таблицы migrations все записи со статусом
``rollback``. И для каждой из них поочереди берёт лок, и дальше делает
следующее:

1. Если org_id в записи нет, то сразу проставляется статус
   ``rollback_completed``.
2. Если к организации привязаны записи в табличке migrate_email,
   то на каждую такую запись берётся лок по id и если хотя бы для одной
   лок взять не получилось, то решаем, что всё ещё идёт миграция
   (что странно, ведь миграция в статусе ``rollback``) и просто делаем
   выход из процедуры ``migrate_rollback``.

   .. note:: При этом откат других организаций перестаёт обрабатываться
             до тех пор, пока не откатится эта странная организация у
             которой всё ещё мигрируют ящики.

             В целом, попытка брать локи - весьма стрёмный способ
             определить, что миграция всё ещё идёт.
             
3. Если на все записи из ``migrate_email`` удалось взять лок, или их уже
   нет, то пробуем достать из базы Директории ``master_domain``. Если
   его почему-то нет, то делаем ``continue`` и **откат миграции
   зависает**. Возможно именно это произошло с 45 организациями, у
   которых сейчас ``ready=False``.
4. Если все предыдущие пункты прошли, то запускаем
   ``PddDomainMigrationRollback.rollback``:

   * Удаляются рассылки, созданные для Отделов.
   * Деактивируются b2b пользователи в Диске.
   * Удаляется признак воркспейсности домена в паспорте (через сброс
     organization_name).
   * Дожидаемся, пока у всех пользователей пропадёт атрибут 1011.
   * Делаем downgrade домена в ПДД.
   * Дёргаем ручку Abook ``workspace/rollback`` для отката общей
     адресной книги.
   * Записываем действие ``organization_delete``.
   * Удаляем пользователей из мета-базы Директории.
   * Сбрасываем руководителей отделов и авторов Команд в None.
   * Удаляем через ПДД аккаунт каждого робота, созданного для сервисов.
   * Чистим RobotServiceModel.
   * Удаляем всех пользователей, отделы и команды, а так же записи про
     домены, действия, события, migrate_email, ресурсы с так далее.

     .. note:: но кстати, почему-то ``migrate_log`` табличка не
               очищается.

   * В довершение всего, удаляются записи про организацию из обычной и
     мета баз.
     
5. Если метод ``rollback`` отработал корректно, то миграции
   проставляется статус ``rollback_completed``. Если произошло
   исключение ``PddException``, то статус ``error`` и в ``data`` пишется
   код из исключения. В случае любого другого исключения типа Exception,
   просто в статус ``error`` переходит миграция.
          
Переходы статусов
=================

::

  check
  |- in_progress, если уже есть параллельная такая же миграция (здесь
  |  возможна ошибка, потому что получается две записи в таблице migration).
  |- error, или другие ошибки если миграция не возможна
  |- setup
     |- ???
  |- rollback
     |- rollback_completed
     |- error
   
