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 my $add_storage = sub {
199 my ($pool, $storeid) = @_;
201 my $storage_params = {
206 content
=> 'rootdir,images',
209 PVE
::API2
::Storage
::Config-
>create($storage_params);
212 my $get_storages = sub {
215 my $cfg = PVE
::Storage
::config
();
217 my $storages = $cfg->{ids
};
219 foreach my $storeid (keys %$storages) {
220 my $curr = $storages->{$storeid};
221 $res->{$storeid} = $storages->{$storeid}
222 if $curr->{type
} eq 'rbd' && $pool eq $curr->{pool
};
228 __PACKAGE__-
>register_method ({
232 description
=> "Create initial ceph default configuration and setup symlinks.",
236 check
=> ['perm', '/', [ 'Sys.Modify' ]],
239 additionalProperties
=> 0,
241 node
=> get_standard_option
('pve-node'),
243 description
=> "Use specific network for all ceph related traffic",
244 type
=> 'string', format
=> 'CIDR',
248 'cluster-network' => {
249 description
=> "Declare a separate cluster network, OSDs will route" .
250 "heartbeat, object replication and recovery traffic over it",
251 type
=> 'string', format
=> 'CIDR',
252 requires
=> 'network',
257 description
=> 'Targeted number of replicas per object',
265 description
=> 'Minimum number of available replicas per object to allow I/O',
273 description
=> "Placement group bits, used to specify the " .
274 "default number of placement groups.\n\nNOTE: 'osd pool " .
275 "default pg num' does not work for default pools.",
283 description
=> "Disable cephx authentication.\n\n" .
284 "WARNING: cephx is a security feature protecting against " .
285 "man-in-the-middle attacks. Only consider disabling cephx ".
286 "if your network is private!",
293 returns
=> { type
=> 'null' },
297 my $version = PVE
::Ceph
::Tools
::get_local_version
(1);
299 if (!$version || $version < 14) {
300 die "Ceph Nautilus required - please run 'pveceph install'\n";
302 PVE
::Ceph
::Tools
::check_ceph_installed
('ceph_bin');
305 # simply load old config if it already exists
306 PVE
::Cluster
::cfs_lock_file
('ceph.conf', undef, sub {
307 my $cfg = cfs_read_file
('ceph.conf');
309 if (!$cfg->{global
}) {
314 UUID
::generate
($uuid);
315 UUID
::unparse
($uuid, $fsid);
317 my $auth = $param->{disable_cephx
} ?
'none' : 'cephx';
321 'auth cluster required' => $auth,
322 'auth service required' => $auth,
323 'auth client required' => $auth,
324 'osd pool default size' => $param->{size
} // 3,
325 'osd pool default min size' => $param->{min_size
} // 2,
326 'mon allow pool delete' => 'true',
329 # this does not work for default pools
330 #'osd pool default pg num' => $pg_num,
331 #'osd pool default pgp num' => $pg_num,
334 if ($auth eq 'cephx') {
335 $cfg->{client
}->{keyring
} = '/etc/pve/priv/$cluster.$name.keyring';
338 if ($param->{pg_bits
}) {
339 $cfg->{global
}->{'osd pg bits'} = $param->{pg_bits
};
340 $cfg->{global
}->{'osd pgp bits'} = $param->{pg_bits
};
343 if ($param->{network
}) {
344 $cfg->{global
}->{'public network'} = $param->{network
};
345 $cfg->{global
}->{'cluster network'} = $param->{network
};
348 if ($param->{'cluster-network'}) {
349 $cfg->{global
}->{'cluster network'} = $param->{'cluster-network'};
352 cfs_write_file
('ceph.conf', $cfg);
354 if ($auth eq 'cephx') {
355 PVE
::Ceph
::Tools
::get_or_create_admin_keyring
();
357 PVE
::Ceph
::Tools
::setup_pve_symlinks
();
363 __PACKAGE__-
>register_method ({
367 description
=> "Stop ceph services.",
371 check
=> ['perm', '/', [ 'Sys.Modify' ]],
374 additionalProperties
=> 0,
376 node
=> get_standard_option
('pve-node'),
378 description
=> 'Ceph service name.',
381 default => 'ceph.target',
382 pattern
=> '(ceph|mon|mds|osd|mgr)\.[A-Za-z0-9\-]{1,32}',
386 returns
=> { type
=> 'string' },
390 my $rpcenv = PVE
::RPCEnvironment
::get
();
392 my $authuser = $rpcenv->get_user();
394 PVE
::Ceph
::Tools
::check_ceph_inited
();
396 my $cfg = cfs_read_file
('ceph.conf');
397 scalar(keys %$cfg) || die "no configuration\n";
403 if ($param->{service
}) {
404 push @$cmd, $param->{service
};
407 PVE
::Ceph
::Services
::ceph_service_cmd
(@$cmd);
410 return $rpcenv->fork_worker('srvstop', $param->{service
} || 'ceph',
414 __PACKAGE__-
>register_method ({
418 description
=> "Start ceph services.",
422 check
=> ['perm', '/', [ 'Sys.Modify' ]],
425 additionalProperties
=> 0,
427 node
=> get_standard_option
('pve-node'),
429 description
=> 'Ceph service name.',
432 default => 'ceph.target',
433 pattern
=> '(ceph|mon|mds|osd|mgr)\.[A-Za-z0-9\-]{1,32}',
437 returns
=> { type
=> 'string' },
441 my $rpcenv = PVE
::RPCEnvironment
::get
();
443 my $authuser = $rpcenv->get_user();
445 PVE
::Ceph
::Tools
::check_ceph_inited
();
447 my $cfg = cfs_read_file
('ceph.conf');
448 scalar(keys %$cfg) || die "no configuration\n";
454 if ($param->{service
}) {
455 push @$cmd, $param->{service
};
458 PVE
::Ceph
::Services
::ceph_service_cmd
(@$cmd);
461 return $rpcenv->fork_worker('srvstart', $param->{service
} || 'ceph',
465 __PACKAGE__-
>register_method ({
469 description
=> "Restart ceph services.",
473 check
=> ['perm', '/', [ 'Sys.Modify' ]],
476 additionalProperties
=> 0,
478 node
=> get_standard_option
('pve-node'),
480 description
=> 'Ceph service name.',
483 default => 'ceph.target',
484 pattern
=> '(mon|mds|osd|mgr)\.[A-Za-z0-9\-]{1,32}',
488 returns
=> { type
=> 'string' },
492 my $rpcenv = PVE
::RPCEnvironment
::get
();
494 my $authuser = $rpcenv->get_user();
496 PVE
::Ceph
::Tools
::check_ceph_inited
();
498 my $cfg = cfs_read_file
('ceph.conf');
499 scalar(keys %$cfg) || die "no configuration\n";
504 my $cmd = ['restart'];
505 if ($param->{service
}) {
506 push @$cmd, $param->{service
};
509 PVE
::Ceph
::Services
::ceph_service_cmd
(@$cmd);
512 return $rpcenv->fork_worker('srvrestart', $param->{service
} || 'ceph',
516 __PACKAGE__-
>register_method ({
520 description
=> "Get ceph status.",
524 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
527 additionalProperties
=> 0,
529 node
=> get_standard_option
('pve-node'),
532 returns
=> { type
=> 'object' },
536 PVE
::Ceph
::Tools
::check_ceph_inited
();
538 my $rados = PVE
::RADOS-
>new();
539 my $status = $rados->mon_command({ prefix
=> 'status' });
540 $status->{health
} = $rados->mon_command({ prefix
=> 'health', detail
=> 'detail' });
544 __PACKAGE__-
>register_method ({
548 description
=> "List all pools.",
552 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
555 additionalProperties
=> 0,
557 node
=> get_standard_option
('pve-node'),
565 pool
=> { type
=> 'integer' },
566 pool_name
=> { type
=> 'string' },
567 size
=> { type
=> 'integer' },
570 links
=> [ { rel
=> 'child', href
=> "{pool_name}" } ],
575 PVE
::Ceph
::Tools
::check_ceph_inited
();
577 my $rados = PVE
::RADOS-
>new();
580 my $res = $rados->mon_command({ prefix
=> 'df' });
582 foreach my $d (@{$res->{pools
}}) {
583 next if !$d->{stats
};
584 next if !defined($d->{id
});
585 $stats->{$d->{id
}} = $d->{stats
};
588 $res = $rados->mon_command({ prefix
=> 'osd dump' });
589 my $rulestmp = $rados->mon_command({ prefix
=> 'osd crush rule dump'});
592 for my $rule (@$rulestmp) {
593 $rules->{$rule->{rule_id
}} = $rule->{rule_name
};
597 foreach my $e (@{$res->{pools
}}) {
599 foreach my $attr (qw(pool pool_name size min_size pg_num crush_rule)) {
600 $d->{$attr} = $e->{$attr} if defined($e->{$attr});
603 if (defined($d->{crush_rule
}) && defined($rules->{$d->{crush_rule
}})) {
604 $d->{crush_rule_name
} = $rules->{$d->{crush_rule
}};
607 if (my $s = $stats->{$d->{pool
}}) {
608 $d->{bytes_used
} = $s->{bytes_used
};
609 $d->{percent_used
} = $s->{percent_used
};
618 __PACKAGE__-
>register_method ({
619 name
=> 'createpool',
622 description
=> "Create POOL",
626 check
=> ['perm', '/', [ 'Sys.Modify' ]],
629 additionalProperties
=> 0,
631 node
=> get_standard_option
('pve-node'),
633 description
=> "The name of the pool. It must be unique.",
637 description
=> 'Number of replicas per object',
645 description
=> 'Minimum number of replicas per object',
653 description
=> "Number of placement groups.",
661 description
=> "The rule to use for mapping object placement in the cluster.",
666 description
=> "The application of the pool, 'rbd' by default.",
668 enum
=> ['rbd', 'cephfs', 'rgw'],
672 description
=> "Configure VM and CT storage using the new pool.",
678 returns
=> { type
=> 'string' },
682 PVE
::Cluster
::check_cfs_quorum
();
683 PVE
::Ceph
::Tools
::check_ceph_inited
();
685 my $pve_ckeyring_path = PVE
::Ceph
::Tools
::get_config
('pve_ckeyring_path');
687 die "not fully configured - missing '$pve_ckeyring_path'\n"
688 if ! -f
$pve_ckeyring_path;
690 my $pool = $param->{name
};
691 my $rpcenv = PVE
::RPCEnvironment
::get
();
692 my $user = $rpcenv->get_user();
694 if ($param->{add_storages
}) {
695 $rpcenv->check($user, '/storage', ['Datastore.Allocate']);
696 die "pool name contains characters which are illegal for storage naming\n"
697 if !PVE
::JSONSchema
::parse_storage_id
($pool);
700 my $pg_num = $param->{pg_num
} || 128;
701 my $size = $param->{size
} || 3;
702 my $min_size = $param->{min_size
} || 2;
703 my $application = $param->{application
} // 'rbd';
707 PVE
::Ceph
::Tools
::create_pool
($pool, $param);
709 if ($param->{add_storages
}) {
711 eval { $add_storage->($pool, "${pool}"); };
713 warn "failed to add storage: $@";
716 die "adding storage for pool '$pool' failed, check log and add manually!\n"
721 return $rpcenv->fork_worker('cephcreatepool', $pool, $user, $worker);
724 __PACKAGE__-
>register_method ({
728 description
=> "get all set ceph flags",
732 check
=> ['perm', '/', [ 'Sys.Audit' ]],
735 additionalProperties
=> 0,
737 node
=> get_standard_option
('pve-node'),
740 returns
=> { type
=> 'string' },
744 PVE
::Ceph
::Tools
::check_ceph_inited
();
746 my $pve_ckeyring_path = PVE
::Ceph
::Tools
::get_config
('pve_ckeyring_path');
748 die "not fully configured - missing '$pve_ckeyring_path'\n"
749 if ! -f
$pve_ckeyring_path;
751 my $rados = PVE
::RADOS-
>new();
753 my $stat = $rados->mon_command({ prefix
=> 'osd dump' });
755 return $stat->{flags
} // '';
758 __PACKAGE__-
>register_method ({
760 path
=> 'flags/{flag}',
762 description
=> "Set a ceph flag",
766 check
=> ['perm', '/', [ 'Sys.Modify' ]],
769 additionalProperties
=> 0,
771 node
=> get_standard_option
('pve-node'),
773 description
=> 'The ceph flag to set/unset',
775 enum
=> [ 'full', 'pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill', 'norebalance', 'norecover', 'noscrub', 'nodeep-scrub', 'notieragent'],
779 returns
=> { type
=> 'null' },
783 PVE
::Ceph
::Tools
::check_ceph_inited
();
785 my $pve_ckeyring_path = PVE
::Ceph
::Tools
::get_config
('pve_ckeyring_path');
787 die "not fully configured - missing '$pve_ckeyring_path'\n"
788 if ! -f
$pve_ckeyring_path;
790 my $set = $param->{set
} // !$param->{unset
};
791 my $rados = PVE
::RADOS-
>new();
793 $rados->mon_command({
795 key
=> $param->{flag
},
801 __PACKAGE__-
>register_method ({
802 name
=> 'unset_flag',
803 path
=> 'flags/{flag}',
805 description
=> "Unset a ceph flag",
809 check
=> ['perm', '/', [ 'Sys.Modify' ]],
812 additionalProperties
=> 0,
814 node
=> get_standard_option
('pve-node'),
816 description
=> 'The ceph flag to set/unset',
818 enum
=> [ 'full', 'pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill', 'norebalance', 'norecover', 'noscrub', 'nodeep-scrub', 'notieragent'],
822 returns
=> { type
=> 'null' },
826 PVE
::Ceph
::Tools
::check_ceph_inited
();
828 my $pve_ckeyring_path = PVE
::Ceph
::Tools
::get_config
('pve_ckeyring_path');
830 die "not fully configured - missing '$pve_ckeyring_path'\n"
831 if ! -f
$pve_ckeyring_path;
833 my $set = $param->{set
} // !$param->{unset
};
834 my $rados = PVE
::RADOS-
>new();
836 $rados->mon_command({
837 prefix
=> "osd unset",
838 key
=> $param->{flag
},
844 __PACKAGE__-
>register_method ({
845 name
=> 'destroypool',
846 path
=> 'pools/{name}',
848 description
=> "Destroy pool",
852 check
=> ['perm', '/', [ 'Sys.Modify' ]],
855 additionalProperties
=> 0,
857 node
=> get_standard_option
('pve-node'),
859 description
=> "The name of the pool. It must be unique.",
863 description
=> "If true, destroys pool even if in use",
869 description
=> "Remove all pveceph-managed storages configured for this pool",
876 returns
=> { type
=> 'string' },
880 PVE
::Ceph
::Tools
::check_ceph_inited
();
882 my $rpcenv = PVE
::RPCEnvironment
::get
();
883 my $user = $rpcenv->get_user();
884 $rpcenv->check($user, '/storage', ['Datastore.Allocate'])
885 if $param->{remove_storages
};
887 my $pool = $param->{name
};
890 my $storages = $get_storages->($pool);
892 # if not forced, destroy ceph pool only when no
893 # vm disks are on it anymore
894 if (!$param->{force
}) {
895 my $storagecfg = PVE
::Storage
::config
();
896 foreach my $storeid (keys %$storages) {
897 my $storage = $storages->{$storeid};
899 # check if any vm disks are on the pool
900 print "checking storage '$storeid' for RBD images..\n";
901 my $res = PVE
::Storage
::vdisk_list
($storagecfg, $storeid);
902 die "ceph pool '$pool' still in use by storage '$storeid'\n"
903 if @{$res->{$storeid}} != 0;
907 PVE
::Ceph
::Tools
::destroy_pool
($pool);
909 if ($param->{remove_storages
}) {
911 foreach my $storeid (keys %$storages) {
912 # skip external clusters, not managed by pveceph
913 next if $storages->{$storeid}->{monhost
};
914 eval { PVE
::API2
::Storage
::Config-
>delete({storage
=> $storeid}) };
916 warn "failed to remove storage '$storeid': $@\n";
920 die "failed to remove (some) storages - check log and remove manually!\n"
924 return $rpcenv->fork_worker('cephdestroypool', $pool, $user, $worker);
928 __PACKAGE__-
>register_method ({
932 description
=> "Get OSD crush map",
936 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
939 additionalProperties
=> 0,
941 node
=> get_standard_option
('pve-node'),
944 returns
=> { type
=> 'string' },
948 PVE
::Ceph
::Tools
::check_ceph_inited
();
950 # this produces JSON (difficult to read for the user)
951 # my $txt = &$run_ceph_cmd_text(['osd', 'crush', 'dump'], quiet => 1);
955 my $mapfile = "/var/tmp/ceph-crush.map.$$";
956 my $mapdata = "/var/tmp/ceph-crush.txt.$$";
958 my $rados = PVE
::RADOS-
>new();
961 my $bindata = $rados->mon_command({ prefix
=> 'osd getcrushmap', format
=> 'plain' });
962 file_set_contents
($mapfile, $bindata);
963 run_command
(['crushtool', '-d', $mapfile, '-o', $mapdata]);
964 $txt = file_get_contents
($mapdata);
976 __PACKAGE__-
>register_method({
980 description
=> "Read ceph log",
983 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
987 additionalProperties
=> 0,
989 node
=> get_standard_option
('pve-node'),
1008 description
=> "Line number",
1012 description
=> "Line text",
1021 PVE
::Ceph
::Tools
::check_ceph_inited
();
1023 my $rpcenv = PVE
::RPCEnvironment
::get
();
1024 my $user = $rpcenv->get_user();
1025 my $node = $param->{node
};
1027 my $logfile = "/var/log/ceph/ceph.log";
1028 my ($count, $lines) = PVE
::Tools
::dump_logfile
($logfile, $param->{start
}, $param->{limit
});
1030 $rpcenv->set_result_attrib('total', $count);
1035 __PACKAGE__-
>register_method ({
1039 description
=> "List ceph rules.",
1043 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
1046 additionalProperties
=> 0,
1048 node
=> get_standard_option
('pve-node'),
1057 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
1062 PVE
::Ceph
::Tools
::check_ceph_inited
();
1064 my $rados = PVE
::RADOS-
>new();
1066 my $rules = $rados->mon_command({ prefix
=> 'osd crush rule ls' });
1070 foreach my $rule (@$rules) {
1071 push @$res, { name
=> $rule };