1 package PMG
::API2
::Cluster
;
8 use PVE
::Tools
qw(extract_param);
9 use HTTP
::Status
qw(:constants);
10 use Storable
qw(dclone);
11 use PVE
::JSONSchema
qw(get_standard_option);
14 use PVE
::APIClient
::LWP
;
16 use PMG
::RESTEnvironment
;
17 use PMG
::ClusterConfig
;
24 use base
qw(PVE::RESTHandler);
27 my ($cinfo, $conn_setup) = @_;
29 my $conn = PVE
::APIClient
::LWP-
>new(%$conn_setup);
31 my $info = PMG
::Cluster
::read_local_cluster_info
();
33 my $res = $conn->post("/config/cluster/nodes", $info);
35 foreach my $node (@$res) {
36 $cinfo->{ids
}->{$node->{cid
}} = $node;
40 print STDERR
"stop all services accessing the database\n";
41 # stop all services accessing the database
42 PMG
::Utils
::service_wait_stopped
(40, $PMG::Utils
::db_service_list
);
44 print STDERR
"save new cluster configuration\n";
47 PMG
::Cluster
::update_ssh_keys
($cinfo);
49 print STDERR
"cluster node successfully joined\n";
51 $cinfo = PMG
::ClusterConfig-
>new(); # reload
53 my $role = $cinfo->{'local'}->{type
} // '-';
54 die "local node '$cinfo->{local}->{name}' not part of cluster\n"
57 die "got unexpected role '$role' for local node '$cinfo->{local}->{name}'\n"
60 my $cid = $cinfo->{'local'}->{cid
};
62 PMG
::MailQueue
::create_spooldirs
($cid);
64 PMG
::Cluster
::sync_config_from_master
($cinfo->{master
}->{name
}, $cinfo->{master
}->{ip
});
66 PMG
::DBTools
::init_nodedb
($cinfo);
68 my $cfg = PMG
::Config-
>new();
69 my $ruledb = PMG
::RuleDB-
>new();
70 my $rulecache = PMG
::RuleCache-
>new($ruledb);
72 $cfg->rewrite_config($rulecache, 1);
74 print STDERR
"syncing quarantine data\n";
75 PMG
::Cluster
::sync_master_quar
($cinfo->{master
}->{ip
}, $cinfo->{master
}->{name
});
76 print STDERR
"syncing quarantine data finished\n";
80 foreach my $service (reverse @$PMG::Utils
::db_service_list
) {
81 eval { PVE
::Tools
::run_command
(['systemctl', 'start', $service]); };
88 __PACKAGE__-
>register_method({
92 description
=> "Directory index.",
93 permissions
=> { user
=> 'all' },
95 additionalProperties
=> 0,
104 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
111 { name
=> 'status' },
112 { name
=> 'create' },
119 __PACKAGE__-
>register_method({
123 description
=> "Cluster node index.",
124 # alway read local file
126 additionalProperties
=> 0,
129 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit' ] },
135 type
=> { type
=> 'string' },
136 cid
=> { type
=> 'integer' },
137 ip
=> { type
=> 'string' },
138 name
=> { type
=> 'string' },
139 hostrsapubkey
=> { type
=> 'string' },
140 rootrsapubkey
=> { type
=> 'string' },
141 fingerprint
=> { type
=> 'string' },
148 my $cinfo = PMG
::ClusterConfig-
>new();
150 if (scalar(keys %{$cinfo->{ids
}})) {
151 my $role = $cinfo->{local}->{type
} // '-';
153 die "local node '$cinfo->{local}->{name}' not part of cluster\n";
157 return PVE
::RESTHandler
::hash_to_array
($cinfo->{ids
}, 'cid');
160 __PACKAGE__-
>register_method({
164 description
=> "Cluster node status.",
165 # alway read local file
167 additionalProperties
=> 0,
169 list_single_node
=> {
170 description
=> "List local node if there is no cluster defined. Please note that RSA keys and fingerprint are not valid in that case.",
177 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit' ] },
183 type
=> { type
=> 'string' },
184 cid
=> { type
=> 'integer' },
185 ip
=> { type
=> 'string' },
186 name
=> { type
=> 'string' },
187 hostrsapubkey
=> { type
=> 'string' },
188 rootrsapubkey
=> { type
=> 'string' },
189 fingerprint
=> { type
=> 'string' },
192 links
=> [ { rel
=> 'child', href
=> "{cid}" } ],
197 my $cinfo = PMG
::ClusterConfig-
>new();
198 my $nodename = PVE
::INotify
::nodename
();
201 if (scalar(keys %{$cinfo->{ids
}})) {
202 my $role = $cinfo->{local}->{type
} // '-';
204 die "local node '$cinfo->{local}->{name}' not part of cluster\n";
206 $res = PVE
::RESTHandler
::hash_to_array
($cinfo->{ids
}, 'cid');
208 } elsif ($param->{list_single_node
}) {
209 my $ni = { type
=> '-' };
210 foreach my $k (qw(ip name cid)) {
211 $ni->{$k} = $cinfo->{local}->{$k};
213 foreach my $k (qw(hostrsapubkey rootrsapubkey fingerprint)) {
214 $ni->{$k} = '-'; # invalid
219 my $rpcenv = PMG
::RESTEnvironment-
>get();
220 my $authuser = $rpcenv->get_user();
221 my $ticket = $rpcenv->get_ticket();
223 foreach my $ni (@$res) {
226 if ($ni->{cid
} eq $cinfo->{local}->{cid
}) {
227 $info = PMG
::API2
::NodeInfo-
>status({ node
=> $nodename });
229 my $conn = PVE
::APIClient
::LWP-
>new(
231 cookie_name
=> 'PMGAuthCookie',
233 cached_fingerprints
=> {
234 $ni->{fingerprint
} => 1,
237 $info = $conn->get("/nodes/localhost/status", {});
241 $ni->{conn_error
} = "$err"; # convert $err to string
244 foreach my $k (keys %$info) {
245 $ni->{$k} = $info->{$k} if !defined($ni->{$k});
252 my $add_node_schema = PMG
::ClusterConfig
::Node-
>createSchema(1);
253 delete $add_node_schema->{properties
}->{cid
};
255 __PACKAGE__-
>register_method({
259 description
=> "Add an node to the cluster config.",
262 parameters
=> $add_node_schema,
264 description
=> "Returns the resulting node list.",
269 cid
=> { type
=> 'integer' },
277 my $cinfo = PMG
::ClusterConfig-
>new();
279 die "no cluster defined\n" if !scalar(keys %{$cinfo->{ids
}});
281 my $master = $cinfo->{master
} || die "unable to lookup master node\n";
284 foreach my $cid (keys %{$cinfo->{ids
}}) {
285 my $d = $cinfo->{ids
}->{$cid};
287 if ($d->{type
} eq 'node' && $d->{ip
} eq $param->{ip
} && $d->{name
} eq $param->{name
}) {
288 $next_cid = $cid; # allow overwrite existing node data
292 if ($d->{ip
} eq $param->{ip
}) {
293 die "ip address '$param->{ip}' is already used by existing node $d->{name}\n";
296 if ($d->{name
} eq $param->{name
}) {
297 die "node with name '$param->{name}' already exists\n";
301 if (!defined($next_cid)) {
302 $next_cid = ++$master->{maxcid
};
307 cid
=> $master->{maxcid
},
310 foreach my $k (qw(ip name hostrsapubkey rootrsapubkey fingerprint)) {
311 $node->{$k} = $param->{$k};
314 $cinfo->{ids
}->{$node->{cid
}} = $node;
318 PMG
::DBTools
::update_master_clusterinfo
($node->{cid
});
320 PMG
::Cluster
::update_ssh_keys
($cinfo);
322 return PVE
::RESTHandler
::hash_to_array
($cinfo->{ids
}, 'cid');
325 return PMG
::ClusterConfig
::lock_config
($code, "add node failed");
328 __PACKAGE__-
>register_method({
332 description
=> "Create initial cluster config with current node as master.",
333 # alway read local file
335 additionalProperties
=> 0,
339 returns
=> { type
=> 'string' },
343 my $rpcenv = PMG
::RESTEnvironment-
>get();
344 my $authuser = $rpcenv->get_user();
347 my $cinfo = PMG
::ClusterConfig-
>new();
349 die "cluster already defined\n" if scalar(keys %{$cinfo->{ids
}});
351 my $info = PMG
::Cluster
::read_local_cluster_info
();
355 $info->{type
} = 'master';
357 $info->{maxcid
} = $cid,
359 $cinfo->{ids
}->{$cid} = $info;
362 print STDERR
"stop all services accessing the database\n";
363 # stop all services accessing the database
364 PMG
::Utils
::service_wait_stopped
(40, $PMG::Utils
::db_service_list
);
366 print STDERR
"save new cluster configuration\n";
369 PMG
::DBTools
::init_masterdb
($cid);
371 PMG
::MailQueue
::create_spooldirs
($cid);
373 print STDERR
"cluster master successfully created\n";
377 foreach my $service (reverse @$PMG::Utils
::db_service_list
) {
378 eval { PVE
::Tools
::run_command
(['systemctl', 'start', $service]); };
386 return $rpcenv->fork_worker('clustercreate', undef, $authuser, $realcmd);
389 return PMG
::ClusterConfig
::lock_config
($code, "create cluster failed");
392 __PACKAGE__-
>register_method({
396 description
=> "Join local node to an existing cluster.",
397 # alway read local file
400 additionalProperties
=> 0,
403 description
=> "IP address.",
404 type
=> 'string', format
=> 'ip',
407 description
=> "SSL certificate fingerprint.",
409 pattern
=> '^(:?[A-Z0-9][A-Z0-9]:){31}[A-Z0-9][A-Z0-9]$',
412 description
=> "Superuser password.",
418 returns
=> { type
=> 'string' },
422 my $rpcenv = PMG
::RESTEnvironment-
>get();
423 my $authuser = $rpcenv->get_user();
426 my $cinfo = PMG
::ClusterConfig-
>new();
428 die "cluster already defined\n" if scalar(keys %{$cinfo->{ids
}});
431 username
=> 'root@pam',
432 password
=> $param->{password
},
433 cookie_name
=> 'PMGAuthCookie',
434 host
=> $param->{master_ip
},
435 cached_fingerprints
=> {
436 $param->{fingerprint
} => 1,
440 cluster_join
($cinfo, $setup);
444 return $rpcenv->fork_worker('clusterjoin', undef, $authuser, $realcmd);
447 return PMG
::ClusterConfig
::lock_config
($code, "cluster join failed");