]>
git.proxmox.com Git - pve-access-control.git/blob - src/PVE/Jobs/RealmSync.pm
1 package PVE
::Jobs
::RealmSync
;
6 use JSON
qw(decode_json encode_json);
9 use PVE
::JSONSchema
qw(get_standard_option);
11 use PVE
::CalendarEvent
();
14 use PVE
::API2
::Domains
();
16 # load user-* standard options
17 use PVE
::API2
::User
();
19 use base
qw(PVE::Job::Registry);
25 my $props = get_standard_option
('realm-sync-options', {
26 realm
=> get_standard_option
('realm'),
35 enabled
=> { optional
=> 1 },
37 comment
=> { optional
=> 1 },
40 for my $opt (keys %$props) {
41 next if defined($options->{$opt});
42 # ignore legacy props from realm-sync schema
43 next if $opt eq 'full' || $opt eq 'purge';
44 if ($props->{$opt}->{optional
}) {
45 $options->{$opt} = { optional
=> 1 };
47 $options->{$opt} = {};
50 $options->{realm
}->{fixed
} = 1;
56 my ($class, $type, $key, $value) = @_;
61 my ($class, $type, $key, $value) = @_;
66 my ($class, $skip_type) = @_;
68 my $schema = $class->SUPER::createSchema
($skip_type);
70 my $opts = $class->options();
71 for my $opt (keys $schema->{properties
}->%*) {
72 next if defined($opts->{$opt}) || $opt eq 'id';
73 delete $schema->{properties
}->{$opt};
80 my ($class, $skip_type) = @_;
81 my $schema = $class->SUPER::updateSchema
($skip_type);
83 my $opts = $class->options();
84 for my $opt (keys $schema->{properties
}->%*) {
85 next if defined($opts->{$opt});
86 next if $opt eq 'id' || $opt eq 'delete';
87 delete $schema->{properties
}->{$opt};
93 my $statedir = "/etc/pve/priv/jobs";
99 my $statefile = "$statedir/realm-sync-$id.json";
100 my $raw = eval { PVE
::Tools
::file_get_contents
($statefile) } // '';
102 my $state = ($raw =~ m/^(\{.*\})$/) ? decode_json
($1) : {};
108 my ($id, $state) = @_;
111 my $statefile = "$statedir/realm-sync-$id.json";
113 if (defined($state)) {
114 PVE
::Tools
::file_set_contents
($statefile, encode_json
($state));
116 unlink $statefile or $! == ENOENT
or die "could not delete state for $id - $!\n";
123 my ($class, $conf, $id, $schedule) = @_;
125 for my $opt (keys %$conf) {
126 delete $conf->{$opt} if !defined($props->{$opt});
129 my $realm = $conf->{realm
};
133 my $nodename = PVE
::INotify
::nodename
();
135 # check statefile in pmxcfs if we should start
136 my $shouldrun = PVE
::Cluster
::cfs_lock_domain
('realm-sync', undef, sub {
137 my $members = PVE
::Cluster
::get_members
();
139 my $state = get_state
($id);
140 my $last_node = $state->{node
} // $nodename;
141 my $last_upid = $state->{upid
};
142 my $last_time = $state->{time};
144 my $last_node_online = $last_node eq $nodename || ($members->{$last_node} // {})->{online
};
146 if (defined($last_upid)) {
147 # first check if the next run is scheduled
148 if (my $parsed = PVE
::Tools
::upid_decode
($last_upid, 1)) {
149 my $cal_spec = PVE
::CalendarEvent
::parse_calendar_event
($schedule);
150 my $next_sync = PVE
::CalendarEvent
::compute_next_event
($cal_spec, $parsed->{starttime
});
151 return 0 if !defined($next_sync) || $now < $next_sync; # not yet its (next) turn
153 # check if still running and node is online
154 my $tasks = PVE
::Cluster
::get_tasklist
();
155 for my $task (@$tasks) {
156 next if $task->{upid
} ne $last_upid;
157 last if defined($task->{endtime
}); # it's already finished
158 last if !$last_node_online; # it's not finished and the node is offline
159 return 0; # not finished and online
161 } elsif (defined($last_time) && ($last_time+60) > $now && $last_node_online) {
162 # another node started this job in the last 60 seconds and is still online
166 # any of the following conditions should be true here:
167 # * it was started on another node but that node is offline now
168 # * it was started but either too long ago, or with an error
169 # * the started task finished
180 my $upid = eval { PVE
::API2
::Domains-
>sync($conf) };
182 PVE
::Cluster
::cfs_lock_domain
('realm-sync', undef, sub {
183 if ($err && !$upid) {
201 return "OK"; # all other cases should not run the sync on this node