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 extract_param);
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' },
97 { name
=> 'disks' }, # FIXME: remove with 7.0
98 { name
=> 'flags' }, # FIXME: remove with 7.0
105 # FIXME: Remove with PVE 7.0
106 __PACKAGE__-
>register_method ({
110 description
=> "List local disks.",
114 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
117 additionalProperties
=> 0,
119 node
=> get_standard_option
('pve-node'),
121 description
=> "Only list specific types of disks.",
123 enum
=> ['unused', 'journal_disks'],
133 dev
=> { type
=> 'string' },
134 used
=> { type
=> 'string', optional
=> 1 },
135 gpt
=> { type
=> 'boolean' },
136 size
=> { type
=> 'integer' },
137 osdid
=> { type
=> 'integer' },
138 vendor
=> { type
=> 'string', optional
=> 1 },
139 model
=> { type
=> 'string', optional
=> 1 },
140 serial
=> { type
=> 'string', optional
=> 1 },
143 # links => [ { rel => 'child', href => "{}" } ],
148 PVE
::Ceph
::Tools
::check_ceph_inited
();
150 my $disks = PVE
::Diskmanage
::get_disks
(undef, 1);
153 foreach my $dev (keys %$disks) {
154 my $d = $disks->{$dev};
155 if ($param->{type
}) {
156 if ($param->{type
} eq 'journal_disks') {
157 next if $d->{osdid
} >= 0;
159 } elsif ($param->{type
} eq 'unused') {
162 die "internal error"; # should not happen
166 $d->{dev
} = "/dev/$dev";
173 __PACKAGE__-
>register_method ({
179 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
181 description
=> "Get Ceph configuration.",
183 additionalProperties
=> 0,
185 node
=> get_standard_option
('pve-node'),
188 returns
=> { type
=> 'string' },
192 PVE
::Ceph
::Tools
::check_ceph_inited
();
194 my $path = PVE
::Ceph
::Tools
::get_config
('pve_ceph_cfgpath');
195 return file_get_contents
($path);
199 __PACKAGE__-
>register_method ({
206 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
208 description
=> "Get Ceph configuration database.",
210 additionalProperties
=> 0,
212 node
=> get_standard_option
('pve-node'),
220 section
=> { type
=> "string", },
221 name
=> { type
=> "string", },
222 value
=> { type
=> "string", },
223 level
=> { type
=> "string", },
224 'can_update_at_runtime' => { type
=> "boolean", },
225 mask
=> { type
=> "string" },
232 PVE
::Ceph
::Tools
::check_ceph_inited
();
234 my $rados = PVE
::RADOS-
>new();
235 my $res = $rados->mon_command( { prefix
=> 'config dump', format
=> 'json' });
236 foreach my $entry (@$res) {
237 $entry->{can_update_at_runtime
} = $entry->{can_update_at_runtime
}?
1 : 0; # JSON::true/false -> 1/0
243 my $add_storage = sub {
244 my ($pool, $storeid) = @_;
246 my $storage_params = {
251 content
=> 'rootdir,images',
254 PVE
::API2
::Storage
::Config-
>create($storage_params);
257 my $get_storages = sub {
260 my $cfg = PVE
::Storage
::config
();
262 my $storages = $cfg->{ids
};
264 foreach my $storeid (keys %$storages) {
265 my $curr = $storages->{$storeid};
266 $res->{$storeid} = $storages->{$storeid}
267 if $curr->{type
} eq 'rbd' && $pool eq $curr->{pool
};
273 __PACKAGE__-
>register_method ({
277 description
=> "Create initial ceph default configuration and setup symlinks.",
281 check
=> ['perm', '/', [ 'Sys.Modify' ]],
284 additionalProperties
=> 0,
286 node
=> get_standard_option
('pve-node'),
288 description
=> "Use specific network for all ceph related traffic",
289 type
=> 'string', format
=> 'CIDR',
293 'cluster-network' => {
294 description
=> "Declare a separate cluster network, OSDs will route" .
295 "heartbeat, object replication and recovery traffic over it",
296 type
=> 'string', format
=> 'CIDR',
297 requires
=> 'network',
302 description
=> 'Targeted number of replicas per object',
310 description
=> 'Minimum number of available replicas per object to allow I/O',
318 description
=> "Placement group bits, used to specify the " .
319 "default number of placement groups.\n\nNOTE: 'osd pool " .
320 "default pg num' does not work for default pools.",
328 description
=> "Disable cephx authentication.\n\n" .
329 "WARNING: cephx is a security feature protecting against " .
330 "man-in-the-middle attacks. Only consider disabling cephx ".
331 "if your network is private!",
338 returns
=> { type
=> 'null' },
342 my $version = PVE
::Ceph
::Tools
::get_local_version
(1);
344 if (!$version || $version < 14) {
345 die "Ceph Nautilus required - please run 'pveceph install'\n";
347 PVE
::Ceph
::Tools
::check_ceph_installed
('ceph_bin');
350 my $auth = $param->{disable_cephx
} ?
'none' : 'cephx';
352 # simply load old config if it already exists
353 PVE
::Cluster
::cfs_lock_file
('ceph.conf', undef, sub {
354 my $cfg = cfs_read_file
('ceph.conf');
356 if (!$cfg->{global
}) {
361 UUID
::generate
($uuid);
362 UUID
::unparse
($uuid, $fsid);
366 'auth cluster required' => $auth,
367 'auth service required' => $auth,
368 'auth client required' => $auth,
369 'osd pool default size' => $param->{size
} // 3,
370 'osd pool default min size' => $param->{min_size
} // 2,
371 'mon allow pool delete' => 'true',
374 # this does not work for default pools
375 #'osd pool default pg num' => $pg_num,
376 #'osd pool default pgp num' => $pg_num,
379 if ($auth eq 'cephx') {
380 $cfg->{client
}->{keyring
} = '/etc/pve/priv/$cluster.$name.keyring';
383 if ($param->{pg_bits
}) {
384 $cfg->{global
}->{'osd pg bits'} = $param->{pg_bits
};
385 $cfg->{global
}->{'osd pgp bits'} = $param->{pg_bits
};
388 if ($param->{network
}) {
389 $cfg->{global
}->{'public network'} = $param->{network
};
390 $cfg->{global
}->{'cluster network'} = $param->{network
};
393 if ($param->{'cluster-network'}) {
394 $cfg->{global
}->{'cluster network'} = $param->{'cluster-network'};
397 cfs_write_file
('ceph.conf', $cfg);
399 if ($auth eq 'cephx') {
400 PVE
::Ceph
::Tools
::get_or_create_admin_keyring
();
402 PVE
::Ceph
::Tools
::setup_pve_symlinks
();
409 __PACKAGE__-
>register_method ({
413 description
=> "Stop ceph services.",
417 check
=> ['perm', '/', [ 'Sys.Modify' ]],
420 additionalProperties
=> 0,
422 node
=> get_standard_option
('pve-node'),
424 description
=> 'Ceph service name.',
427 default => 'ceph.target',
428 pattern
=> '(ceph|mon|mds|osd|mgr)(\.'.PVE
::Ceph
::Services
::SERVICE_REGEX
.')?',
432 returns
=> { type
=> 'string' },
436 my $rpcenv = PVE
::RPCEnvironment
::get
();
438 my $authuser = $rpcenv->get_user();
440 PVE
::Ceph
::Tools
::check_ceph_inited
();
442 my $cfg = cfs_read_file
('ceph.conf');
443 scalar(keys %$cfg) || die "no configuration\n";
449 if ($param->{service
}) {
450 push @$cmd, $param->{service
};
453 PVE
::Ceph
::Services
::ceph_service_cmd
(@$cmd);
456 return $rpcenv->fork_worker('srvstop', $param->{service
} || 'ceph',
460 __PACKAGE__-
>register_method ({
464 description
=> "Start ceph services.",
468 check
=> ['perm', '/', [ 'Sys.Modify' ]],
471 additionalProperties
=> 0,
473 node
=> get_standard_option
('pve-node'),
475 description
=> 'Ceph service name.',
478 default => 'ceph.target',
479 pattern
=> '(ceph|mon|mds|osd|mgr)(\.'.PVE
::Ceph
::Services
::SERVICE_REGEX
.')?',
483 returns
=> { type
=> 'string' },
487 my $rpcenv = PVE
::RPCEnvironment
::get
();
489 my $authuser = $rpcenv->get_user();
491 PVE
::Ceph
::Tools
::check_ceph_inited
();
493 my $cfg = cfs_read_file
('ceph.conf');
494 scalar(keys %$cfg) || die "no configuration\n";
500 if ($param->{service
}) {
501 push @$cmd, $param->{service
};
504 PVE
::Ceph
::Services
::ceph_service_cmd
(@$cmd);
507 return $rpcenv->fork_worker('srvstart', $param->{service
} || 'ceph',
511 __PACKAGE__-
>register_method ({
515 description
=> "Restart ceph services.",
519 check
=> ['perm', '/', [ 'Sys.Modify' ]],
522 additionalProperties
=> 0,
524 node
=> get_standard_option
('pve-node'),
526 description
=> 'Ceph service name.',
529 default => 'ceph.target',
530 pattern
=> '(mon|mds|osd|mgr)(\.'.PVE
::Ceph
::Services
::SERVICE_REGEX
.')?',
534 returns
=> { type
=> 'string' },
538 my $rpcenv = PVE
::RPCEnvironment
::get
();
540 my $authuser = $rpcenv->get_user();
542 PVE
::Ceph
::Tools
::check_ceph_inited
();
544 my $cfg = cfs_read_file
('ceph.conf');
545 scalar(keys %$cfg) || die "no configuration\n";
550 my $cmd = ['restart'];
551 if ($param->{service
}) {
552 push @$cmd, $param->{service
};
555 PVE
::Ceph
::Services
::ceph_service_cmd
(@$cmd);
558 return $rpcenv->fork_worker('srvrestart', $param->{service
} || 'ceph',
562 __PACKAGE__-
>register_method ({
566 description
=> "Get ceph status.",
570 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
573 additionalProperties
=> 0,
575 node
=> get_standard_option
('pve-node'),
578 returns
=> { type
=> 'object' },
582 PVE
::Ceph
::Tools
::check_ceph_inited
();
584 return PVE
::Ceph
::Tools
::ceph_cluster_status
();
587 __PACKAGE__-
>register_method ({
591 description
=> "List all pools.",
595 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
598 additionalProperties
=> 0,
600 node
=> get_standard_option
('pve-node'),
608 pool
=> { type
=> 'integer', title
=> 'ID' },
609 pool_name
=> { type
=> 'string', title
=> 'Name' },
610 size
=> { type
=> 'integer', title
=> 'Size' },
611 min_size
=> { type
=> 'integer', title
=> 'Min Size' },
612 pg_num
=> { type
=> 'integer', title
=> 'PG Num' },
613 pg_autoscale_mode
=> { type
=> 'string', optional
=> 1, title
=> 'PG Autoscale Mode' },
614 crush_rule
=> { type
=> 'integer', title
=> 'Crush Rule' },
615 crush_rule_name
=> { type
=> 'string', title
=> 'Crush Rule Name' },
616 percent_used
=> { type
=> 'number', title
=> '%-Used' },
617 bytes_used
=> { type
=> 'integer', title
=> 'Used' },
620 links
=> [ { rel
=> 'child', href
=> "{pool_name}" } ],
625 PVE
::Ceph
::Tools
::check_ceph_inited
();
627 my $rados = PVE
::RADOS-
>new();
630 my $res = $rados->mon_command({ prefix
=> 'df' });
632 foreach my $d (@{$res->{pools
}}) {
633 next if !$d->{stats
};
634 next if !defined($d->{id
});
635 $stats->{$d->{id
}} = $d->{stats
};
638 $res = $rados->mon_command({ prefix
=> 'osd dump' });
639 my $rulestmp = $rados->mon_command({ prefix
=> 'osd crush rule dump'});
642 for my $rule (@$rulestmp) {
643 $rules->{$rule->{rule_id
}} = $rule->{rule_name
};
657 foreach my $e (@{$res->{pools
}}) {
659 foreach my $attr (@$attr_list) {
660 $d->{$attr} = $e->{$attr} if defined($e->{$attr});
663 if (defined($d->{crush_rule
}) && defined($rules->{$d->{crush_rule
}})) {
664 $d->{crush_rule_name
} = $rules->{$d->{crush_rule
}};
667 if (my $s = $stats->{$d->{pool
}}) {
668 $d->{bytes_used
} = $s->{bytes_used
};
669 $d->{percent_used
} = $s->{percent_used
};
679 my $ceph_pool_common_options = sub {
680 my ($nodefault) = shift;
683 description
=> "The name of the pool. It must be unique.",
687 description
=> 'Number of replicas per object',
695 description
=> 'Minimum number of replicas per object',
703 description
=> "Number of placement groups.",
711 description
=> "The rule to use for mapping object placement in the cluster.",
716 description
=> "The application of the pool.",
719 enum
=> ['rbd', 'cephfs', 'rgw'],
722 pg_autoscale_mode
=> {
723 description
=> "The automatic PG scaling mode of the pool.",
725 enum
=> ['on', 'off', 'warn'],
732 delete $options->{$_}->{default} for keys %$options;
738 __PACKAGE__-
>register_method ({
739 name
=> 'createpool',
742 description
=> "Create POOL",
746 check
=> ['perm', '/', [ 'Sys.Modify' ]],
749 additionalProperties
=> 0,
751 node
=> get_standard_option
('pve-node'),
753 description
=> "Configure VM and CT storage using the new pool.",
757 %{ $ceph_pool_common_options->() },
760 returns
=> { type
=> 'string' },
764 PVE
::Cluster
::check_cfs_quorum
();
765 PVE
::Ceph
::Tools
::check_ceph_configured
();
767 my $pool = extract_param
($param, 'name');
768 my $node = extract_param
($param, 'node');
769 my $add_storages = extract_param
($param, 'add_storages');
771 my $rpcenv = PVE
::RPCEnvironment
::get
();
772 my $user = $rpcenv->get_user();
775 $rpcenv->check($user, '/storage', ['Datastore.Allocate']);
776 die "pool name contains characters which are illegal for storage naming\n"
777 if !PVE
::JSONSchema
::parse_storage_id
($pool);
781 $param->{pg_num
} //= 128;
782 $param->{size
} //= 3;
783 $param->{min_size
} //= 2;
784 $param->{application
} //= 'rbd';
785 $param->{pg_autoscale_mode
} //= 'warn';
789 PVE
::Ceph
::Tools
::create_pool
($pool, $param);
793 eval { $add_storage->($pool, "${pool}"); };
795 warn "failed to add storage: $@";
798 die "adding storage for pool '$pool' failed, check log and add manually!\n"
803 return $rpcenv->fork_worker('cephcreatepool', $pool, $user, $worker);
806 my $possible_flags = PVE
::Ceph
::Tools
::get_possible_osd_flags
();
807 my $possible_flags_list = [ sort keys %$possible_flags ];
809 # FIXME: Remove with PVE 7.0
810 __PACKAGE__-
>register_method ({
814 description
=> "get all set ceph flags",
818 check
=> ['perm', '/', [ 'Sys.Audit' ]],
821 additionalProperties
=> 0,
823 node
=> get_standard_option
('pve-node'),
826 returns
=> { type
=> 'string' },
830 PVE
::Ceph
::Tools
::check_ceph_configured
();
832 my $rados = PVE
::RADOS-
>new();
834 my $stat = $rados->mon_command({ prefix
=> 'osd dump' });
836 return $stat->{flags
} // '';
839 # FIXME: Remove with PVE 7.0
840 __PACKAGE__-
>register_method ({
842 path
=> 'flags/{flag}',
844 description
=> "Set a specific ceph flag",
848 check
=> ['perm', '/', [ 'Sys.Modify' ]],
851 additionalProperties
=> 0,
853 node
=> get_standard_option
('pve-node'),
855 description
=> 'The ceph flag to set',
857 enum
=> $possible_flags_list,
861 returns
=> { type
=> 'null' },
865 PVE
::Ceph
::Tools
::check_ceph_configured
();
867 my $rados = PVE
::RADOS-
>new();
869 $rados->mon_command({
871 key
=> $param->{flag
},
877 __PACKAGE__-
>register_method ({
878 name
=> 'unset_flag',
879 path
=> 'flags/{flag}',
881 description
=> "Unset a ceph flag",
885 check
=> ['perm', '/', [ 'Sys.Modify' ]],
888 additionalProperties
=> 0,
890 node
=> get_standard_option
('pve-node'),
892 description
=> 'The ceph flag to unset',
894 enum
=> $possible_flags_list,
898 returns
=> { type
=> 'null' },
902 PVE
::Ceph
::Tools
::check_ceph_configured
();
904 my $rados = PVE
::RADOS-
>new();
906 $rados->mon_command({
907 prefix
=> "osd unset",
908 key
=> $param->{flag
},
914 __PACKAGE__-
>register_method ({
915 name
=> 'destroypool',
916 path
=> 'pools/{name}',
918 description
=> "Destroy pool",
922 check
=> ['perm', '/', [ 'Sys.Modify' ]],
925 additionalProperties
=> 0,
927 node
=> get_standard_option
('pve-node'),
929 description
=> "The name of the pool. It must be unique.",
933 description
=> "If true, destroys pool even if in use",
939 description
=> "Remove all pveceph-managed storages configured for this pool",
946 returns
=> { type
=> 'string' },
950 PVE
::Ceph
::Tools
::check_ceph_inited
();
952 my $rpcenv = PVE
::RPCEnvironment
::get
();
953 my $user = $rpcenv->get_user();
954 $rpcenv->check($user, '/storage', ['Datastore.Allocate'])
955 if $param->{remove_storages
};
957 my $pool = $param->{name
};
960 my $storages = $get_storages->($pool);
962 # if not forced, destroy ceph pool only when no
963 # vm disks are on it anymore
964 if (!$param->{force
}) {
965 my $storagecfg = PVE
::Storage
::config
();
966 foreach my $storeid (keys %$storages) {
967 my $storage = $storages->{$storeid};
969 # check if any vm disks are on the pool
970 print "checking storage '$storeid' for RBD images..\n";
971 my $res = PVE
::Storage
::vdisk_list
($storagecfg, $storeid);
972 die "ceph pool '$pool' still in use by storage '$storeid'\n"
973 if @{$res->{$storeid}} != 0;
977 PVE
::Ceph
::Tools
::destroy_pool
($pool);
979 if ($param->{remove_storages
}) {
981 foreach my $storeid (keys %$storages) {
982 # skip external clusters, not managed by pveceph
983 next if $storages->{$storeid}->{monhost
};
984 eval { PVE
::API2
::Storage
::Config-
>delete({storage
=> $storeid}) };
986 warn "failed to remove storage '$storeid': $@\n";
990 die "failed to remove (some) storages - check log and remove manually!\n"
994 return $rpcenv->fork_worker('cephdestroypool', $pool, $user, $worker);
998 __PACKAGE__-
>register_method ({
1000 path
=> 'pools/{name}',
1002 description
=> "Change POOL settings",
1006 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1009 additionalProperties
=> 0,
1011 node
=> get_standard_option
('pve-node'),
1012 %{ $ceph_pool_common_options->('nodefault') },
1015 returns
=> { type
=> 'string' },
1019 PVE
::Ceph
::Tools
::check_ceph_configured
();
1021 my $rpcenv = PVE
::RPCEnvironment
::get
();
1022 my $authuser = $rpcenv->get_user();
1024 my $pool = $param->{name
};
1025 my $ceph_param = \
%$param;
1026 for my $item ('name', 'node') {
1027 # not ceph parameters
1028 delete $ceph_param->{$item};
1032 PVE
::Ceph
::Tools
::set_pool
($pool, $ceph_param);
1035 return $rpcenv->fork_worker('cephsetpool', $pool, $authuser, $worker);
1039 __PACKAGE__-
>register_method ({
1043 description
=> "Get OSD crush map",
1047 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
1050 additionalProperties
=> 0,
1052 node
=> get_standard_option
('pve-node'),
1055 returns
=> { type
=> 'string' },
1059 PVE
::Ceph
::Tools
::check_ceph_inited
();
1061 # this produces JSON (difficult to read for the user)
1062 # my $txt = &$run_ceph_cmd_text(['osd', 'crush', 'dump'], quiet => 1);
1066 my $mapfile = "/var/tmp/ceph-crush.map.$$";
1067 my $mapdata = "/var/tmp/ceph-crush.txt.$$";
1069 my $rados = PVE
::RADOS-
>new();
1072 my $bindata = $rados->mon_command({ prefix
=> 'osd getcrushmap', format
=> 'plain' });
1073 file_set_contents
($mapfile, $bindata);
1074 run_command
(['crushtool', '-d', $mapfile, '-o', $mapdata]);
1075 $txt = file_get_contents
($mapdata);
1087 __PACKAGE__-
>register_method({
1091 description
=> "Read ceph log",
1094 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
1098 additionalProperties
=> 0,
1100 node
=> get_standard_option
('pve-node'),
1119 description
=> "Line number",
1123 description
=> "Line text",
1132 PVE
::Ceph
::Tools
::check_ceph_inited
();
1134 my $rpcenv = PVE
::RPCEnvironment
::get
();
1135 my $user = $rpcenv->get_user();
1136 my $node = $param->{node
};
1138 my $logfile = "/var/log/ceph/ceph.log";
1139 my ($count, $lines) = PVE
::Tools
::dump_logfile
($logfile, $param->{start
}, $param->{limit
});
1141 $rpcenv->set_result_attrib('total', $count);
1146 __PACKAGE__-
>register_method ({
1150 description
=> "List ceph rules.",
1154 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
1157 additionalProperties
=> 0,
1159 node
=> get_standard_option
('pve-node'),
1168 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
1173 PVE
::Ceph
::Tools
::check_ceph_inited
();
1175 my $rados = PVE
::RADOS-
>new();
1177 my $rules = $rados->mon_command({ prefix
=> 'osd crush rule ls' });
1181 foreach my $rule (@$rules) {
1182 push @$res, { name
=> $rule };