Merge pull request #173 from ClusterLabs/anvil-tools-dev

Continuing work on anvil-manage-server.
main
digimer-bot 3 years ago committed by GitHub
commit dc34e6e3b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 366
      Anvil/Tools/DRBD.pm
  2. 6
      Anvil/Tools/Database.pm
  3. 347
      Anvil/Tools/Storage.pm
  4. 141
      tools/anvil-manage-server

@ -551,7 +551,11 @@ This calls C<< drbdadm >> to collect the configuration of the local system and p
On error, C<< 1 >> is returned. On success, C<< 0 >> is returned.
This method takes no parameters.
Parameters;
=head3 xml (optional)
If set to the XML generated by C<< drbdadm dump-xml >> elsewhere, this will be parsed instead of making the call.
=cut
sub gather_data
@ -562,220 +566,230 @@ sub gather_data
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "DRBD->gather_data()" }});
### NOTE: Left off here - take an XML and parse that instead of collecting it, if passed.
my $xml = defined $parameter->{xml} ? $parameter->{xml} : "";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
xml => $xml,
}});
# Is DRBD even installed?
if (not -e $anvil->data->{path}{exe}{drbdadm})
if (not $xml)
{
# This is an error, but it happens a lot because we're called by scan_drbd from Striker
# dashboards often. As such, this log level is '2'.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "error_0251"});
return(1);
if (not -e $anvil->data->{path}{exe}{drbdadm})
{
# This is an error, but it happens a lot because we're called by scan_drbd from Striker
# dashboards often. As such, this log level is '2'.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "error_0251"});
return(1);
}
($xml, my $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{drbdadm}." dump-xml"});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
xml => $xml,
return_code => $return_code,
}});
if ($return_code)
{
# Failed to dump the XML.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "error_0252", variables => { return_code => $return_code }});
return(1);
}
}
my ($drbd_xml, $return_code) = $anvil->System->call({shell_call => $anvil->data->{path}{exe}{drbdadm}." dump-xml"});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { drbd_xml => $drbd_xml, return_code => $return_code }});
if ($return_code)
local $@;
my $dom = eval { XML::LibXML->load_xml(string => $xml); };
if ($@)
{
# Failed to dump the XML.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "error_0252", variables => { return_code => $return_code }});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "error_0253", variables => {
xml => $xml,
error => $@,
}});
return(1);
}
else
{
local $@;
my $dom = eval { XML::LibXML->load_xml(string => $drbd_xml); };
if ($@)
# Successful parse!
### TODO: Might be best to config these default values by calling/parsing
### 'drbdsetup show <resource> --show-defaults'.
$anvil->data->{new}{scan_drbd}{scan_drbd_common_xml} = $xml;
$anvil->data->{new}{scan_drbd}{scan_drbd_flush_disk} = 1;
$anvil->data->{new}{scan_drbd}{scan_drbd_flush_md} = 1;
$anvil->data->{new}{scan_drbd}{scan_drbd_timeout} = 6; # Default is '60', 6 seconds
$anvil->data->{new}{scan_drbd}{scan_drbd_total_sync_speed} = 0;
foreach my $name ($dom->findnodes('/config/common/section'))
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "error_0253", variables => {
xml => $drbd_xml,
error => $@,
}});
return(1);
my $section = $name->{name};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { section => $section }});
foreach my $option_name ($name->findnodes('./option'))
{
my $variable = $option_name->{name};
my $value = $option_name->{value};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
's1:variable' => $variable,
's2:value' => $value,
}});
if ($section eq "net")
{
if ($variable eq "timeout")
{
$value /= 10;
$anvil->data->{new}{scan_drbd}{scan_drbd_timeout} = ($value / 10);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"new::scan_drbd::scan_drbd_timeout" => $anvil->data->{new}{scan_drbd}{scan_drbd_timeout},
}});
}
}
if ($section eq "disk")
{
if ($variable eq "disk-flushes")
{
$anvil->data->{new}{scan_drbd}{scan_drbd_flush_disk} = $value eq "no" ? 0 : 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"new::scan_drbd::scan_drbd_flush_disk" => $anvil->data->{new}{scan_drbd}{scan_drbd_flush_disk},
}});
}
if ($variable eq "md-flushes")
{
$anvil->data->{new}{scan_drbd}{scan_drbd_flush_md} = $value eq "no" ? 0 : 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"new::scan_drbd::scan_drbd_flush_md" => $anvil->data->{new}{scan_drbd}{scan_drbd_flush_md},
}});
}
}
}
}
else
foreach my $name ($dom->findnodes('/config/resource'))
{
# Successful parse!
### TODO: Might be best to config these default values by calling/parsing
### 'drbdsetup show <resource> --show-defaults'.
$anvil->data->{new}{scan_drbd}{scan_drbd_common_xml} = $drbd_xml;
$anvil->data->{new}{scan_drbd}{scan_drbd_flush_disk} = 1;
$anvil->data->{new}{scan_drbd}{scan_drbd_flush_md} = 1;
$anvil->data->{new}{scan_drbd}{scan_drbd_timeout} = 6; # Default is '60', 6 seconds
$anvil->data->{new}{scan_drbd}{scan_drbd_total_sync_speed} = 0;
my $resource = $name->{name};
my $conf_file = $name->{'conf-file-line'};
$conf_file =~ s/:\d+$//;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
's1:resource' => $resource,
's2:conf_file' => $conf_file,
}});
foreach my $name ($dom->findnodes('/config/common/section'))
$anvil->data->{new}{resource}{$resource}{up} = 0;
$anvil->data->{new}{resource}{$resource}{xml} = $name->toString;
$anvil->data->{new}{resource}{$resource}{config_file} = $conf_file;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"new::resource::${resource}::xml" => $anvil->data->{new}{resource}{$resource}{xml},
"new::resource::${resource}::config_file" => $anvil->data->{new}{resource}{$resource}{config_file},
}});
foreach my $host ($name->findnodes('./host'))
{
my $section = $name->{name};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { section => $section }});
foreach my $option_name ($name->findnodes('./option'))
my $this_host_name = $host->{name};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { this_host_name => $this_host_name }});
# Record the details under the hosts
foreach my $volume_vnr ($host->findnodes('./volume'))
{
my $variable = $option_name->{name};
my $value = $option_name->{value};
my $volume = $volume_vnr->{vnr};
my $meta_disk = $volume_vnr->findvalue('./meta-disk');
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
's1:variable' => $variable,
's2:value' => $value,
's1:volume' => $volume,
's2:meta_disk' => $meta_disk,
}});
if ($section eq "net")
{
if ($variable eq "timeout")
{
$value /= 10;
$anvil->data->{new}{scan_drbd}{scan_drbd_timeout} = ($value / 10);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"new::scan_drbd::scan_drbd_timeout" => $anvil->data->{new}{scan_drbd}{scan_drbd_timeout},
}});
}
}
if ($section eq "disk")
$anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{device_path} = $volume_vnr->findvalue('./device');
$anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{backing_disk} = $volume_vnr->findvalue('./disk');
$anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{device_minor} = $volume_vnr->findvalue('./device/@minor');
$anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{size} = 0;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s1:new::resource::${resource}::host::${this_host_name}::volume::${volume}::device_path" => $anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{device_path},
"s2:new::resource::${resource}::host::${this_host_name}::volume::${volume}::backing_disk" => $anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{backing_disk},
"s3:new::resource::${resource}::host::${this_host_name}::volume::${volume}::device_minor" => $anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{device_minor},
}});
# Record the local data only.
if (($this_host_name eq $anvil->Get->host_name) or ($this_host_name eq $anvil->Get->short_host_name))
{
if ($variable eq "disk-flushes")
{
$anvil->data->{new}{scan_drbd}{scan_drbd_flush_disk} = $value eq "no" ? 0 : 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"new::scan_drbd::scan_drbd_flush_disk" => $anvil->data->{new}{scan_drbd}{scan_drbd_flush_disk},
}});
}
if ($variable eq "md-flushes")
{
$anvil->data->{new}{scan_drbd}{scan_drbd_flush_md} = $value eq "no" ? 0 : 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"new::scan_drbd::scan_drbd_flush_md" => $anvil->data->{new}{scan_drbd}{scan_drbd_flush_md},
}});
}
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{device_path} = $volume_vnr->findvalue('./device');
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{backing_disk} = $volume_vnr->findvalue('./disk');
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{device_minor} = $volume_vnr->findvalue('./device/@minor');
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{size} = 0;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s1:new::resource::${resource}::volume::${volume}::device_path" => $anvil->data->{new}{resource}{$resource}{volume}{$volume}{device_path},
"s2:new::resource::${resource}::volume::${volume}::backing_disk" => $anvil->data->{new}{resource}{$resource}{volume}{$volume}{backing_disk},
"s3:new::resource::${resource}::volume::${volume}::device_minor" => $anvil->data->{new}{resource}{$resource}{volume}{$volume}{device_minor},
}});
}
}
}
foreach my $name ($dom->findnodes('/config/resource'))
foreach my $connection ($name->findnodes('./connection'))
{
my $resource = $name->{name};
my $conf_file = $name->{'conf-file-line'};
$conf_file =~ s/:\d+$//;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
's1:resource' => $resource,
's2:conf_file' => $conf_file,
}});
$anvil->data->{new}{resource}{$resource}{up} = 0;
$anvil->data->{new}{resource}{$resource}{xml} = $name->toString;
$anvil->data->{new}{resource}{$resource}{config_file} = $conf_file;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"new::resource::${resource}::xml" => $anvil->data->{new}{resource}{$resource}{xml},
"new::resource::${resource}::config_file" => $anvil->data->{new}{resource}{$resource}{config_file},
}});
foreach my $host ($name->findnodes('./host'))
my $peer = "";
foreach my $host ($connection->findnodes('./host'))
{
my $this_host_name = $host->{name};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { this_host_name => $this_host_name }});
# Record the details under the hosts
foreach my $volume_vnr ($host->findnodes('./volume'))
next if (($this_host_name eq $anvil->Get->host_name) or ($this_host_name eq $anvil->Get->short_host_name));
$peer = $this_host_name;
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{peer_ip_address} = $host->findvalue('./address');
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{tcp_port} = $host->findvalue('./address/@port');;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s1:new::resource::${resource}::peer::${peer}::peer_ip_address" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{peer_ip_address},
"s2:new::resource::${resource}::peer::${peer}::tcp_port" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{tcp_port},
}});
if (not exists $anvil->data->{new}{resource}{$resource}{peer}{$peer}{protocol})
{
my $volume = $volume_vnr->{vnr};
my $meta_disk = $volume_vnr->findvalue('./meta-disk');
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
's1:volume' => $volume,
's2:meta_disk' => $meta_disk,
}});
$anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{device_path} = $volume_vnr->findvalue('./device');
$anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{backing_disk} = $volume_vnr->findvalue('./disk');
$anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{device_minor} = $volume_vnr->findvalue('./device/@minor');
$anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{size} = 0;
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{protocol} = "unknown";
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{fencing} = "unknown";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s1:new::resource::${resource}::host::${this_host_name}::volume::${volume}::device_path" => $anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{device_path},
"s2:new::resource::${resource}::host::${this_host_name}::volume::${volume}::backing_disk" => $anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{backing_disk},
"s3:new::resource::${resource}::host::${this_host_name}::volume::${volume}::device_minor" => $anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{device_minor},
"s1:new::resource::${resource}::peer::${peer}::protocol" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{protocol},
"s2:new::resource::${resource}::peer::${peer}::fencing" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{fencing},
}});
# Record the local data only.
if (($this_host_name eq $anvil->Get->host_name) or ($this_host_name eq $anvil->Get->short_host_name))
{
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{device_path} = $volume_vnr->findvalue('./device');
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{backing_disk} = $volume_vnr->findvalue('./disk');
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{device_minor} = $volume_vnr->findvalue('./device/@minor');
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{size} = 0;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s1:new::resource::${resource}::volume::${volume}::device_path" => $anvil->data->{new}{resource}{$resource}{volume}{$volume}{device_path},
"s2:new::resource::${resource}::volume::${volume}::backing_disk" => $anvil->data->{new}{resource}{$resource}{volume}{$volume}{backing_disk},
"s3:new::resource::${resource}::volume::${volume}::device_minor" => $anvil->data->{new}{resource}{$resource}{volume}{$volume}{device_minor},
}});
}
}
foreach my $volume (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}{$resource}{volume}})
{
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{connection_state} = "disconnected";
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{local_disk_state} = "down";
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{peer_disk_state} = "unknown";
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{local_role} = "down";
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{peer_role} = "unknown";
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{out_of_sync_size} = -1;
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{replication_speed} = 0;
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{estimated_time_to_sync} = 0;
}
}
foreach my $connection ($name->findnodes('./connection'))
foreach my $name ($connection->findnodes('./section'))
{
my $peer = "";
foreach my $host ($connection->findnodes('./host'))
my $section = $name->{name};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { section => $section }});
foreach my $option_name ($name->findnodes('./option'))
{
my $this_host_name = $host->{name};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { this_host_name => $this_host_name }});
next if (($this_host_name eq $anvil->Get->host_name) or ($this_host_name eq $anvil->Get->short_host_name));
$peer = $this_host_name;
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{peer_ip_address} = $host->findvalue('./address');
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{tcp_port} = $host->findvalue('./address/@port');;
my $variable = $option_name->{name};
my $value = $option_name->{value};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s1:new::resource::${resource}::peer::${peer}::peer_ip_address" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{peer_ip_address},
"s2:new::resource::${resource}::peer::${peer}::tcp_port" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{tcp_port},
's1:variable' => $variable,
's2:value' => $value,
}});
if (not exists $anvil->data->{new}{resource}{$resource}{peer}{$peer}{protocol})
{
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{protocol} = "unknown";
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{fencing} = "unknown";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s1:new::resource::${resource}::peer::${peer}::protocol" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{protocol},
"s2:new::resource::${resource}::peer::${peer}::fencing" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{fencing},
}});
}
foreach my $volume (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}{$resource}{volume}})
{
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{connection_state} = "disconnected";
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{local_disk_state} = "down";
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{peer_disk_state} = "unknown";
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{local_role} = "down";
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{peer_role} = "unknown";
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{out_of_sync_size} = -1;
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{replication_speed} = 0;
$anvil->data->{new}{resource}{$resource}{volume}{$volume}{peer}{$peer}{estimated_time_to_sync} = 0;
}
}
foreach my $name ($connection->findnodes('./section'))
{
my $section = $name->{name};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { section => $section }});
foreach my $option_name ($name->findnodes('./option'))
{
my $variable = $option_name->{name};
my $value = $option_name->{value};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
's1:variable' => $variable,
's2:value' => $value,
}});
if ($section eq "net")
if ($section eq "net")
{
if ($variable eq "protocol")
{
if ($variable eq "protocol")
{
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{protocol} = $value;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"new::resource::${resource}::peer::${peer}::protocol" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{protocol},
}});
}
if ($variable eq "fencing")
{
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{fencing} = $value;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"new::resource::${resource}::peer::${peer}::fencing" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{fencing},
}});
}
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{protocol} = $value;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"new::resource::${resource}::peer::${peer}::protocol" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{protocol},
}});
}
if ($variable eq "fencing")
{
$anvil->data->{new}{resource}{$resource}{peer}{$peer}{fencing} = $value;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"new::resource::${resource}::peer::${peer}::fencing" => $anvil->data->{new}{resource}{$resource}{peer}{$peer}{fencing},
}});
}
}
}

