Запуск сайтов от разных пользователей в связке nginx + php-fpm

nginx_php_process_flowПо умолчанию все сайты будут запускаться от пользователя, указанного в настройках php-fpm. Чтобы запускать сайты от разных пользователей, необходимо создать отдельные конфигурационные файлы в директории /etc/php7/fpm/pool.d, убедившись при этом, что в файле /etc/php7/fpm/php-fpm.conf указана строчка:

include=/etc/php7/fpm/pool.d/*.conf

Теперь создаем файл конфигурации для нашего сайта (покажу на примере 891rpm.arthead.ru) /etc/php7/fpm/pool.d/891rpm.arthead.ru.conf:

[891rpm.arthead.ru]
listen = /run/php7-891rpm.sock
listen.mode = 0660
user = 891rpm_com    # user
group = 891rpm_com   # group
chdir = /var/www/891rpm.arthead.ru

php_admin_value[upload_tmp_dir] = /var/www/891rpm.arthead.ru/tmp
php_admin_value[soap.wsdl_cache_dir] = /var/www/891rpm.arthead.ru/tmp
php_admin_value[date.timezone] = Europe/Moscow
php_admin_value[upload_max_filesize] = 100M
php_admin_value[post_max_size] = 100M
php_admin_value[open_basedir] = "/var/www/891rpm.arthead.ru/"
php_admin_value[session.save_path] = /var/www/891rpm.arthead.ru/tmp
php_admin_value[disable_functions] = dl,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,parse_ini_file,show_source
php_admin_value[cgi.fix_pathinfo] = 0
php_admin_value[apc.cache_by_default] = 0

# В зависимости от нагрузки меняем параметры
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 4

В файле настроек /etc/nginx/sites-available/891rpm.arthead.ru.conf указываем сокет:

upstream 891rpm-sock {
    server unix:/var/run/php7-891rpm.sock;
}

server {
        listen 80;
        server_name 891rpm.arthead.ru www.891rpm.arthead.ru;
...
location ~ \.php$
        {
                include fastcgi.conf;
                fastcgi_intercept_errors on;
                fastcgi_pass 891rpm-sock;
        }
...
}

/etc/group

www-data:x:33:
891rpm_com:x:1001:www-data

 

Перезапускаем php-fpm и nginx:

sudo /etc/init.d/php7-fpm restart
sudo /etc/init.d/nginx restart

Теперь сайт будет запускаться от указанного пользователя.

Как разбить tar.gz архив на тома и как его потом склеить

tar-gzДля того чтобы создать разбитый на тома архив в консоли нужно выполнить обычное архивирование с передачей результата в OUTPUT и таким образом на вторую команду конвейера, которая и разбивает полученный результат на тома нужного размера.

tar czf - ./backup | split -d -b 10m - backup.tar.gz.

(Про точку в конце первой команды не забываем)

В результате получится несколько файлов по 10 Мб с окончанием .01 .02 .03 и т.д.

Для того чтобы потом склеить полученные тома нужно выполнить cat с передачей через OUTPUT данных архиватору.

cat backup.tar* | tar xzf -

 

Accessing and debugging the Django development server from another machine

In continuing my series about setting up a Linux virtual machine for Django development (see parts one and two), I wanted to share another tip for accessing the VM from the host machine.

Set up development server to listen to external requests

By default, when using the Django management runserver command, the development server will only listen to requests originating from the local machine (using the loopback address 127.0.0.1). Luckily, the runserver command accepts an IP address and port. Specifying 0.0.0.0 will allow the server to accept requests from any machine:

python manage.py runserver 0.0.0.0:8000

I do not have to worry about security issues with the development server listening to all requests, since the VM is protected from external access by a firewall.

Set up server to send debug information to local network

While the first step will allow us to access the development server from the host machine, we will not be able to see debugging information (for example, thedjango-debug-toolbar will not be displayed on the host machine, even if DEBUG is set to True). Django uses another setting, INTERNAL_IPS, to determine which machines are allowed to view debugging information.

For a typical installation, I set INTERNAL_IPS to only specify 127.0.0.1, allowing me to easily debug Django apps running on the local machine.

INTERNAL_IPS = ('127.0.0.1',)

Now, since the setting is a tuple, we could easily add the IP address of the host machine and call it a day. We would run into the same problem as the last post, which is the reason I installed Samba in order to access the VM using its host name instead of IP address. Whenever the machine gets a new IP address from the DHCP server, I would have to update the setting. I found a great solution to this problem in this snippet, which adds wildcard support to INTERNAL_IPS. I personally put this code inside an if block, so that it only executes in a development setting. Here is the code from the end of my settings.py file:

if DEBUG:

    from fnmatch import fnmatch
    class glob_list(list):
        def __contains__(self, key):
            for elt in self:
                if fnmatch(key, elt): return True
            return False

    INTERNAL_IPS = glob_list(['127.0.0.1', '192.168.*.*'])

Now, I can access and debug Django projects using the development server from any machine on my local network.

How to Install and Connect to PostgreSQL on CentOS 7

postgresqlPostgreSQL (pronounced ‘post-gres-Q-L’) is a free, open-source object-relational database management system (object-RDBMS), similar to MySQL, and is standards-compliant and extensible. It is commonly used as a back-end for web and mobile applications. PostgreSQL, or ‘Postgres’ as it is nicknamed, adopts the ANSI/ISO SQL standards together, with the revisions.

Pre-Flight Check
  • These instructions are intended specifically for installing PostgreSQL on CentOS 7.
  • I’ll be working from a Liquid Web Self Managed CentOS 7 server, and I’ll be logged in as root.
Step 1: Add the PostgreSQL 9.3 Repository

In this case we want to install PostgreSQL 9.3 directly from the Postgres repository. Let’s add that repo:

wget http://yum.postgresql.org/9.4/redhat/rhel-7-x86_64/pgdg-centos94-9.4-1.noarch.rpm
rpm -ihvU pgdg-centos94-9.4-1.noarch.rpm

Step 2: Install PostgreSQL

First, you’ll follow a simple best practice: ensuring the list of available packages is up to date before installing anything new.

yum -y update

Then it’s a matter of just running one command for installation via apt-get:

yum -y install postgresql94 postgresql94-server postgresql94-contrib postgresql94-libs --disablerepo=* --enablerepo=pgdg94

PostgreSQL should now be installed.

Step 3: Start PostgreSQL

Configure Postgres to start when the server boots:

systemctl enable postgresql-9.4

Start Postgres:

/usr/pgsql-9.4/bin/postgresql94-setup initdb

systemctl start postgresql-9.4

Step 4: Switch to the Default PostgreSQL User

As part of the installation Postgres adds the system user postgres and is setup to use “ident” authentication. Rolesinternal to Postgres (which are similar to users) match with a system user account.

Let’s switch into that system user:

su – postgres

And then connect to the PostgreSQL terminal (in the postgres role):

$ psql

That’s it! You’re connected and ready to run commands in PostgreSQL as the postgres role.

Colored Bash man pages and Log files

colored_bash

Below you can find bash codes which you can use with man pages or while reading log files:

'\e[0;30m' # Black - Regular
'\e[0;31m' # Red
'\e[0;32m' # Green
'\e[0;33m' # Yellow
'\e[0;34m' # Blue
'\e[0;35m' # Purple
'\e[0;36m' # Cyan
'\e[0;37m' # White

'\e[1;30m' # Black - Bold
'\e[1;31m' # Red
'\e[1;32m' # Green
'\e[1;33m' # Yellow
'\e[1;34m' # Blue
'\e[1;35m' # Purple
'\e[1;36m' # Cyan
'\e[1;37m' # White

'\e[4;30m' # Black - Underline
'\e[4;31m' # Red
'\e[4;32m' # Green
'\e[4;33m' # Yellow
'\e[4;34m' # Blue
'\e[4;35m' # Purple
'\e[4;36m' # Cyan
'\e[4;37m' # White

'\e[40m'   # Black - Background
'\e[41m'   # Red
'\e[42m'   # Green
'\e[43m'   # Yellow
'\e[44m'   # Blue
'\e[45m'   # Purple
'\e[46m'   # Cyan
'\e[47m'   # White
'\e[0m'    # Text Reset

If you are using less application for viewing man pages you can add below variables to your .bashrc file. After that reload .bashrc variables with command: source /home/user/.bashrc and you will see colored man pages.

export LESS_TERMCAP_mb=$'\E[01;31m'
export LESS_TERMCAP_md=$'\E[01;31m'
export LESS_TERMCAP_me=$'\E[0m'
export LESS_TERMCAP_se=$'\E[0m'
export LESS_TERMCAP_so=$'\E[01;44;33m'
export LESS_TERMCAP_ue=$'\E[0m'
export LESS_TERMCAP_us=$'\E[01;32m'

For log files use perl syntax with your combined regex:

 tail -f  /var/log/mail.log | perl -pe 's/(fatal|error|panic|success)/\e[1;31m$&\e[0m/g'

Last colored trick. Use grep command with —color switch.

Dig

digDig is a powerful Linux tool and today I’ll demonstrate some useful everyday examples including a reverse lookup, zone transfer, and how to find the SOA (start of authority) in a zone file.

So what is dig?

man dig

«dig (domain information groper) is a flexible tool for interrogating DNS name servers.»

A simple example

How to find the IP address (A record) associated with a domain:

dig tomhayman.co.uk +short

Which outputs:

75.127.99.28

Reverse lookup example

How to find the domain name associated with an IP address:

dig -x 75.127.99.28 +short

Which outputs:

zoe.asmallorange.com.

(For more information remove +short)

Zone transfer example

First, find the name server to query:

dig ns tomhayman.co.uk +short

Which outputs:

ns1.asmallorange.com.
ns2.asmallorange.com.

Then:

dig -t axfr @ns1.asmallorange.com tomhayman.co.uk

Which outputs:

; <<>> DiG 9.3.4-P1 <<>> -t axfr @ns1.asmallorange.com tomhayman.co.uk
; (1 server found)
;; global options:  printcmd
; Transfer failed.

But the transfer failed!  This is normally due to security settings on the name server.  Sometimes you can request this to be removed, although most providers prevent it.

However, some organisations allow this behaviour.  One of them is Wikipedia

So if we try the process again:

dig ns wikipedia.org +short

Which outputs:

ns0.wikimedia.org.

Then:

dig -t axfr @ns0.wikimedia.org wikipedia.org | head -n 10

Which outputs:

; <<>> DiG 9.3.4-P1 <<>> -t axfr @ns0.wikimedia.org wikipedia.org
; (1 server found)
;; global options:  printcmd
wikipedia.org.          86400   IN      SOA     ns0.wikimedia.org. hostmaster.wikimedia.org. 2010082803 43200 7200 1209600 3600
wikipedia.org.          3600    IN      A       208.80.152.2
wikipedia.org.          86400   IN      NS      ns0.wikimedia.org.
wikipedia.org.          86400   IN      NS      ns1.wikimedia.org.
wikipedia.org.          86400   IN      NS      ns2.wikimedia.org.
wikipedia.org.          3600    IN      MX      50 lists.wikimedia.org.

(N.B. I used head to output the first 10 lines only as wikipedia.org has thousands of CNAME’s)

Start of authority (SOA) example

Find the SOA record in a zone file:

dig +nocmd wikipedia.org any +multiline +noall +answer

Which outputs:

wikipedia.org.          1589 IN A 208.80.152.2
wikipedia.org.          84389 IN NS ns0.wikimedia.org.
wikipedia.org.          84389 IN SOA ns0.wikimedia.org. hostmaster.wikimedia.org. (
2010082803 ; serial
43200      ; refresh (12 hours)
7200       ; retry (2 hours)
1209600    ; expire (2 weeks)
3600       ; minimum (1 hour)
)
wikipedia.org.          1589 IN MX 50 lists.wikimedia.org.

Dig can do a lot more than the examples I’ve illustrated today.  You can build some useful scripts with it too, which I’ll demonstrate at another time.

Конвертация БД из CP1251 в UTF8

Конвертацию БД из Win-1251 в UTF8 можно произвести разными способами, но самый быстрый и простой — использование SQL-запроса, приведенного ниже.

ALTER TABLE `db_name`.`table_name` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;

При помощи этого запроса, можно конвертировать таблицу базы данных в любую, доступную в MySQL кодировку. Но что делать, если таблиц 100, 200 или больше, и все таблицы необходимо перекодировать в UTF8 из Win-1251? Для решения этой проблемы, можно отправить в MySQL запрос, который сгенерирует необходимые SQL-запросы для всех таблиц БД. При использовании PHPMyAdmin останется только скопировать результаты и запустить их как SQL-запрос:

SELECT CONCAT( 'ALTER TABLE `', t.`TABLE_SCHEMA` , '`.`', t.`TABLE_NAME` , '` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;' ) AS sqlcode
FROM `information_schema`.`TABLES` t
WHERE 1
AND t.`TABLE_SCHEMA` = 'My_DB_for_convert'
ORDER BY 1
LIMIT 0 , 90

Этот запрос будет работать в MySQL версии 5 и выше. My_DB_for_convert — следует заменить на имя БД, таблицы в которой необходимо конвертировать в UTF-8.

utf8_general_ci или utf8_unicode_ci

utf8_general_ci и utf8_unicode_ci отличаются только скоростью работы и порядком сортировки. Поскольку utf8_general_ci работает быстрее — это и есть предпочтительный выбор. Подробнее, про отличия utf8_general_ci и utf8_unicode_ci можно прочитать в официальной документации MySQL. В случаее необходимости, способом описанным выше, можно будет изменить подкодировку UTF8 c utf8_general_ci на utf8_unicode_ci.

MongoDB + Django

mongodb
mongoDB — документо-ориентированная система управления базами данных (СУБД) с открытым исходным кодом, не требующая описания схемы таблиц. Написана на языке C++ и распространяется в рамках лицензии Creative Commons.

В последнее время становится довольно популярной и восстребованой. И вот возникла идея использовать ее в связке с фреймворком Django. Собственно о чем далее и пойдет речь.

Для решения поставленной задачи мы будем использовать приложение mongodb-engine. Данное приложение тесно связано еще с несколькими приложениями, установкой которых мы и займемся вначале.

Django-nonrel — используется для поддержки NoSQL в Django.

pip install hg+https://bitbucket.org/wkornewald/django-nonrel

djangotoolbox — набор инструментов для работы с нереляционными базами данных, лишним не будет.

pip install hg+https://bitbucket.org/wkornewald/djangotoolbox

А теперь уже ставим и mongodb-engine:

pip install git+https://github.com/django-nonrel/mongodb-engine

Указываем нашу базу данных в settings:

DATABASES = {
   'default' : {
      'ENGINE' : 'django_mongodb_engine',
      'NAME' : 'my_database'
   }
}

При необходимости также можно указать host, port, user, password.

Данное приложение предоставляет два типа полей для хранения произвольных данных, не входящих в стандартную django модель.

ListField

Списки и им подобные, представление массивов в формате BSON

from djangotoolbox.fields import ListField

class Post(models.Model):
    ...
    tags = ListField()
>>> Post(tags=['django', 'mongodb'], ...).save()
>>> Post.objecs.get(...).tags
['django', 'mongodb']

Вариант с указанием типа:

class Post(models.Model):
    ...
    edited_on = ListField(models.DateTimeField())
>>> post = Post(edited_on=['1010-10-10 10:10:10'])
>>> post.save()
>>> Post.objects.get(...).edited_on
[datetime.datetime([1010, 10, 10, 10, 10, 10])]

Данный тип поля удобно использовать для организации связи один-ко-многим:

from djangotoolbox.fields import EmbeddedModelField, ListField

class Post(models.Model):
    ...
    comments = ListField(EmbeddedModelField('Comment'))

class Comment(models.Model):
    ...
    text = models.TextField()

EmbeddedModelField — используется для организации связей между моделями.

DictField

Второй тип поля DictField, который используется в BSON для обьектов.

from djangotoolbox.fields import DictField

class Image(models.Model):
    ...
    exif = DictField()
>>> Image(exif=get_exif_data(...), ...).save()
>>> Image.objects.get(...).exif
{u'camera_model' : 'Spamcams 4242', 'exposure_time' : 0.3, ...}

Вариант с указанием типа:

class Poll(models.Model):
    ...
    votes = DictField(models.IntegerField())
>>> Poll(votes={'bob' : 3.14, 'alice' : '42'}, ...).save()
>>> Poll.objects.get(...).votes
{u'bob' : 3, u'alice' : 42}
Обновление данных
Post.objects.filter(...).update(title='Everything is the same')

Можно использовать для обновления оператор $set

.update(..., {'$set': {'title': 'Everything is the same'}})

А также функцию F()

Post.objects.filter(...).update(visits=F('visits')+1)

В результате получится что то такое:

.update(..., {'$inc': {'visits': 1}})
Использование низко-уровневых запросов

Если Вам не хватает возможностей Django ORM, можно использовать запросы к MongoDB минуя стандартный механизм.

raw_query() — принимает один аргумент, возвращает данные в виде стандартного Django queryset. Что хорошо для дальнейшей обработки данных.

Пример с geo данными, модель:

from djangotoolbox.fields import EmbeddedModelField
from django_mongodb_engine.contrib import MongoDBManager

class Point(models.Model):
    latitude = models.FloatField()
    longtitude = models.FloatField()

class Place(models.Model):
    ...
    location = EmbeddedModelField(Point)

    objects = MongoDBManager()

получим все точки рядом с конкретными координатами:

>>> here = {'latitude' : 42, 'longtitude' : 3.14}
>>> Place.objects.raw_query({'location' : {'$near' : here}})

raw_update() — используется если нам недостаточно стандартных средств для обновления данных.

Модель:

from django_mongodb_engine.contrib import MongoDBManager

class FancyNumbers(models.Model):
    foo = models.IntegerField()

    objects = MongoDBManager()

использование:

FancyNumbers.objects.raw_update({}, {'$bit' : {'foo' : {'or' : 42}}})

В данном примере выполняется побитовое or для каждого foo в базе.

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

Ссылки:
MongoDB
mongodb-engine
GitHub

Python и Twisted

twistedTwisted — это фреймворк на Python для разработки сетевых приложений, который среди многих других применений, может быть использован и для параллельной обработки данных — мультипроцессности. Это замечательно, но мне пришлось попотеть для того, чтобы найти то, что мне нужно.

twisted_ntwk_pro_ess_comp.indd

Я листал документацию Twisted и книгу O’Reilly Twisted. Существует также рецепт в Python Cookbook. Однако, самое интересное я нашел в статье Брюса Эккель — Параллельность с Python, Twisted и Flex. Также стоит прочитать первоначальные статьи Брюса Эккель про Twisted: Grokking Twisted.

Вот мои замечания о текущем примере Брюса.

Python CookbookЯ убрал Flex — отчасти потому, что мне это не нужно и я ничего не хочу знать об этом. В примере запускается контроллер, который инициализирует ряд отдельных параллельных процессов-вычислителей, в которых уже запускаются какие-то сложные действия (эти процессы называют solvers). Также тут имеется взаимодействие между контроллером и вычислителями. Хотя этот пример запускается только на одной машине, те принципы, о которых говориться в статье — не трудно распространить и на систему из нескольких компьютеров.

Для хорошего примера, как это работает, пожалуйста, смотрите оригинал статьи.

Вот solver.py который скопирован с оригинала. Настоящая «работа» происходит в методе step(). Я только добавил некоторую отладочную информацию для себя.

"""
solver.py
Original version by Bruce Eckel
Solves one portion of a problem, in a separate process on a separate CPU
"""
import sys, random, math
from twisted.spread import pb
from twisted.internet import reactor

class Solver(pb.Root):

    def __init__(self, id):
        print "solver.py %s: solver init" % id
        self.id = id

    def __str__(self): # String representation
        return "Solver %s" % self.id

    def remote_initialize(self, initArg):
        return "%s initialized" % self

    def step(self, arg):
        print "solver.py %s: solver step" % self.id
        "Simulate work and return result"
        result = 0
        for i in range(random.randint(1000000, 3000000)):
            angle = math.radians(random.randint(0, 45))
            result += math.tanh(angle)/math.cosh(angle)
        return "%s, %s, result: %.2f" % (self, str(arg), result)

    # Alias methods, for demonstration version:
    remote_step1 = step
    remote_step2 = step
    remote_step3 = step

    def remote_status(self):
        print "solver.py %s: remote_status" % self.id
        return "%s operational" % self

    def remote_terminate(self):
        print "solver.py %s: remote_terminate" % self.id
        reactor.callLater(0.5, reactor.stop)
        return "%s terminating..." % self

if __name__ == "__main__":
    port = int(sys.argv[1])
    reactor.listenTCP(port, pb.PBServerFactory(Solver(sys.argv[1])))
    reactor.run()

Вот controller.py. Он также скопирован из оригинальной статьи, но я убрал Flex и создал сигналы start и terminate в классе контроллера. Я не уверен, что это имеет смысл, но, по крайней мере, это позволило мне нормально использовать пример. Я также перенес метод terminate из FlexInterface в Controller.

"""
Controller.py
Original version by Bruce Eckel
Starts and manages solvers in separate processes for parallel processing.
"""
import sys
from subprocess import Popen
from twisted.spread import pb
from twisted.internet import reactor, defer

START_PORT = 5566
MAX_PROCESSES = 2

class Controller(object):

    def broadcastCommand(self, remoteMethodName, arguments, nextStep, failureMessage):
        print "controller.py: broadcasting..."
        deferreds = [solver.callRemote(remoteMethodName, arguments) 
                     for solver in self.solvers.values()]
        print "controller.py: broadcasted"
        reactor.callLater(3, self.checkStatus)

        defer.DeferredList(deferreds, consumeErrors=True).addCallbacks(
            nextStep, self.failed, errbackArgs=(failureMessage))

    def checkStatus(self):
        print "controller.py: checkStatus"
        for solver in self.solvers.values():
            solver.callRemote("status").addCallbacks(
                lambda r: sys.stdout.write(r + "\n"), self.failed, 
                errbackArgs=("Status Check Failed"))

    def failed(self, results, failureMessage="Call Failed"):
        print "controller.py: failed"
        for (success, returnValue), (address, port) in zip(results, self.solvers):
            if not success:
                raise Exception("address: %s port: %d %s" % (address, port, failureMessage))

    def __init__(self):
        print "controller.py: init"
        self.solvers = dict.fromkeys(
            [("localhost", i) for i in range(START_PORT, START_PORT+MAX_PROCESSES)])
        self.pids = [Popen(["python", "solver.py", str(port)]).pid
                     for ip, port in self.solvers]
        print "PIDS: ", self.pids
        self.connected = False
        reactor.callLater(1, self.connect)

    def connect(self):
        print "controller.py: connect"
        connections = []
        for address, port in self.solvers:
            factory = pb.PBClientFactory()
            reactor.connectTCP(address, port, factory)
            connections.append(factory.getRootObject())
        defer.DeferredList(connections, consumeErrors=True).addCallbacks(
            self.storeConnections, self.failed, errbackArgs=("Failed to Connect"))

        print "controller.py: starting parallel jobs"
        self.start()

    def storeConnections(self, results):
        print "controller.py: storeconnections"
        for (success, solver), (address, port) in zip(results, self.solvers):
            self.solvers[address, port] = solver
        print "controller.py: Connected; self.solvers:", self.solvers
        self.connected = True

    def start(self):
        "controller.py: Begin the solving process"
        if not self.connected:
            return reactor.callLater(0.5, self.start)
        self.broadcastCommand("step1", ("step 1"), self.step2, "Failed Step 1")

    def step2(self, results):
        print "controller.py: step 1 results:", results
        self.broadcastCommand("step2", ("step 2"), self.step3, "Failed Step 2")

    def step3(self, results):
        print "controller.py: step 2 results:", results
        self.broadcastCommand("step3", ("step 3"), self.collectResults, "Failed Step 3")

    def collectResults(self, results):
        print "controller.py: step 3 results:", results
        self.terminate()

    def terminate(self):
        print "controller.py: terminate"
        for solver in self.solvers.values():
            solver.callRemote("terminate").addErrback(self.failed, "Termination Failed")
        reactor.callLater(1, reactor.stop)
        return "Terminating remote solvers"

if __name__ == "__main__":
    controller = Controller()
    reactor.run()

Чтобы запустить программу, положите оба файла в одну папку и запустите

python controller.py

Вы должны увидеть, как загрузка двух процессоров (если их, конечно, у вас — 2) поднимется до 100%. А вот и вывод скрипта на экран:

controller.py: init
PIDS:  [12173, 12174]
solver.py 5567: solver init
solver.py 5566: solver init
controller.py: connect
controller.py: starting parallel jobs
controller.py: storeconnections
controller.py: Connected; self.solvers: {('localhost', 5567): , ('localhost', 5566): }
controller.py: broadcasting...
controller.py: broadcasted
solver.py 5566: solver step
solver.py 5567: solver step
controller.py: checkStatus
solver.py 5566: remote_status
Solver 5566 operational
solver.py 5567: remote_status
controller.py: step 1 results: [(True, 'Solver 5567, step 1, result: 683825.75'), (True, 'Solver 5566, step 1, result: 543177.17')]
controller.py: broadcasting...
controller.py: broadcasted
Solver 5567 operational
solver.py 5566: solver step
solver.py 5567: solver step
controller.py: checkStatus
solver.py 5566: remote_status
Solver 5566 operational
solver.py 5567: remote_status
controller.py: step 2 results: [(True, 'Solver 5567, step 2, result: 636793.90'), (True, 'Solver 5566, step 2, result: 335358.16')]
controller.py: broadcasting...
controller.py: broadcasted
Solver 5567 operational
solver.py 5566: solver step
solver.py 5567: solver step
controller.py: checkStatus
solver.py 5566: remote_status
Solver 5566 operational
solver.py 5567: remote_status
controller.py: step 3 results: [(True, 'Solver 5567, step 3, result: 847386.43'), (True, 'Solver 5566, step 3, result: 512120.15')]
controller.py: terminate
Solver 5567 operational
solver.py 5566: remote_terminate
solver.py 5567: remote_terminate