]>
Commit | Line | Data |
---|---|---|
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, '/var/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; |