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' },
98 { name
=> 'flags' }, # FIXME: remove with 7.0
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)(\.'.PVE
::Ceph
::Services
::SERVICE_REGEX
.')?',
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)(\.'.PVE
::Ceph
::Services
::SERVICE_REGEX
.')?',
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)(\.'.PVE
::Ceph
::Services
::SERVICE_REGEX
.')?',
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 return PVE
::Ceph
::Tools
::ceph_cluster_status
();
586 __PACKAGE__-
>register_method ({
590 description
=> "List all pools.",
594 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
597 additionalProperties
=> 0,
599 node
=> get_standard_option
('pve-node'),
607 pool
=> { type
=> 'integer' },
608 pool_name
=> { type
=> 'string' },
609 size
=> { type
=> 'integer' },
612 links
=> [ { rel
=> 'child', href
=> "{pool_name}" } ],
617 PVE
::Ceph
::Tools
::check_ceph_inited
();
619 my $rados = PVE
::RADOS-
>new();
622 my $res = $rados->mon_command({ prefix
=> 'df' });
624 foreach my $d (@{$res->{pools
}}) {
625 next if !$d->{stats
};
626 next if !defined($d->{id
});
627 $stats->{$d->{id
}} = $d->{stats
};
630 $res = $rados->mon_command({ prefix
=> 'osd dump' });
631 my $rulestmp = $rados->mon_command({ prefix
=> 'osd crush rule dump'});
634 for my $rule (@$rulestmp) {
635 $rules->{$rule->{rule_id
}} = $rule->{rule_name
};
639 foreach my $e (@{$res->{pools
}}) {
641 foreach my $attr (qw(pool pool_name size min_size pg_num crush_rule)) {
642 $d->{$attr} = $e->{$attr} if defined($e->{$attr});
645 if (defined($d->{crush_rule
}) && defined($rules->{$d->{crush_rule
}})) {
646 $d->{crush_rule_name
} = $rules->{$d->{crush_rule
}};
649 if (my $s = $stats->{$d->{pool
}}) {
650 $d->{bytes_used
} = $s->{bytes_used
};
651 $d->{percent_used
} = $s->{percent_used
};
660 __PACKAGE__-
>register_method ({
661 name
=> 'createpool',
664 description
=> "Create POOL",
668 check
=> ['perm', '/', [ 'Sys.Modify' ]],
671 additionalProperties
=> 0,
673 node
=> get_standard_option
('pve-node'),
675 description
=> "The name of the pool. It must be unique.",
679 description
=> 'Number of replicas per object',
687 description
=> 'Minimum number of replicas per object',
695 description
=> "Number of placement groups.",
703 description
=> "The rule to use for mapping object placement in the cluster.",
708 description
=> "The application of the pool, 'rbd' by default.",
710 enum
=> ['rbd', 'cephfs', 'rgw'],
714 description
=> "Configure VM and CT storage using the new pool.",
720 returns
=> { type
=> 'string' },
724 PVE
::Cluster
::check_cfs_quorum
();
725 PVE
::Ceph
::Tools
::check_ceph_configured
();
727 my $pool = $param->{name
};
728 my $rpcenv = PVE
::RPCEnvironment
::get
();
729 my $user = $rpcenv->get_user();
731 if ($param->{add_storages
}) {
732 $rpcenv->check($user, '/storage', ['Datastore.Allocate']);
733 die "pool name contains characters which are illegal for storage naming\n"
734 if !PVE
::JSONSchema
::parse_storage_id
($pool);
737 my $pg_num = $param->{pg_num
} || 128;
738 my $size = $param->{size
} || 3;
739 my $min_size = $param->{min_size
} || 2;
740 my $application = $param->{application
} // 'rbd';
744 PVE
::Ceph
::Tools
::create_pool
($pool, $param);
746 if ($param->{add_storages
}) {
748 eval { $add_storage->($pool, "${pool}"); };
750 warn "failed to add storage: $@";
753 die "adding storage for pool '$pool' failed, check log and add manually!\n"
758 return $rpcenv->fork_worker('cephcreatepool', $pool, $user, $worker);
761 my $possible_flags = PVE
::Ceph
::Tools
::get_possible_osd_flags
();
762 my $possible_flags_list = [ sort keys %$possible_flags ];
764 # FIXME: Remove with PVE 7.0
765 __PACKAGE__-
>register_method ({
769 description
=> "get all set ceph flags",
773 check
=> ['perm', '/', [ 'Sys.Audit' ]],
776 additionalProperties
=> 0,
778 node
=> get_standard_option
('pve-node'),
781 returns
=> { type
=> 'string' },
785 PVE
::Ceph
::Tools
::check_ceph_configured
();
787 my $rados = PVE
::RADOS-
>new();
789 my $stat = $rados->mon_command({ prefix
=> 'osd dump' });
791 return $stat->{flags
} // '';
794 # FIXME: Remove with PVE 7.0
795 __PACKAGE__-
>register_method ({
797 path
=> 'flags/{flag}',
799 description
=> "Set a specific ceph flag",
803 check
=> ['perm', '/', [ 'Sys.Modify' ]],
806 additionalProperties
=> 0,
808 node
=> get_standard_option
('pve-node'),
810 description
=> 'The ceph flag to set',
812 enum
=> $possible_flags_list,
816 returns
=> { type
=> 'null' },
820 PVE
::Ceph
::Tools
::check_ceph_configured
();
822 my $rados = PVE
::RADOS-
>new();
824 $rados->mon_command({
826 key
=> $param->{flag
},
832 __PACKAGE__-
>register_method ({
833 name
=> 'unset_flag',
834 path
=> 'flags/{flag}',
836 description
=> "Unset a ceph flag",
840 check
=> ['perm', '/', [ 'Sys.Modify' ]],
843 additionalProperties
=> 0,
845 node
=> get_standard_option
('pve-node'),
847 description
=> 'The ceph flag to unset',
849 enum
=> $possible_flags_list,
853 returns
=> { type
=> 'null' },
857 PVE
::Ceph
::Tools
::check_ceph_configured
();
859 my $rados = PVE
::RADOS-
>new();
861 $rados->mon_command({
862 prefix
=> "osd unset",
863 key
=> $param->{flag
},
869 __PACKAGE__-
>register_method ({
870 name
=> 'destroypool',
871 path
=> 'pools/{name}',
873 description
=> "Destroy pool",
877 check
=> ['perm', '/', [ 'Sys.Modify' ]],
880 additionalProperties
=> 0,
882 node
=> get_standard_option
('pve-node'),
884 description
=> "The name of the pool. It must be unique.",
888 description
=> "If true, destroys pool even if in use",
894 description
=> "Remove all pveceph-managed storages configured for this pool",
901 returns
=> { type
=> 'string' },
905 PVE
::Ceph
::Tools
::check_ceph_inited
();
907 my $rpcenv = PVE
::RPCEnvironment
::get
();
908 my $user = $rpcenv->get_user();
909 $rpcenv->check($user, '/storage', ['Datastore.Allocate'])
910 if $param->{remove_storages
};
912 my $pool = $param->{name
};
915 my $storages = $get_storages->($pool);
917 # if not forced, destroy ceph pool only when no
918 # vm disks are on it anymore
919 if (!$param->{force
}) {
920 my $storagecfg = PVE
::Storage
::config
();
921 foreach my $storeid (keys %$storages) {
922 my $storage = $storages->{$storeid};
924 # check if any vm disks are on the pool
925 print "checking storage '$storeid' for RBD images..\n";
926 my $res = PVE
::Storage
::vdisk_list
($storagecfg, $storeid);
927 die "ceph pool '$pool' still in use by storage '$storeid'\n"
928 if @{$res->{$storeid}} != 0;
932 PVE
::Ceph
::Tools
::destroy_pool
($pool);
934 if ($param->{remove_storages
}) {
936 foreach my $storeid (keys %$storages) {
937 # skip external clusters, not managed by pveceph
938 next if $storages->{$storeid}->{monhost
};
939 eval { PVE
::API2
::Storage
::Config-
>delete({storage
=> $storeid}) };
941 warn "failed to remove storage '$storeid': $@\n";
945 die "failed to remove (some) storages - check log and remove manually!\n"
949 return $rpcenv->fork_worker('cephdestroypool', $pool, $user, $worker);
953 __PACKAGE__-
>register_method ({
957 description
=> "Get OSD crush map",
961 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
964 additionalProperties
=> 0,
966 node
=> get_standard_option
('pve-node'),
969 returns
=> { type
=> 'string' },
973 PVE
::Ceph
::Tools
::check_ceph_inited
();
975 # this produces JSON (difficult to read for the user)
976 # my $txt = &$run_ceph_cmd_text(['osd', 'crush', 'dump'], quiet => 1);
980 my $mapfile = "/var/tmp/ceph-crush.map.$$";
981 my $mapdata = "/var/tmp/ceph-crush.txt.$$";
983 my $rados = PVE
::RADOS-
>new();
986 my $bindata = $rados->mon_command({ prefix
=> 'osd getcrushmap', format
=> 'plain' });
987 file_set_contents
($mapfile, $bindata);
988 run_command
(['crushtool', '-d', $mapfile, '-o', $mapdata]);
989 $txt = file_get_contents
($mapdata);
1001 __PACKAGE__-
>register_method({
1005 description
=> "Read ceph log",
1008 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
1012 additionalProperties
=> 0,
1014 node
=> get_standard_option
('pve-node'),
1033 description
=> "Line number",
1037 description
=> "Line text",
1046 PVE
::Ceph
::Tools
::check_ceph_inited
();
1048 my $rpcenv = PVE
::RPCEnvironment
::get
();
1049 my $user = $rpcenv->get_user();
1050 my $node = $param->{node
};
1052 my $logfile = "/var/log/ceph/ceph.log";
1053 my ($count, $lines) = PVE
::Tools
::dump_logfile
($logfile, $param->{start
}, $param->{limit
});
1055 $rpcenv->set_result_attrib('total', $count);
1060 __PACKAGE__-
>register_method ({
1064 description
=> "List ceph rules.",
1068 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
1071 additionalProperties
=> 0,
1073 node
=> get_standard_option
('pve-node'),
1082 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
1087 PVE
::Ceph
::Tools
::check_ceph_inited
();
1089 my $rados = PVE
::RADOS-
>new();
1091 my $rules = $rados->mon_command({ prefix
=> 'osd crush rule ls' });
1095 foreach my $rule (@$rules) {
1096 push @$res, { name
=> $rule };