Задача.
Необходимо реализовать автоматический рестарт службы Агент сервера 1С: Предприятия 8.2 с помощью планировщика задач Windows. Но перед рестартом необходимо проверить, не работает ли кто-нибудь в базе Base, расположенной на сервере 1С: Предприятия. Если кто-нибудь работает, то перезапуск службы недопустим.
Решение.
Итак. Задача поставлена. Основной проблемой для меня стало условие проверки существующих подключений к определенной БД. Для реализации поставленной задачи я решил не использовать встроенный язык 1С: Предприятия, ни один из .Net-языков, а также Java (only console, only true linux-way).
В синтаксис-помощнике 1С: Предприятия был найден метод проверки наличия подключений к БД, но он предполагал использование OLE/COM-соединения к Агенту сервера 1С: Предприятия. Что ж… Приступим.
Соединение с агентом сервера (IServerAgentConnection)
GetInfoBaseConnections (GetInfoBaseConnections)
Синтаксис:
GetInfoBaseConnections(, )
Параметры:
(обязательный)
Тип: Кластер серверов. Кластер серверов, для которого должен быть получен массив описаний соединений.
(обязательный)
Тип: Описание информационной базы. Информационная база, для которой должен быть получен массив описаний соединений.
Возвращаемое значение:
Тип: COMSafeArray. Массив описаний соединений кластера. Каждое описание соединения представлено объектом с интерфейсом Описание соединения.
Описание:
Получает массив описаний соединений информационной базы.
Доступность:
Интеграция.
Для работы с OLE/COM-объектами средствами Perl нам понадобиться задействовать модуль Win32::OLE. В используемой мной сборке StrawberryPerl данный модуль уже был предустановлен. Но даже если это не так, можно установить его через CPAN:
C:\Perl\perl\bin\cpan Win32::OLE
Чтобы получить список соединений базы данных 1С, нам нужно:
- Создать COM-подключение к объекту V82.COMConnector;
- Подключиться к агенту кластера серверов 1С: Предприятия;
- Получить объект кластера 1С;
- Авторизоваться на кластере;
- Получить список баз 1С на этом кластере;
- Найти необходимую нам базу;
- Получить список активных соединений к этой базе данных.
Вот код с комментариями, который получает список активных соединений клиентов с базой данных 1С: Предприятия, расположенной на сервере 1С: Предприятия.
use strict;
use warnings;
use Win32::OLE;
use Data::Dumper;
sub getConnectionsCount {
# имя сервера (с портом) и имя базы будем получать из параметров метода
my ($server, $dbname) = @_;
# создаем COM-соединение
my $ole1c = Win32::OLE->new("V82.COMConnector") or die "Could not create OLE-connector!\n";
# создаем объект Агента сервера 1С:Предприятия
my $_agent = $ole1c->ConnectAgent($server) or die "Could not connect to server!\n" . cnv(Dumper $ole1c->ErrorDescription());
# получаем объект кластера (у меня один кластер)
my $_cluster = $_agent->GetClusters()->[0] or die "Could not get cluster 0 from array\n";
# авторизуемся на кластере серверов.
# Авторизоваться надо с помощь логина и пароля Администратора кластера серверов,
# которого можно задать в утилите Администрирования сервера 1С:Предприятия.
# не путайте этого администратора с администратором базы данных, что заводится
# через конфигуратор в пользователях.
# У меня нет администраторов, оставляю пустыми параметры логина и пароля.
$_agent->Authenticate($_cluster, "", "");
# получаем список баз данных, расположенных в данном кластере
my $_basesinfo = $_agent->GetInfoBases($_cluster);
# найдем нашу базу данных
my @_bases = grep { defined $_->{Name} && $_->{Name} =~ m/^$dbname$/ } @$_basesinfo;
# она точно будет одна
my $_base = $_bases[0];
# заведем счетчик подключений
my $connCounter = 0;
# получим все активные подключения или сразу вернем 0
my $_baseconns = $_agent->GetInfoBaseConnections($_cluster, $_base) or return 0;
# “отсечем” все соединения по типу приложения, на которые можно не обращать внимание.
# если эти подключения есть, то можно смело перегружать службу
# Агента сервера 1С:Предприятия
foreach (@$_baseconns) {
# нам не важны планировщики задач и подключения через консоль кластера
if ($_->{Application} !~ m/JobScheduler/ && $_->{Application} !~ m/SrvrConsole/) {
$connCounter++;
}
}
return $connCounter;
}
# вызов очень простоой.
# только порт нужно указывать тот, который слушает агент кластера серверов 1С:Предприятия
print “There are “ . getConnectionsCount(“localhost:1540”, “Base”) . “ active connections\n”;
Перезапустить службу Агента можно с помощью команд “net start” и “net stop”:
net stop
net start
Полный код, выполняющий поставленную задачу я расположил на Bitbucket-е (для тех, у кого есть доступ). У кого нет доступа, привожу здесь:
use strict; use warnings; #use locale; #use encoding 'cp1251'; use utf8; use Win32::OLE; use Win32::OLE::Variant; # раскомментировать, если непонятные ошибки или не работает # но тогда не будет видно комментариев о перезапуске службы #Win32::OLE->Option(Warn => 3); #binmode STDOUT, ':encoding(cp1251)'; #`chcp 1251`; use Data::Dumper; my $DEBUG = 0; # настройки my $_S = { host => 'localhost', #адрес агента сервера 1С port => '1540', #порт агента! bases => [ #список баз { name => 'GTVCopy', #имя базы, которую надо проверять, как оно задано в кластере сервера 1С killall => 0 #удалять ли открытые сессии }, { name => 'MET', killall => 1 } ], serviceName => '1C:Enterprise 8.2 Server Agent (x86-64)', #имя службы сервера 1С serviceRestart => { need => 1, #нужно ли рестартовать службу сервера 1С ifNoConnectionsOnly => 1 #только в том случае, если нет подключений ко всем БД из списка выше } }; sub prn { print "\n" . shift . "\n" if $DEBUG; } sub getConnectionsCount { my ($server, $dbname) = @_; my $counter = 1; print "GET CONNECTIONS\n" if $DEBUG; prn 1; my $ole1c = Win32::OLE->new("V82.COMConnector") or die "Could not create OLE-connector!\n"; print "COM-connector created...\n" if $DEBUG; prn 2; my $_agent = $ole1c->ConnectAgent($server) or die "Could not connect to server!\n" . cnv(Dumper $ole1c->ErrorDescription()); prn 3; my $_cluster = $_agent->GetClusters()->[0] or die "Could not get cluster 0 from array\n"; print "CLUSTER: \n" . Dumper $_cluster if $DEBUG; prn 4; $_agent->Authenticate($_cluster, "", ""); prn 5; my $_basesinfo = $_agent->GetInfoBases($_cluster); my @_bases = grep { defined $_->{Name} && $_->{Name} =~ m/^$dbname$/ } @$_basesinfo; my $_base = $_bases[0]; print Dumper $_base if $DEBUG; prn 6; my $connCounter = 0; my $_baseconns = $_agent->GetInfoBaseConnections($_cluster, $_base) or return 0; foreach (@$_baseconns) { if ($_->{Application} !~ m/JobScheduler/ && $_->{Application} !~ m/SrvrConsole/) { print $_->{Application} . "\n" if $DEBUG; $connCounter++; } } return $connCounter; } sub killAllSessions { my ($server, $dbname) = @_; my $counter = 1; print "KILL SESSIONS\n" if $DEBUG; prn 1; my $ole1c = Win32::OLE->new("V82.COMConnector") or die "Could not create OLE-connector!\n"; print "COM-connector created...\n" if $DEBUG; prn 2; my $_agent = $ole1c->ConnectAgent($server) or die "Could not connect to server!\n" . cnv(Dumper $ole1c->ErrorDescription()); prn 3; my $_cluster = $_agent->GetClusters()->[0] or die "Could not get cluster 0 from array\n"; print "CLUSTER: \n" . Dumper $_cluster if $DEBUG; prn 4; $_agent->Authenticate($_cluster, "", ""); prn 5; my $_basesinfo = $_agent->GetInfoBases($_cluster); my @_bases = grep { defined $_->{Name} && $_->{Name} =~ m/^$dbname$/ } @$_basesinfo; my $_base = $_bases[0]; print Dumper $_base if $DEBUG; my $_sessions = $_agent->GetSessions($_cluster); return unless defined $_sessions; my @_sessions = grep { defined $_->{infoBase}->{Name} && $_->{infoBase}->{Name} eq $_base->{Name} } @$_sessions; #print Dumper @_sessions; foreach (@_sessions) { if ($_->{AppID} !~ m/JobScheduler/ && $_->{AppID} !~ m/SrvrConsole/) { $_agent->TerminateSession($_cluster, $_); } } } #print "Connections count: " . getConnectionsCount("localhost", ) . "\n"; #killAllSessions("localhost:2040", $ARGV[0]); #print "Connections count: " . getConnectionsCount("localhost", $ARGV[0]) . "\n"; my $host = $_S->{host} . $_S->{port} ne '' ? ":" . $_S->{port} : ''; my $cons = 0; for my $base (@{$_S->{bases}}) { print "Checking connections in base named $base->{name}..."; my $connCount = getConnectionsCount($host, $base->{name}); print "$connCount\n"; if ($base->{killall} && $connCount > 0) { print "Killing connections...\n"; killAllSessions($host, $base->{name}); } $cons += getConnectionsCount($host, $base->{name}); } print "Total active connections: $cons\n"; if ($_S->{serviceRestart}->{need}) { if ($_S->{serviceRestart}->{ifNoConnectionsOnly} && $cons > 0) { print "Will not restart service.\n"; exit; } print "Restarting service named $_S->{serviceName}...\n"; print `net stop "$_S->{serviceName}"`; print `net start "$_S->{serviceName}"`; }