@ -4534,6 +4534,12 @@ ORDER BY
"storage_groups::vg_uuid::${storage_group_member_vg_uuid}::storage_group_uuid" => $anvil->data->{storage_groups}{vg_uuid}{$storage_group_member_vg_uuid}{storage_group_uuid},
}});
# Make it possible to sort the storage groups by name.
$anvil->data->{storage_groups}{anvil_uuid}{$storage_group_anvil_uuid}{storage_group_name}{$storage_group_name}{storage_group_uuid} = $storage_group_uuid;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"storage_groups::anvil_uuid::${storage_group_anvil_uuid}::storage_group_name::${storage_group_name}::storage_group_uuid}" => $anvil->data->{storage_groups}{anvil_uuid}{$storage_group_anvil_uuid}{storage_group_name}{$storage_group_name}{storage_group_uuid},
}});
# If scan_lvm has been run, read is the free space on the VG
if ($scan_lvm_exists)
{

@ -25,6 +25,7 @@ my $THIS_FILE = "Storage.pm";
# find
# get_file_stats
# get_storage_group_details
# get_storage_group_from_path
# make_directory
# manage_lvm_conf
# move_file
@ -1739,6 +1740,352 @@ AND
}
=head2 get_storage_group_from_path
This method takes a block device path and returns the C<< storage_group_uuid >> is belongs to, if any. On success, C<< storage_group_uuid >> is returned. If the path is not found to exist on any storage group, an empty string is returned. If there is a problem, C<< !!error!! >> is returned.
B<< Note >>: If there are multiple results, the first found will be returned. If the results span multiple Anvil! systems, this could be a problem. If this is a concern, specifify either the C<< host_uuid >> or C<< anvil_uuid >> parameters.
Parameters;
=head3 anvil_uuid (optional)
In the case of an ambiguous path (a path found on multiple Anvil! systems), this can be set to specify which Anvil! we're searching for.
=head3 host_uuid (optional)
In the case of an ambiguous path (a path found on multiple hosts), this can be set to specify which host we're searching for.
=head3 path (required)
This is the full block device path.
=cut
sub get_storage_group_from_path
{
my $self = shift;
my $parameter = shift;
my $anvil = $self->parent;
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Storage->get_storage_group_from_path()" }});
my $anvil_uuid = defined $parameter->{anvil_uuid} ? $parameter->{anvil_uuid} : "";
my $host_uuid = defined $parameter->{host_uuid} ? $parameter->{host_uuid} : "";
my $path = defined $parameter->{path} ? $parameter->{path} : "";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
anvil_uuid => $anvil_uuid,
host_uuid => $host_uuid,
path => $path,
}});
if (not $path)
{
# No source passed.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->get_storage_group_from_path()", parameter => "path" }});
return('!!error!!');
}
# Is this a DRBD path?
my $logical_volume = "";
if ($path !~ /drbd/)
{
$logical_volume = $path;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { logical_volume => $logical_volume }});
}
else
{
# Looks like it. If the device path is '/dev/drbd/by-res/...' we'll need to pull out the
# resource name (server name) and volume number as the path only actually exists when DRBD is
# up and isn't referenced in the config file.
my $resource = "";
my $volume = "";
$anvil->DRBD->gather_data({debug => $debug});
if ($path =~ /\/dev\/drbd\/by-res\/(.*)\/(\d+)$/)
{
$resource = $1;
$volume = $2;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
resource => $resource,
volume => $volume,
}});
}
elsif ($path =~ /\/dev\/drbd_(.*)_(\d+)$/)
{
$resource = $1;
$volume = $2;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
resource => $resource,
volume => $volume,
}});
}
elsif ($path =~ /\/dev\/drbd(\d+)$/)
{
# This is a direct path to a minor device, we'll need to find it in the config.
my $minor = $1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { minor => $minor }});
$anvil->Database->get_anvils({debug => $debug});
my $local_host_uuid = $anvil->Get->host_uuid({debug => $debug});
my $local_anvil_uuid = $anvil->Cluster->get_anvil_uuid({debug => $debug});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { local_anvil_uuid => $local_anvil_uuid }});
my $node1_host_uuid = "";
my $node2_host_uuid = "";
my $dr1_host_uuid = "";
if ($anvil_uuid)
{
$node1_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_node1_host_uuid};
$node2_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_node2_host_uuid};
$dr1_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_dr1_host_uuid};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
node1_host_uuid => $node1_host_uuid,
node1_host_uuid => $node2_host_uuid,
dr1_host_uuid => $dr1_host_uuid,
}});
}
# If we were passed an anvil_uuid but not a host_uuid, don't use this machine's host UUID
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host_uuid => $host_uuid }});
# These will be set if multiple options are found in the database.
foreach my $this_resource (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}})
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { this_resource => $this_resource }});
foreach my $this_host_name (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}{$this_resource}{host}})
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { this_host_name => $this_host_name }});
foreach my $this_volume (sort {$a cmp $b} keys %{$$anvil->data->{new}{resource}{$this_resource}{host}{$this_host_name}{volume}})
{
my $this_minor = $anvil->data->{new}{resource}{$this_resource}{host}{$this_host_name}{volume}{$this_volume}{device_minor};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
this_volume => $this_volume,
this_minor => $this_minor,
}});
next if $this_minor ne $minor;
my $this_host_uuid = $anvil->Get->host_uuid_from_name({host_name => $this_host_name});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { this_host_uuid => $this_host_uuid }});
next if not $this_host_uuid;
# Sorry, this is a bit of a mess. Logic is; If we're given a
# host_uuid, and it matches, use it. Otherwise, if an
# anvil_uuid is passed, and either node 1 or 2's UUID, or if
# there is a DR host, if it's host UUID matches, then we can
# use this.
if (
(
($host_uuid) && ($host_uuid eq $this_host_uuid)
)
or
(
($anvil_uuid) &&
(
($this_host_uuid eq $node1_host_uuid) or
($this_host_uuid eq $node2_host_uuid) or
(
($dr1_host_uuid) &&
($this_host_uuid eq $dr1_host_uuid)
)
)
)
)
{
# This is a node in the requested cluster.
$resource = $this_resource;
$volume = $this_volume;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
resource => $resource,
volume => $volume,
}});
last;
if (not $host_uuid)
{
$host_uuid = $this_host_uuid;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host_uuid => $host_uuid }});
}
}
}
}
}
}
# Did I find the resource and volume?
if ($resource)
{
my $query = "
SELECT
scan_drbd_resource_host_uuid,
scan_drbd_resource_xml,
modified_date
FROM
scan_drbd_resources
WHERE
scan_drbd_resource_name = ".$anvil->Database->quote($resource);
if ($host_uuid)
{
$query .= "
AND
scan_drbd_resource_host_uuid = ".$anvil->Database->quote($host_uuid);
}
$query .= "
ORDER BY
modified_date DESC
LIMIT 1
;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }});
my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__});
my $count = @{$results};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
results => $results,
count => $count,
}});
if (not $count)
{
# Group not found.
return("");
}
my $scan_drbd_resource_host_uuid = $results->[0]->[0];
my $scan_drbd_resource_xml = $results->[0]->[1];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
scan_drbd_resource_host_uuid => $scan_drbd_resource_host_uuid,
scan_drbd_resource_xml => $scan_drbd_resource_xml,
}});
$anvil->DRBD->gather_data({
debug => 3,
xml => $scan_drbd_resource_xml,
});
# Dig out the LV behind the volume.
foreach my $this_host_name (sort {$a cmp $b} keys %{$anvil->data->{new}{resource}{$resource}{host}})
{
my $this_host_uuid = $anvil->Get->host_uuid_from_name({host_name => $this_host_name});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s1:this_host_name" => $this_host_name,
"s2:this_host_uuid" => $this_host_uuid,
}});
next if (($host_uuid) && ($this_host_uuid ne $host_uuid));
my $device_path = $anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{device_path};
my $backing_disk = $anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{backing_disk};
my $device_minor = $anvil->data->{new}{resource}{$resource}{host}{$this_host_name}{volume}{$volume}{device_minor};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
"s3:device_path" => $device_path,
"s4:backing_disk" => $backing_disk,
"s5:device_minor" => $device_minor,
}});
if (not $host_uuid)
{
$host_uuid = $scan_drbd_resource_host_uuid;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host_uuid => $host_uuid }});
}
$logical_volume = $backing_disk;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { logical_volume => $logical_volume }});
last;
}
}
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { logical_volume => $logical_volume }});
if ($logical_volume)
{
### NOTE: We're pulling more columns than we need to help with logging.
# Verify this is an LV and, if so, what VG is it on?
my $query = "
SELECT
a.scan_lvm_lv_name,
a.scan_lvm_lv_on_vg,
b.scan_lvm_vg_internal_uuid
FROM
scan_lvm_lvs a,
scan_lvm_vgs b
WHERE
a.scan_lvm_lv_host_uuid = b.scan_lvm_vg_host_uuid
AND
a.scan_lvm_lv_on_vg = b.scan_lvm_vg_name
AND
a.scan_lvm_lv_path = ".$anvil->Database->quote($logical_volume);
if ($host_uuid)
{
$query .= "
AND
scan_lvm_lv_host_uuid = ".$anvil->Database->quote($host_uuid);
}
$query .= "
LIMIT 1
;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }});
my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__});
my $count = @{$results};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
results => $results,
count => $count,
}});
if (not $count)
{
# LV not found.
return("");
}
my $scan_lvm_lv_name = $results->[0]->[0];
my $scan_lvm_lv_on_vg = $results->[0]->[1];
my $scan_lvm_vg_internal_uuid = $results->[0]->[2];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
scan_lvm_lv_name => $scan_lvm_lv_name,
scan_lvm_lv_on_vg => $scan_lvm_lv_on_vg,
scan_lvm_vg_internal_uuid => $scan_lvm_vg_internal_uuid,
}});
$query = "
SELECT
a.storage_group_uuid,
a.storage_group_name
FROM
storage_groups a,
storage_group_members b
WHERE
a.storage_group_uuid = b.storage_group_member_storage_group_uuid
AND
b.storage_group_member_vg_uuid = ".$anvil->Database->quote($scan_lvm_vg_internal_uuid)."
LIMIT 1
;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }});
$results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__});
$count = @{$results};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
results => $results,
count => $count,
}});
if (not $count)
{
# Storage group not found.
return("");
}
my $storage_group_uuid = $results->[0]->[0];
my $storage_group_name = $results->[0]->[1];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
storage_group_uuid => $storage_group_uuid,
storage_group_name => $storage_group_name,
}});
# Done!
return($storage_group_uuid);
}
return("");
}
=head2 make_directory
This creates a directory (and any parent directories).

