]> git.proxmox.com Git - pmg-api.git/blob - src/PMG/Service/pmgmirror.pm
339bf7b52af9282d1de1967e9468105796b37dba
[pmg-api.git] / src / PMG / Service / pmgmirror.pm
1 package PMG::Service::pmgmirror;
2
3 use strict;
4 use warnings;
5 use Data::Dumper;
6 use Time::HiRes qw (gettimeofday tv_interval);
7
8 use PVE::SafeSyslog;
9 use PVE::Tools qw(extract_param);
10 use PVE::INotify;
11 use PVE::Daemon;
12 use PVE::ProcFSTools;
13
14 use PMG::Ticket;
15 use PMG::RESTEnvironment;
16 use PMG::DBTools;
17 use PMG::RuleDB;
18 use PMG::Cluster;
19 use PMG::ClusterConfig;
20 use PMG::Statistic;
21
22 use base qw(PVE::Daemon);
23
24 my $cmdline = [$0, @ARGV];
25
26 my %daemon_options = (restart_on_error => 5, stop_wait_time => 5);
27
28 my $daemon = __PACKAGE__->new('pmgmirror', $cmdline, %daemon_options);
29
30 my $restart_request = 0;
31 my $next_update = 0;
32
33 my $cycle = 0;
34 my $updatetime = 60*2;
35 my $maxtimediff = 5;
36
37 my $initial_memory_usage;
38
39 sub init {
40 # syslog('INIT');
41 }
42
43 sub hup {
44 my ($self) = @_;
45
46 $restart_request = 1;
47 }
48
49 sub sync_data_from_node {
50 my ($dbh, $rdb, $cinfo, $ni, $ticket, $rsynctime_ref) = @_;
51
52 my $ctime = PMG::DBTools::get_remote_time($rdb);
53 my $ltime = time();
54
55 my $timediff = abs($ltime - $ctime);
56 if ($timediff > $maxtimediff) {
57 die "large time difference (> $timediff seconds) - not syncing\n";
58 }
59
60 if ($ni->{type} eq 'master') {
61 PMG::Cluster::sync_ruledb_from_master($dbh, $rdb, $ni, $ticket);
62 PMG::Cluster::sync_deleted_nodes_from_master($dbh, $rdb, $cinfo, $ni, $rsynctime_ref);
63 }
64
65 PMG::Cluster::sync_quarantine_db($dbh, $rdb, $ni, $rsynctime_ref);
66
67 PMG::Cluster::sync_greylist_db($dbh, $rdb, $ni);
68
69 PMG::Cluster::sync_userprefs_db($dbh, $rdb, $ni);
70
71 PMG::Cluster::sync_statistic_db($dbh, $rdb, $ni);
72
73 if ($ni->{type} eq 'master') {
74 PMG::Cluster::sync_domainstat_db($dbh, $rdb, $ni);
75
76 PMG::Cluster::sync_dailystat_db($dbh, $rdb, $ni);
77
78 PMG::Cluster::sync_virusinfo_db($dbh, $rdb, $ni);
79 }
80
81 PMG::Cluster::sync_localstat_db($dbh, $rdb, $ni);
82 }
83
84 sub cluster_sync {
85
86 my $cinfo = PMG::ClusterConfig->new(); # reload
87 my $role = $cinfo->{local}->{type} // '-';
88
89 return if $role eq '-';
90 return if !$cinfo->{master}; # just to be sure
91
92 my $start_time = [ gettimeofday() ];
93
94 syslog ('info', "starting cluster syncronization");
95
96 my $master_ip = $cinfo->{master}->{ip};
97 my $master_name = $cinfo->{master}->{name};
98
99 if ($role ne 'master') {
100 PMG::Cluster::sync_config_from_master($master_name, $master_ip);
101 }
102
103 my $csynctime = tv_interval($start_time);
104
105 $cinfo = PMG::ClusterConfig->new(); # reload
106 $role = $cinfo->{local}->{type} // '-';
107
108 return if $role eq '-';
109 return if !$cinfo->{master}; # just to be sure
110
111 my $ticket = PMG::Ticket::assemble_ticket('root@pam');
112
113 my $dbh = PMG::DBTools::open_ruledb();
114
115 my $errcount = 0;
116
117 my $rsynctime = 0;
118
119 my $sync_node = sub {
120 my ($ni) = @_;
121
122 my $rdb;
123 eval {
124 $rdb = PMG::DBTools::open_ruledb(undef, '/run/pmgtunnel', $ni->{cid});
125 sync_data_from_node($dbh, $rdb, $cinfo, $ni, $ticket, \$rsynctime);
126 };
127 my $err = $@;
128
129 $rdb->disconnect() if $rdb;
130
131 if ($err) {
132 $errcount++;
133 syslog ('err', "database sync '$ni->{name}' failed - $err");
134 } else {
135 PMG::DBTools::create_clusterinfo_default($dbh, $ni->{cid}, 'lastsync', 0, undef);
136 PMG::DBTools::write_maxint_clusterinfo($dbh, $ni->{cid}, 'lastsync', time());
137 }
138 };
139
140 # sync data from master first
141 if ($cinfo->{master}->{cid} ne $cinfo->{local}->{cid}) {
142 $sync_node->($cinfo->{master});
143
144 # rewrite config after sync from master
145 my $cfg = PMG::Config->new();
146 my $ruledb = PMG::RuleDB->new($dbh);
147 my $rulecache = PMG::RuleCache->new($ruledb);
148 $cfg->rewrite_config($rulecache, 1);
149 }
150
151 foreach my $ni (values %{$cinfo->{ids}}) {
152 next if $ni->{cid} eq $cinfo->{local}->{cid};
153 next if $ni->{cid} eq $cinfo->{master}->{cid};
154 $sync_node->($ni);
155 }
156
157 $dbh->disconnect();
158
159 my $cptime = tv_interval($start_time);
160
161 my $dbtime = $cptime - $rsynctime - $csynctime;
162
163 syslog('info', sprintf("cluster syncronization finished (%d errors, %.2f seconds " .
164 "(files %.2f, database %.2f, config %.2f))",
165 $errcount, $cptime, $rsynctime, $dbtime, $csynctime));
166
167 }
168
169 sub run {
170 my ($self) = @_;
171
172 for (;;) { # forever
173
174 $next_update = time() + $updatetime;
175
176 eval {
177 # Note: do nothing in first cycle (give pmgtunnel some time to startup)
178 cluster_sync() if $cycle > 0;
179 };
180 if (my $err = $@) {
181 syslog('err', "sync error: $err");
182 }
183
184 $cycle++;
185
186 last if $self->{terminate};
187
188 my $mem = PVE::ProcFSTools::read_memory_usage();
189
190 if (!defined($initial_memory_usage) || ($cycle < 10)) {
191 $initial_memory_usage = $mem->{resident};
192 } else {
193 my $diff = $mem->{resident} - $initial_memory_usage;
194 if ($diff > 5*1024*1024) {
195 syslog ('info', "restarting server after $cycle cycles to " .
196 "reduce memory usage (free $mem->{resident} ($diff) bytes)");
197 $self->restart_daemon();
198 }
199 }
200
201 my $wcount = 0;
202 while ((time() < $next_update) &&
203 ($wcount < $updatetime) && # protect against time wrap
204 !$restart_request && !$self->{terminate}) {
205
206 $wcount++; sleep (1);
207 };
208
209 last if $self->{terminate};
210
211 $self->restart_daemon() if $restart_request;
212 }
213 }
214
215 $daemon->register_start_command("Start the Database Mirror Daemon");
216 $daemon->register_stop_command("Stop the Database Mirror Daemon");
217 $daemon->register_restart_command(1, "Restart the Database Mirror Daemon");
218
219 our $cmddef = {
220 start => [ __PACKAGE__, 'start', []],
221 restart => [ __PACKAGE__, 'restart', []],
222 stop => [ __PACKAGE__, 'stop', []],
223 };
224
225 1;