1 package PVE
::API2
::Ceph
;
11 use PVE
::Ceph
::Services
;
12 use PVE
::Cluster
qw(cfs_read_file cfs_write_file);
13 use PVE
::JSONSchema
qw(get_standard_option);
17 use PVE
::RPCEnvironment
;
19 use PVE
::Tools
qw(run_command file_get_contents file_set_contents);
21 use PVE
::API2
::Ceph
::OSD
;
22 use PVE
::API2
::Ceph
::FS
;
23 use PVE
::API2
::Ceph
::MDS
;
24 use PVE
::API2
::Ceph
::MGR
;
25 use PVE
::API2
::Ceph
::MON
;
26 use PVE
::API2
::Storage
::Config
;
28 use base
qw(PVE::RESTHandler);
30 my $pve_osd_default_journal_size = 1024*5;
32 __PACKAGE__-
>register_method ({
33 subclass
=> "PVE::API2::Ceph::OSD",
37 __PACKAGE__-
>register_method ({
38 subclass
=> "PVE::API2::Ceph::MDS",
42 __PACKAGE__-
>register_method ({
43 subclass
=> "PVE::API2::Ceph::MGR",
47 __PACKAGE__-
>register_method ({
48 subclass
=> "PVE::API2::Ceph::MON",
52 __PACKAGE__-
>register_method ({
53 subclass
=> "PVE::API2::Ceph::FS",
57 __PACKAGE__-
>register_method ({
61 description
=> "Directory index.",
62 permissions
=> { user
=> 'all' },
64 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
67 additionalProperties
=> 0,
69 node
=> get_standard_option
('pve-node'),
78 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
92 { name
=> 'restart' },
105 __PACKAGE__-
>register_method ({
109 description
=> "List local disks.",
113 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
116 additionalProperties
=> 0,
118 node
=> get_standard_option
('pve-node'),
120 description
=> "Only list specific types of disks.",
122 enum
=> ['unused', 'journal_disks'],
132 dev
=> { type
=> 'string' },
133 used
=> { type
=> 'string', optional
=> 1 },
134 gpt
=> { type
=> 'boolean' },
135 size
=> { type
=> 'integer' },
136 osdid
=> { type
=> 'integer' },
137 vendor
=> { type
=> 'string', optional
=> 1 },
138 model
=> { type
=> 'string', optional
=> 1 },
139 serial
=> { type
=> 'string', optional
=> 1 },
142 # links => [ { rel => 'child', href => "{}" } ],
147 PVE
::Ceph
::Tools
::check_ceph_inited
();
149 my $disks = PVE
::Diskmanage
::get_disks
(undef, 1);
152 foreach my $dev (keys %$disks) {
153 my $d = $disks->{$dev};
154 if ($param->{type
}) {
155 if ($param->{type
} eq 'journal_disks') {
156 next if $d->{osdid
} >= 0;
158 } elsif ($param->{type
} eq 'unused') {
161 die "internal error"; # should not happen
165 $d->{dev
} = "/dev/$dev";
172 __PACKAGE__-
>register_method ({
178 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
180 description
=> "Get Ceph configuration.",
182 additionalProperties
=> 0,
184 node
=> get_standard_option
('pve-node'),
187 returns
=> { type
=> 'string' },
191 PVE
::Ceph
::Tools
::check_ceph_inited
();
193 my $path = PVE
::Ceph
::Tools
::get_config
('pve_ceph_cfgpath');
194 return file_get_contents
($path);
198 __PACKAGE__-
>register_method ({
205 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
207 description
=> "Get Ceph configuration database.",
209 additionalProperties
=> 0,
211 node
=> get_standard_option
('pve-node'),
219 section
=> { type
=> "string", },
220 name
=> { type
=> "string", },
221 value
=> { type
=> "string", },
222 level
=> { type
=> "string", },
223 'can_update_at_runtime' => { type
=> "boolean", },
224 mask
=> { type
=> "string" },
231 PVE
::Ceph
::Tools
::check_ceph_inited
();
233 my $rados = PVE
::RADOS-
>new();
234 my $res = $rados->mon_command( { prefix
=> 'config dump', format
=> 'json' });
235 foreach my $entry (@$res) {
236 $entry->{can_update_at_runtime
} = $entry->{can_update_at_runtime
}?
1 : 0; # JSON::true/false -> 1/0
242 my $add_storage = sub {
243 my ($pool, $storeid) = @_;
245 my $storage_params = {
250 content
=> 'rootdir,images',
253 PVE
::API2
::Storage
::Config-
>create($storage_params);
256 my $get_storages = sub {
259 my $cfg = PVE
::Storage
::config
();
261 my $storages = $cfg->{ids
};
263 foreach my $storeid (keys %$storages) {
264 my $curr = $storages->{$storeid};
265 $res->{$storeid} = $storages->{$storeid}
266 if $curr->{type
} eq 'rbd' && $pool eq $curr->{pool
};
272 __PACKAGE__-
>register_method ({
276 description
=> "Create initial ceph default configuration and setup symlinks.",
280 check
=> ['perm', '/', [ 'Sys.Modify' ]],
283 additionalProperties
=> 0,
285 node
=> get_standard_option
('pve-node'),
287 description
=> "Use specific network for all ceph related traffic",
288 type
=> 'string', format
=> 'CIDR',
292 'cluster-network' => {
293 description
=> "Declare a separate cluster network, OSDs will route" .
294 "heartbeat, object replication and recovery traffic over it",
295 type
=> 'string', format
=> 'CIDR',
296 requires
=> 'network',
301 description
=> 'Targeted number of replicas per object',
309 description
=> 'Minimum number of available replicas per object to allow I/O',
317 description
=> "Placement group bits, used to specify the " .
318 "default number of placement groups.\n\nNOTE: 'osd pool " .
319 "default pg num' does not work for default pools.",
327 description
=> "Disable cephx authentication.\n\n" .
328 "WARNING: cephx is a security feature protecting against " .
329 "man-in-the-middle attacks. Only consider disabling cephx ".
330 "if your network is private!",
337 returns
=> { type
=> 'null' },
341 my $version = PVE
::Ceph
::Tools
::get_local_version
(1);
343 if (!$version || $version < 14) {
344 die "Ceph Nautilus required - please run 'pveceph install'\n";
346 PVE
::Ceph
::Tools
::check_ceph_installed
('ceph_bin');
349 my $auth = $param->{disable_cephx
} ?
'none' : 'cephx';
351 # simply load old config if it already exists
352 PVE
::Cluster
::cfs_lock_file
('ceph.conf', undef, sub {
353 my $cfg = cfs_read_file
('ceph.conf');
355 if (!$cfg->{global
}) {
360 UUID
::generate
($uuid);
361 UUID
::unparse
($uuid, $fsid);
365 'auth cluster required' => $auth,
366 'auth service required' => $auth,
367 'auth client required' => $auth,
368 'osd pool default size' => $param->{size
} // 3,
369 'osd pool default min size' => $param->{min_size
} // 2,
370 'mon allow pool delete' => 'true',
373 # this does not work for default pools
374 #'osd pool default pg num' => $pg_num,
375 #'osd pool default pgp num' => $pg_num,
378 if ($auth eq 'cephx') {
379 $cfg->{client
}->{keyring
} = '/etc/pve/priv/$cluster.$name.keyring';
382 if ($param->{pg_bits
}) {
383 $cfg->{global
}->{'osd pg bits'} = $param->{pg_bits
};
384 $cfg->{global
}->{'osd pgp bits'} = $param->{pg_bits
};
387 if ($param->{network
}) {
388 $cfg->{global
}->{'public network'} = $param->{network
};
389 $cfg->{global
}->{'cluster network'} = $param->{network
};
392 if ($param->{'cluster-network'}) {
393 $cfg->{global
}->{'cluster network'} = $param->{'cluster-network'};
396 cfs_write_file
('ceph.conf', $cfg);
398 if ($auth eq 'cephx') {
399 PVE
::Ceph
::Tools
::get_or_create_admin_keyring
();
401 PVE
::Ceph
::Tools
::setup_pve_symlinks
();
408 __PACKAGE__-
>register_method ({
412 description
=> "Stop ceph services.",
416 check
=> ['perm', '/', [ 'Sys.Modify' ]],
419 additionalProperties
=> 0,
421 node
=> get_standard_option
('pve-node'),
423 description
=> 'Ceph service name.',
426 default => 'ceph.target',
427 pattern
=> '(ceph|mon|mds|osd|mgr)\.[A-Za-z0-9\-]{1,32}',
431 returns
=> { type
=> 'string' },
435 my $rpcenv = PVE
::RPCEnvironment
::get
();
437 my $authuser = $rpcenv->get_user();
439 PVE
::Ceph
::Tools
::check_ceph_inited
();
441 my $cfg = cfs_read_file
('ceph.conf');
442 scalar(keys %$cfg) || die "no configuration\n";
448 if ($param->{service
}) {
449 push @$cmd, $param->{service
};
452 PVE
::Ceph
::Services
::ceph_service_cmd
(@$cmd);
455 return $rpcenv->fork_worker('srvstop', $param->{service
} || 'ceph',
459 __PACKAGE__-
>register_method ({
463 description
=> "Start ceph services.",
467 check
=> ['perm', '/', [ 'Sys.Modify' ]],
470 additionalProperties
=> 0,
472 node
=> get_standard_option
('pve-node'),
474 description
=> 'Ceph service name.',
477 default => 'ceph.target',
478 pattern
=> '(ceph|mon|mds|osd|mgr)\.[A-Za-z0-9\-]{1,32}',
482 returns
=> { type
=> 'string' },
486 my $rpcenv = PVE
::RPCEnvironment
::get
();
488 my $authuser = $rpcenv->get_user();
490 PVE
::Ceph
::Tools
::check_ceph_inited
();
492 my $cfg = cfs_read_file
('ceph.conf');
493 scalar(keys %$cfg) || die "no configuration\n";
499 if ($param->{service
}) {
500 push @$cmd, $param->{service
};
503 PVE
::Ceph
::Services
::ceph_service_cmd
(@$cmd);
506 return $rpcenv->fork_worker('srvstart', $param->{service
} || 'ceph',
510 __PACKAGE__-
>register_method ({
514 description
=> "Restart ceph services.",
518 check
=> ['perm', '/', [ 'Sys.Modify' ]],
521 additionalProperties
=> 0,
523 node
=> get_standard_option
('pve-node'),
525 description
=> 'Ceph service name.',
528 default => 'ceph.target',
529 pattern
=> '(mon|mds|osd|mgr)\.[A-Za-z0-9\-]{1,32}',
533 returns
=> { type
=> 'string' },
537 my $rpcenv = PVE
::RPCEnvironment
::get
();
539 my $authuser = $rpcenv->get_user();
541 PVE
::Ceph
::Tools
::check_ceph_inited
();
543 my $cfg = cfs_read_file
('ceph.conf');
544 scalar(keys %$cfg) || die "no configuration\n";
549 my $cmd = ['restart'];
550 if ($param->{service
}) {
551 push @$cmd, $param->{service
};
554 PVE
::Ceph
::Services
::ceph_service_cmd
(@$cmd);
557 return $rpcenv->fork_worker('srvrestart', $param->{service
} || 'ceph',
561 __PACKAGE__-
>register_method ({
565 description
=> "Get ceph status.",
569 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
572 additionalProperties
=> 0,
574 node
=> get_standard_option
('pve-node'),
577 returns
=> { type
=> 'object' },
581 PVE
::Ceph
::Tools
::check_ceph_inited
();
583 my $rados = PVE
::RADOS-
>new();
584 my $status = $rados->mon_command({ prefix
=> 'status' });
585 $status->{health
} = $rados->mon_command({ prefix
=> 'health', detail
=> 'detail' });
589 __PACKAGE__-
>register_method ({
593 description
=> "List all pools.",
597 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
600 additionalProperties
=> 0,
602 node
=> get_standard_option
('pve-node'),
610 pool
=> { type
=> 'integer' },
611 pool_name
=> { type
=> 'string' },
612 size
=> { type
=> 'integer' },
615 links
=> [ { rel
=> 'child', href
=> "{pool_name}" } ],
620 PVE
::Ceph
::Tools
::check_ceph_inited
();
622 my $rados = PVE
::RADOS-
>new();
625 my $res = $rados->mon_command({ prefix
=> 'df' });
627 foreach my $d (@{$res->{pools
}}) {
628 next if !$d->{stats
};
629 next if !defined($d->{id
});
630 $stats->{$d->{id
}} = $d->{stats
};
633 $res = $rados->mon_command({ prefix
=> 'osd dump' });
634 my $rulestmp = $rados->mon_command({ prefix
=> 'osd crush rule dump'});
637 for my $rule (@$rulestmp) {
638 $rules->{$rule->{rule_id
}} = $rule->{rule_name
};
642 foreach my $e (@{$res->{pools
}}) {
644 foreach my $attr (qw(pool pool_name size min_size pg_num crush_rule)) {
645 $d->{$attr} = $e->{$attr} if defined($e->{$attr});
648 if (defined($d->{crush_rule
}) && defined($rules->{$d->{crush_rule
}})) {
649 $d->{crush_rule_name
} = $rules->{$d->{crush_rule
}};
652 if (my $s = $stats->{$d->{pool
}}) {
653 $d->{bytes_used
} = $s->{bytes_used
};
654 $d->{percent_used
} = $s->{percent_used
};
663 __PACKAGE__-
>register_method ({
664 name
=> 'createpool',
667 description
=> "Create POOL",
671 check
=> ['perm', '/', [ 'Sys.Modify' ]],
674 additionalProperties
=> 0,
676 node
=> get_standard_option
('pve-node'),
678 description
=> "The name of the pool. It must be unique.",
682 description
=> 'Number of replicas per object',
690 description
=> 'Minimum number of replicas per object',
698 description
=> "Number of placement groups.",
706 description
=> "The rule to use for mapping object placement in the cluster.",
711 description
=> "The application of the pool, 'rbd' by default.",
713 enum
=> ['rbd', 'cephfs', 'rgw'],
717 description
=> "Configure VM and CT storage using the new pool.",
723 returns
=> { type
=> 'string' },
727 PVE
::Cluster
::check_cfs_quorum
();
728 PVE
::Ceph
::Tools
::check_ceph_inited
();
730 my $pve_ckeyring_path = PVE
::Ceph
::Tools
::get_config
('pve_ckeyring_path');
732 die "not fully configured - missing '$pve_ckeyring_path'\n"
733 if ! -f
$pve_ckeyring_path;
735 my $pool = $param->{name
};
736 my $rpcenv = PVE
::RPCEnvironment
::get
();
737 my $user = $rpcenv->get_user();
739 if ($param->{add_storages
}) {
740 $rpcenv->check($user, '/storage', ['Datastore.Allocate']);
741 die "pool name contains characters which are illegal for storage naming\n"
742 if !PVE
::JSONSchema
::parse_storage_id
($pool);
745 my $pg_num = $param->{pg_num
} || 128;
746 my $size = $param->{size
} || 3;
747 my $min_size = $param->{min_size
} || 2;
748 my $application = $param->{application
} // 'rbd';
752 PVE
::Ceph
::Tools
::create_pool
($pool, $param);
754 if ($param->{add_storages
}) {
756 eval { $add_storage->($pool, "${pool}"); };
758 warn "failed to add storage: $@";
761 die "adding storage for pool '$pool' failed, check log and add manually!\n"
766 return $rpcenv->fork_worker('cephcreatepool', $pool, $user, $worker);
769 __PACKAGE__-
>register_method ({
773 description
=> "get all set ceph flags",
777 check
=> ['perm', '/', [ 'Sys.Audit' ]],
780 additionalProperties
=> 0,
782 node
=> get_standard_option
('pve-node'),
785 returns
=> { type
=> 'string' },
789 PVE
::Ceph
::Tools
::check_ceph_inited
();
791 my $pve_ckeyring_path = PVE
::Ceph
::Tools
::get_config
('pve_ckeyring_path');
793 die "not fully configured - missing '$pve_ckeyring_path'\n"
794 if ! -f
$pve_ckeyring_path;
796 my $rados = PVE
::RADOS-
>new();
798 my $stat = $rados->mon_command({ prefix
=> 'osd dump' });
800 return $stat->{flags
} // '';
803 __PACKAGE__-
>register_method ({
805 path
=> 'flags/{flag}',
807 description
=> "Set a ceph flag",
811 check
=> ['perm', '/', [ 'Sys.Modify' ]],
814 additionalProperties
=> 0,
816 node
=> get_standard_option
('pve-node'),
818 description
=> 'The ceph flag to set/unset',
820 enum
=> [ 'full', 'pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill', 'norebalance', 'norecover', 'noscrub', 'nodeep-scrub', 'notieragent'],
824 returns
=> { type
=> 'null' },
828 PVE
::Ceph
::Tools
::check_ceph_inited
();
830 my $pve_ckeyring_path = PVE
::Ceph
::Tools
::get_config
('pve_ckeyring_path');
832 die "not fully configured - missing '$pve_ckeyring_path'\n"
833 if ! -f
$pve_ckeyring_path;
835 my $set = $param->{set
} // !$param->{unset
};
836 my $rados = PVE
::RADOS-
>new();
838 $rados->mon_command({
840 key
=> $param->{flag
},
846 __PACKAGE__-
>register_method ({
847 name
=> 'unset_flag',
848 path
=> 'flags/{flag}',
850 description
=> "Unset a ceph flag",
854 check
=> ['perm', '/', [ 'Sys.Modify' ]],
857 additionalProperties
=> 0,
859 node
=> get_standard_option
('pve-node'),
861 description
=> 'The ceph flag to set/unset',
863 enum
=> [ 'full', 'pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill', 'norebalance', 'norecover', 'noscrub', 'nodeep-scrub', 'notieragent'],
867 returns
=> { type
=> 'null' },
871 PVE
::Ceph
::Tools
::check_ceph_inited
();
873 my $pve_ckeyring_path = PVE
::Ceph
::Tools
::get_config
('pve_ckeyring_path');
875 die "not fully configured - missing '$pve_ckeyring_path'\n"
876 if ! -f
$pve_ckeyring_path;
878 my $set = $param->{set
} // !$param->{unset
};
879 my $rados = PVE
::RADOS-
>new();
881 $rados->mon_command({
882 prefix
=> "osd unset",
883 key
=> $param->{flag
},
889 __PACKAGE__-
>register_method ({
890 name
=> 'destroypool',
891 path
=> 'pools/{name}',
893 description
=> "Destroy pool",
897 check
=> ['perm', '/', [ 'Sys.Modify' ]],
900 additionalProperties
=> 0,
902 node
=> get_standard_option
('pve-node'),
904 description
=> "The name of the pool. It must be unique.",
908 description
=> "If true, destroys pool even if in use",
914 description
=> "Remove all pveceph-managed storages configured for this pool",
921 returns
=> { type
=> 'string' },
925 PVE
::Ceph
::Tools
::check_ceph_inited
();
927 my $rpcenv = PVE
::RPCEnvironment
::get
();
928 my $user = $rpcenv->get_user();
929 $rpcenv->check($user, '/storage', ['Datastore.Allocate'])
930 if $param->{remove_storages
};
932 my $pool = $param->{name
};
935 my $storages = $get_storages->($pool);
937 # if not forced, destroy ceph pool only when no
938 # vm disks are on it anymore
939 if (!$param->{force
}) {
940 my $storagecfg = PVE
::Storage
::config
();
941 foreach my $storeid (keys %$storages) {
942 my $storage = $storages->{$storeid};
944 # check if any vm disks are on the pool
945 print "checking storage '$storeid' for RBD images..\n";
946 my $res = PVE
::Storage
::vdisk_list
($storagecfg, $storeid);
947 die "ceph pool '$pool' still in use by storage '$storeid'\n"
948 if @{$res->{$storeid}} != 0;
952 PVE
::Ceph
::Tools
::destroy_pool
($pool);
954 if ($param->{remove_storages
}) {
956 foreach my $storeid (keys %$storages) {
957 # skip external clusters, not managed by pveceph
958 next if $storages->{$storeid}->{monhost
};
959 eval { PVE
::API2
::Storage
::Config-
>delete({storage
=> $storeid}) };
961 warn "failed to remove storage '$storeid': $@\n";
965 die "failed to remove (some) storages - check log and remove manually!\n"
969 return $rpcenv->fork_worker('cephdestroypool', $pool, $user, $worker);
973 __PACKAGE__-
>register_method ({
977 description
=> "Get OSD crush map",
981 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
984 additionalProperties
=> 0,
986 node
=> get_standard_option
('pve-node'),
989 returns
=> { type
=> 'string' },
993 PVE
::Ceph
::Tools
::check_ceph_inited
();
995 # this produces JSON (difficult to read for the user)
996 # my $txt = &$run_ceph_cmd_text(['osd', 'crush', 'dump'], quiet => 1);
1000 my $mapfile = "/var/tmp/ceph-crush.map.$$";
1001 my $mapdata = "/var/tmp/ceph-crush.txt.$$";
1003 my $rados = PVE
::RADOS-
>new();
1006 my $bindata = $rados->mon_command({ prefix
=> 'osd getcrushmap', format
=> 'plain' });
1007 file_set_contents
($mapfile, $bindata);
1008 run_command
(['crushtool', '-d', $mapfile, '-o', $mapdata]);
1009 $txt = file_get_contents
($mapdata);
1021 __PACKAGE__-
>register_method({
1025 description
=> "Read ceph log",
1028 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
1032 additionalProperties
=> 0,
1034 node
=> get_standard_option
('pve-node'),
1053 description
=> "Line number",
1057 description
=> "Line text",
1066 PVE
::Ceph
::Tools
::check_ceph_inited
();
1068 my $rpcenv = PVE
::RPCEnvironment
::get
();
1069 my $user = $rpcenv->get_user();
1070 my $node = $param->{node
};
1072 my $logfile = "/var/log/ceph/ceph.log";
1073 my ($count, $lines) = PVE
::Tools
::dump_logfile
($logfile, $param->{start
}, $param->{limit
});
1075 $rpcenv->set_result_attrib('total', $count);
1080 __PACKAGE__-
>register_method ({
1084 description
=> "List ceph rules.",
1088 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
1091 additionalProperties
=> 0,
1093 node
=> get_standard_option
('pve-node'),
1102 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
1107 PVE
::Ceph
::Tools
::check_ceph_inited
();
1109 my $rados = PVE
::RADOS-
>new();
1111 my $rules = $rados->mon_command({ prefix
=> 'osd crush rule ls' });
1115 foreach my $rule (@$rules) {
1116 push @$res, { name
=> $rule };