mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-08-03 11:36:47 +00:00
Merge remote-tracking branch 'origin/pve-ceph'
This commit is contained in:
commit
9dace87245
1281
PVE/API2/Ceph.pm
Normal file
1281
PVE/API2/Ceph.pm
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
||||
include ../../defines.mk
|
||||
|
||||
PERLSOURCE = \
|
||||
Ceph.pm \
|
||||
APT.pm \
|
||||
Subscription.pm \
|
||||
VZDump.pm \
|
||||
|
@ -32,6 +32,7 @@ use PVE::API2::Qemu;
|
||||
use PVE::API2::OpenVZ;
|
||||
use PVE::API2::VZDump;
|
||||
use PVE::API2::APT;
|
||||
use PVE::API2::Ceph;
|
||||
use JSON;
|
||||
|
||||
use base qw(PVE::RESTHandler);
|
||||
@ -41,6 +42,11 @@ __PACKAGE__->register_method ({
|
||||
path => 'qemu',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
subclass => "PVE::API2::Ceph",
|
||||
path => 'ceph',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
subclass => "PVE::API2::OpenVZ",
|
||||
path => 'openvz',
|
||||
@ -110,6 +116,7 @@ __PACKAGE__->register_method ({
|
||||
my ($param) = @_;
|
||||
|
||||
my $result = [
|
||||
{ name => 'ceph' },
|
||||
{ name => 'apt' },
|
||||
{ name => 'version' },
|
||||
{ name => 'syslog' },
|
||||
|
@ -3,6 +3,7 @@ include ../defines.mk
|
||||
SUBDIRS = init.d cron ocf test
|
||||
|
||||
SCRIPTS = \
|
||||
pveceph \
|
||||
vzdump \
|
||||
vzrestore \
|
||||
pvestatd \
|
||||
@ -20,6 +21,7 @@ SCRIPTS = \
|
||||
pveperf
|
||||
|
||||
MANS = \
|
||||
pveceph.1 \
|
||||
pvectl.1 \
|
||||
vzdump.1 \
|
||||
vzrestore.1 \
|
||||
@ -44,6 +46,9 @@ all: ${MANS} pvemailforward
|
||||
pvectl.1.pod: pvectl
|
||||
perl -I.. ./pvectl printmanpod >$@
|
||||
|
||||
pveceph.1.pod: pveceph
|
||||
perl -I.. ./pveceph printmanpod >$@
|
||||
|
||||
vzdump.1.pod: vzdump
|
||||
perl -I.. -T ./vzdump printmanpod >$@
|
||||
|
||||
|
162
bin/pveceph
Executable file
162
bin/pveceph
Executable file
@ -0,0 +1,162 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Getopt::Long;
|
||||
use Fcntl ':flock';
|
||||
use File::Path;
|
||||
use IO::File;
|
||||
use JSON;
|
||||
use Data::Dumper;
|
||||
|
||||
use PVE::SafeSyslog;
|
||||
use PVE::Cluster;
|
||||
use PVE::INotify;
|
||||
use PVE::RPCEnvironment;
|
||||
use PVE::Storage;
|
||||
use PVE::Tools qw(run_command);
|
||||
use PVE::JSONSchema qw(get_standard_option);
|
||||
use PVE::API2::Ceph;
|
||||
|
||||
use PVE::CLIHandler;
|
||||
|
||||
use base qw(PVE::CLIHandler);
|
||||
|
||||
$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
|
||||
|
||||
initlog ('pveceph');
|
||||
|
||||
die "please run as root\n" if $> != 0;
|
||||
|
||||
PVE::INotify::inotify_init();
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment->init('cli');
|
||||
|
||||
$rpcenv->init_request();
|
||||
$rpcenv->set_language($ENV{LANG});
|
||||
$rpcenv->set_user('root@pam');
|
||||
|
||||
my $upid_exit = sub {
|
||||
my $upid = shift;
|
||||
my $status = PVE::Tools::upid_read_status($upid);
|
||||
exit($status eq 'OK' ? 0 : -1);
|
||||
};
|
||||
|
||||
my $nodename = PVE::INotify::nodename();
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'purge',
|
||||
path => 'purge',
|
||||
method => 'POST',
|
||||
description => "Destroy ceph related data and configuration files.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $monstat;
|
||||
|
||||
eval { $monstat = PVE::API2::Ceph::ceph_mon_status(1); };
|
||||
my $err = $@;
|
||||
|
||||
die "detected running ceph services- unable to purge data\n"
|
||||
if !$err;
|
||||
|
||||
# fixme: this is dangerous - should we really support this function?
|
||||
PVE::API2::Ceph::purge_all_ceph_files();
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'install',
|
||||
path => 'install',
|
||||
method => 'POST',
|
||||
description => "Install ceph related packages.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
my $cephver = 'emperor';
|
||||
|
||||
local $ENV{DEBIAN_FRONTEND} = 'noninteractive';
|
||||
|
||||
my $keyurl = "https://ceph.com/git/?p=ceph.git;a=blob_plain;f=keys/release.asc";
|
||||
|
||||
print "download and import ceph reqpository keys\n";
|
||||
system("wget -q -O- '$keyurl'| apt-key add - 2>&1 >/dev/null") == 0 ||
|
||||
die "unable to download ceph release key\n";
|
||||
|
||||
|
||||
my $source = "deb http://ceph.com/debian-$cephver wheezy main\n";
|
||||
|
||||
PVE::Tools::file_set_contents("/etc/apt/sources.list.d/ceph.list", $source);
|
||||
|
||||
print "update available package list\n";
|
||||
eval { run_command(['apt-get', '-q', 'update'], outfunc => sub {}, errfunc => sub {}); };
|
||||
|
||||
run_command(['apt-get', '-q', '--assume-yes', '--no-install-recommends',
|
||||
'-o', 'Dpkg::Options::=--force-confnew',
|
||||
'install', '--',
|
||||
'ceph', 'ceph-common', 'gdisk']);
|
||||
|
||||
return undef;
|
||||
}});
|
||||
|
||||
my $cmddef = {
|
||||
init => [ 'PVE::API2::Ceph', 'init', [], { node => $nodename } ],
|
||||
lspools => [ 'PVE::API2::Ceph', 'lspools', [], { node => $nodename }, sub {
|
||||
my $res = shift;
|
||||
|
||||
printf("%-20s %10s %10s\n", "Name", "size", "pg_num");
|
||||
foreach my $p (sort {$a->{pool_name} cmp $b->{pool_name}} @$res) {
|
||||
printf("%-20s %10d %10d\n", $p->{pool_name}, $p->{size}, $p->{pg_num});
|
||||
}
|
||||
}],
|
||||
createpool => [ 'PVE::API2::Ceph', 'createpool', ['name'], { node => $nodename }],
|
||||
destroypool => [ 'PVE::API2::Ceph', 'destroypool', ['name'], { node => $nodename } ],
|
||||
createosd => [ 'PVE::API2::Ceph', 'createosd', ['dev'], { node => $nodename }, $upid_exit],
|
||||
destroyosd => [ 'PVE::API2::Ceph', 'destroyosd', ['osdid'], { node => $nodename }, $upid_exit],
|
||||
createmon => [ 'PVE::API2::Ceph', 'createmon', [], { node => $nodename }, $upid_exit],
|
||||
destroymon => [ 'PVE::API2::Ceph', 'destroymon', ['monid'], { node => $nodename }, $upid_exit],
|
||||
start => [ 'PVE::API2::Ceph', 'start', ['service'], { node => $nodename }, $upid_exit],
|
||||
stop => [ 'PVE::API2::Ceph', 'stop', ['service'], { node => $nodename }, $upid_exit],
|
||||
install => [ __PACKAGE__, 'install', [] ],
|
||||
purge => [ __PACKAGE__, 'purge', [] ],
|
||||
status => [ 'PVE::API2::Ceph', 'status', [], { node => $nodename }, sub {
|
||||
my $res = shift;
|
||||
my $json = JSON->new->allow_nonref;
|
||||
print $json->pretty->encode($res) . "\n";
|
||||
}],
|
||||
};
|
||||
|
||||
my $cmd = shift;
|
||||
|
||||
PVE::CLIHandler::handle_cmd($cmddef, "pveceph", $cmd, \@ARGV, undef, $0);
|
||||
|
||||
exit 0;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
pveceph - tool to manage ceph services on pve nodes
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
=include synopsis
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Tool to manage ceph services on pve nodes.
|
||||
|
||||
=include pve_copyright
|
2
debian/control.in
vendored
2
debian/control.in
vendored
@ -3,7 +3,7 @@ Version: @VERSION@-@PACKAGERELEASE@
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
Depends: perl (>= 5.10.0-19), libtimedate-perl, libauthen-pam-perl, libintl-perl, rsync, libjson-perl, liblockfile-simple-perl, vncterm, qemu-server (>= 1.1-1), libwww-perl (>= 6.04-1), libnet-http-perl (>= 6.06-1), libhttp-daemon-perl, wget, libnet-dns-perl, vlan, ifenslave-2.6 (>= 1.1.0-10), liblinux-inotify2-perl, debconf (>= 0.5) | debconf-2.0, netcat-traditional, pve-cluster (>= 1.0-29), libpve-common-perl, libpve-storage-perl, libterm-readline-gnu-perl, libpve-access-control (>= 3.0-2), libio-socket-ssl-perl, libfilesys-df-perl, libfile-readbackwards-perl, libfile-sync-perl, redhat-cluster-pve, resource-agents-pve, fence-agents-pve, cstream, postfix | mail-transport-agent, libxml-parser-perl, lzop, dtach, libanyevent-perl, liburi-perl, logrotate, libanyevent-http-perl, apt-transport-https, libapt-pkg-perl, libcrypt-ssleay-perl, liblwp-protocol-https-perl, spiceterm
|
||||
Depends: perl (>= 5.10.0-19), libtimedate-perl, libauthen-pam-perl, libintl-perl, rsync, libjson-perl, liblockfile-simple-perl, vncterm, qemu-server (>= 1.1-1), libwww-perl (>= 6.04-1), libnet-http-perl (>= 6.06-1), libhttp-daemon-perl, wget, libnet-dns-perl, vlan, ifenslave-2.6 (>= 1.1.0-10), liblinux-inotify2-perl, debconf (>= 0.5) | debconf-2.0, netcat-traditional, pve-cluster (>= 1.0-29), libpve-common-perl, libpve-storage-perl, libterm-readline-gnu-perl, libpve-access-control (>= 3.0-2), libio-socket-ssl-perl, libfilesys-df-perl, libfile-readbackwards-perl, libfile-sync-perl, redhat-cluster-pve, resource-agents-pve, fence-agents-pve, cstream, postfix | mail-transport-agent, libxml-parser-perl, lzop, dtach, libanyevent-perl, liburi-perl, logrotate, libanyevent-http-perl, apt-transport-https, libapt-pkg-perl, libcrypt-ssleay-perl, liblwp-protocol-https-perl, spiceterm, libuuid-perl, hdparm
|
||||
Conflicts: netcat-openbsd, vzdump
|
||||
Replaces: vzdump
|
||||
Provides: vzdump
|
||||
|
@ -86,6 +86,7 @@ JSSRC= \
|
||||
node/Tasks.js \
|
||||
node/Subscription.js \
|
||||
node/APT.js \
|
||||
node/Ceph.js \
|
||||
node/Config.js \
|
||||
qemu/StatusView.js \
|
||||
window/Migrate.js \
|
||||
|
@ -553,6 +553,10 @@ Ext.define('PVE.Utils', { statics: {
|
||||
srvstop: ['SRV', gettext('Stop') ],
|
||||
srvrestart: ['SRV', gettext('Restart') ],
|
||||
srvreload: ['SRV', gettext('Reload') ],
|
||||
cephcreatemon: ['Ceph Monitor', gettext('Create') ],
|
||||
cephdestroymon: ['Ceph Monitor', gettext('Destroy') ],
|
||||
cephcreateosd: ['Ceph OSD', gettext('Create') ],
|
||||
cephdestroyosd: ['Ceph OSD', gettext('Destroy') ],
|
||||
imgcopy: ['', gettext('Copy data') ],
|
||||
imgdel: ['', gettext('Erase data') ],
|
||||
download: ['', gettext('Download') ],
|
||||
|
991
www/manager/node/Ceph.js
Normal file
991
www/manager/node/Ceph.js
Normal file
@ -0,0 +1,991 @@
|
||||
Ext.define('PVE.CephCreatePool', {
|
||||
extend: 'PVE.window.Edit',
|
||||
alias: ['widget.pveCephCreatePool'],
|
||||
|
||||
create: true,
|
||||
|
||||
subject: 'Ceph Pool',
|
||||
|
||||
initComponent : function() {
|
||||
var me = this;
|
||||
|
||||
if (!me.nodename) {
|
||||
throw "no node name specified";
|
||||
}
|
||||
|
||||
Ext.applyIf(me, {
|
||||
url: "/nodes/" + me.nodename + "/ceph/pools",
|
||||
method: 'POST',
|
||||
items: [
|
||||
{
|
||||
xtype: 'textfield',
|
||||
fieldLabel: gettext('Name'),
|
||||
name: 'name',
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
xtype: 'numberfield',
|
||||
fieldLabel: gettext('Size'),
|
||||
name: 'size',
|
||||
value: 2,
|
||||
minValue: 1,
|
||||
maxValue: 3,
|
||||
allowBlank: false
|
||||
},
|
||||
{
|
||||
xtype: 'numberfield',
|
||||
fieldLabel: 'pg_num',
|
||||
name: 'pg_num',
|
||||
value: 512,
|
||||
minValue: 8,
|
||||
maxValue: 32768,
|
||||
allowBlank: false
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
me.callParent();
|
||||
}
|
||||
});
|
||||
|
||||
Ext.define('PVE.node.CephPoolList', {
|
||||
extend: 'Ext.grid.GridPanel',
|
||||
alias: 'widget.pveNodeCephPoolList',
|
||||
|
||||
initComponent: function() {
|
||||
var me = this;
|
||||
|
||||
var nodename = me.pveSelNode.data.node;
|
||||
if (!nodename) {
|
||||
throw "no node name specified";
|
||||
}
|
||||
|
||||
var sm = Ext.create('Ext.selection.RowModel', {});
|
||||
|
||||
var rstore = Ext.create('PVE.data.UpdateStore', {
|
||||
interval: 3000,
|
||||
storeid: 'ceph-pool-list',
|
||||
model: 'ceph-pool-list',
|
||||
proxy: {
|
||||
type: 'pve',
|
||||
url: "/api2/json/nodes/" + nodename + "/ceph/pools"
|
||||
}
|
||||
});
|
||||
|
||||
var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
|
||||
|
||||
PVE.Utils.monStoreErrors(me, rstore);
|
||||
|
||||
var create_btn = new Ext.Button({
|
||||
text: gettext('Create'),
|
||||
handler: function() {
|
||||
var win = Ext.create('PVE.CephCreatePool', {
|
||||
nodename: nodename
|
||||
});
|
||||
win.show();
|
||||
}
|
||||
});
|
||||
|
||||
var remove_btn = new PVE.button.Button({
|
||||
text: gettext('Remove'),
|
||||
selModel: sm,
|
||||
disabled: true,
|
||||
confirmMsg: function(rec) {
|
||||
var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
|
||||
"'" + rec.data.pool_name + "'");
|
||||
msg += " " + gettext('This will permanently erase all image data.');
|
||||
|
||||
return msg;
|
||||
},
|
||||
handler: function() {
|
||||
var rec = sm.getSelection()[0];
|
||||
|
||||
if (!rec.data.pool_name) {
|
||||
return;
|
||||
}
|
||||
|
||||
PVE.Utils.API2Request({
|
||||
url: "/nodes/" + nodename + "/ceph/pools/" +
|
||||
rec.data.pool_name,
|
||||
method: 'DELETE',
|
||||
failure: function(response, opts) {
|
||||
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Ext.apply(me, {
|
||||
store: store,
|
||||
selModel: sm,
|
||||
stateful: false,
|
||||
tbar: [ create_btn, remove_btn ],
|
||||
columns: [
|
||||
{
|
||||
header: gettext('Name'),
|
||||
width: 100,
|
||||
sortable: true,
|
||||
dataIndex: 'pool_name'
|
||||
},
|
||||
{
|
||||
header: gettext('Size') + '/min',
|
||||
width: 50,
|
||||
sortable: false,
|
||||
renderer: function(v, meta, rec) {
|
||||
return v + '/' + rec.data.min_size;
|
||||
},
|
||||
dataIndex: 'size'
|
||||
},
|
||||
{
|
||||
header: 'pg_num',
|
||||
width: 100,
|
||||
sortable: false,
|
||||
dataIndex: 'pg_num'
|
||||
},
|
||||
{
|
||||
header: 'ruleset',
|
||||
width: 50,
|
||||
sortable: false,
|
||||
dataIndex: 'crush_ruleset'
|
||||
}
|
||||
],
|
||||
listeners: {
|
||||
show: rstore.startUpdate,
|
||||
hide: rstore.stopUpdate,
|
||||
destroy: rstore.stopUpdate
|
||||
}
|
||||
});
|
||||
|
||||
me.callParent();
|
||||
}
|
||||
}, function() {
|
||||
|
||||
Ext.define('ceph-pool-list', {
|
||||
extend: 'Ext.data.Model',
|
||||
fields: [ 'pool_name',
|
||||
{ name: 'pool', type: 'integer'},
|
||||
{ name: 'size', type: 'integer'},
|
||||
{ name: 'min_size', type: 'integer'},
|
||||
{ name: 'pg_num', type: 'integer'},
|
||||
{ name: 'crush_ruleset', type: 'integer'}
|
||||
],
|
||||
idProperty: 'pool_name'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Ext.define('PVE.node.CephOsdTree', {
|
||||
extend: 'Ext.tree.Panel',
|
||||
alias: 'widget.pveNodeCephOsdTree',
|
||||
|
||||
initComponent: function() {
|
||||
var me = this;
|
||||
|
||||
var nodename = me.pveSelNode.data.node;
|
||||
if (!nodename) {
|
||||
throw "no node name specified";
|
||||
}
|
||||
|
||||
var sm = Ext.create('Ext.selection.TreeModel', {});
|
||||
|
||||
var service_cmd = function(cmd) {
|
||||
var rec = sm.getSelection()[0];
|
||||
if (!(rec && rec.data.name && rec.data.host)) {
|
||||
return;
|
||||
}
|
||||
PVE.Utils.API2Request({
|
||||
url: "/nodes/" + rec.data.host + "/ceph/" + cmd,
|
||||
params: { service: rec.data.name },
|
||||
waitMsgTarget: me,
|
||||
method: 'POST',
|
||||
failure: function(response, opts) {
|
||||
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var start_btn = new Ext.Button({
|
||||
text: gettext('Start'),
|
||||
disabled: true,
|
||||
handler: function(){ service_cmd('start'); }
|
||||
});
|
||||
|
||||
var stop_btn = new Ext.Button({
|
||||
text: gettext('Stop'),
|
||||
disabled: true,
|
||||
handler: function(){ service_cmd('stop'); }
|
||||
});
|
||||
|
||||
var remove_btn = new Ext.Button({
|
||||
text: gettext('Remove'),
|
||||
disabled: true,
|
||||
handler: function(){
|
||||
var rec = sm.getSelection()[0];
|
||||
if (!(rec && (rec.data.id >= 0) && rec.data.host)) {
|
||||
return;
|
||||
}
|
||||
PVE.Utils.API2Request({
|
||||
url: "/nodes/" + rec.data.host + "/ceph/osd/" + rec.data.id,
|
||||
waitMsgTarget: me,
|
||||
method: 'DELETE',
|
||||
failure: function(response, opts) {
|
||||
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var set_button_status = function() {
|
||||
var rec = sm.getSelection()[0];
|
||||
|
||||
if (!rec) {
|
||||
start_btn.setDisabled(true);
|
||||
stop_btn.setDisabled(true);
|
||||
remove_btn.setDisabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var isOsd = (rec.data.host && (rec.data.type === 'osd') && (rec.data.id >= 0));
|
||||
|
||||
start_btn.setDisabled(!(isOsd && (rec.data.status !== 'up')));
|
||||
stop_btn.setDisabled(!(isOsd && (rec.data.status !== 'down')));
|
||||
remove_btn.setDisabled(!(isOsd && (rec.data.status === 'down')));
|
||||
};
|
||||
|
||||
sm.on('selectionchange', set_button_status);
|
||||
|
||||
var reload = function() {
|
||||
PVE.Utils.API2Request({
|
||||
url: "/nodes/" + nodename + "/ceph/osd",
|
||||
waitMsgTarget: me,
|
||||
method: 'GET',
|
||||
failure: function(response, opts) {
|
||||
PVE.Utils.setErrorMask(me, response.htmlStatus);
|
||||
},
|
||||
success: function(response, opts) {
|
||||
sm.deselectAll();
|
||||
me.setRootNode(response.result.data.root);
|
||||
me.expandAll();
|
||||
set_button_status();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var reload_btn = new Ext.Button({
|
||||
text: gettext('Reload'),
|
||||
handler: reload
|
||||
});
|
||||
|
||||
Ext.apply(me, {
|
||||
tbar: [ reload_btn, start_btn, stop_btn, remove_btn ],
|
||||
rootVisible: false,
|
||||
fields: ['name', 'type', 'status', 'host',
|
||||
{ type: 'integre', name: 'id' },
|
||||
{ type: 'number', name: 'reweight' },
|
||||
{ type: 'number', name: 'crush_weight' }],
|
||||
stateful: false,
|
||||
selModel: sm,
|
||||
columns: [
|
||||
{
|
||||
xtype: 'treecolumn',
|
||||
text: 'Name',
|
||||
dataIndex: 'name',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
text: 'ID',
|
||||
dataIndex: 'id',
|
||||
align: 'right',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
text: 'weight',
|
||||
dataIndex: 'crush_weight',
|
||||
align: 'right',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
text: 'Type',
|
||||
dataIndex: 'type',
|
||||
align: 'right',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
text: 'Status',
|
||||
dataIndex: 'status',
|
||||
align: 'right',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
text: 'reweight',
|
||||
dataIndex: 'reweight',
|
||||
align: 'right',
|
||||
width: 60
|
||||
}
|
||||
],
|
||||
listeners: {
|
||||
show: function() {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
me.callParent();
|
||||
|
||||
reload();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Ext.define('PVE.node.CephDiskList', {
|
||||
extend: 'Ext.grid.GridPanel',
|
||||
alias: 'widget.pveNodeCephDiskList',
|
||||
|
||||
|
||||
initComponent: function() {
|
||||
var me = this;
|
||||
|
||||
var nodename = me.pveSelNode.data.node;
|
||||
if (!nodename) {
|
||||
throw "no node name specified";
|
||||
}
|
||||
|
||||
var sm = Ext.create('Ext.selection.RowModel', {});
|
||||
|
||||
var rstore = Ext.create('PVE.data.UpdateStore', {
|
||||
interval: 3000,
|
||||
storeid: 'ceph-disk-list',
|
||||
model: 'ceph-disk-list',
|
||||
proxy: {
|
||||
type: 'pve',
|
||||
url: "/api2/json/nodes/" + nodename + "/ceph/disks"
|
||||
}
|
||||
});
|
||||
|
||||
var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
|
||||
|
||||
PVE.Utils.monStoreErrors(me, rstore);
|
||||
|
||||
var create_btn = new PVE.button.Button({
|
||||
text: gettext('Create') + ': OSD',
|
||||
selModel: sm,
|
||||
disabled: true,
|
||||
handler: function() {
|
||||
var rec = sm.getSelection()[0];
|
||||
|
||||
PVE.Utils.API2Request({
|
||||
url: "/nodes/" + nodename + "/ceph/osd",
|
||||
method: 'POST',
|
||||
params: { dev: "/dev/" + rec.data.dev },
|
||||
failure: function(response, opts) {
|
||||
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Ext.apply(me, {
|
||||
store: store,
|
||||
selModel: sm,
|
||||
stateful: false,
|
||||
tbar: [ create_btn ],
|
||||
columns: [
|
||||
{
|
||||
header: gettext('Device'),
|
||||
width: 100,
|
||||
sortable: true,
|
||||
dataIndex: 'dev'
|
||||
},
|
||||
{
|
||||
header: gettext('used'),
|
||||
width: 50,
|
||||
sortable: false,
|
||||
renderer: function(v, metaData, rec) {
|
||||
if (rec && (rec.data.osdid >= 0)) {
|
||||
return "osd." + rec.data.osdid;
|
||||
}
|
||||
return PVE.Utils.format_boolean(v);
|
||||
},
|
||||
dataIndex: 'used'
|
||||
},
|
||||
{
|
||||
header: gettext('Size'),
|
||||
width: 100,
|
||||
sortable: false,
|
||||
renderer: PVE.Utils.format_size,
|
||||
dataIndex: 'size'
|
||||
},
|
||||
{
|
||||
header: gettext('Vendor'),
|
||||
width: 100,
|
||||
sortable: true,
|
||||
dataIndex: 'vendor'
|
||||
},
|
||||
{
|
||||
header: gettext('Model'),
|
||||
width: 200,
|
||||
sortable: true,
|
||||
dataIndex: 'model'
|
||||
},
|
||||
{
|
||||
header: gettext('Serial'),
|
||||
flex: 1,
|
||||
sortable: true,
|
||||
dataIndex: 'serial'
|
||||
}
|
||||
],
|
||||
listeners: {
|
||||
show: rstore.startUpdate,
|
||||
hide: rstore.stopUpdate,
|
||||
destroy: rstore.stopUpdate
|
||||
}
|
||||
});
|
||||
|
||||
me.callParent();
|
||||
}
|
||||
}, function() {
|
||||
|
||||
Ext.define('ceph-disk-list', {
|
||||
extend: 'Ext.data.Model',
|
||||
fields: [ 'dev', 'used', { name: 'size', type: 'number'},
|
||||
{name: 'osdid', type: 'number'},
|
||||
'vendor', 'model', 'serial'],
|
||||
idProperty: 'dev'
|
||||
});
|
||||
});
|
||||
|
||||
Ext.define('PVE.CephCreateMon', {
|
||||
extend: 'PVE.window.Edit',
|
||||
alias: ['widget.pveCephCreateMon'],
|
||||
|
||||
create: true,
|
||||
|
||||
subject: 'Ceph Monitor',
|
||||
|
||||
setNode: function(nodename) {
|
||||
var me = this;
|
||||
|
||||
me.nodename = nodename;
|
||||
me.url = "/nodes/" + nodename + "/ceph/mon";
|
||||
},
|
||||
|
||||
initComponent : function() {
|
||||
var me = this;
|
||||
|
||||
if (!me.nodename) {
|
||||
throw "no node name specified";
|
||||
}
|
||||
|
||||
me.setNode(me.nodename);
|
||||
|
||||
Ext.applyIf(me, {
|
||||
method: 'POST',
|
||||
items: [
|
||||
{
|
||||
xtype: 'PVE.form.NodeSelector',
|
||||
submitValue: false,
|
||||
fieldLabel: gettext('Host'),
|
||||
selectCurNode: true,
|
||||
allowBlank: false,
|
||||
listeners: {
|
||||
change: function(f, value) {
|
||||
me.setNode(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
me.callParent();
|
||||
}
|
||||
});
|
||||
|
||||
Ext.define('PVE.node.CephMonList', {
|
||||
extend: 'Ext.grid.GridPanel',
|
||||
alias: 'widget.pveNodeCephMonList',
|
||||
|
||||
|
||||
initComponent: function() {
|
||||
var me = this;
|
||||
|
||||
var nodename = me.pveSelNode.data.node;
|
||||
if (!nodename) {
|
||||
throw "no node name specified";
|
||||
}
|
||||
|
||||
var sm = Ext.create('Ext.selection.RowModel', {});
|
||||
|
||||
var rstore = Ext.create('PVE.data.UpdateStore', {
|
||||
interval: 3000,
|
||||
storeid: 'ceph-mon-list',
|
||||
model: 'ceph-mon-list',
|
||||
proxy: {
|
||||
type: 'pve',
|
||||
url: "/api2/json/nodes/" + nodename + "/ceph/mon"
|
||||
}
|
||||
});
|
||||
|
||||
var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
|
||||
|
||||
PVE.Utils.monStoreErrors(me, rstore);
|
||||
|
||||
var service_cmd = function(cmd) {
|
||||
var rec = sm.getSelection()[0];
|
||||
if (!rec.data.host) {
|
||||
Ext.Msg.alert(gettext('Error'), "entry has no host");
|
||||
return;
|
||||
}
|
||||
PVE.Utils.API2Request({
|
||||
url: "/nodes/" + rec.data.host + "/ceph/" + cmd,
|
||||
method: 'POST',
|
||||
params: { service: "mon." + rec.data.name },
|
||||
failure: function(response, opts) {
|
||||
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var start_btn = new PVE.button.Button({
|
||||
text: gettext('Start'),
|
||||
selModel: sm,
|
||||
disabled: true,
|
||||
handler: function(){
|
||||
service_cmd("start");
|
||||
}
|
||||
});
|
||||
|
||||
var stop_btn = new PVE.button.Button({
|
||||
text: gettext('Stop'),
|
||||
selModel: sm,
|
||||
disabled: true,
|
||||
handler: function(){
|
||||
service_cmd("stop");
|
||||
}
|
||||
});
|
||||
|
||||
var create_btn = new Ext.Button({
|
||||
text: gettext('Create'),
|
||||
handler: function(){
|
||||
var win = Ext.create('PVE.CephCreateMon', {
|
||||
nodename: nodename
|
||||
});
|
||||
win.show();
|
||||
}
|
||||
});
|
||||
|
||||
var remove_btn = new PVE.button.Button({
|
||||
text: gettext('Remove'),
|
||||
selModel: sm,
|
||||
disabled: true,
|
||||
handler: function() {
|
||||
var rec = sm.getSelection()[0];
|
||||
|
||||
if (!rec.data.host) {
|
||||
Ext.Msg.alert(gettext('Error'), "entry has no host");
|
||||
return;
|
||||
}
|
||||
|
||||
PVE.Utils.API2Request({
|
||||
url: "/nodes/" + rec.data.host + "/ceph/mon/" +
|
||||
rec.data.name,
|
||||
method: 'DELETE',
|
||||
failure: function(response, opts) {
|
||||
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Ext.apply(me, {
|
||||
store: store,
|
||||
selModel: sm,
|
||||
stateful: false,
|
||||
tbar: [ start_btn, stop_btn, create_btn, remove_btn ],
|
||||
columns: [
|
||||
{
|
||||
header: gettext('Name'),
|
||||
width: 50,
|
||||
sortable: true,
|
||||
renderer: function(v) { return "mon." + v; },
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
header: gettext('Host'),
|
||||
width: 100,
|
||||
sortable: true,
|
||||
renderer: function(v) {
|
||||
return v ? v : 'unknown';
|
||||
},
|
||||
dataIndex: 'host'
|
||||
},
|
||||
{
|
||||
header: gettext('Quorum'),
|
||||
width: 50,
|
||||
sortable: false,
|
||||
renderer: PVE.Utils.format_boolean,
|
||||
dataIndex: 'quorum'
|
||||
},
|
||||
{
|
||||
header: gettext('Address'),
|
||||
flex: 1,
|
||||
sortable: true,
|
||||
dataIndex: 'addr'
|
||||
}
|
||||
],
|
||||
listeners: {
|
||||
show: rstore.startUpdate,
|
||||
hide: rstore.stopUpdate,
|
||||
destroy: rstore.stopUpdate
|
||||
}
|
||||
});
|
||||
|
||||
me.callParent();
|
||||
}
|
||||
}, function() {
|
||||
|
||||
Ext.define('ceph-mon-list', {
|
||||
extend: 'Ext.data.Model',
|
||||
fields: [ 'addr', 'name', 'rank', 'host', 'quorum' ],
|
||||
idProperty: 'name'
|
||||
});
|
||||
});
|
||||
|
||||
Ext.define('PVE.node.CephConfig', {
|
||||
extend: 'Ext.panel.Panel',
|
||||
alias: 'widget.pveNodeCephConfig',
|
||||
|
||||
load: function() {
|
||||
var me = this;
|
||||
|
||||
PVE.Utils.API2Request({
|
||||
url: me.url,
|
||||
waitMsgTarget: me,
|
||||
failure: function(response, opts) {
|
||||
me.update(gettext('Error') + " " + response.htmlStatus);
|
||||
},
|
||||
success: function(response, opts) {
|
||||
var data = response.result.data;
|
||||
me.update(Ext.htmlEncode(data));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
initComponent: function() {
|
||||
var me = this;
|
||||
|
||||
var nodename = me.pveSelNode.data.node;
|
||||
if (!nodename) {
|
||||
throw "no node name specified";
|
||||
}
|
||||
|
||||
Ext.apply(me, {
|
||||
url: '/nodes/' + nodename + '/ceph/config',
|
||||
bodyStyle: 'white-space:pre',
|
||||
bodyPadding: 5,
|
||||
autoScroll: true,
|
||||
listeners: {
|
||||
show: function() {
|
||||
me.load();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
me.callParent();
|
||||
|
||||
me.load();
|
||||
}
|
||||
});
|
||||
|
||||
Ext.define('PVE.node.CephCrushMap', {
|
||||
extend: 'Ext.panel.Panel',
|
||||
alias: 'widget.pveNodeCephCrushMap',
|
||||
|
||||
load: function() {
|
||||
var me = this;
|
||||
|
||||
PVE.Utils.API2Request({
|
||||
url: me.url,
|
||||
waitMsgTarget: me,
|
||||
failure: function(response, opts) {
|
||||
me.update(gettext('Error') + " " + response.htmlStatus);
|
||||
},
|
||||
success: function(response, opts) {
|
||||
var data = response.result.data;
|
||||
me.update(Ext.htmlEncode(data));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
initComponent: function() {
|
||||
var me = this;
|
||||
|
||||
var nodename = me.pveSelNode.data.node;
|
||||
if (!nodename) {
|
||||
throw "no node name specified";
|
||||
}
|
||||
|
||||
Ext.apply(me, {
|
||||
url: '/nodes/' + nodename + '/ceph/crush',
|
||||
bodyStyle: 'white-space:pre',
|
||||
bodyPadding: 5,
|
||||
autoScroll: true,
|
||||
listeners: {
|
||||
show: function() {
|
||||
me.load();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
me.callParent();
|
||||
|
||||
me.load();
|
||||
}
|
||||
});
|
||||
|
||||
Ext.define('PVE.node.CephStatus', {
|
||||
extend: 'PVE.grid.ObjectGrid',
|
||||
alias: 'widget.pveNodeCephStatus',
|
||||
|
||||
initComponent: function() {
|
||||
var me = this;
|
||||
|
||||
var nodename = me.pveSelNode.data.node;
|
||||
if (!nodename) {
|
||||
throw "no node name specified";
|
||||
}
|
||||
|
||||
var renderquorum = function(value) {
|
||||
if (!value || value.length < 0) {
|
||||
return 'No';
|
||||
}
|
||||
|
||||
return 'Yes {' + value.join(' ') + '}';
|
||||
};
|
||||
|
||||
var rendermonmap = function(d) {
|
||||
if (!d) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var txt = 'e' + d.epoch + ': ' + d.mons.length + " mons at ";
|
||||
|
||||
Ext.Array.each(d.mons, function(d) {
|
||||
txt += d.name + '=' + d.addr + ',';
|
||||
});
|
||||
|
||||
return txt;
|
||||
};
|
||||
|
||||
var renderosdmap = function(value) {
|
||||
if (!value || !value.osdmap) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var d = value.osdmap;
|
||||
|
||||
var txt = 'e' + d.epoch + ': ';
|
||||
|
||||
txt += d.num_osds + ' osds: ' + d.num_up_osds + ' up, ' +
|
||||
d.num_in_osds + " in";
|
||||
|
||||
return txt;
|
||||
};
|
||||
|
||||
var renderhealth = function(value) {
|
||||
if (!value || !value.overall_status) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var txt = value.overall_status;
|
||||
|
||||
Ext.Array.each(value.summary, function(d) {
|
||||
txt += " " + d.summary + ';';
|
||||
});
|
||||
|
||||
return txt;
|
||||
};
|
||||
|
||||
var renderpgmap = function(d) {
|
||||
if (!d) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var txt = 'v' + d.version + ': ';
|
||||
|
||||
txt += d.num_pgs + " pgs:";
|
||||
|
||||
Ext.Array.each(d.pgs_by_state, function(s) {
|
||||
txt += " " + s.count + " " + s.state_name;
|
||||
});
|
||||
txt += '; ';
|
||||
|
||||
txt += PVE.Utils.format_size(d.data_bytes) + " data, ";
|
||||
txt += PVE.Utils.format_size(d.bytes_used) + " used, ";
|
||||
txt += PVE.Utils.format_size(d.bytes_avail) + " avail";
|
||||
|
||||
return txt;
|
||||
};
|
||||
|
||||
Ext.applyIf(me, {
|
||||
url: "/api2/json/nodes/" + nodename + "/ceph/status",
|
||||
cwidth1: 150,
|
||||
interval: 3000,
|
||||
rows: {
|
||||
health: {
|
||||
header: 'health',
|
||||
renderer: renderhealth,
|
||||
required: true
|
||||
},
|
||||
quorum_names: {
|
||||
header: 'quorum',
|
||||
renderer: renderquorum,
|
||||
required: true
|
||||
},
|
||||
fsid: {
|
||||
header: 'cluster',
|
||||
required: true
|
||||
},
|
||||
monmap: {
|
||||
header: 'monmap',
|
||||
renderer: rendermonmap,
|
||||
required: true
|
||||
},
|
||||
osdmap: {
|
||||
header: 'osdmap',
|
||||
renderer: renderosdmap,
|
||||
required: true
|
||||
},
|
||||
pgmap: {
|
||||
header: 'pgmap',
|
||||
renderer: renderpgmap,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
me.callParent();
|
||||
|
||||
me.on('show', me.rstore.startUpdate);
|
||||
me.on('hide', me.rstore.stopUpdate);
|
||||
me.on('destroy', me.rstore.stopUpdate);
|
||||
}
|
||||
});
|
||||
|
||||
Ext.define('PVE.node.Ceph', {
|
||||
extend: 'Ext.tab.Panel',
|
||||
alias: 'widget.pveNodeCeph',
|
||||
|
||||
initComponent: function() {
|
||||
var me = this;
|
||||
|
||||
var nodename = me.pveSelNode.data.node;
|
||||
if (!nodename) {
|
||||
throw "no node name specified";
|
||||
}
|
||||
|
||||
if (!me.phstateid) {
|
||||
throw "no parent history state specified";
|
||||
}
|
||||
|
||||
var sp = Ext.state.Manager.getProvider();
|
||||
var state = sp.get(me.phstateid);
|
||||
var hsregex = /^ceph-(\S+)$/;
|
||||
|
||||
if (state && state.value) {
|
||||
var res = hsregex.exec(state.value);
|
||||
if (res && res[1]) {
|
||||
me.activeTab = res[1];
|
||||
}
|
||||
}
|
||||
|
||||
Ext.apply(me, {
|
||||
plain: true,
|
||||
tabPosition: 'bottom',
|
||||
defaults: {
|
||||
border: false,
|
||||
pveSelNode: me.pveSelNode
|
||||
},
|
||||
items: [
|
||||
{
|
||||
xtype: 'pveNodeCephStatus',
|
||||
title: 'Status',
|
||||
itemId: 'status'
|
||||
},
|
||||
{
|
||||
xtype: 'pveNodeCephConfig',
|
||||
title: 'Config',
|
||||
itemId: 'config'
|
||||
},
|
||||
{
|
||||
xtype: 'pveNodeCephMonList',
|
||||
title: 'Monitor',
|
||||
itemId: 'monlist'
|
||||
},
|
||||
{
|
||||
xtype: 'pveNodeCephDiskList',
|
||||
title: 'Disks',
|
||||
itemId: 'disklist'
|
||||
},
|
||||
{
|
||||
xtype: 'pveNodeCephOsdTree',
|
||||
title: 'OSD',
|
||||
itemId: 'osdtree'
|
||||
},
|
||||
{
|
||||
xtype: 'pveNodeCephPoolList',
|
||||
title: 'Pools',
|
||||
itemId: 'pools'
|
||||
},
|
||||
{
|
||||
title: 'Crush',
|
||||
xtype: 'pveNodeCephCrushMap',
|
||||
itemId: 'crushmap'
|
||||
},
|
||||
{
|
||||
title: 'Log',
|
||||
itemId: 'log',
|
||||
xtype: 'pveLogView',
|
||||
url: "/api2/extjs/nodes/" + nodename + "/ceph/log"
|
||||
}
|
||||
],
|
||||
listeners: {
|
||||
afterrender: function(tp) {
|
||||
var first = tp.items.get(0);
|
||||
if (first) {
|
||||
first.fireEvent('show', first);
|
||||
}
|
||||
},
|
||||
tabchange: function(tp, newcard, oldcard) {
|
||||
var first = tp.items.get(0);
|
||||
var ntab;
|
||||
|
||||
// Note: '' is alias for first tab.
|
||||
if (newcard.itemId === first.itemId) {
|
||||
ntab = 'ceph';
|
||||
} else {
|
||||
ntab = 'ceph-' + newcard.itemId;
|
||||
}
|
||||
|
||||
var state = { value: ntab };
|
||||
sp.set(me.phstateid, state);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
me.callParent();
|
||||
|
||||
var statechange = function(sp, key, state) {
|
||||
if ((key === me.phstateid) && state) {
|
||||
var first = me.items.get(0);
|
||||
var atab = me.getActiveTab().itemId;
|
||||
var res = hsregex.exec(state.value);
|
||||
var ntab = (res && res[1]) ? res[1] : first.itemId;
|
||||
if (ntab && (atab != ntab)) {
|
||||
me.setActiveTab(ntab);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
me.mon(sp, 'statechange', statechange);
|
||||
}
|
||||
});
|
@ -147,6 +147,13 @@ Ext.define('PVE.node.Config', {
|
||||
xtype: 'pveNodeAPT',
|
||||
nodename: nodename
|
||||
}]);
|
||||
me.items.push([{
|
||||
title: 'Ceph',
|
||||
itemId: 'ceph',
|
||||
xtype: 'pveNodeCeph',
|
||||
phstateid: me.hstateid,
|
||||
nodename: nodename
|
||||
}]);
|
||||
}
|
||||
|
||||
me.callParent();
|
||||
|
@ -11,10 +11,15 @@ Ext.define('PVE.panel.Config', {
|
||||
|
||||
var activeTab;
|
||||
|
||||
var hsregex = /^([^\-\s]+)(-\S+)?$/;
|
||||
|
||||
if (stateid) {
|
||||
var state = sp.get(stateid);
|
||||
if (state && state.value) {
|
||||
activeTab = state.value;
|
||||
var res = hsregex.exec(state.value);
|
||||
if (res && res[1]) {
|
||||
activeTab = res[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,13 +75,14 @@ Ext.define('PVE.panel.Config', {
|
||||
},
|
||||
tabchange: function(tp, newcard, oldcard) {
|
||||
var ntab = newcard.itemId;
|
||||
|
||||
// Note: '' is alias for first tab.
|
||||
// First tab can be 'search' or something else
|
||||
if (newcard.itemId === items[0].itemId) {
|
||||
ntab = '';
|
||||
}
|
||||
var state = { value: ntab };
|
||||
if (stateid) {
|
||||
if (stateid && !newcard.phstateid) {
|
||||
sp.set(stateid, state);
|
||||
}
|
||||
}
|
||||
@ -91,10 +97,11 @@ Ext.define('PVE.panel.Config', {
|
||||
me.callParent();
|
||||
|
||||
var statechange = function(sp, key, state) {
|
||||
if (stateid && key === stateid) {
|
||||
if (stateid && (key === stateid) && state) {
|
||||
var atab = tab.getActiveTab().itemId;
|
||||
var ntab = state.value || items[0].itemId;
|
||||
if (state && ntab && (atab != ntab)) {
|
||||
var res = hsregex.exec(state.value);
|
||||
var ntab = (res && res[1]) ? res[1] : items[0].itemId;
|
||||
if (ntab && (atab != ntab)) {
|
||||
tab.setActiveTab(ntab);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user