@ -94,6 +94,9 @@ if ($anvil->data->{switches}{'job-uuid'})
else
{
# Interactive!
$anvil->data->{new_config}{cpu}{sockets} = "";
$anvil->data->{new_config}{cpu}{cores} = "";
$anvil->data->{new_config}{ram}{'bytes'} = "";
interactive_question($anvil);
}
@ -633,6 +636,7 @@ sub interactive_configure_main
}})."\n";
my $changes = 0;
my $cpu_star = "";
my $ram_star = "";
if (($anvil->data->{new_config}{cpu}{sockets}) or ($anvil->data->{new_config}{cpu}{cores}))
{
$cpu_star = "*";
@ -642,9 +646,18 @@ sub interactive_configure_main
changes => $changes,
}});
}
if ($anvil->data->{new_config}{ram}{'bytes'})
{
$ram_star = "*";
$changes = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
ram_star => $ram_star,
changes => $changes,
}});
}
print "[ 1 ] - CPU".$cpu_star."\n";
print "[ 2 ] - RAM\n";
print "[ 2 ] - RAM".$ram_star."\n";
print "[ 3 ] - Storage\n";
print "[ 4 ] - Network\n";
print "[ 5 ] - Boot Order\n";
@ -663,16 +676,15 @@ sub interactive_configure_main
if ($answer eq "1")
{
interactive_configure_cpu($anvil, $terminal)
interactive_configure_cpu($anvil, $terminal);
}
elsif ($answer eq "2")
{
interactive_configure_ram($anvil, $terminal)
interactive_configure_ram($anvil, $terminal);
}
elsif ($answer eq "3")
{
print "Going to Storage menu\n";
sleep 1;
interactive_configure_storage($anvil, $terminal);
}
elsif ($answer eq "4")
{
@ -747,6 +759,51 @@ sub interactive_configure_main
return(0);
}
sub interactive_configure_storage
{
my ($anvil, $terminal) = @_;
# Get the
$anvil->Database->get_storage_group_data({debug => 2});
while(1)
{
# Here, we'll list each storage pool and for each indicate if it's used and, if so
my $anvil_uuid = $anvil->data->{target_server}{anvil_uuid};
foreach my $storage_group_name (sort {$a cmp $b} keys %{$anvil->data->{storage_groups}{anvil_uuid}{$anvil_uuid}{storage_group_name}})
{
my $storage_group_uuid = $anvil->data->{storage_groups}{anvil_uuid}{$anvil_uuid}{storage_group_name}{$storage_group_name}{storage_group_uuid};
my $vg_size = $anvil->data->{anvil_resources}{$anvil_uuid}{storage_group}{$storage_group_uuid}{vg_size};
my $free_size = $anvil->data->{anvil_resources}{$anvil_uuid}{storage_group}{$storage_group_uuid}{free_size
my $vg_size_on_dr = $anvil->data->{anvil_resources}{$anvil_uuid}{storage_group}{$storage_group_uuid}{vg_size_on_dr};
my $available_on_dr = $anvil->data->{anvil_resources}{$anvil_uuid}{storage_group}{$storage_group_uuid}{available_on_dr};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
's1:storage_group_name' => $storage_group_name,
's2:storage_group_uuid' => $storage_group_uuid,
's3:vg_size' => $vg_size." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $vg_size}).")",
's4:free_size' => $free_size." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $free_size}).")",
's5:vg_size_on_dr' => $vg_size_on_dr." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $vg_size_on_dr}).")",
's6:available_on_dr' => $free_size." (".$anvil->Convert->bytes_to_human_readable({'bytes' => $available_on_dr}).")",
}});
foreach my $target (sort {$a cmp $b} keys %{$anvil->data->{target_server}{disk}})
{
my $device_bus = $anvil->data->{target_server}{disk}{$target}{device_bus}
my $cache = $anvil->data->{target_server}{disk}{$target}{cache};
my $io = $anvil->data->{target_server}{disk}{$target}{io};
my $path = $anvil->data->{target_server}{disk}{$target}{path};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
device_bus => $device_bus,
cache => $cache,
io => $io,
path => $path,
}});
}
}
}
return(0);
}
sub interactive_configure_ram
{
my ($anvil, $terminal) = @_;
@ -754,39 +811,35 @@ sub interactive_configure_ram
while(1)
{
# Threads will be the maximum, even without hyperthreading being available.
my $anvil_uuid = $anvil->data->{target_server}{anvil_uuid};
my $say_current_ram = $anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{target_server}{configured_ram}});
my $say_total_ram = $anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{anvil_resources}{$anvil_uuid}{ram}{hardware}});
my $say_available_ram = $anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{anvil_resources}{$anvil_uuid}{ram}{available}});
my $max_assigned = $anvil->data->{anvil_resources}{$anvil_uuid}{ram}{available} - $anvil->data->{target_server}{configured_ram};
my $say_max_assigned = $anvil->Convert->bytes_to_human_readable({'bytes' => $max_assigned});
my $anvil_uuid = $anvil->data->{target_server}{anvil_uuid};
my $say_current_ram = $anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{target_server}{configured_ram}});
my $say_total_ram = $anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{anvil_resources}{$anvil_uuid}{ram}{hardware}});
my $say_available_ram = $anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{anvil_resources}{$anvil_uuid}{ram}{available}});
my $max_assignable = $anvil->data->{anvil_resources}{$anvil_uuid}{ram}{available} + $anvil->data->{target_server}{configured_ram};
my $say_max_assignable = $anvil->Convert->bytes_to_human_readable({'bytes' => $max_assignable});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
anvil_uuid => $anvil_uuid,
say_current_ram => $say_current_ram,
say_total_ram => $say_total_ram,
say_available_ram => $say_available_ram,
max_assigned => $say_max_assigned,
anvil_uuid => $anvil_uuid,
say_current_ram => $say_current_ram,
say_total_ram => $say_total_ram,
say_available_ram => $say_available_ram,
say_max_assignable => $say_max_assignable,
}});
my $changes = 0;
my $say_new_ram = 0;
if (not exists $anvil->data->{new_config}{ram}{'bytes'})
{
$anvil->data->{new_config}{ram}{'bytes'} = 0;
}
elsif ($anvil->data->{new_config}{ram}{'bytes'})
if ($anvil->data->{new_config}{ram}{'bytes'})
{
$say_new_ram = $anvil->Convert->bytes_to_human_readable({'bytes' => $anvil->data->{new_config}{ram}{'bytes'}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { say_new_ram => $say_new_ram }});
}
my $star_ram = " ";
my $ram_star = " ";
if (($anvil->data->{new_config}{ram}{'bytes'}) && ($anvil->data->{new_config}{ram}{'bytes'} ne $anvil->data->{target_server}{server_ram_bytes}))
{
$star_ram = "*";
$ram_star = "*";
$changes = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
star_ram => $star_ram,
ram_star => $ram_star,
changes => $changes,
}});
}
@ -796,8 +849,8 @@ sub interactive_configure_ram
anvil_name => $anvil->data->{target_server}{anvil_name},
server_name => $anvil->data->{target_server}{server_name},
}})."\n";
print "* RAM configuration. Currently allocated: [".$say_current_ram."], Maximum RAM: [".$say_max_assigned."] (current + free).\n";
print "[ 1 ] - RAM".$star_ram." (currently: [".$say_current_ram."], new: [".$say_new_ram."])\n";
print "* RAM configuration. Currently allocated: [".$say_current_ram."], Maximum RAM: [".$say_max_assignable."] (current + free).\n";
print "[ 1 ] - RAM".$ram_star." (currently: [".$say_current_ram."], new: [".$say_new_ram."])\n";
print "\n";
print "[ B ] - Back\n";
@ -833,11 +886,11 @@ sub interactive_configure_ram
{
# Convert to bytes
my $requested_bytes = $answer;
if ($answer =~ /\D/)
if ($requested_bytes =~ /\D/)
{
$requested_bytes = $anvil->Convert->human_readable_to_bytes({
base2 => 1,
size => $answer,
size => $requested_bytes,
});
if ($requested_bytes eq "!!error!!")
{
@ -848,13 +901,20 @@ sub interactive_configure_ram
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { answer => $answer }});
interactive_configure_ram($anvil, $terminal);
}
else
{
$anvil->data->{new_config}{ram}{'bytes'} = $requested_bytes;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"new_config::ram::bytes" => $anvil->data->{new_config}{ram}{'bytes'},
}});
}
}
elsif (not $requested_bytes)
{
interactive_configure_ram($anvil, $terminal);
}
if (($requested_bytes =~ /^\d+$/) && ($requested_bytes > $max_assignable))
{
$anvil->data->{new_config}{ram}{'bytes'} = 0;
print "[ Error ] - You asked for: [".$anvil->Convert->bytes_to_human_readable({'bytes' => $requested_bytes})."] (".$anvil->Convert->add_commas({number => $max_assignable})." bytes) RAM, but the max available is: [".$say_max_assignable."] (".$anvil->Convert->add_commas({number => $max_assignable})." bytes).\n";
my $answer = <STDIN>;
chomp $answer;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { answer => $answer }});
interactive_configure_ram($anvil, $terminal);
}
else
{
@ -899,16 +959,7 @@ sub interactive_configure_cpu
current_cores => $current_cores,
}});
my $changes = 0;
if (not exists $anvil->data->{new_config}{cpu}{sockets})
{
$anvil->data->{new_config}{cpu}{sockets} = "";
}
if (not exists $anvil->data->{new_config}{cpu}{cores})
{
$anvil->data->{new_config}{cpu}{cores} = "";
}
my $changes = 0;
my $star_cores = " ";
if (($anvil->data->{new_config}{cpu}{cores}) && ($anvil->data->{new_config}{cpu}{cores} ne $anvil->data->{target_server}{server_cpu_cores}))
{

Loading…
Cancel
Save