diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm index 6745afa2..0a37e938 100644 --- a/Anvil/Tools.pm +++ b/Anvil/Tools.pm @@ -1115,6 +1115,7 @@ sub _set_paths ifcfg => "/etc/sysconfig/network-scripts", journald => "/var/log/journal", libvirtd_definitions => "/etc/libvirt/qemu/", + opt_alteeve => "/opt/alteeve", pgsql => "/var/lib/pgsql/", resource_status => "/sys/kernel/debug/drbd/resources", scan_agents => "/usr/sbin/scancore-agents", diff --git a/Anvil/Tools/Database.pm b/Anvil/Tools/Database.pm index dade1c1d..34830a0d 100644 --- a/Anvil/Tools/Database.pm +++ b/Anvil/Tools/Database.pm @@ -620,25 +620,26 @@ sub check_agent_data }}); } - # Now check to see if a resync is required, it likely is. - if ($anvil->data->{sys}{database}{connections} > 1) - { - # The source is the agent - $anvil->Database->_find_behind_databases({ - debug => $debug, - source => $agent, - tables => $tables, - }); - } - - # Hold if a lock has been requested. - $anvil->Database->locking({debug => $debug}); - - # Mark that we're now active. - $anvil->Database->mark_active({debug => $debug, set => 1}); - - # Sync the database, if needed. - $anvil->Database->resync_databases({debug => $debug}); + ### NOTE: Don't sync here, leave it for anvil-daemon to handle. +# # Now check to see if a resync is required, it likely is. +# if ($anvil->data->{sys}{database}{connections} > 1) +# { +# # The source is the agent +# $anvil->Database->_find_behind_databases({ +# debug => $debug, +# source => $agent, +# tables => $tables, +# }); +# } +# +# # Hold if a lock has been requested. +# $anvil->Database->locking({debug => $debug}); +# +# # Mark that we're now active. +# $anvil->Database->mark_active({debug => $debug, set => 1}); +# +# # Sync the database, if needed. +# $anvil->Database->resync_databases({debug => $debug, force }); } } @@ -1199,10 +1200,6 @@ This module will return the number of databases that were successfully connected Parameters; -=head3 all (optional, default 0) - -If set, then the checks to see if a database is active or not is ignored. If the postgresql server is running, we will connect to it. - =head3 check_for_resync (optional, default 0) If set to C<< 1 >>, and there are 2 or more databases available, a check will be make to see if the databases need to be resync'ed or not. This is also set if the command line switch C<< --resync-db >> is used. @@ -1287,7 +1284,6 @@ sub connect my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Database->connect()" }}); - my $all = defined $parameter->{all} ? $parameter->{all} : 0; my $check_if_configured = defined $parameter->{check_if_configured} ? $parameter->{check_if_configured} : 0; my $db_uuid = defined $parameter->{db_uuid} ? $parameter->{db_uuid} : ""; my $check_for_resync = defined $parameter->{check_for_resync} ? $parameter->{check_for_resync} : 0; @@ -1299,7 +1295,6 @@ sub connect my $tables = defined $parameter->{tables} ? $parameter->{tables} : ""; my $test_table = defined $parameter->{test_table} ? $parameter->{test_table} : $anvil->data->{sys}{database}{test_table}; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - all => $all, check_if_configured => $check_if_configured, db_uuid => $db_uuid, check_for_resync => $check_for_resync, @@ -1618,25 +1613,18 @@ sub connect name => $name, uuid => $uuid, }}); - - # Set this database handle as the one to use for reading, if no handle is yet set. - if (($is_local) or (not $anvil->data->{sys}{database}{read_uuid}) or (not $anvil->Database->read)) + + # Only the first database to connect will be "Active". This is the database used for + # reads and the DB that will deal with resyncs + if (not $anvil->data->{sys}{database}{primary_db}) { - $anvil->data->{sys}{database}{read_uuid} = $uuid; + $anvil->data->{sys}{database}{primary_db} = $uuid; + $anvil->data->{sys}{database}{read_uuid} = $uuid; $anvil->Database->read({set => $dbh}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - 'anvil->Database->read' => $anvil->Database->read, + "sys::database::primary_db" => $anvil->data->{sys}{database}{primary_db}, "sys::database::read_uuid" => $anvil->data->{sys}{database}{read_uuid}, - }}); - } - - # Only the first database to connect will be "Active". What this means will expand - # over time. As of now, only the active DB will do resyncs. - if (not $anvil->data->{sys}{database}{active_uuid}) - { - $anvil->data->{sys}{database}{active_uuid} = $uuid; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "sys::database::active_uuid" => $anvil->data->{sys}{database}{active_uuid}, + 'anvil->Database->read' => $anvil->Database->read, }}); } @@ -1695,56 +1683,61 @@ sub connect } $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "sys::database::read_uuid" => $anvil->data->{sys}{database}{read_uuid}, "cache::database_handle::${uuid}" => $anvil->data->{cache}{database_handle}{$uuid}, }}); - # Before I continue, see if this database is inactive (unless 'all' is set). - if (not $all) + # Before I continue, see if this database is inactive. + my ($active_value, undef, undef) = $anvil->Database->read_variable({ + debug => $debug, + uuid => $uuid, + variable_name => "database::".$uuid."::active", + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { active_value => $active_value }}); + if ($active_value eq "0") { - my ($active_value, undef, undef) = $anvil->Database->read_variable({ - debug => $debug, - uuid => $uuid, - variable_name => "database::".$uuid."::active", - }); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { active_value => $active_value }}); - if ($active_value eq "0") + # If we're "retry", we just started up. + if (($retry) && ($is_local)) { - # If we're "retry", we just started up. - if (($retry) && ($is_local)) + # Set the variable saying we're active. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0698"}); + my $variable_uuid = $anvil->Database->insert_or_update_variables({ + uuid => $uuid, + variable_name => "database::".$uuid."::active", + variable_value => "1", + variable_default => "0", + variable_description => "striker_0294", + variable_section => "database", + variable_source_uuid => "NULL", + variable_source_table => "", + }); + + $anvil->data->{db_status}{$uuid}{active} = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "db_status::${uuid}::active" => $anvil->data->{db_status}{$uuid}{active}, + }}); + } + else + { + # Don't use this database. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0699", variables => { host => $uuid }}); + $anvil->data->{cache}{database_handle}{$uuid}->disconnect; + delete $anvil->data->{cache}{database_handle}{$uuid}; + + if ($anvil->data->{sys}{database}{read_uuid} eq $uuid) { - # Set the variable saying we're active. - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0698"}); - my $variable_uuid = $anvil->Database->insert_or_update_variables({ - uuid => $uuid, - variable_name => "database::".$uuid."::active", - variable_value => "1", - variable_default => "0", - variable_description => "striker_0294", - variable_section => "database", - variable_source_uuid => "NULL", - variable_source_table => "", - }); - - $anvil->data->{db_status}{$uuid}{active} = 1; + $anvil->data->{sys}{database}{read_uuid} = ""; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "db_status::${uuid}::active" => $anvil->data->{db_status}{$uuid}{active}, + "sys::database::read_uuid" => $anvil->data->{sys}{database}{read_uuid}, }}); } - else + if ($anvil->data->{sys}{database}{primary_db} eq $uuid) { - # Don't use this database. - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0699", variables => { host => $uuid }}); - $anvil->data->{cache}{database_handle}{$uuid}->disconnect; - delete $anvil->data->{cache}{database_handle}{$uuid}; - - if ($anvil->data->{sys}{database}{read_uuid} eq $uuid) - { - $anvil->data->{sys}{database}{read_uuid} = ""; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "sys::database::read_uuid" => $anvil->data->{sys}{database}{read_uuid} }}); - } - next; + $anvil->data->{sys}{database}{primary_db} = ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "sys::database::primary_db" => $anvil->data->{sys}{database}{primary_db}, + }}); } + next; } } @@ -1765,10 +1758,7 @@ sub connect { $anvil->Database->refresh_timestamp({debug => $debug}); } - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "sys::database::read_uuid" => $anvil->data->{sys}{database}{read_uuid}, - 'anvil->Database->read' => $anvil->Database->read, "sys::database::timestamp" => $anvil->data->{sys}{database}{timestamp}, }}); @@ -1780,30 +1770,25 @@ sub connect # Before we try to connect, see if this is a local database and, if so, make sure it's setup. if ($is_local) { - # If we're being called with 'all', don't set active as we could be just checking if - # we're active or not. - if (not $all) + # If we're a striker, set the variable saying we're active if we need to. + my ($active_value, undef, undef) = $anvil->Database->read_variable({ + debug => $debug, + uuid => $uuid, + variable_name => "database::".$uuid."::active", + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { active_value => $active_value }}); + if (not $active_value) { - # If we're a striker, set the variable saying we're active if we need to. - my ($active_value, undef, undef) = $anvil->Database->read_variable({ - debug => $debug, - uuid => $uuid, - variable_name => "database::".$uuid."::active", + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0698"}); + my $variable_uuid = $anvil->Database->insert_or_update_variables({ + variable_name => "database::".$uuid."::active", + variable_value => "1", + variable_default => "0", + variable_description => "striker_0294", + variable_section => "database", + variable_source_uuid => "NULL", + variable_source_table => "", }); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { active_value => $active_value }}); - if (not $active_value) - { - $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0698"}); - my $variable_uuid = $anvil->Database->insert_or_update_variables({ - variable_name => "database::".$uuid."::active", - variable_value => "1", - variable_default => "0", - variable_description => "striker_0294", - variable_section => "database", - variable_source_uuid => "NULL", - variable_source_table => "", - }); - } } } # If this isn't a local database, read the target's Anvil! version (if available) and make @@ -1836,8 +1821,8 @@ sub connect # Delete the information about this database. We'll try again on next # ->connect(). - $anvil->data->{sys}{database}{active_uuid} = "" if $anvil->data->{sys}{database}{read_active} eq $uuid; - $anvil->data->{sys}{database}{read_uuid} = "" if $anvil->data->{sys}{database}{read_uuid} eq $uuid; + $anvil->data->{sys}{database}{primary_db} = "" if $anvil->data->{sys}{database}{read_active} eq $uuid; + $anvil->data->{sys}{database}{read_uuid} = "" if $anvil->data->{sys}{database}{read_uuid} eq $uuid; $anvil->data->{sys}{database}{connections}--; delete $anvil->data->{database}{$uuid}; next; @@ -2013,9 +1998,6 @@ sub connect } } - my $total = tv_interval ($start_time, [gettimeofday]); - #print "Total runtime: [".$total."]\n"; - # Do I have any connections? Don't die, if not, just return. $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "sys::database::connections" => $anvil->data->{sys}{database}{connections} }}); if (not $anvil->data->{sys}{database}{connections}) @@ -2151,46 +2133,34 @@ sub connect return($anvil->data->{sys}{database}{connections}); } - if (exists $anvil->data->{'log'}{scan_agent}) + # If 'check_for_resync' is set to '2', then only check if we're primary. + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "sys::database::primary_db" => $anvil->data->{sys}{database}{primary_db}, + "sys::host_uuid" => $anvil->data->{sys}{host_uuid}, + check_for_resync => $check_for_resync, + }}); + if ($check_for_resync == 2) { - my $agent = $anvil->data->{'log'}{scan_agent}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { agent => $agent }}); - if (exists $anvil->data->{scan_agent}{$agent}{last_db_count}) + if ($anvil->data->{sys}{database}{primary_db} eq $anvil->data->{sys}{host_uuid}) { - $anvil->data->{sys}{database}{last_db_count} = $anvil->data->{scan_agent}{$agent}{last_db_count}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "sys::database::last_db_count" => $anvil->data->{sys}{database}{last_db_count}, - }}); + # We're primary. + $check_for_resync = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { check_for_resync => $check_for_resync }}); + } + else + { + # We're not primary + $check_for_resync = 0; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { check_for_resync => $check_for_resync }}); } - } - - # If we have a previous count and the new count is higher, resync. - if (not exists $anvil->data->{sys}{database}{last_db_count}) - { - $anvil->data->{sys}{database}{last_db_count} = 0; - } - - # If "last_db_count" is the lower than the current number of connections, check for a resync. - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "sys::database::last_db_count" => $anvil->data->{sys}{database}{last_db_count}, - "sys::database::connections" => $anvil->data->{sys}{database}{connections}, - }}); - if ($anvil->data->{sys}{database}{connections} > $anvil->data->{sys}{database}{last_db_count}) - { - $check_for_resync = 1; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { check_for_resync => $check_for_resync }}); } # Check for behind databases only if there are 2+ DBs, we're the active DB, and we're set to do so. $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "sys::database::connections" => $anvil->data->{sys}{database}{connections}, - "sys::database::active_uuid" => $anvil->data->{sys}{database}{active_uuid}, - "sys::host_uuid" => $anvil->data->{sys}{host_uuid}, check_for_resync => $check_for_resync, }}); - if (($anvil->data->{sys}{database}{connections} > 1) && - (($anvil->data->{sys}{database}{active_uuid} eq $anvil->data->{sys}{host_uuid}) or - ($check_for_resync))) + if (($anvil->data->{sys}{database}{connections} > 1) && ($check_for_resync)) { $anvil->Database->_find_behind_databases({ debug => $debug, @@ -3580,7 +3550,7 @@ FROM }}); } - # If this host is a node in an Anvil!, set the old 'file_location_anvil_uuid' to maintain + # If this host is an Anvil! subnode, set the old 'file_location_anvil_uuid' to maintain # backwards compatibility. if ((exists $anvil->data->{hosts}{host_uuid}{$file_location_host_uuid}) && ($anvil->data->{hosts}{host_uuid}{$file_location_host_uuid}{anvil_uuid})) @@ -4769,7 +4739,7 @@ Jobs that reached 100% within this number of seconds ago will be included. If th =head3 job_host_uuid (default $anvil->Get->host_uuid) -This is the host that we're getting a list of jobs from. +This is the host that we're getting a list of jobs from. If this is set to C<< all >>, all jobs are loaded from all hosts. This method takes no parameters. @@ -4782,13 +4752,25 @@ sub get_jobs my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; my $return = []; - my $ended_within = defined $parameter->{ended_within} ? $parameter->{ended_within} : 300; - my $job_host_uuid = defined $parameter->{job_host_uuid} ? $parameter->{job_host_uuid} : $anvil->Get->host_uuid; + my $ended_within = defined $parameter->{ended_within} ? $parameter->{ended_within} : 0; + my $job_host_uuid = defined $parameter->{job_host_uuid} ? $parameter->{job_host_uuid} : ""; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { ended_within => $ended_within, job_host_uuid => $job_host_uuid, }}); + if ($ended_within !~ /^\d+$/) + { + $ended_within = 300; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { ended_within => $ended_within }}); + } + + if (not $job_host_uuid) + { + $job_host_uuid = $anvil->Get->host_uuid; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { job_host_uuid => $job_host_uuid }}); + } + if (exists $anvil->data->{jobs}{running}) { delete $anvil->data->{jobs}{running}; @@ -4811,11 +4793,18 @@ SELECT job_title, job_description, job_status, - modified_date + job_host_uuid, + modified_date, + round(extract(epoch from modified_date)) FROM - jobs + jobs "; + if ($job_host_uuid ne "all") + { + $query .= " WHERE - job_host_uuid = ".$anvil->Database->quote($job_host_uuid)." + job_host_uuid = ".$anvil->Database->quote($job_host_uuid); + } + $query .= " ;"; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }}); @@ -4838,7 +4827,9 @@ WHERE my $job_title = $row->[8]; my $job_description = $row->[9]; my $job_status = $row->[10]; - my $modified_date = $row->[11]; + my $job_host_uuid = $row->[11]; + my $modified_date = $row->[12]; + my $modified_date_unix = $row->[13]; my $now_time = time; my $started_seconds_ago = $job_picked_up_at ? ($now_time - $job_picked_up_at) : 0; my $updated_seconds_ago = $job_updated ? ($now_time - $job_updated) : 0; @@ -4853,8 +4844,10 @@ WHERE job_progress => $job_progress, job_title => $job_title, job_description => $job_description, + job_host_uuid => $job_host_uuid, job_status => $job_status, modified_date => $modified_date, + modified_date_unix => $modified_date_unix, now_time => $now_time, started_seconds_ago => $started_seconds_ago, updated_seconds_ago => $updated_seconds_ago, @@ -4868,67 +4861,77 @@ WHERE } push @{$return}, { - job_uuid => $job_uuid, - job_command => $job_command, - job_data => $job_data, - job_picked_up_by => $job_picked_up_by, - job_picked_up_at => $job_picked_up_at, - job_updated => $job_updated, - job_name => $job_name, - job_progress => $job_progress, - job_title => $job_title, - job_description => $job_description, - job_status => $job_status, - modified_date => $modified_date, + job_uuid => $job_uuid, + job_command => $job_command, + job_data => $job_data, + job_picked_up_by => $job_picked_up_by, + job_picked_up_at => $job_picked_up_at, + job_updated => $job_updated, + job_name => $job_name, + job_progress => $job_progress, + job_title => $job_title, + job_description => $job_description, + job_status => $job_status, + job_host_uuid => $job_host_uuid, + modified_date => $modified_date, + modified_date_unix => $modified_date_unix, }; - $anvil->data->{jobs}{running}{$job_uuid}{job_command} = $job_command; - $anvil->data->{jobs}{running}{$job_uuid}{job_data} = $job_data; - $anvil->data->{jobs}{running}{$job_uuid}{job_picked_up_by} = $job_picked_up_by; - $anvil->data->{jobs}{running}{$job_uuid}{job_picked_up_at} = $job_picked_up_at; - $anvil->data->{jobs}{running}{$job_uuid}{job_updated} = $job_updated; - $anvil->data->{jobs}{running}{$job_uuid}{job_name} = $job_name; - $anvil->data->{jobs}{running}{$job_uuid}{job_progress} = $job_progress; - $anvil->data->{jobs}{running}{$job_uuid}{job_title} = $job_title; - $anvil->data->{jobs}{running}{$job_uuid}{job_description} = $job_description; - $anvil->data->{jobs}{running}{$job_uuid}{job_status} = $job_status; - $anvil->data->{jobs}{running}{$job_uuid}{modified_date} = $modified_date; + $anvil->data->{jobs}{running}{$job_uuid}{job_command} = $job_command; + $anvil->data->{jobs}{running}{$job_uuid}{job_data} = $job_data; + $anvil->data->{jobs}{running}{$job_uuid}{job_picked_up_by} = $job_picked_up_by; + $anvil->data->{jobs}{running}{$job_uuid}{job_picked_up_at} = $job_picked_up_at; + $anvil->data->{jobs}{running}{$job_uuid}{job_updated} = $job_updated; + $anvil->data->{jobs}{running}{$job_uuid}{job_name} = $job_name; + $anvil->data->{jobs}{running}{$job_uuid}{job_progress} = $job_progress; + $anvil->data->{jobs}{running}{$job_uuid}{job_title} = $job_title; + $anvil->data->{jobs}{running}{$job_uuid}{job_description} = $job_description; + $anvil->data->{jobs}{running}{$job_uuid}{job_status} = $job_status; + $anvil->data->{jobs}{running}{$job_uuid}{job_host_uuid} = $job_host_uuid; + $anvil->data->{jobs}{running}{$job_uuid}{modified_date} = $modified_date; + $anvil->data->{jobs}{running}{$job_uuid}{modified_date_unix} = $modified_date_unix; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "jobs::running::${job_uuid}::job_command" => $anvil->data->{jobs}{running}{$job_uuid}{job_command}, - "jobs::running::${job_uuid}::job_data" => $anvil->data->{jobs}{running}{$job_uuid}{job_data}, - "jobs::running::${job_uuid}::job_picked_up_by" => $anvil->data->{jobs}{running}{$job_uuid}{job_picked_up_by}, - "jobs::running::${job_uuid}::job_picked_up_at" => $anvil->data->{jobs}{running}{$job_uuid}{job_picked_up_at}, - "jobs::running::${job_uuid}::job_updated" => $anvil->data->{jobs}{running}{$job_uuid}{job_updated}, - "jobs::running::${job_uuid}::job_name" => $anvil->data->{jobs}{running}{$job_uuid}{job_name}, - "jobs::running::${job_uuid}::job_progress" => $anvil->data->{jobs}{running}{$job_uuid}{job_progress}, - "jobs::running::${job_uuid}::job_title" => $anvil->data->{jobs}{running}{$job_uuid}{job_title}, - "jobs::running::${job_uuid}::job_description" => $anvil->data->{jobs}{running}{$job_uuid}{job_description}, - "jobs::running::${job_uuid}::job_status" => $anvil->data->{jobs}{running}{$job_uuid}{job_status}, - "jobs::running::${job_uuid}::modified_date" => $anvil->data->{jobs}{running}{$job_uuid}{modified_date}, + "jobs::running::${job_uuid}::job_command" => $anvil->data->{jobs}{running}{$job_uuid}{job_command}, + "jobs::running::${job_uuid}::job_data" => $anvil->data->{jobs}{running}{$job_uuid}{job_data}, + "jobs::running::${job_uuid}::job_picked_up_by" => $anvil->data->{jobs}{running}{$job_uuid}{job_picked_up_by}, + "jobs::running::${job_uuid}::job_picked_up_at" => $anvil->data->{jobs}{running}{$job_uuid}{job_picked_up_at}, + "jobs::running::${job_uuid}::job_updated" => $anvil->data->{jobs}{running}{$job_uuid}{job_updated}, + "jobs::running::${job_uuid}::job_name" => $anvil->data->{jobs}{running}{$job_uuid}{job_name}, + "jobs::running::${job_uuid}::job_progress" => $anvil->data->{jobs}{running}{$job_uuid}{job_progress}, + "jobs::running::${job_uuid}::job_title" => $anvil->data->{jobs}{running}{$job_uuid}{job_title}, + "jobs::running::${job_uuid}::job_description" => $anvil->data->{jobs}{running}{$job_uuid}{job_description}, + "jobs::running::${job_uuid}::job_status" => $anvil->data->{jobs}{running}{$job_uuid}{job_status}, + "jobs::running::${job_uuid}::job_host_uuid" => $anvil->data->{jobs}{running}{$job_uuid}{job_host_uuid}, + "jobs::running::${job_uuid}::modified_date" => $anvil->data->{jobs}{running}{$job_uuid}{modified_date}, + "jobs::running::${job_uuid}::modified_date_unix" => $anvil->data->{jobs}{running}{$job_uuid}{modified_date}, }}); # Make it possible to sort by modified date for serial execution of similar jobs. - $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_command} = $job_command; - $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_data} = $job_data; - $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_picked_up_by} = $job_picked_up_by; - $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_picked_up_at} = $job_picked_up_at; - $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_updated} = $job_updated; - $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_name} = $job_name; - $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_progress} = $job_progress; - $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_title} = $job_title; - $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_description} = $job_description; - $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_status} = $job_status; + $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_command} = $job_command; + $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_data} = $job_data; + $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_picked_up_by} = $job_picked_up_by; + $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_picked_up_at} = $job_picked_up_at; + $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_updated} = $job_updated; + $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_name} = $job_name; + $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_progress} = $job_progress; + $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_title} = $job_title; + $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_description} = $job_description; + $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_status} = $job_status; + $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_host_uuid} = $job_host_uuid; + $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{modified_date_unix} = $modified_date_unix; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_command" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_command}, - "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_data" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_data}, - "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_picked_up_by" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_picked_up_by}, - "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_picked_up_at" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_picked_up_at}, - "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_updated" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_updated}, - "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_name" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_name}, - "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_progress" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_progress}, - "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_title" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_title}, - "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_description" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_description}, - "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_status" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_status}, + "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_command" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_command}, + "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_data" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_data}, + "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_picked_up_by" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_picked_up_by}, + "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_picked_up_at" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_picked_up_at}, + "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_updated" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_updated}, + "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_name" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_name}, + "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_progress" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_progress}, + "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_title" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_title}, + "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_description" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_description}, + "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_status" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_status}, + "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::job_host_uuid" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{job_host_uuid}, + "jobs::modified_date::${modified_date}::job_uuid::${job_uuid}::modified_date_unix" => $anvil->data->{jobs}{modified_date}{$modified_date}{job_uuid}{$job_uuid}{modified_date_unix}, }}); } @@ -4940,18 +4943,20 @@ WHERE foreach my $hash_ref (@{$return}) { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - job_uuid => $hash_ref->{job_uuid}, - job_command => $hash_ref->{job_command}, - job_data => $hash_ref->{job_data}, - job_picked_up_by => $hash_ref->{job_picked_up_by}, - job_picked_up_at => $hash_ref->{job_picked_up_at}, - job_updated => $hash_ref->{job_updated}, - job_name => $hash_ref->{job_name}, - job_progress => $hash_ref->{job_progress}, - job_title => $hash_ref->{job_title}, - job_description => $hash_ref->{job_description}, - job_status => $hash_ref->{job_status}, - modified_date => $hash_ref->{modified_date}, + job_uuid => $hash_ref->{job_uuid}, + job_command => $hash_ref->{job_command}, + job_data => $hash_ref->{job_data}, + job_picked_up_by => $hash_ref->{job_picked_up_by}, + job_picked_up_at => $hash_ref->{job_picked_up_at}, + job_updated => $hash_ref->{job_updated}, + job_name => $hash_ref->{job_name}, + job_progress => $hash_ref->{job_progress}, + job_title => $hash_ref->{job_title}, + job_description => $hash_ref->{job_description}, + job_status => $hash_ref->{job_status}, + job_host_uuid => $hash_ref->{job_host_uuid}, + modified_date => $hash_ref->{modified_date}, + modified_date_unix => $hash_ref->{modified_date_unix}, }}); } } diff --git a/Anvil/Tools/Network.pm b/Anvil/Tools/Network.pm index b5b3aa05..0434afcb 100644 --- a/Anvil/Tools/Network.pm +++ b/Anvil/Tools/Network.pm @@ -1525,6 +1525,20 @@ Parameters; This is the target's C<< host_uuid >> that we're looking to contact. +=head3 networks (optional, default 'bcn,mn,sn,ifn,any') + +This is the comma-separated list of networks to search for access over. The order presented is the order searched. Valid values are; + +* bcn (Back-Channel Network) +* mn (Migration Network) +* sn (Storage Network) +* ifn (Internet-Facing Network) +* any (Any other interface) + +=head3 test_access (optional, default '0') + +If set to C<< 1 >>, any matched IP will be tested. If this is set and the target can't be reached using that IP, it is skipped. If this is not set, the first match is returned. + =cut sub find_target_ip { @@ -1534,10 +1548,13 @@ sub find_target_ip my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Network->find_target_ip()" }}); - my $target_ip = ""; - my $host_uuid = defined $parameter->{host_uuid} ? $parameter->{host_uuid} : ""; + my $host_uuid = defined $parameter->{host_uuid} ? $parameter->{host_uuid} : ""; + my $networks = defined $parameter->{networks} ? $parameter->{networks} : ""; + my $test_access = defined $parameter->{test_access} ? $parameter->{test_access} : 0; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - host_uuid => $host_uuid, + host_uuid => $host_uuid, + networks => $networks, + test_access => $test_access, }}); if (not $host_uuid) @@ -1554,6 +1571,12 @@ sub find_target_ip return(""); } + if (not $networks) + { + $networks = "bcn,mn,sn,ifn,any"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { networks => $networks }}); + } + my $target_host_name = $anvil->data->{hosts}{host_uuid}{$host_uuid}{short_host_name}; my $short_host_name = $anvil->Get->short_host_name({debug => $debug}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { @@ -1561,38 +1584,52 @@ sub find_target_ip short_host_name => $short_host_name, }}); - $anvil->Network->load_ips({ - debug => $debug, - host_uuid => $anvil->Get->host_uuid, - host => $short_host_name, - clear => 1, - }); - - $anvil->Network->load_ips({ - debug => $debug, - host_uuid => $host_uuid, - host => $target_host_name, - clear => 1, - }); - - my ($match) = $anvil->Network->find_matches({ + my $target_ip = ""; + my $matches = $anvil->Network->find_access({ debug => $debug, - first => $short_host_name, - second => $target_host_name, - source => $THIS_FILE, - line => __LINE__, + target => $target_host_name, }); - if ($match) + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { matches => $matches }}); + + foreach my $preferred_network (split/,/, $networks) { - # Yup! - my $match_found = 0; - foreach my $interface (sort {$a cmp $b} keys %{$match->{$target_host_name}}) + last if $target_ip; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { preferred_network => $preferred_network }}); + foreach my $network_name (sort {$a cmp $b} keys %{$anvil->data->{network_access}}) { - $target_ip = $match->{$target_host_name}{$interface}{ip}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { target_ip => $target_ip }}); - last; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { network_name => $network_name }}); + if (($network_name !~ /^$preferred_network/) && ($preferred_network ne "any")) + { + next; + } + + my $this_target_ip = $anvil->data->{network_access}{$network_name}{target_ip_address}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { this_target_ip => $this_target_ip }}); + + if ($test_access) + { + my $access = $anvil->Remote->test_access({target => $this_target_ip}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + 's1:network_name' => $network_name, + 's2:this_target_ip' => $this_target_ip, + 's3:access' => $access, + }}); + + if ($access) + { + # We can use this one. + $target_ip = $this_target_ip; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { target_ip => $target_ip }}); + last; + } + } + else + { + # We're done. + $target_ip = $this_target_ip; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { target_ip => $target_ip }}); + } } - } return($target_ip); diff --git a/Anvil/Tools/Server.pm b/Anvil/Tools/Server.pm index 78b2c895..9d82781e 100644 --- a/Anvil/Tools/Server.pm +++ b/Anvil/Tools/Server.pm @@ -7,6 +7,8 @@ use strict; use warnings; use Scalar::Util qw(weaken isweak); use Data::Dumper; +use Text::Diff; +use Sys::Virt; our $VERSION = "3.0.0"; my $THIS_FILE = "Server.pm"; @@ -14,16 +16,19 @@ my $THIS_FILE = "Server.pm"; ### Methods; # active_migrations # boot_virsh +# connect_to_virsh # count_servers -# find +# find # To be replaced by Server->locate(); # find_processes # get_definition # get_runtime # get_status +# locate # map_network -# parse_definition # migrate_virsh +# parse_definition # shutdown_virsh +# update_definition =cut TODO @@ -293,6 +298,170 @@ WHERE return($success); } +=head2 connect_to_libvirt + +This creates a connection to the libvirtd daemon on the target host. The connection to the host will be stored in: + +* libvirtd::::connection + +If the connection succeeds, C<< 0 >> will be returned. If the connection fails, C<< 1 >> will be returned. + +parameters + +=head3 server_name (optional) + +If this is set to the name of a server, that server will be searched for and, if found, the handle to it will be stored in: + +* libvirtd::::server::::connection + +If the server is not found, that will be set to C<< 0 >>. + +B<< Note >>: This can be set to C<< all >> and all servers we can connect to will be stored. + +=head3 target (optional, default is the local short host name) + +This is the target to connect to. + +B<< Note >>: Don't use C<< localhost >>! If you do, it will be changed to the short host name. This is because C<< localhost >> is converted to C<< ::1 >> which can cause connection problems. + +=head3 target_ip (optional) + +If this is set, when building the URI, this IP or host name is used to connect. This allows the hash to use the C<< target >> name separately. + +=cut +sub connect_to_libvirt +{ + 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 => "Server->connect_to_libvirt()" }}); + + my $server_name = defined $parameter->{server_name} ? $parameter->{server_name} : ""; + my $target = defined $parameter->{target} ? $parameter->{target} : ""; + my $target_ip = defined $parameter->{target_ip} ? $parameter->{target_ip} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + server_name => $server_name, + target => $target, + target_ip => $target_ip, + }}); + + if ((not $target)or ($target eq "localhost")) + { + # Change to the short host name. + $target = $anvil->Get->short_host_name; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { target => $target }}); + } + + if (not $target_ip) + { + $target_ip = $target; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { target_ip => $target_ip }}); + } + + # Does the handle already exist? + if ((exists $anvil->data->{libvirtd}{$target}) && (ref($anvil->data->{libvirtd}{$target}{connection}) eq "Sys::Virt")) + { + # Is this connection alive? + my $info = $anvil->data->{libvirtd}{$target}{connection}->get_node_info(); + if (ref($info) eq "HASH") + { + # No need to connect. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0814", variables => { target => $target }}); + } + else + { + # Stale connection. + $anvil->data->{libvirtd}{$target}{connection} = ""; + } + } + else + { + $anvil->data->{libvirtd}{$target}{connection} = ""; + } + + # If we don't have a connection, try to establish one now. + if (not $anvil->data->{libvirtd}{$target}{connection}) + { + my $uri = "qemu+ssh://".$target_ip."/system"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { uri => $uri }}); + + # Test connect + eval { $anvil->data->{libvirtd}{$target}{connection} = Sys::Virt->new(uri => $uri); }; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + "libvirtd::${target}::connection" => $anvil->data->{libvirtd}{$target}{connection}, + }}); + if ($@) + { + # Throw an error, then clear the URI so that we just update the DB/on-disk definitions. + $anvil->data->{libvirtd}{$target}{connection} = 0; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "warning_0162", variables => { + host_name => $target, + uri => $uri, + error => $@, + }}); + return(1); + } + } + + if (($server_name) && ($server_name ne "all")) + { + if (ref($anvil->data->{libvirtd}{$target}{server}{$server_name}{connection}) eq "Sys::Virt::Domain") + { + # If this connection still valid? + my $uuid = $anvil->data->{libvirtd}{$target}{server}{$server_name}{connection}->get_uuid_string(); + if ($uuid) + { + # We're good. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0815", variables => { server_name => $server_name }}); + return(0); + } + else + { + # Stale connection. + $anvil->data->{libvirtd}{$target}{server}{$server_name}{connection} = ""; + } + } + else + { + $anvil->data->{libvirtd}{$target}{server}{$server_name}{connection} = ""; + } + } + + # If we have a server name, or if it's 'all', connect. + if ($server_name) + { + my $domain = ""; + my @domains = $anvil->data->{libvirtd}{$target}{connection}->list_all_domains(); + foreach my $domain_handle (@domains) + { + my $this_server_name = $domain_handle->get_name; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + domain_handle => $domain_handle, + this_server_name => $this_server_name, + }}); + if (($server_name ne "all") && ($this_server_name ne $server_name)) + { + next; + } + + $anvil->data->{libvirtd}{$target}{server}{$server_name}{connection} = $domain_handle; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + "libvirtd::${target}::server::${server_name}::connection" => $anvil->data->{libvirtd}{$target}{server}{$server_name}{connection}, + }}); + last; + } + } + + my $return = 0; + if (($server_name) && ($server_name ne "all") && (not $anvil->data->{libvirtd}{$target}{server}{$server_name}{connection})) + { + # Didn't find the server + return(1) + } + + return(0); +} =head2 count_servers @@ -361,6 +530,7 @@ pmsuspended - The domain has been suspended by guest power management, e.g. ente } +### TODO: Phase this out in favor for Server->locate() =head2 find This will look on the local or a remote machine for the list of servers that are running. @@ -985,8 +1155,7 @@ sub map_network target => $target, }}); - # NOTE: We don't use 'Server->find' as the hassle of tracking hosts to target isn't worth it. - # Get a list of servers. + ### TODO: Switch to using Server->locate() my $shell_call = $anvil->data->{path}{exe}{setsid}." --wait ".$anvil->data->{path}{exe}{virsh}." list"; my $output = ""; if ($anvil->Network->is_local({host => $target})) @@ -1059,6 +1228,275 @@ sub map_network } +=head2 locate + +B<< Note >>: This is meant to replace C<< Server->find >>. + +This walks through all known and accessible subnodes and DR hosts looking for a server. If a specific server is searched for and it's found running, the C<< short_host_name >> is returned. If there is a problem, C<< !!error!! >> is returned. + +If a specific requested server is found, or is being asked to search for all servers, the following data is stored; + +* server_location::host::::access = [0,1] +* server_location::host::::server::::status = +* server_location::host::::server::::active_definition = +* server_location::host::::server::::inactive_definition = +* server_location::host::::server::::definition_diff = +* server_location::host::::server::::file_definition = +* server_location::host::::server::::drbd_config = + +If the target was not accessible, C<< access >> is set to C<< 0 >>. This is meant to allow telling the difference between "we know there's no servers on that host" versus "we don't know what's there because we couldn't access it". + +If the server is found to be C<< running >> or C<< paused >>, then C<< active_definition >> is set and, if there's a difference, that will be stored. In all other states, the inactive XML is stored. + +The C<< status >> can be: + +* unknown # The server was found, but it has an unknown state +* running # Server is running. +* blocked # Server is blocked (IO contention?). +* paused # Server is paused (migration target?). +* in shutdown # Server is shutting down. +* shut off # Server is shut off. +* crashed # Server is crashed! +* pmsuspended # Server is suspended. + +If there is a problem, C<< !!error!! >> is returned. If the server is found on at least one host, C<< 0 >> is returned. If the server is not located anywhere, C<< 1 >> is returned. + +If the server has a replicated storage (DRBD) config and/or a definition file, whether the server is found running or not, will be recorded. This can be used to see if the server has been configured to run there or not. + +The connection to the host and to the server(s) is cached, for your use; + +* server_location::host::::connection = +* server_location::host::::server::::connection = + +C<< Note >>: By design, servers are set to 'undefined' on subnodes, so when the server shuts off, it disappears from libvirtd. This is normal and expected. + +Parameters; + +=head3 server_name (required) + +This is the name of the server being located. It can be set to C<< all >>, in which case all servers on all hosts are located. + +=cut +sub locate +{ + 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 => "Server->locate()" }}); + + my $server_name = defined $parameter->{server_name} ? $parameter->{server_name} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + server_name => $server_name, + }}); + + if (not $server_name) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Server->locate()", parameter => "server_name" }}); + return('!!error!!'); + } + + if (exists $anvil->data->{server_location}{host}) + { + delete $anvil->data->{server_location}{host}; + } + + # This will be set if the server is found to be 'running' on a host. + my $server_host = ""; + + # Connect to all hosts. + $anvil->Database->get_hosts({debug => $debug}); + + foreach my $host_name (sort {$a cmp $b} keys %{$anvil->data->{sys}{hosts}{by_name}}) + { + my $host_uuid = $anvil->data->{sys}{hosts}{by_name}{$host_name}; + my $host_type = $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_type}; + my $short_host_name = $anvil->data->{hosts}{host_uuid}{$host_uuid}{short_host_name}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:host_name' => $host_name, + 's2:host_uuid' => $host_uuid, + 's3:host_type' => $host_type, + 's4:short_host_name' => $short_host_name, + }}); + next if $host_type eq "striker"; + + # This will switch to '1' if we connect to libvirtd. + $anvil->data->{server_location}{host}{$short_host_name}{access} = 0; + + # What IP to use? Don't test access, it's too slow if there's several down hosts. + my $target_ip = $anvil->Network->find_target_ip({ + debug => $debug, + host_uuid => $host_uuid, + networks => "bcn,mn,sn,ifn", + test_access => 0, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { target_ip => $target_ip }}); + + if ($target_ip) + { + # Try to connect to libvirtd. + $anvil->Server->connect_to_libvirt({ + debug => $debug, + target => $short_host_name, + target_ip => $target_ip, + server_name => $server_name, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "libvirtd::${short_host_name}::connection" => $anvil->data->{libvirtd}{$short_host_name}{connection}, + }}); + if (ref($anvil->data->{libvirtd}{$short_host_name}{connection}) eq "Sys::Virt") + { + # We're connected! Collect the data on the requested server(s), if applicable. + $anvil->data->{server_location}{host}{$short_host_name}{access} = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "server_location::host::${short_host_name}::access" => $anvil->data->{server_location}{host}{$short_host_name}{access}, + }}); + + if ($server_name) + { + my $connection_handle = $anvil->data->{libvirtd}{$short_host_name}{connection}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { connection_handle => $connection_handle }}); + foreach my $this_server_name (sort {$a cmp $b} keys %{$anvil->data->{libvirtd}{$short_host_name}{server}}) + { + next if (ref($anvil->data->{libvirtd}{$short_host_name}{server}{$server_name}{connection}) ne "Sys::Virt::Domain"); + if (($server_name eq "all") or ($server_name eq $this_server_name)) + { + my $server_handle = $anvil->data->{libvirtd}{$short_host_name}{server}{$server_name}{connection}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { server_handle => $server_handle }}); + + # Get the server's state, then convert to a string + my ($state, $reason) = $server_handle->get_state(); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 'state' => $state, + reason => $reason, + }}); + + ### Reasons are dependent on the state. + ### See: https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainShutdownReason + my $server_state = "unknown"; + if ($state == 1) { $server_state = "running"; } # Server is running. + elsif ($state == 2) { $server_state = "blocked"; } # Server is blocked (IO contention?). + elsif ($state == 3) { $server_state = "paused"; } # Server is paused (migration target?). + elsif ($state == 4) { $server_state = "in shutdown"; } # Server is shutting down. + elsif ($state == 5) { $server_state = "shut off"; } # Server is shut off. + elsif ($state == 6) { $server_state = "crashed"; } # Server is crashed! + elsif ($state == 7) { $server_state = "pmsuspended"; } # Server is suspended. + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { server_state => $server_state }}); + + # Get the persistent definition + my $inactive_definition = $server_handle->get_xml_description(Sys::Virt::Domain::XML_INACTIVE); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { inactive_definition => $inactive_definition }}); + + # Get the active definition, if applicable. + my $active_definition = ""; + my $definition_diff = ""; + if (($server_state eq "running") or ($server_state eq "paused")) + { + # Get the active definition + $active_definition = $server_handle->get_xml_description(); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { active_definition => $active_definition }}); + + # Check for a diff. + $definition_diff = diff \$active_definition, \$inactive_definition, { STYLE => 'Unified' }; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { definition_diff => $definition_diff }}); + } + + if ($server_state eq "running") + { + $server_host = $short_host_name; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { server_host => $server_host }}); + } + + # If it's running, record the host. + $anvil->data->{server_location}{host}{$short_host_name}{server}{$server_name}{status} = $server_state; + $anvil->data->{server_location}{host}{$short_host_name}{server}{$server_name}{active_definition} = $active_definition; + $anvil->data->{server_location}{host}{$short_host_name}{server}{$server_name}{inactive_definition} = $inactive_definition; + $anvil->data->{server_location}{host}{$short_host_name}{server}{$server_name}{definition_diff} = $definition_diff; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "server_location::host::${short_host_name}::server::${server_name}::status" => $anvil->data->{server_location}{host}{$short_host_name}{server}{$server_name}{status}, + "server_location::host::${short_host_name}::server::${server_name}::active_definition" => $anvil->data->{server_location}{host}{$short_host_name}{server}{$server_name}{active_definition}, + "server_location::host::${short_host_name}::server::${server_name}::inactive_definition" => $anvil->data->{server_location}{host}{$short_host_name}{server}{$server_name}{inactive_definition}, + "server_location::host::${short_host_name}::server::${server_name}::definition_diff" => $anvil->data->{server_location}{host}{$short_host_name}{server}{$server_name}{definition_diff}, + }}); + } + } + + # If we've connected to the host, see if the XML definition file + # and/or DRBD config file exist. + my $servers = []; + if ($server_name eq "all") + { + # Search for any server we can find. + $anvil->Database->get_servers(); + foreach my $server_uuid (sort {$a cmp $b} keys %{$anvil->data->{servers}{server_uuid}}) + { + next if $anvil->data->{servers}{server_uuid}{$server_uuid}{server_state} eq "DELETED"; + my $this_server_name = $anvil->data->{servers}{server_uuid}{$server_uuid}{server_name}; + + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + server_uuid => $server_uuid, + this_server_name => $this_server_name, + }}); + push @{$servers}, $this_server_name; + } + } + else + { + push @{$servers}, $server_name; + } + + foreach my $this_server_name (sort {$a cmp $b} @{$servers}) + { + # Look for the files for the specified server. + $anvil->data->{server_location}{host}{$short_host_name}{server}{$this_server_name}{file_definition} = ""; + $anvil->data->{server_location}{host}{$short_host_name}{server}{$this_server_name}{drbd_config} = ""; + + # See if there's a definition file and/or a DRBD + # config file on this host. + my $definition_file = $anvil->data->{path}{directories}{shared}{definitions}."/".$this_server_name.".xml"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { definition_file => $definition_file }}); + + # Can I read the definition file? + my $definition_body = $anvil->Storage->read_file({ + debug => $debug, + file => $definition_file, + target => $target_ip, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { definition_body => $definition_body }}); + + if (($definition_body) && ($definition_body ne "!!error!!")) + { + $anvil->data->{server_location}{host}{$short_host_name}{server}{$this_server_name}{file_definition} = $definition_body; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "server_location::host::${short_host_name}::server::${this_server_name}::file_definition" => $anvil->data->{server_location}{host}{$short_host_name}{server}{$this_server_name}{file_definition}, + }}); + } + + my $drbd_config_file = $anvil->data->{path}{directories}{drbd_resources}."/".$this_server_name.".res"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { drbd_config_file => $drbd_config_file }}); + + my $drbd_body = $anvil->Storage->read_file({ + debug => $debug, + file => $drbd_config_file, + target => $target_ip, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { drbd_body => $drbd_body }}); + if (($drbd_body) && ($drbd_body ne "!!error!!")) + { + $anvil->data->{server_location}{host}{$short_host_name}{server}{$this_server_name}{drbd_config} = $drbd_body; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "server_location::host::${short_host_name}::server::${this_server_name}::drbd_config" => $anvil->data->{server_location}{host}{$short_host_name}{server}{$this_server_name}{drbd_config}, + }}); + } + } + } + } + } + } + + return($server_host); +} + =head2 migrate_virsh This will migrate (push or pull) a server from one node to another. If the migration was successful, C<< 1 >> is returned. Otherwise, C<< 0 >> is returned with a (hopefully) useful error being logged. @@ -1462,6 +1900,10 @@ The XML was dumped by C<< virsh >> from memory. The XML was read from the C<< definitions >> database table. +=head4 C<< test >> + +The XML is a test definition, and not actually from anywhere. + =head3 definition (required) This is the actual XML to be parsed. @@ -2193,29 +2635,27 @@ sub shutdown_virsh $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "sys::database::connections" => $anvil->data->{sys}{database}{connections} }}); if ($anvil->data->{sys}{database}{connections}) { - if ($anvil->data->{sys}{database}{connections}) + my $anvil_uuid = $anvil->Cluster->get_anvil_uuid({debug => $debug}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { anvil_uuid => $anvil_uuid }}); + + $server_uuid = $anvil->Get->server_uuid_from_name({ + debug => $debug, + server_name => $server, + anvil_uuid => $anvil_uuid, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { server_uuid => $server_uuid }}); + if (($server_uuid) && ($server_uuid ne "!!error!!")) { - my $anvil_uuid = $anvil->Cluster->get_anvil_uuid({debug => $debug}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { anvil_uuid => $anvil_uuid }}); - - $server_uuid = $anvil->Get->server_uuid_from_name({ - debug => $debug, - server_name => $server, - anvil_uuid => $anvil_uuid, - }); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { server_uuid => $server_uuid }}); - if (($server_uuid) && ($server_uuid ne "!!error!!")) + $anvil->Database->get_servers({debug => $debug}); + if (exists $anvil->data->{servers}{server_uuid}{$server_uuid}) { - $anvil->Database->get_servers({debug => $debug}); - if (exists $anvil->data->{servers}{server_uuid}{$server_uuid}) + my $old_state = $anvil->data->{servers}{server_uuid}{$server_uuid}{server_state}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { old_state => $old_state }}); + + if ($old_state ne "in shutdown") { - my $old_state = $anvil->data->{servers}{server_uuid}{$server_uuid}{server_state}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { old_state => $old_state }}); - - if ($old_state ne "in shutdown") - { - # Update it. - my $query = " + # Update it. + my $query = " UPDATE servers SET @@ -2224,9 +2664,8 @@ SET WHERE server_uuid = ".$anvil->Database->quote($server_uuid)." ;"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }}); - $anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__}); - } + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }}); + $anvil->Database->write({query => $query, source => $THIS_FILE, line => __LINE__}); } } } @@ -2287,8 +2726,12 @@ WHERE }}); # Mark it as stopped now. (if we have a server_uuid, we have a database connection) + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { server_uuid => $server_uuid }}); if ($server_uuid) { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + "sys::database::connections" => $anvil->data->{sys}{database}{connections}, + }}); if ($anvil->data->{sys}{database}{connections}) { $anvil->Database->get_servers({debug => $debug}); @@ -2342,6 +2785,224 @@ WHERE return($success); } +=head2 update_definition + +This takes a new server XML definition and saves it in the database and writes it out to the on-disk files. If either subnode or DR host is inacessible, this still returns success as C<< scan-server >> will pick up the new definition when the server comes back online. + +If there is a problem, C<< !!error!! >> is returned. If it is updated, C<< 0 >> is returned. + +Parameters; + +=head3 server (required) + +This is the name or UUID of the server being updated. + +=head3 new_definition_xml + +This is the new XML definition file. It will be parsed and sanity checked. + +=cut +sub update_definition +{ + 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 => "Server->update_definition()" }}); + + my $server = defined $parameter->{server} ? $parameter->{server} : ""; + my $new_definition_xml = defined $parameter->{new_definition_xml} ? $parameter->{new_definition_xml} : 0; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + server => $server, + new_definition_xml => $new_definition_xml, + }}); + + if (not $server) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Server->update_definition()", parameter => "server" }}); + return('!!error!!'); + } + if (not $new_definition_xml) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Server->update_definition()", parameter => "new_definition_xml" }}); + return('!!error!!'); + } + + my ($server_name, $server_uuid) = $anvil->Get->server_from_switch({ + debug => $debug, + server_string => $server, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + server_name => $server_name, + server_uuid => $server_uuid, + }}); + + # Do we have a valid server UUID? + $anvil->Database->get_anvils({debug => $debug}); + $anvil->Database->get_servers({debug => $debug}); + + if (not exists $anvil->data->{servers}{server_uuid}{$server_uuid}) + { + # Invalid. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "error_0463", variables => { + server => $server, + server_uuid => $server_uuid, + }}); + return('!!error!!'); + } + + # Validate the new XML + my $short_host_name = $anvil->Get->short_host_name(); + my $problem = $anvil->Server->parse_definition({ + debug => 2, + target => $short_host_name, + server => $server_name, + source => "test", + definition => $new_definition_xml, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { problem => $problem }}); + if ($problem) + { + # Failed to parse. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "error_0464", variables => { + server_name => $server_name, + xml => $new_definition_xml, + }}); + return('!!error!!'); + } + else + { + my $test_uuid = $anvil->data->{server}{$short_host_name}{$server_name}{test}{info}{uuid} // ""; + if ((not $test_uuid) or ($test_uuid ne $server_uuid)) + { + # Somehow the new XML is invalid. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "error_0464", variables => { + server_name => $server_name, + xml => $new_definition_xml, + }}); + return('!!error!!'); + } + } + + # Prep our variables. + my $anvil_uuid = $anvil->data->{servers}{server_uuid}{$server_uuid}{server_anvil_uuid}; + my $definition_uuid = $anvil->data->{servers}{server_uuid}{$server_uuid}{server_definition_uuid}; + my $db_definition_xml = $anvil->data->{servers}{server_uuid}{$server_uuid}{server_definition_xml}; + my $node1_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_node1_host_uuid}; + my $node1_host_name = $anvil->data->{hosts}{host_uuid}{$node1_host_uuid}{host_name}; + my $node2_host_uuid = $anvil->data->{anvils}{anvil_uuid}{$anvil_uuid}{anvil_node2_host_uuid}; + my $node2_host_name = $anvil->data->{hosts}{host_uuid}{$node2_host_uuid}{host_name}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + anvil_uuid => $anvil_uuid, + definition_uuid => $definition_uuid, + db_definition_xml => $db_definition_xml, + node1_host_uuid => $node1_host_uuid, + node1_host_name => $node1_host_name, + node2_host_uuid => $node2_host_uuid, + node2_host_name => $node2_host_name, + }}); + + # Is there a difference between the new and DB definition? + my $db_difference = diff \$db_definition_xml, \$new_definition_xml, { STYLE => 'Unified' }; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { db_difference => $db_difference }}); + + if ($db_difference) + { + # Update the DB. + $anvil->Database->insert_or_update_server_definitions({ + debug => $debug, + server_definition_uuid => $definition_uuid, + server_definition_server_uuid => $server_uuid, + server_definition_xml => $new_definition_xml, + }); + } + + # Look for definitions + my $hosts = [$node1_host_uuid, $node2_host_uuid]; + foreach my $dr_host_name (sort {$a cmp $b} keys %{$anvil->data->{dr_links}{by_anvil_uuid}{$anvil_uuid}{dr_link_host_name}}) + { + my $dr_link_uuid = $anvil->data->{dr_links}{by_anvil_uuid}{$anvil_uuid}{dr_link_host_name}{$dr_host_name}{dr_link_uuid}; + my $dr_host_uuid = $anvil->data->{dr_links}{dr_link_uuid}{$dr_link_uuid}{dr_link_host_uuid}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:dr_host_name' => $dr_host_name, + 's2:dr_host_uuid' => $dr_host_uuid, + 's3:dr_link_uuid' => $dr_link_uuid, + }}); + push @{$hosts}, $dr_host_uuid; + } + + # Get the host UUIDs for the node this server is hosted by. + my $definition_file = $anvil->data->{path}{directories}{shared}{definitions}."/".$server_name.".xml"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { definition_file => $definition_file }}); + foreach my $host_uuid (@{$hosts}) + { + # Find a target_ip (local will be detected as local in the file read/write) + my $target_ip = $anvil->Network->find_target_ip({ + debug => 2, + host_uuid => $host_uuid, + test_access => 1, + networks => "bcn,mn,sn,ifn,any", + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { target_ip => $target_ip }}); + if ($target_ip) + { + # Read the old definition. + my $old_definition_xml = $anvil->Storage->read_file({ + debug => $debug, + file => $definition_file, + target => $target_ip, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { old_definition_xml => $old_definition_xml }}); + ### TODO: Handle when the definition file simply doesn't exist. + if ((not $old_definition_xml) or ($old_definition_xml eq "!!error!!") or ($old_definition_xml !~ /data->{hosts}{host_uuid}{$host_uuid}{host_name}; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "warning_0163", variables => { + server_name => $server_name, + host_name => $host_name, + file => $definition_file, + }}); + } + else + { + my $file_difference = diff \$old_definition_xml, \$new_definition_xml, { STYLE => 'Unified' }; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { file_difference => $file_difference }}); + + if ($file_difference) + { + # Update + my $error = $anvil->Storage->write_file({ + debug => $debug, + file => $definition_file, + body => $new_definition_xml, + backup => 1, + overwrite => 1, + mode => "0644", + group => "root", + user => "root", + target => $target_ip, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { error => $error }}); + if ($error) + { + my $host_name = $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_name}; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => "warning_0164", variables => { + server_name => $server_name, + host_name => $host_name, + file => $definition_file, + }}); + } + } + } + + # If the server running or defined here? + + } + } + + return(0); +} + # =head3 # # Private Functions; diff --git a/Anvil/Tools/Storage.pm b/Anvil/Tools/Storage.pm index 40fc9d82..da7491a4 100644 --- a/Anvil/Tools/Storage.pm +++ b/Anvil/Tools/Storage.pm @@ -776,7 +776,7 @@ sub check_files } elsif ($file_location_ready) { - # File doesn't exist but is marked as read, mark it as not ready. + # File doesn't exist but is marked as ready, mark it as not ready. $anvil->Database->insert_or_update_file_locations({ debug => $debug, file_location_uuid => $file_location_uuid, @@ -2744,7 +2744,7 @@ AND This creates a directory (and any parent directories). - $anvil->Storage->make_directory({directory => "/foo/bar/baz", owner => "me", grou[ => "me", group => 755}); + $anvil->Storage->make_directory({directory => "/foo/bar/baz", owner => "me", group => "me", mode => "0755"}); If it fails to create the directory, C<< 1 >> will be returned. Otherwise, C<< 0 >> will be returned. diff --git a/cgi-bin/set_power b/cgi-bin/set_power index e49129ac..536fbb7d 100755 --- a/cgi-bin/set_power +++ b/cgi-bin/set_power @@ -107,7 +107,7 @@ sub set_host_power elsif (not $is_host_on && $on) { $anvil->Database->insert_or_update_jobs({ - job_command => $anvil->data->{path}{directories}{tools}."/striker-boot-machine --host-uuid ".$host_uuid, + job_command => $anvil->data->{path}{directories}{tools}."/striker-boot-machine --host ".$host_uuid, job_description => "job_0335", job_name => "cgi-bin::set_power::on", job_progress => 0, diff --git a/man/Makefile.am b/man/Makefile.am index effeaebd..44a81a00 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -71,6 +71,7 @@ dist_man8_MANS = \ striker-purge-target.8 \ striker-scan-network.8 \ striker-show-db-counts.8 \ + striker-show-jobs.8 \ striker-update-cluster.8 \ tool-fio-tester.8 \ unfence_pacemaker.8 diff --git a/man/anvil-manage-host.8 b/man/anvil-manage-host.8 index e69de29b..f6dbf9d8 100644 --- a/man/anvil-manage-host.8 +++ b/man/anvil-manage-host.8 @@ -0,0 +1,57 @@ +.\" Manpage for the Anvil! server boot program +.\" Contact mkelly@alteeve.com to report issues, concerns or suggestions. +.TH anvil-manage-host "8" "Octobober 12 2023" "Anvil! Intelligent Availability™ Platform" +.SH NAME +anvil-manage-host \- Tool used to check or set various configuration options for a host. +.SH SYNOPSIS +.B anvil-manage-host +\fI\, \/\fR[\fI\,options\/\fR] +.SH DESCRIPTION + +.TP +.SH OPTIONS +.TP +\-?, \-h, \fB\-\-help\fR +Show this man page. +.TP +\fB\-\-log-secure\fR +When logging, record sensitive data, like passwords. +.TP +\-v, \-vv, \-vvv +Set the log level to 1, 2 or 3 respectively. Be aware that level 3 generates a significant amount of log data. +.SS "Commands:" +.TP +\fB\-\-age\-out\-database\fR +This requests the database check for records that are too old and purge them. +.TP +\fB\-\-check\-configured\fR +Check to see if the host is marked as configured or yet. +.TP +\fB\-\-check\-database\fR +This checks to see if the database is enabled or not. +.TP +\fB\-\-check\-network\-mapping\fR +This reports if the host is currently in network mapping (this disables several features and watches the network states much more frequently) +.TP +\fB\-\-database\-active\fR +This enables the database on the local Striker dashboard. +.TP +\fB\-\-database\-inactive\fR +This disables the database on the local Striker dashboard. +.TP +\fB\-\-disable\-network\-mapping\fR +This disables the network mapping mode. +.TP +\fB\-\-enable\-network\-mapping\fR +This enables the network mapping mode. +.TP +\fB\-\-mark\-configured\fR +This marks the host as having been configured. +.TP +\fB\-\-resync\-database\fR +This forces a database resync if two or more strikers are online. +.IP +.SH AUTHOR +Written by Madison Kelly, Alteeve staff and the Anvil! project contributors. +.SH "REPORTING BUGS" +Report bugs to users@clusterlabs.org diff --git a/man/anvil-manage-server-storage.8 b/man/anvil-manage-server-storage.8 index d7c9c79c..2e2c23f7 100644 --- a/man/anvil-manage-server-storage.8 +++ b/man/anvil-manage-server-storage.8 @@ -42,7 +42,7 @@ This is the disk being worked on. For optical disks, it's the drive that an opti When not specified, if only one disk exists, it will be chosen automatically. .TP \fB\-\-eject\fR -This ejects the optical disc (ISO) in the drive specified by \fB\-\-disk\fR. +This ejects the optical disc (ISO) in the drive specified by \fB\-\-optical\fR. .TP \fB\-\-job\-uuid\fR This is the jobs -> job_uuid to execute. Generally this is only used by other programs. diff --git a/man/anvil-manage-server-system.8 b/man/anvil-manage-server-system.8 new file mode 100644 index 00000000..c8079cbc --- /dev/null +++ b/man/anvil-manage-server-system.8 @@ -0,0 +1,39 @@ +.\" Manpage for the Anvil! server system manager +.\" Contact mkelly@alteeve.com to report issues, concerns or suggestions. +.TH anvil-manage-server-system "8" "August 30 2023" "Anvil! Intelligent Availability™ Platform" +.SH NAME +anvil-manage-server-system \- Tool used to manage the system configuration of a hosted server. +.SH SYNOPSIS +.B anvil-manage-server-system +\fI\, \/\fR[\fI\,options\/\fR] +.SH DESCRIPTION +anvil-manage-server-system \- This tool is used to manage various system configuration components of hosted servers. Storage is NOT managed here, see 'anvil-manage-server-storage' for that. +.TP +When called without switches, the list of servers than can be worked on will be displayed. +.TP +.SH OPTIONS +.TP +\-?, \-h, \fB\-\-help\fR +Show this man page. +.TP +\fB\-\-log-secure\fR +When logging, record sensitive data, like passwords. +.TP +\-v, \-vv, \-vvv +Set the log level to 1, 2 or 3 respectively. Be aware that level 3 generates a significant amount of log data. +.SS "Commands:" +.TP +\fB\-\-boot\-menu\fR +.TP +When called without a value, it shows if the boot menu is enabled or not. If called with 'yes', it enables the boot menu. If called with 'no', the boot meny is disabled. +.TP +\fB\-\-job\-uuid\fR +This is the jobs -> job_uuid to execute. Generally this is only used by other programs. +.TP +\fB\-\-\fR + +.IP +.SH AUTHOR +Written by Madison Kelly, Alteeve staff and the Anvil! project contributors. +.SH "REPORTING BUGS" +Report bugs to users@clusterlabs.org diff --git a/man/striker-boot-machine.8 b/man/striker-boot-machine.8 index e69de29b..32f90945 100644 --- a/man/striker-boot-machine.8 +++ b/man/striker-boot-machine.8 @@ -0,0 +1,38 @@ +.\" Manpage for the Anvil! server boot program +.\" Contact mkelly@alteeve.com to report issues, concerns or suggestions. +.TH striker-boot-machine "8" "October 12 2023" "Anvil! Intelligent Availability™ Platform" +.SH NAME +striker-boot-machine \- Tool used to boot physical machines that have IPMI configuration information. +.SH SYNOPSIS +.B striker-boot-machine +\fI\, \/\fR[\fI\,options\/\fR] +.SH DESCRIPTION +This tool is used to power up any machine in the cluster with IPMI configuration in the database. Typically this is subnodes and DR hosts, but could also be Striker dashboards if they have an IPMI BMC. If the server is found to already be om, it will NOT be booted again. +.TP +.SH OPTIONS +.TP +\-?, \-h, \fB\-\-help\fR +Show this man page. +.TP +\fB\-\-log-secure\fR +When logging, record sensitive data, like passwords. +.TP +\-v, \-vv, \-vvv +Set the log level to 1, 2 or 3 respectively. Be aware that level 3 generates a significant amount of log data. +.SS "Commands:" +.TP +\fB\-\-host\fR +This is the host name or UUID of the server to power on. If this is set to 'all', all machines that have IPMI configuration information will be checked, and if it's found to be off, it will be booted. +.TP +\fB\-\-host-uuid\fR +This is effectively the same as \fB\-\-host\fR, but provides backwards compatibility. It's use is discouraged. +.TP +\fB\-\-job\-uuid\fR +If this is set, the job will be processed. +.TP +Be aware that when this is used, if a server fails to boot, no further servers will be started. +.IP +.SH AUTHOR +Written by Madison Kelly, Alteeve staff and the Anvil! project contributors. +.SH "REPORTING BUGS" +Report bugs to users@clusterlabs.org diff --git a/man/striker-show-jobs.8 b/man/striker-show-jobs.8 new file mode 100644 index 00000000..f202991d --- /dev/null +++ b/man/striker-show-jobs.8 @@ -0,0 +1,37 @@ +.\" Manpage for the Anvil! server boot program +.\" Contact mkelly@alteeve.com to report issues, concerns or suggestions. +.TH striker-show-jobs "8" "October 18 2023" "Anvil! Intelligent Availability™ Platform" +.SH NAME +striker-show-jobs \- This shows the queued, running and (recently) completed jobs. +.SH SYNOPSIS +.B striker-show-jobs +\fI\, \/\fR[\fI\,options\/\fR] +.SH DESCRIPTION +This shows information about jobs that are queued, running or completed within a set amount of time on the command line. +.TP +.SH OPTIONS +.TP +\-?, \-h, \fB\-\-help\fR +Show this man page. +.TP +\fB\-\-log-secure\fR +When logging, record sensitive data, like passwords. +.TP +\-v, \-vv, \-vvv +Set the log level to 1, 2 or 3 respectively. Be aware that level 3 generates a significant amount of log data. +.SS "Commands:" +.TP +\fB\-\-ended\-within\fR +This allows viewing jobs that have already completed. This is the number of seconds (default '300') back in time that you want to see completed jobs. Jobs that completed more than this amount of time ago will be ignored. +.TP +\fB\-\-host\fR +This is the host name or UUID that you want to see jobs from. The default is 'all' and shows all machines in the Anvil! cluster. +.TP +Setting this to '0' will hide completed jobs. +\fB\-\-job\-uuid\fR +This allows showing the details of only one specific job. +.IP +.SH AUTHOR +Written by Madison Kelly, Alteeve staff and the Anvil! project contributors. +.SH "REPORTING BUGS" +Report bugs to users@clusterlabs.org diff --git a/ocf/alteeve/server b/ocf/alteeve/server index c8ef3978..63776983 100755 --- a/ocf/alteeve/server +++ b/ocf/alteeve/server @@ -623,6 +623,13 @@ sub start_server # Start the resource, if needed. start_drbd_resource($anvil); + # Get a connection so that we can mark is as being in shutdown and shut off. + $anvil->Database->connect({ + check_for_resync => 0, + retry => 0, + sensitive => 1, + }); + # Still alive? Boot! my ($success) = $anvil->Server->boot_virsh({debug => 2, server => $server}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { success => $success }}); @@ -1080,6 +1087,7 @@ sub start_drbd_resource return(0); } +### TODO: Rework this to use Server->connect_to_libvirtd and phase out Server->find(). # This uses the DRBD information to find other peers and see if the server is running on them. sub find_server { @@ -1173,6 +1181,13 @@ sub stop_server $anvil->System->check_storage(); $anvil->Server->get_status({debug => 2, server => $server}); + # Get a connection so that we can mark is as being in shutdown and shut off. + $anvil->Database->connect({ + check_for_resync => 0, + retry => 0, + sensitive => 1, + }); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0313", variables => { server => $server }}); my $success = $anvil->Server->shutdown_virsh({server => $server}); if (not $success) diff --git a/scancore-agents/scan-apc-pdu/scan-apc-pdu b/scancore-agents/scan-apc-pdu/scan-apc-pdu index a49d97b9..604aa394 100755 --- a/scancore-agents/scan-apc-pdu/scan-apc-pdu +++ b/scancore-agents/scan-apc-pdu/scan-apc-pdu @@ -185,11 +185,11 @@ if ($anvil->data->{switches}{purge}) # active DB. my $host_uuid = $anvil->Get->host_uuid(); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { - host_uuid => $host_uuid, - "sys::database::active_uuid" => $anvil->data->{sys}{database}{active_uuid}, - "switches::force" => $anvil->data->{switches}{force}, + host_uuid => $host_uuid, + "sys::database::primary_db" => $anvil->data->{sys}{database}{primary_db}, + "switches::force" => $anvil->data->{switches}{force}, }}); -if ((not $anvil->data->{switches}{force}) && ($anvil->data->{sys}{database}{active_uuid} ne $host_uuid)) +if ((not $anvil->data->{switches}{force}) && ($anvil->data->{sys}{database}{primary_db} ne $host_uuid)) { # Don't run. $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "scan_apc_pdu_message_0041", variables => { program => $THIS_FILE }}); diff --git a/scancore-agents/scan-cluster/scan-cluster b/scancore-agents/scan-cluster/scan-cluster index 3d92ed4f..dac1623c 100755 --- a/scancore-agents/scan-cluster/scan-cluster +++ b/scancore-agents/scan-cluster/scan-cluster @@ -205,6 +205,7 @@ sub cib_cleanup return(0); } +### TODO: Rework this to use Server->connect_to_libvirtd and phase out Server->find(). # This looks for failed resource and, if found, tries to recover them. sub check_resources { diff --git a/scancore-agents/scan-server/scan-server b/scancore-agents/scan-server/scan-server index f0b30d2f..939cf4a7 100755 --- a/scancore-agents/scan-server/scan-server +++ b/scancore-agents/scan-server/scan-server @@ -12,8 +12,7 @@ # 2 = libvirtd is not running. # # BUG: -# - Check that an update on disk is not overwritten by the old config still being in memory for a still- -# running VM (specifically, RAM updates) +# - Servers that are off, but not marked as such in the DB, needs to be updated. (See: line ~1105) # # TODO: # - Move location constraints to the host node if the server is not on the preferred host (this happens after @@ -436,7 +435,12 @@ sub collect_data # Get the server state. my $server_state = get_server_state($anvil, $server_name); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { server_state => $server_state }}); - + + $anvil->data->{'scan-server'}{server_name}{$server_name}{server_state} = $server_state; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + "scan-server::server_name::${server_name}::server_state" => $anvil->data->{'scan-server'}{server_name}{$server_name}{server_state}, + }}); + # This is set to 1 when we add a server, supressing detailed change checks as we'll have # already registered an alert. my $added = 0; diff --git a/share/words.xml b/share/words.xml index 8b851e10..0ee0c687 100644 --- a/share/words.xml +++ b/share/words.xml @@ -726,6 +726,13 @@ The creation of the new replicatedd disk is incomplete, manual intervention is r ==================== The creation of the new replicated disk is incomplete, manual intervention is required!]]> + + @@ -1149,6 +1156,10 @@ resource #!variable!server!# { On Battery Estimated Runtime Last Updated + Optical Disc + Queued Jobs + Jobs In Progress + Completed Jobs Configure Network @@ -2613,6 +2624,9 @@ The file: [#!variable!file!#] needs to be updated. The difference is: - The host: [#!variable!host_name!#] is not configured, skipping it. The file: [#!variable!full_path!#] is in the database multiple times. This could be an artifact from peering Strikers. Selecting an entry to remove... Deleting the 'files' database entry for the file uuid: [#!variable!file_uuid!#]. + There is an existing a functioning connection to: [#!variable!target!#], no need to reconnect. + There is an existing a functioning connection to the server: [#!variable!server_name!#], no need to reconnect. + Waiting for: [#!variable!delay!#] seconds. The host name: [#!variable!target!#] does not resolve to an IP address. @@ -3161,6 +3175,10 @@ Proceed? [y/N] Preparing to managing storage for a server. Running checks before processing power request... The job to: [#!variable!task!#] this host has been picked up. + This host is already configured to map the network. + This host is now configured to map the network. + This host is already NOT configured to map the network. + This host is no longer configured to map the network. Normal Password @@ -3663,6 +3681,7 @@ Here we will inject 't_0006', which injects 't_0001' which has a variable: [#!st - Server is crashed! - Server is suspended. - Server is in an unknown state (int: [#!variable!state!#]). + ]]> diff --git a/striker-ui-api/out/index.js b/striker-ui-api/out/index.js index c235cd3a..7ce859f2 100644 --- a/striker-ui-api/out/index.js +++ b/striker-ui-api/out/index.js @@ -1,2 +1,2 @@ /*! For license information please see index.js.LICENSE.txt */ -(()=>{var e={9078:(e,t,n)=>{"use strict";var r=n(159),a=n(983);function o(e){if(!(this instanceof o))return new o(e);this.headers=e.headers,this.negotiator=new r(e)}function i(e){return-1===e.indexOf("/")?a.lookup(e):e}function s(e){return"string"==typeof e}e.exports=o,o.prototype.type=o.prototype.types=function(e){var t=e;if(t&&!Array.isArray(t)){t=new Array(arguments.length);for(var n=0;n{"use strict";function t(e,n,r){for(var a=0;a0&&Array.isArray(o)?t(o,n,r-1):n.push(o)}return n}function n(e,t){for(var r=0;r{"use strict";var r=n(412)("body-parser"),a=Object.create(null);function o(e){return function(){return function(e){var t=a[e];if(void 0!==t)return t;switch(e){case"json":t=n(6035);break;case"raw":t=n(187);break;case"text":t=n(6560);break;case"urlencoded":t=n(4861)}return a[e]=t}(e)}}t=e.exports=r.function((function(e){var n=Object.create(e||null,{type:{configurable:!0,enumerable:!0,value:void 0,writable:!0}}),r=t.urlencoded(n),a=t.json(n);return function(e,t,n){a(e,t,(function(a){if(a)return n(a);r(e,t,n)}))}}),"bodyParser: use individual json/urlencoded middlewares"),Object.defineProperty(t,"json",{configurable:!0,enumerable:!0,get:o("json")}),Object.defineProperty(t,"raw",{configurable:!0,enumerable:!0,get:o("raw")}),Object.defineProperty(t,"text",{configurable:!0,enumerable:!0,get:o("text")}),Object.defineProperty(t,"urlencoded",{configurable:!0,enumerable:!0,get:o("urlencoded")})},3211:(e,t,n)=>{"use strict";var r=n(9009),a=n(6149),o=n(1045),i=n(4914),s=n(338),c=n(8170),u=n(9796);e.exports=function(e,t,n,p,l,d){var f,m,v=d;e._body=!0;var h=null!==v.encoding?v.encoding:null,b=v.verify;try{m=function(e,t,n){var a,o=(e.headers["content-encoding"]||"identity").toLowerCase(),i=e.headers["content-length"];if(t('content-encoding "%s"',o),!1===n&&"identity"!==o)throw r(415,"content encoding unsupported",{encoding:o,type:"encoding.unsupported"});switch(o){case"deflate":a=u.createInflate(),t("inflate body"),e.pipe(a);break;case"gzip":a=u.createGunzip(),t("gunzip body"),e.pipe(a);break;case"identity":(a=e).length=i;break;default:throw r(415,'unsupported content encoding "'+o+'"',{encoding:o,type:"encoding.unsupported"})}return a}(e,l,v.inflate),f=m.length,m.length=void 0}catch(e){return n(e)}if(v.length=f,v.encoding=b?null:h,null===v.encoding&&null!==h&&!i.encodingExists(h))return n(r(415,'unsupported charset "'+h.toUpperCase()+'"',{charset:h.toLowerCase(),type:"charset.unsupported"}));l("read body"),o(m,v,(function(o,u){var d;if(o)return d="encoding.unsupported"===o.type?r(415,'unsupported charset "'+h.toUpperCase()+'"',{charset:h.toLowerCase(),type:"charset.unsupported"}):r(400,o),m!==e&&(c(e),a(m,!0)),void function(e,t){s.isFinished(e)?t():(s(e,t),e.resume())}(e,(function(){n(r(400,d))}));if(b)try{l("verify body"),b(e,t,u,h)}catch(e){return void n(r(403,e,{body:u,type:e.type||"entity.verify.failed"}))}var f=u;try{l("parse body"),f="string"!=typeof u&&null!==h?i.decode(u,h):u,e.body=p(f)}catch(e){return void n(r(400,e,{body:f,type:e.type||"entity.parse.failed"}))}n()}))}},6035:(e,t,n)=>{"use strict";var r=n(9830),a=n(7811),o=n(9009),i=n(5158)("body-parser:json"),s=n(3211),c=n(273);e.exports=function(e){var t=e||{},n="number"!=typeof t.limit?r.parse(t.limit||"100kb"):t.limit,l=!1!==t.inflate,d=t.reviver,f=!1!==t.strict,m=t.type||"application/json",v=t.verify||!1;if(!1!==v&&"function"!=typeof v)throw new TypeError("option verify must be function");var h="function"!=typeof m?function(e){return function(t){return Boolean(c(t,e))}}(m):m;function b(e){if(0===e.length)return{};if(f){var t=(n=e,(r=u.exec(n))?r[1]:void 0);if("{"!==t&&"["!==t)throw i("strict violation"),function(e,t){var n=e.indexOf(t),r=-1!==n?e.substring(0,n)+"#":"";try{throw JSON.parse(r),new SyntaxError("strict violation")}catch(e){return p(e,{message:e.message.replace("#",t),stack:e.stack})}}(e,t)}var n,r;try{return i("parse json"),JSON.parse(e,d)}catch(e){throw p(e,{message:e.message,stack:e.stack})}}return function(e,t,r){if(e._body)return i("body already parsed"),void r();if(e.body=e.body||{},!c.hasBody(e))return i("skip empty body"),void r();if(i("content-type %j",e.headers["content-type"]),!h(e))return i("skip parsing"),void r();var u=function(e){try{return(a.parse(e).parameters.charset||"").toLowerCase()}catch(e){return}}(e)||"utf-8";if("utf-"!==u.slice(0,4))return i("invalid charset"),void r(o(415,'unsupported charset "'+u.toUpperCase()+'"',{charset:u,type:"charset.unsupported"}));s(e,t,r,b,i,{encoding:u,inflate:l,limit:n,verify:v})}};var u=/^[\x20\x09\x0a\x0d]*([^\x20\x09\x0a\x0d])/;function p(e,t){for(var n=Object.getOwnPropertyNames(e),r=0;r{"use strict";var r=n(9830),a=n(5158)("body-parser:raw"),o=n(3211),i=n(273);e.exports=function(e){var t=e||{},n=!1!==t.inflate,s="number"!=typeof t.limit?r.parse(t.limit||"100kb"):t.limit,c=t.type||"application/octet-stream",u=t.verify||!1;if(!1!==u&&"function"!=typeof u)throw new TypeError("option verify must be function");var p="function"!=typeof c?function(e){return function(t){return Boolean(i(t,e))}}(c):c;function l(e){return e}return function(e,t,r){return e._body?(a("body already parsed"),void r()):(e.body=e.body||{},i.hasBody(e)?(a("content-type %j",e.headers["content-type"]),p(e)?void o(e,t,r,l,a,{encoding:null,inflate:n,limit:s,verify:u}):(a("skip parsing"),void r())):(a("skip empty body"),void r()))}}},6560:(e,t,n)=>{"use strict";var r=n(9830),a=n(7811),o=n(5158)("body-parser:text"),i=n(3211),s=n(273);e.exports=function(e){var t=e||{},n=t.defaultCharset||"utf-8",c=!1!==t.inflate,u="number"!=typeof t.limit?r.parse(t.limit||"100kb"):t.limit,p=t.type||"text/plain",l=t.verify||!1;if(!1!==l&&"function"!=typeof l)throw new TypeError("option verify must be function");var d="function"!=typeof p?function(e){return function(t){return Boolean(s(t,e))}}(p):p;function f(e){return e}return function(e,t,r){if(e._body)return o("body already parsed"),void r();if(e.body=e.body||{},!s.hasBody(e))return o("skip empty body"),void r();if(o("content-type %j",e.headers["content-type"]),!d(e))return o("skip parsing"),void r();var p=function(e){try{return(a.parse(e).parameters.charset||"").toLowerCase()}catch(e){return}}(e)||n;i(e,t,r,f,o,{encoding:p,inflate:c,limit:u,verify:l})}}},4861:(e,t,n)=>{"use strict";var r=n(9830),a=n(7811),o=n(9009),i=n(5158)("body-parser:urlencoded"),s=n(412)("body-parser"),c=n(3211),u=n(273);e.exports=function(e){var t=e||{};void 0===t.extended&&s("undefined extended: provide extended option");var n=!1!==t.extended,p=!1!==t.inflate,f="number"!=typeof t.limit?r.parse(t.limit||"100kb"):t.limit,m=t.type||"application/x-www-form-urlencoded",v=t.verify||!1;if(!1!==v&&"function"!=typeof v)throw new TypeError("option verify must be function");var h=n?function(e){var t=void 0!==e.parameterLimit?e.parameterLimit:1e3,n=d("qs");if(isNaN(t)||t<1)throw new TypeError("option parameterLimit must be a positive number");return isFinite(t)&&(t|=0),function(e){var r=l(e,t);if(void 0===r)throw i("too many parameters"),o(413,"too many parameters",{type:"parameters.too.many"});var a=Math.max(100,r);return i("parse extended urlencoding"),n(e,{allowPrototypes:!0,arrayLimit:a,depth:1/0,parameterLimit:t})}}(t):function(e){var t=void 0!==e.parameterLimit?e.parameterLimit:1e3,n=d("querystring");if(isNaN(t)||t<1)throw new TypeError("option parameterLimit must be a positive number");return isFinite(t)&&(t|=0),function(e){if(void 0===l(e,t))throw i("too many parameters"),o(413,"too many parameters",{type:"parameters.too.many"});return i("parse urlencoding"),n(e,void 0,void 0,{maxKeys:t})}}(t),b="function"!=typeof m?function(e){return function(t){return Boolean(u(t,e))}}(m):m;function g(e){return e.length?h(e):{}}return function(e,t,n){if(e._body)return i("body already parsed"),void n();if(e.body=e.body||{},!u.hasBody(e))return i("skip empty body"),void n();if(i("content-type %j",e.headers["content-type"]),!b(e))return i("skip parsing"),void n();var r=function(e){try{return(a.parse(e).parameters.charset||"").toLowerCase()}catch(e){return}}(e)||"utf-8";if("utf-8"!==r)return i("invalid charset"),void n(o(415,'unsupported charset "'+r.toUpperCase()+'"',{charset:r,type:"charset.unsupported"}));c(e,t,n,g,i,{debug:i,encoding:r,inflate:p,limit:f,verify:v})}};var p=Object.create(null);function l(e,t){for(var n=0,r=0;-1!==(r=e.indexOf("&",r));)if(r++,++n===t)return;return n}function d(e){var t=p[e];if(void 0!==t)return t.parse;switch(e){case"qs":t=n(129);break;case"querystring":t=n(3477)}return p[e]=t,t.parse}},6744:(e,t,n)=>{"use strict";const r=n(3349),a=n(7529),o=n(8050),i=n(4339),s=(e,t={})=>{let n=[];if(Array.isArray(e))for(let r of e){let e=s.create(r,t);Array.isArray(e)?n.push(...e):n.push(e)}else n=[].concat(s.create(e,t));return t&&!0===t.expand&&!0===t.nodupes&&(n=[...new Set(n)]),n};s.parse=(e,t={})=>i(e,t),s.stringify=(e,t={})=>r("string"==typeof e?s.parse(e,t):e,t),s.compile=(e,t={})=>("string"==typeof e&&(e=s.parse(e,t)),a(e,t)),s.expand=(e,t={})=>{"string"==typeof e&&(e=s.parse(e,t));let n=o(e,t);return!0===t.noempty&&(n=n.filter(Boolean)),!0===t.nodupes&&(n=[...new Set(n)]),n},s.create=(e,t={})=>""===e||e.length<3?[e]:!0!==t.expand?s.compile(e,t):s.expand(e,t),e.exports=s},7529:(e,t,n)=>{"use strict";const r=n(2664),a=n(3083);e.exports=(e,t={})=>{let n=(e,o={})=>{let i=a.isInvalidBrace(o),s=!0===e.invalid&&!0===t.escapeInvalid,c=!0===i||!0===s,u=!0===t.escapeInvalid?"\\":"",p="";if(!0===e.isOpen)return u+e.value;if(!0===e.isClose)return u+e.value;if("open"===e.type)return c?u+e.value:"(";if("close"===e.type)return c?u+e.value:")";if("comma"===e.type)return"comma"===e.prev.type?"":c?e.value:"|";if(e.value)return e.value;if(e.nodes&&e.ranges>0){let n=a.reduce(e.nodes),o=r(...n,{...t,wrap:!1,toRegex:!0});if(0!==o.length)return n.length>1&&o.length>1?`(${o})`:o}if(e.nodes)for(let t of e.nodes)p+=n(t,e);return p};return n(e)}},6611:e=>{"use strict";e.exports={MAX_LENGTH:65536,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:"\n",CHAR_NO_BREAK_SPACE:" ",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:"\t",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\ufeff"}},8050:(e,t,n)=>{"use strict";const r=n(2664),a=n(3349),o=n(3083),i=(e="",t="",n=!1)=>{let r=[];if(e=[].concat(e),!(t=[].concat(t)).length)return e;if(!e.length)return n?o.flatten(t).map((e=>`{${e}}`)):t;for(let a of e)if(Array.isArray(a))for(let e of a)r.push(i(e,t,n));else for(let e of t)!0===n&&"string"==typeof e&&(e=`{${e}}`),r.push(Array.isArray(e)?i(a,e,n):a+e);return o.flatten(r)};e.exports=(e,t={})=>{let n=void 0===t.rangeLimit?1e3:t.rangeLimit,s=(e,c={})=>{e.queue=[];let u=c,p=c.queue;for(;"brace"!==u.type&&"root"!==u.type&&u.parent;)u=u.parent,p=u.queue;if(e.invalid||e.dollar)return void p.push(i(p.pop(),a(e,t)));if("brace"===e.type&&!0!==e.invalid&&2===e.nodes.length)return void p.push(i(p.pop(),["{}"]));if(e.nodes&&e.ranges>0){let s=o.reduce(e.nodes);if(o.exceedsLimit(...s,t.step,n))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let c=r(...s,t);return 0===c.length&&(c=a(e,t)),p.push(i(p.pop(),c)),void(e.nodes=[])}let l=o.encloseBrace(e),d=e.queue,f=e;for(;"brace"!==f.type&&"root"!==f.type&&f.parent;)f=f.parent,d=f.queue;for(let t=0;t{"use strict";const r=n(3349),{MAX_LENGTH:a,CHAR_BACKSLASH:o,CHAR_BACKTICK:i,CHAR_COMMA:s,CHAR_DOT:c,CHAR_LEFT_PARENTHESES:u,CHAR_RIGHT_PARENTHESES:p,CHAR_LEFT_CURLY_BRACE:l,CHAR_RIGHT_CURLY_BRACE:d,CHAR_LEFT_SQUARE_BRACKET:f,CHAR_RIGHT_SQUARE_BRACKET:m,CHAR_DOUBLE_QUOTE:v,CHAR_SINGLE_QUOTE:h,CHAR_NO_BREAK_SPACE:b,CHAR_ZERO_WIDTH_NOBREAK_SPACE:g}=n(6611);e.exports=(e,t={})=>{if("string"!=typeof e)throw new TypeError("Expected a string");let n=t||{},y="number"==typeof n.maxLength?Math.min(a,n.maxLength):a;if(e.length>y)throw new SyntaxError(`Input length (${e.length}), exceeds max characters (${y})`);let x,w={type:"root",input:e,nodes:[]},_=[w],S=w,k=w,E=0,A=e.length,j=0,O=0;const C=()=>e[j++],R=e=>{if("text"===e.type&&"dot"===k.type&&(k.type="text"),!k||"text"!==k.type||"text"!==e.type)return S.nodes.push(e),e.parent=S,e.prev=k,k=e,e;k.value+=e.value};for(R({type:"bos"});j0){if(S.ranges>0){S.ranges=0;let e=S.nodes.shift();S.nodes=[e,{type:"text",value:r(S)}]}R({type:"comma",value:x}),S.commas++}else if(x===c&&O>0&&0===S.commas){let e=S.nodes;if(0===O||0===e.length){R({type:"text",value:x});continue}if("dot"===k.type){if(S.range=[],k.value+=x,k.type="range",3!==S.nodes.length&&5!==S.nodes.length){S.invalid=!0,S.ranges=0,k.type="text";continue}S.ranges++,S.args=[];continue}if("range"===k.type){e.pop();let t=e[e.length-1];t.value+=k.value+x,k=t,S.ranges--;continue}R({type:"dot",value:x})}else R({type:"text",value:x});else{if("brace"!==S.type){R({type:"text",value:x});continue}let e="close";S=_.pop(),S.close=!0,R({type:e,value:x}),O--,S=_[_.length-1]}else{O++;let e=k.value&&"$"===k.value.slice(-1)||!0===S.dollar;S=R({type:"brace",open:!0,close:!1,dollar:e,depth:O,commas:0,ranges:0,nodes:[]}),_.push(S),R({type:"open",value:x})}else{let e,n=x;for(!0!==t.keepQuotes&&(x="");j{e.nodes||("open"===e.type&&(e.isOpen=!0),"close"===e.type&&(e.isClose=!0),e.nodes||(e.type="text"),e.invalid=!0)}));let e=_[_.length-1],t=e.nodes.indexOf(S);e.nodes.splice(t,1,...S.nodes)}}while(_.length>0);return R({type:"eos"}),w}},3349:(e,t,n)=>{"use strict";const r=n(3083);e.exports=(e,t={})=>{let n=(e,a={})=>{let o=t.escapeInvalid&&r.isInvalidBrace(a),i=!0===e.invalid&&!0===t.escapeInvalid,s="";if(e.value)return(o||i)&&r.isOpenOrClose(e)?"\\"+e.value:e.value;if(e.value)return e.value;if(e.nodes)for(let t of e.nodes)s+=n(t);return s};return n(e)}},3083:(e,t)=>{"use strict";t.isInteger=e=>"number"==typeof e?Number.isInteger(e):"string"==typeof e&&""!==e.trim()&&Number.isInteger(Number(e)),t.find=(e,t)=>e.nodes.find((e=>e.type===t)),t.exceedsLimit=(e,n,r=1,a)=>!1!==a&&!(!t.isInteger(e)||!t.isInteger(n))&&(Number(n)-Number(e))/Number(r)>=a,t.escapeNode=(e,t=0,n)=>{let r=e.nodes[t];r&&(n&&r.type===n||"open"===r.type||"close"===r.type)&&!0!==r.escaped&&(r.value="\\"+r.value,r.escaped=!0)},t.encloseBrace=e=>"brace"===e.type&&e.commas>>0+e.ranges>>0==0&&(e.invalid=!0,!0),t.isInvalidBrace=e=>!("brace"!==e.type||!0!==e.invalid&&!e.dollar&&(e.commas>>0+e.ranges>>0!=0&&!0===e.open&&!0===e.close||(e.invalid=!0,0))),t.isOpenOrClose=e=>"open"===e.type||"close"===e.type||!0===e.open||!0===e.close,t.reduce=e=>e.reduce(((e,t)=>("text"===t.type&&e.push(t.value),"range"===t.type&&(t.type="text"),e)),[]),t.flatten=(...e)=>{const t=[],n=e=>{for(let r=0;r{"use strict";const{parseContentType:r}=n(1510),a=[n(7626),n(4403)].filter((function(e){return"function"==typeof e.detect}));e.exports=e=>{if("object"==typeof e&&null!==e||(e={}),"object"!=typeof e.headers||null===e.headers||"string"!=typeof e.headers["content-type"])throw new Error("Missing Content-Type");return function(e){const t=e.headers,n=r(t["content-type"]);if(!n)throw new Error("Malformed content type");for(const r of a){if(!r.detect(n))continue;const a={limits:e.limits,headers:t,conType:n,highWaterMark:void 0,fileHwm:void 0,defCharset:void 0,defParamCharset:void 0,preservePath:!1};return e.highWaterMark&&(a.highWaterMark=e.highWaterMark),e.fileHwm&&(a.fileHwm=e.fileHwm),a.defCharset=e.defCharset,a.defParamCharset=e.defParamCharset,a.preservePath=e.preservePath,new r(a)}throw new Error(`Unsupported content type: ${t["content-type"]}`)}(e)}},7626:(e,t,n)=>{"use strict";const{Readable:r,Writable:a}=n(2781),o=n(1301),{basename:i,convertToUTF8:s,getDecoder:c,parseContentType:u,parseDisposition:p}=n(1510),l=Buffer.from("\r\n"),d=Buffer.from("\r"),f=Buffer.from("-");function m(){}const v=16384;class h{constructor(e){this.header=Object.create(null),this.pairCount=0,this.byteCount=0,this.state=0,this.name="",this.value="",this.crlf=0,this.cb=e}reset(){this.header=Object.create(null),this.pairCount=0,this.byteCount=0,this.state=0,this.name="",this.value="",this.crlf=0}push(e,t,n){let r=t;for(;t{if(this._read(),0==--t._fileEndsLeft&&t._finalcb){const e=t._finalcb;t._finalcb=null,process.nextTick(e)}}))}_read(e){const t=this._readcb;t&&(this._readcb=null,t())}}const g={push:(e,t)=>{},destroy:()=>{}};function y(e,t){return e}function x(e,t,n){if(n)return t(n);t(n=w(e))}function w(e){if(e._hparser)return new Error("Malformed part header");const t=e._fileStream;return t&&(e._fileStream=null,t.destroy(new Error("Unexpected end of file"))),e._complete?void 0:new Error("Unexpected end of form")}const _=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,1,1,0,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],S=[0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1];e.exports=class extends a{constructor(e){if(super({autoDestroy:!0,emitClose:!0,highWaterMark:"number"==typeof e.highWaterMark?e.highWaterMark:void 0}),!e.conType.params||"string"!=typeof e.conType.params.boundary)throw new Error("Multipart: Boundary not found");const t=e.conType.params.boundary,n="string"==typeof e.defParamCharset&&e.defParamCharset?c(e.defParamCharset):y,r=e.defCharset||"utf8",a=e.preservePath,v={autoDestroy:!0,emitClose:!0,highWaterMark:"number"==typeof e.fileHwm?e.fileHwm:void 0},x=e.limits,w=x&&"number"==typeof x.fieldSize?x.fieldSize:1048576,_=x&&"number"==typeof x.fileSize?x.fileSize:1/0,S=x&&"number"==typeof x.files?x.files:1/0,k=x&&"number"==typeof x.fields?x.fields:1/0,E=x&&"number"==typeof x.parts?x.parts:1/0;let A=-1,j=0,O=0,C=!1;this._fileEndsLeft=0,this._fileStream=void 0,this._complete=!1;let R,T,I,P,U,N=0,D=0,L=!1,M=!1,F=!1;this._hparser=null;const q=new h((e=>{let t;if(this._hparser=null,C=!1,P="text/plain",T=r,I="7bit",U=void 0,L=!1,!e["content-disposition"])return void(C=!0);const o=p(e["content-disposition"][0],n);if(o&&"form-data"===o.type){if(o.params&&(o.params.name&&(U=o.params.name),o.params["filename*"]?t=o.params["filename*"]:o.params.filename&&(t=o.params.filename),void 0===t||a||(t=i(t))),e["content-type"]){const t=u(e["content-type"][0]);t&&(P=`${t.type}/${t.subtype}`,t.params&&"string"==typeof t.params.charset&&(T=t.params.charset.toLowerCase()))}if(e["content-transfer-encoding"]&&(I=e["content-transfer-encoding"][0].toLowerCase()),"application/octet-stream"===P||void 0!==t){if(O===S)return M||(M=!0,this.emit("filesLimit")),void(C=!0);if(++O,0===this.listenerCount("file"))return void(C=!0);N=0,this._fileStream=new b(v,this),++this._fileEndsLeft,this.emit("file",U,this._fileStream,{filename:t,encoding:I,mimeType:P})}else{if(j===k)return F||(F=!0,this.emit("fieldsLimit")),void(C=!0);if(++j,0===this.listenerCount("field"))return void(C=!0);R=[],D=0}}else C=!0}));let B=0;const H=(e,t,n,r,a)=>{e:for(;t;){if(null!==this._hparser){const e=this._hparser.push(t,n,r);if(-1===e){this._hparser=null,q.reset(),this.emit("error",new Error("Malformed part header"));break}n=e}if(n===r)break;if(0!==B){if(1===B){switch(t[n]){case 45:B=2,++n;break;case 13:B=3,++n;break;default:B=0}if(n===r)return}if(2===B){if(B=0,45===t[n])return this._complete=!0,void(this._bparser=g);const e=this._writecb;this._writecb=m,H(!1,f,0,1,!1),this._writecb=e}else if(3===B){if(B=0,10===t[n]){if(++n,A>=E)break;if(this._hparser=q,n===r)break;continue e}{const e=this._writecb;this._writecb=m,H(!1,d,0,1,!1),this._writecb=e}}}if(!C)if(this._fileStream){let e;const o=Math.min(r-n,_-N);a?e=t.slice(n,n+o):(e=Buffer.allocUnsafe(o),t.copy(e,0,n,n+o)),N+=e.length,N===_?(e.length>0&&this._fileStream.push(e),this._fileStream.emit("limit"),this._fileStream.truncated=!0,C=!0):this._fileStream.push(e)||(this._writecb&&(this._fileStream._readcb=this._writecb),this._writecb=null)}else if(void 0!==R){let e;const o=Math.min(r-n,w-D);a?e=t.slice(n,n+o):(e=Buffer.allocUnsafe(o),t.copy(e,0,n,n+o)),D+=o,R.push(e),D===w&&(C=!0,L=!0)}break}if(e){if(B=1,this._fileStream)this._fileStream.push(null),this._fileStream=null;else if(void 0!==R){let e;switch(R.length){case 0:e="";break;case 1:e=s(R[0],T,0);break;default:e=s(Buffer.concat(R,D),T,0)}R=void 0,D=0,this.emit("field",U,e,{nameTruncated:!1,valueTruncated:L,encoding:I,mimeType:P})}++A===E&&this.emit("partsLimit")}};this._bparser=new o(`\r\n--${t}`,H),this._writecb=null,this._finalcb=null,this.write(l)}static detect(e){return"multipart"===e.type&&"form-data"===e.subtype}_write(e,t,n){this._writecb=n,this._bparser.push(e,0),this._writecb&&function(e,t){const n=e._writecb;e._writecb=null,n&&n()}(this)}_destroy(e,t){this._hparser=null,this._bparser=g,e||(e=w(this));const n=this._fileStream;n&&(this._fileStream=null,n.destroy(e)),t(e)}_final(e){if(this._bparser.destroy(),!this._complete)return e(new Error("Unexpected end of form"));this._fileEndsLeft?this._finalcb=x.bind(null,this,e):x(this,e)}}},4403:(e,t,n)=>{"use strict";const{Writable:r}=n(2781),{getDecoder:a}=n(1510);function o(e,t,n,r){if(n>=r)return r;if(-1===e._byte){const a=c[t[n++]];if(-1===a)return-1;if(a>=8&&(e._encode=2),ne.fieldNameSizeLimit){for(e._keyTrunc||e._lastPose.fieldSizeLimit){for(e._valTrunc||e._lastPos=this.fieldsLimit)return n();let r=0;const a=e.length;if(this._lastPos=0,-2!==this._byte){if(r=o(this,e,r,a),-1===r)return n(new Error("Malformed urlencoded form"));if(r>=a)return n();this._inKey?++this._bytesKey:++this._bytesVal}e:for(;r0&&this.emit("field",this._key,"",{nameTruncated:this._keyTrunc,valueTruncated:!1,encoding:this.charset,mimeType:"text/plain"}),this._key="",this._val="",this._keyTrunc=!1,this._valTrunc=!1,this._bytesKey=0,this._bytesVal=0,++this._fields>=this.fieldsLimit)return this.emit("fieldsLimit"),n();continue;case 43:this._lastPos=a)return n();++this._bytesKey,r=i(this,e,r,a);continue}++r,++this._bytesKey,r=i(this,e,r,a)}this._lastPos0||this._bytesVal>0)&&this.emit("field",this._key,this._val,{nameTruncated:this._keyTrunc,valueTruncated:this._valTrunc,encoding:this.charset,mimeType:"text/plain"}),this._key="",this._val="",this._keyTrunc=!1,this._valTrunc=!1,this._bytesKey=0,this._bytesVal=0,++this._fields>=this.fieldsLimit)return this.emit("fieldsLimit"),n();continue e;case 43:this._lastPos=a)return n();++this._bytesVal,r=s(this,e,r,a);continue}++r,++this._bytesVal,r=s(this,e,r,a)}this._lastPos0||this._bytesVal>0)&&(this._inKey?this._key=this._decoder(this._key,this._encode):this._val=this._decoder(this._val,this._encode),this.emit("field",this._key,this._val,{nameTruncated:this._keyTrunc,valueTruncated:this._valTrunc,encoding:this.charset,mimeType:"text/plain"})),e()}}},1510:function(e){"use strict";function t(e,t,n){for(;t=128?r=2:0===r&&(r=1);continue}return}break}}if(m+=e.slice(d,t),m=o(m,f,r),void 0===m)return}else{if(++t===e.length)return;if(34===e.charCodeAt(t)){d=++t;let n=!1;for(;t{if(0===e.length)return"";if("string"==typeof e){if(t<2)return e;e=Buffer.from(e,"latin1")}return e.utf8Slice(0,e.length)},latin1:(e,t)=>0===e.length?"":"string"==typeof e?e:e.latin1Slice(0,e.length),utf16le:(e,t)=>0===e.length?"":("string"==typeof e&&(e=Buffer.from(e,"latin1")),e.ucs2Slice(0,e.length)),base64:(e,t)=>0===e.length?"":("string"==typeof e&&(e=Buffer.from(e,"latin1")),e.base64Slice(0,e.length)),other:(e,t)=>{if(0===e.length)return"";"string"==typeof e&&(e=Buffer.from(e,"latin1"));try{return new TextDecoder(this).decode(e)}catch{}}};function o(e,t,n){const a=r(t);if(a)return a(e,n)}const i=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,1,1,0,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],s=[0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],c=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,1,0,0,0,0,1,0,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],u=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,1,0,0,0,0,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],p=[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,-1,-1,-1,-1,-1,-1,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1];e.exports={basename:function(e){if("string"!=typeof e)return"";for(let t=e.length-1;t>=0;--t)switch(e.charCodeAt(t)){case 47:case 92:return".."===(e=e.slice(t+1))||"."===e?"":e}return".."===e||"."===e?"":e},convertToUTF8:o,getDecoder:r,parseContentType:function(e){if(0===e.length)return;const n=Object.create(null);let r=0;for(;r{"use strict";e.exports=function(e,t){return"string"==typeof e?i(e):"number"==typeof e?o(e,t):null},e.exports.format=o,e.exports.parse=i;var t=/\B(?=(\d{3})+(?!\d))/g,n=/(?:\.0*|(\.[^0]+)0+)$/,r={b:1,kb:1024,mb:1<<20,gb:1<<30,tb:Math.pow(1024,4),pb:Math.pow(1024,5)},a=/^((-|\+)?(\d+(?:\.\d+)?)) *(kb|mb|gb|tb|pb)$/i;function o(e,a){if(!Number.isFinite(e))return null;var o=Math.abs(e),i=a&&a.thousandsSeparator||"",s=a&&a.unitSeparator||"",c=a&&void 0!==a.decimalPlaces?a.decimalPlaces:2,u=Boolean(a&&a.fixedDecimals),p=a&&a.unit||"";p&&r[p.toLowerCase()]||(p=o>=r.pb?"PB":o>=r.tb?"TB":o>=r.gb?"GB":o>=r.mb?"MB":o>=r.kb?"KB":"B");var l=(e/r[p.toLowerCase()]).toFixed(c);return u||(l=l.replace(n,"$1")),i&&(l=l.split(".").map((function(e,n){return 0===n?e.replace(t,i):e})).join(".")),l+s+p}function i(e){if("number"==typeof e&&!isNaN(e))return e;if("string"!=typeof e)return null;var t,n=a.exec(e),o="b";return n?(t=parseFloat(n[1]),o=n[4].toLowerCase()):(t=parseInt(e,10),o="b"),isNaN(t)?null:Math.floor(r[o]*t)}},1924:(e,t,n)=>{"use strict";var r=n(210),a=n(5559),o=a(r("String.prototype.indexOf"));e.exports=function(e,t){var n=r(e,!!t);return"function"==typeof n&&o(e,".prototype.")>-1?a(n):n}},5559:(e,t,n)=>{"use strict";var r=n(8612),a=n(210),o=a("%Function.prototype.apply%"),i=a("%Function.prototype.call%"),s=a("%Reflect.apply%",!0)||r.call(i,o),c=a("%Object.getOwnPropertyDescriptor%",!0),u=a("%Object.defineProperty%",!0),p=a("%Math.max%");if(u)try{u({},"a",{value:1})}catch(e){u=null}e.exports=function(e){var t=s(r,i,arguments);if(c&&u){var n=c(t,"length");n.configurable&&u(t,"length",{value:1+p(0,e.length-(arguments.length-1))})}return t};var l=function(){return s(r,o,arguments)};u?u(e.exports,"apply",{value:l}):e.exports.apply=l},7389:(e,t,n)=>{"use strict";e.exports=function(e,t){var n=t||{},a=n.type||"attachment",o=function(e,t){if(void 0!==e){var n={};if("string"!=typeof e)throw new TypeError("filename must be a string");if(void 0===t&&(t=!0),"string"!=typeof t&&"boolean"!=typeof t)throw new TypeError("fallback must be a string or boolean");if("string"==typeof t&&c.test(t))throw new TypeError("fallback must be ISO-8859-1 string");var a=r(e),o=d.test(a),s="string"!=typeof t?t&&b(a):r(t),u="string"==typeof s&&s!==a;return(u||!o||i.test(a))&&(n["filename*"]=a),(o||u)&&(n.filename=u?s:a),n}}(e,n.fallback);return function(e){var t=e.parameters,n=e.type;if(!n||"string"!=typeof n||!f.test(n))throw new TypeError("invalid type");var r=String(n).toLowerCase();if(t&&"object"==typeof t)for(var a,o=Object.keys(t).sort(),i=0;i?@[\\\]{}\x7f]/g,i=/%[0-9A-Fa-f]{2}/,s=/%([0-9A-Fa-f]{2})/g,c=/[^\x20-\x7e\xa0-\xff]/g,u=/\\([\u0000-\u007f])/g,p=/([\\"])/g,l=/;[\x09\x20]*([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*=[\x09\x20]*("(?:[\x20!\x23-\x5b\x5d-\x7e\x80-\xff]|\\[\x20-\x7e])*"|[!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*/g,d=/^[\x20-\x7e\x80-\xff]+$/,f=/^[!#$%&'*+.0-9A-Z^_`a-z|~-]+$/,m=/^([A-Za-z0-9!#$%&+\-^_`{}~]+)'(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}){0,3}|[A-Za-z]{4,8}|)'((?:%[0-9A-Fa-f]{2}|[A-Za-z0-9!#$&+.^_`|~-])+)$/,v=/^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*(?:$|;)/;function h(e){var t=m.exec(e);if(!t)throw new TypeError("invalid extended field value");var n,r=t[1].toLowerCase(),o=t[2].replace(s,g);switch(r){case"iso-8859-1":n=b(o);break;case"utf-8":n=a.from(o,"binary").toString("utf8");break;default:throw new TypeError("unsupported charset in extended field")}return n}function b(e){return String(e).replace(c,"?")}function g(e,t){return String.fromCharCode(parseInt(t,16))}function y(e){return"%"+String(e).charCodeAt(0).toString(16).toUpperCase()}function x(e){return'"'+String(e).replace(p,"\\$1")+'"'}function w(e){var t=String(e);return"UTF-8''"+encodeURIComponent(t).replace(o,y)}function _(e,t){this.type=e,this.parameters=t}},7296:(e,t,n)=>{var r=n(4300),a=r.Buffer;function o(e,t){for(var n in e)t[n]=e[n]}function i(e,t,n){return a(e,t,n)}a.from&&a.alloc&&a.allocUnsafe&&a.allocUnsafeSlow?e.exports=r:(o(r,t),t.Buffer=i),i.prototype=Object.create(a.prototype),o(a,i),i.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return a(e,t,n)},i.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=a(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},i.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return a(e)},i.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},7811:(e,t)=>{"use strict";var n=/; *([!#$%&'*+.^_`|~0-9A-Za-z-]+) *= *("(?:[\u000b\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u000b\u0020-\u00ff])*"|[!#$%&'*+.^_`|~0-9A-Za-z-]+) */g,r=/^[\u000b\u0020-\u007e\u0080-\u00ff]+$/,a=/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/,o=/\\([\u000b\u0020-\u00ff])/g,i=/([\\"])/g,s=/^[!#$%&'*+.^_`|~0-9A-Za-z-]+\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;function c(e){var t=String(e);if(a.test(t))return t;if(t.length>0&&!r.test(t))throw new TypeError("invalid parameter value");return'"'+t.replace(i,"\\$1")+'"'}function u(e){this.parameters=Object.create(null),this.type=e}t.format=function(e){if(!e||"object"!=typeof e)throw new TypeError("argument obj is required");var t=e.parameters,n=e.type;if(!n||!s.test(n))throw new TypeError("invalid type");var r=n;if(t&&"object"==typeof t)for(var o,i=Object.keys(t).sort(),u=0;u{var r=n(6113);function a(e){return r.createHash("sha1").update(e).digest("hex")}t.sign=function(e,t){if("string"!=typeof e)throw new TypeError("Cookie value must be provided as a string.");if("string"!=typeof t)throw new TypeError("Secret string must be provided.");return e+"."+r.createHmac("sha256",t).update(e).digest("base64").replace(/\=+$/,"")},t.unsign=function(e,n){if("string"!=typeof e)throw new TypeError("Signed cookie string must be provided.");if("string"!=typeof n)throw new TypeError("Secret string must be provided.");var r=e.slice(0,e.lastIndexOf("."));return a(t.sign(r,n))==a(e)&&r}},6489:(e,t)=>{"use strict";t.parse=function(e,t){if("string"!=typeof e)throw new TypeError("argument str must be a string");for(var n={},r=(t||{}).decode||a,o=0;o{var r=n(614),a=n(6330),o=TypeError;e.exports=function(e){if(r(e))return e;throw o(a(e)+" is not a function")}},9483:(e,t,n)=>{var r=n(4411),a=n(6330),o=TypeError;e.exports=function(e){if(r(e))return e;throw o(a(e)+" is not a constructor")}},6077:(e,t,n)=>{var r=n(614),a=String,o=TypeError;e.exports=function(e){if("object"==typeof e||r(e))return e;throw o("Can't set "+a(e)+" as a prototype")}},1223:(e,t,n)=>{var r=n(5112),a=n(30),o=n(3070).f,i=r("unscopables"),s=Array.prototype;null==s[i]&&o(s,i,{configurable:!0,value:a(null)}),e.exports=function(e){s[i][e]=!0}},1530:(e,t,n)=>{"use strict";var r=n(8710).charAt;e.exports=function(e,t,n){return t+(n?r(e,t).length:1)}},5787:(e,t,n)=>{var r=n(7976),a=TypeError;e.exports=function(e,t){if(r(t,e))return e;throw a("Incorrect invocation")}},9670:(e,t,n)=>{var r=n(111),a=String,o=TypeError;e.exports=function(e){if(r(e))return e;throw o(a(e)+" is not an object")}},8533:(e,t,n)=>{"use strict";var r=n(2092).forEach,a=n(9341)("forEach");e.exports=a?[].forEach:function(e){return r(this,e,arguments.length>1?arguments[1]:void 0)}},8457:(e,t,n)=>{"use strict";var r=n(9974),a=n(6916),o=n(7908),i=n(3411),s=n(7659),c=n(4411),u=n(6244),p=n(6135),l=n(4121),d=n(1246),f=Array;e.exports=function(e){var t=o(e),n=c(this),m=arguments.length,v=m>1?arguments[1]:void 0,h=void 0!==v;h&&(v=r(v,m>2?arguments[2]:void 0));var b,g,y,x,w,_,S=d(t),k=0;if(!S||this===f&&s(S))for(b=u(t),g=n?new this(b):f(b);b>k;k++)_=h?v(t[k],k):t[k],p(g,k,_);else for(w=(x=l(t,S)).next,g=n?new this:[];!(y=a(w,x)).done;k++)_=h?i(x,v,[y.value,k],!0):y.value,p(g,k,_);return g.length=k,g}},1318:(e,t,n)=>{var r=n(5656),a=n(1400),o=n(6244),i=function(e){return function(t,n,i){var s,c=r(t),u=o(c),p=a(i,u);if(e&&n!=n){for(;u>p;)if((s=c[p++])!=s)return!0}else for(;u>p;p++)if((e||p in c)&&c[p]===n)return e||p||0;return!e&&-1}};e.exports={includes:i(!0),indexOf:i(!1)}},2092:(e,t,n)=>{var r=n(9974),a=n(1702),o=n(8361),i=n(7908),s=n(6244),c=n(5417),u=a([].push),p=function(e){var t=1==e,n=2==e,a=3==e,p=4==e,l=6==e,d=7==e,f=5==e||l;return function(m,v,h,b){for(var g,y,x=i(m),w=o(x),_=r(v,h),S=s(w),k=0,E=b||c,A=t?E(m,S):n||d?E(m,0):void 0;S>k;k++)if((f||k in w)&&(y=_(g=w[k],k,x),e))if(t)A[k]=y;else if(y)switch(e){case 3:return!0;case 5:return g;case 6:return k;case 2:u(A,g)}else switch(e){case 4:return!1;case 7:u(A,g)}return l?-1:a||p?p:A}};e.exports={forEach:p(0),map:p(1),filter:p(2),some:p(3),every:p(4),find:p(5),findIndex:p(6),filterReject:p(7)}},1194:(e,t,n)=>{var r=n(7293),a=n(5112),o=n(7392),i=a("species");e.exports=function(e){return o>=51||!r((function(){var t=[];return(t.constructor={})[i]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}},9341:(e,t,n)=>{"use strict";var r=n(7293);e.exports=function(e,t){var n=[][e];return!!n&&r((function(){n.call(null,t||function(){return 1},1)}))}},3671:(e,t,n)=>{var r=n(9662),a=n(7908),o=n(8361),i=n(6244),s=TypeError,c=function(e){return function(t,n,c,u){r(n);var p=a(t),l=o(p),d=i(p),f=e?d-1:0,m=e?-1:1;if(c<2)for(;;){if(f in l){u=l[f],f+=m;break}if(f+=m,e?f<0:d<=f)throw s("Reduce of empty array with no initial value")}for(;e?f>=0:d>f;f+=m)f in l&&(u=n(u,l[f],f,p));return u}};e.exports={left:c(!1),right:c(!0)}},1589:(e,t,n)=>{var r=n(1400),a=n(6244),o=n(6135),i=Array,s=Math.max;e.exports=function(e,t,n){for(var c=a(e),u=r(t,c),p=r(void 0===n?c:n,c),l=i(s(p-u,0)),d=0;u{var r=n(1702);e.exports=r([].slice)},4362:(e,t,n)=>{var r=n(1589),a=Math.floor,o=function(e,t){var n=e.length,c=a(n/2);return n<8?i(e,t):s(e,o(r(e,0,c),t),o(r(e,c),t),t)},i=function(e,t){for(var n,r,a=e.length,o=1;o0;)e[r]=e[--r];r!==o++&&(e[r]=n)}return e},s=function(e,t,n,r){for(var a=t.length,o=n.length,i=0,s=0;i{var r=n(3157),a=n(4411),o=n(111),i=n(5112)("species"),s=Array;e.exports=function(e){var t;return r(e)&&(t=e.constructor,(a(t)&&(t===s||r(t.prototype))||o(t)&&null===(t=t[i]))&&(t=void 0)),void 0===t?s:t}},5417:(e,t,n)=>{var r=n(7475);e.exports=function(e,t){return new(r(e))(0===t?0:t)}},3411:(e,t,n)=>{var r=n(9670),a=n(9212);e.exports=function(e,t,n,o){try{return o?t(r(n)[0],n[1]):t(n)}catch(t){a(e,"throw",t)}}},7072:(e,t,n)=>{var r=n(5112)("iterator"),a=!1;try{var o=0,i={next:function(){return{done:!!o++}},return:function(){a=!0}};i[r]=function(){return this},Array.from(i,(function(){throw 2}))}catch(e){}e.exports=function(e,t){if(!t&&!a)return!1;var n=!1;try{var o={};o[r]=function(){return{next:function(){return{done:n=!0}}}},e(o)}catch(e){}return n}},4326:(e,t,n)=>{var r=n(1702),a=r({}.toString),o=r("".slice);e.exports=function(e){return o(a(e),8,-1)}},648:(e,t,n)=>{var r=n(1694),a=n(614),o=n(4326),i=n(5112)("toStringTag"),s=Object,c="Arguments"==o(function(){return arguments}());e.exports=r?o:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=s(e),i))?n:c?o(t):"Object"==(r=o(t))&&a(t.callee)?"Arguments":r}},9920:(e,t,n)=>{var r=n(2597),a=n(3887),o=n(1236),i=n(3070);e.exports=function(e,t,n){for(var s=a(t),c=i.f,u=o.f,p=0;p{var r=n(5112)("match");e.exports=function(e){var t=/./;try{"/./"[e](t)}catch(n){try{return t[r]=!1,"/./"[e](t)}catch(e){}}return!1}},8544:(e,t,n)=>{var r=n(7293);e.exports=!r((function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype}))},6178:e=>{e.exports=function(e,t){return{value:e,done:t}}},8880:(e,t,n)=>{var r=n(9781),a=n(3070),o=n(9114);e.exports=r?function(e,t,n){return a.f(e,t,o(1,n))}:function(e,t,n){return e[t]=n,e}},9114:e=>{e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},6135:(e,t,n)=>{"use strict";var r=n(4948),a=n(3070),o=n(9114);e.exports=function(e,t,n){var i=r(t);i in e?a.f(e,i,o(0,n)):e[i]=n}},7045:(e,t,n)=>{var r=n(6339),a=n(3070);e.exports=function(e,t,n){return n.get&&r(n.get,t,{getter:!0}),n.set&&r(n.set,t,{setter:!0}),a.f(e,t,n)}},8052:(e,t,n)=>{var r=n(614),a=n(3070),o=n(6339),i=n(3072);e.exports=function(e,t,n,s){s||(s={});var c=s.enumerable,u=void 0!==s.name?s.name:t;if(r(n)&&o(n,u,s),s.global)c?e[t]=n:i(t,n);else{try{s.unsafe?e[t]&&(c=!0):delete e[t]}catch(e){}c?e[t]=n:a.f(e,t,{value:n,enumerable:!1,configurable:!s.nonConfigurable,writable:!s.nonWritable})}return e}},3072:(e,t,n)=>{var r=n(7854),a=Object.defineProperty;e.exports=function(e,t){try{a(r,e,{value:t,configurable:!0,writable:!0})}catch(n){r[e]=t}return t}},5117:(e,t,n)=>{"use strict";var r=n(6330),a=TypeError;e.exports=function(e,t){if(!delete e[t])throw a("Cannot delete property "+r(t)+" of "+r(e))}},9781:(e,t,n)=>{var r=n(7293);e.exports=!r((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},4154:e=>{var t="object"==typeof document&&document.all,n=void 0===t&&void 0!==t;e.exports={all:t,IS_HTMLDDA:n}},317:(e,t,n)=>{var r=n(7854),a=n(111),o=r.document,i=a(o)&&a(o.createElement);e.exports=function(e){return i?o.createElement(e):{}}},7207:e=>{var t=TypeError;e.exports=function(e){if(e>9007199254740991)throw t("Maximum allowed index exceeded");return e}},8324:e=>{e.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},8509:(e,t,n)=>{var r=n(317)("span").classList,a=r&&r.constructor&&r.constructor.prototype;e.exports=a===Object.prototype?void 0:a},8886:(e,t,n)=>{var r=n(8113).match(/firefox\/(\d+)/i);e.exports=!!r&&+r[1]},7871:(e,t,n)=>{var r=n(3823),a=n(5268);e.exports=!r&&!a&&"object"==typeof window&&"object"==typeof document},3823:e=>{e.exports="object"==typeof Deno&&Deno&&"object"==typeof Deno.version},256:(e,t,n)=>{var r=n(8113);e.exports=/MSIE|Trident/.test(r)},1528:(e,t,n)=>{var r=n(8113);e.exports=/ipad|iphone|ipod/i.test(r)&&"undefined"!=typeof Pebble},6833:(e,t,n)=>{var r=n(8113);e.exports=/(?:ipad|iphone|ipod).*applewebkit/i.test(r)},5268:(e,t,n)=>{var r=n(4326);e.exports="undefined"!=typeof process&&"process"==r(process)},1036:(e,t,n)=>{var r=n(8113);e.exports=/web0s(?!.*chrome)/i.test(r)},8113:e=>{e.exports="undefined"!=typeof navigator&&String(navigator.userAgent)||""},7392:(e,t,n)=>{var r,a,o=n(7854),i=n(8113),s=o.process,c=o.Deno,u=s&&s.versions||c&&c.version,p=u&&u.v8;p&&(a=(r=p.split("."))[0]>0&&r[0]<4?1:+(r[0]+r[1])),!a&&i&&(!(r=i.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=i.match(/Chrome\/(\d+)/))&&(a=+r[1]),e.exports=a},8008:(e,t,n)=>{var r=n(8113).match(/AppleWebKit\/(\d+)\./);e.exports=!!r&&+r[1]},748:e=>{e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},2109:(e,t,n)=>{var r=n(7854),a=n(1236).f,o=n(8880),i=n(8052),s=n(3072),c=n(9920),u=n(4705);e.exports=function(e,t){var n,p,l,d,f,m=e.target,v=e.global,h=e.stat;if(n=v?r:h?r[m]||s(m,{}):(r[m]||{}).prototype)for(p in t){if(d=t[p],l=e.dontCallGetSet?(f=a(n,p))&&f.value:n[p],!u(v?p:m+(h?".":"#")+p,e.forced)&&void 0!==l){if(typeof d==typeof l)continue;c(d,l)}(e.sham||l&&l.sham)&&o(d,"sham",!0),i(n,p,d,e)}}},7293:e=>{e.exports=function(e){try{return!!e()}catch(e){return!0}}},7007:(e,t,n)=>{"use strict";n(4916);var r=n(1470),a=n(8052),o=n(2261),i=n(7293),s=n(5112),c=n(8880),u=s("species"),p=RegExp.prototype;e.exports=function(e,t,n,l){var d=s(e),f=!i((function(){var t={};return t[d]=function(){return 7},7!=""[e](t)})),m=f&&!i((function(){var t=!1,n=/a/;return"split"===e&&((n={}).constructor={},n.constructor[u]=function(){return n},n.flags="",n[d]=/./[d]),n.exec=function(){return t=!0,null},n[d](""),!t}));if(!f||!m||n){var v=r(/./[d]),h=t(d,""[e],(function(e,t,n,a,i){var s=r(e),c=t.exec;return c===o||c===p.exec?f&&!i?{done:!0,value:v(t,n,a)}:{done:!0,value:s(n,t,a)}:{done:!1}}));a(String.prototype,e,h[0]),a(p,d,h[1])}l&&c(p[d],"sham",!0)}},2104:(e,t,n)=>{var r=n(4374),a=Function.prototype,o=a.apply,i=a.call;e.exports="object"==typeof Reflect&&Reflect.apply||(r?i.bind(o):function(){return i.apply(o,arguments)})},9974:(e,t,n)=>{var r=n(1470),a=n(9662),o=n(4374),i=r(r.bind);e.exports=function(e,t){return a(e),void 0===t?e:o?i(e,t):function(){return e.apply(t,arguments)}}},4374:(e,t,n)=>{var r=n(7293);e.exports=!r((function(){var e=function(){}.bind();return"function"!=typeof e||e.hasOwnProperty("prototype")}))},7065:(e,t,n)=>{"use strict";var r=n(1702),a=n(9662),o=n(111),i=n(2597),s=n(206),c=n(4374),u=Function,p=r([].concat),l=r([].join),d={},f=function(e,t,n){if(!i(d,t)){for(var r=[],a=0;a{var r=n(4374),a=Function.prototype.call;e.exports=r?a.bind(a):function(){return a.apply(a,arguments)}},6530:(e,t,n)=>{var r=n(9781),a=n(2597),o=Function.prototype,i=r&&Object.getOwnPropertyDescriptor,s=a(o,"name"),c=s&&"something"===function(){}.name,u=s&&(!r||r&&i(o,"name").configurable);e.exports={EXISTS:s,PROPER:c,CONFIGURABLE:u}},5668:(e,t,n)=>{var r=n(1702),a=n(9662);e.exports=function(e,t,n){try{return r(a(Object.getOwnPropertyDescriptor(e,t)[n]))}catch(e){}}},1470:(e,t,n)=>{var r=n(4326),a=n(1702);e.exports=function(e){if("Function"===r(e))return a(e)}},1702:(e,t,n)=>{var r=n(4374),a=Function.prototype,o=a.call,i=r&&a.bind.bind(o,o);e.exports=r?i:function(e){return function(){return o.apply(e,arguments)}}},5005:(e,t,n)=>{var r=n(7854),a=n(614),o=function(e){return a(e)?e:void 0};e.exports=function(e,t){return arguments.length<2?o(r[e]):r[e]&&r[e][t]}},1246:(e,t,n)=>{var r=n(648),a=n(8173),o=n(8554),i=n(7497),s=n(5112)("iterator");e.exports=function(e){if(!o(e))return a(e,s)||a(e,"@@iterator")||i[r(e)]}},4121:(e,t,n)=>{var r=n(6916),a=n(9662),o=n(9670),i=n(6330),s=n(1246),c=TypeError;e.exports=function(e,t){var n=arguments.length<2?s(e):t;if(a(n))return o(r(n,e));throw c(i(e)+" is not iterable")}},8044:(e,t,n)=>{var r=n(1702),a=n(3157),o=n(614),i=n(4326),s=n(1340),c=r([].push);e.exports=function(e){if(o(e))return e;if(a(e)){for(var t=e.length,n=[],r=0;r{var r=n(9662),a=n(8554);e.exports=function(e,t){var n=e[t];return a(n)?void 0:r(n)}},647:(e,t,n)=>{var r=n(1702),a=n(7908),o=Math.floor,i=r("".charAt),s=r("".replace),c=r("".slice),u=/\$([$&'`]|\d{1,2}|<[^>]*>)/g,p=/\$([$&'`]|\d{1,2})/g;e.exports=function(e,t,n,r,l,d){var f=n+e.length,m=r.length,v=p;return void 0!==l&&(l=a(l),v=u),s(d,v,(function(a,s){var u;switch(i(s,0)){case"$":return"$";case"&":return e;case"`":return c(t,0,n);case"'":return c(t,f);case"<":u=l[c(s,1,-1)];break;default:var p=+s;if(0===p)return a;if(p>m){var d=o(p/10);return 0===d?a:d<=m?void 0===r[d-1]?i(s,1):r[d-1]+i(s,1):a}u=r[p-1]}return void 0===u?"":u}))}},7854:e=>{var t=function(e){return e&&e.Math==Math&&e};e.exports=t("object"==typeof globalThis&&globalThis)||t("object"==typeof window&&window)||t("object"==typeof self&&self)||t("object"==typeof global&&global)||function(){return this}()||Function("return this")()},2597:(e,t,n)=>{var r=n(1702),a=n(7908),o=r({}.hasOwnProperty);e.exports=Object.hasOwn||function(e,t){return o(a(e),t)}},3501:e=>{e.exports={}},842:e=>{e.exports=function(e,t){try{1==arguments.length?console.error(e):console.error(e,t)}catch(e){}}},490:(e,t,n)=>{var r=n(5005);e.exports=r("document","documentElement")},4664:(e,t,n)=>{var r=n(9781),a=n(7293),o=n(317);e.exports=!r&&!a((function(){return 7!=Object.defineProperty(o("div"),"a",{get:function(){return 7}}).a}))},8361:(e,t,n)=>{var r=n(1702),a=n(7293),o=n(4326),i=Object,s=r("".split);e.exports=a((function(){return!i("z").propertyIsEnumerable(0)}))?function(e){return"String"==o(e)?s(e,""):i(e)}:i},9587:(e,t,n)=>{var r=n(614),a=n(111),o=n(7674);e.exports=function(e,t,n){var i,s;return o&&r(i=t.constructor)&&i!==n&&a(s=i.prototype)&&s!==n.prototype&&o(e,s),e}},2788:(e,t,n)=>{var r=n(1702),a=n(614),o=n(5465),i=r(Function.toString);a(o.inspectSource)||(o.inspectSource=function(e){return i(e)}),e.exports=o.inspectSource},9909:(e,t,n)=>{var r,a,o,i=n(4811),s=n(7854),c=n(111),u=n(8880),p=n(2597),l=n(5465),d=n(6200),f=n(3501),m="Object already initialized",v=s.TypeError,h=s.WeakMap;if(i||l.state){var b=l.state||(l.state=new h);b.get=b.get,b.has=b.has,b.set=b.set,r=function(e,t){if(b.has(e))throw v(m);return t.facade=e,b.set(e,t),t},a=function(e){return b.get(e)||{}},o=function(e){return b.has(e)}}else{var g=d("state");f[g]=!0,r=function(e,t){if(p(e,g))throw v(m);return t.facade=e,u(e,g,t),t},a=function(e){return p(e,g)?e[g]:{}},o=function(e){return p(e,g)}}e.exports={set:r,get:a,has:o,enforce:function(e){return o(e)?a(e):r(e,{})},getterFor:function(e){return function(t){var n;if(!c(t)||(n=a(t)).type!==e)throw v("Incompatible receiver, "+e+" required");return n}}}},7659:(e,t,n)=>{var r=n(5112),a=n(7497),o=r("iterator"),i=Array.prototype;e.exports=function(e){return void 0!==e&&(a.Array===e||i[o]===e)}},3157:(e,t,n)=>{var r=n(4326);e.exports=Array.isArray||function(e){return"Array"==r(e)}},614:(e,t,n)=>{var r=n(4154),a=r.all;e.exports=r.IS_HTMLDDA?function(e){return"function"==typeof e||e===a}:function(e){return"function"==typeof e}},4411:(e,t,n)=>{var r=n(1702),a=n(7293),o=n(614),i=n(648),s=n(5005),c=n(2788),u=function(){},p=[],l=s("Reflect","construct"),d=/^\s*(?:class|function)\b/,f=r(d.exec),m=!d.exec(u),v=function(e){if(!o(e))return!1;try{return l(u,p,e),!0}catch(e){return!1}},h=function(e){if(!o(e))return!1;switch(i(e)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return m||!!f(d,c(e))}catch(e){return!0}};h.sham=!0,e.exports=!l||a((function(){var e;return v(v.call)||!v(Object)||!v((function(){e=!0}))||e}))?h:v},4705:(e,t,n)=>{var r=n(7293),a=n(614),o=/#|\.prototype\./,i=function(e,t){var n=c[s(e)];return n==p||n!=u&&(a(t)?r(t):!!t)},s=i.normalize=function(e){return String(e).replace(o,".").toLowerCase()},c=i.data={},u=i.NATIVE="N",p=i.POLYFILL="P";e.exports=i},5988:(e,t,n)=>{var r=n(111),a=Math.floor;e.exports=Number.isInteger||function(e){return!r(e)&&isFinite(e)&&a(e)===e}},8554:e=>{e.exports=function(e){return null==e}},111:(e,t,n)=>{var r=n(614),a=n(4154),o=a.all;e.exports=a.IS_HTMLDDA?function(e){return"object"==typeof e?null!==e:r(e)||e===o}:function(e){return"object"==typeof e?null!==e:r(e)}},1913:e=>{e.exports=!1},7850:(e,t,n)=>{var r=n(111),a=n(4326),o=n(5112)("match");e.exports=function(e){var t;return r(e)&&(void 0!==(t=e[o])?!!t:"RegExp"==a(e))}},2190:(e,t,n)=>{var r=n(5005),a=n(614),o=n(7976),i=n(3307),s=Object;e.exports=i?function(e){return"symbol"==typeof e}:function(e){var t=r("Symbol");return a(t)&&o(t.prototype,s(e))}},408:(e,t,n)=>{var r=n(9974),a=n(6916),o=n(9670),i=n(6330),s=n(7659),c=n(6244),u=n(7976),p=n(4121),l=n(1246),d=n(9212),f=TypeError,m=function(e,t){this.stopped=e,this.result=t},v=m.prototype;e.exports=function(e,t,n){var h,b,g,y,x,w,_,S=n&&n.that,k=!(!n||!n.AS_ENTRIES),E=!(!n||!n.IS_RECORD),A=!(!n||!n.IS_ITERATOR),j=!(!n||!n.INTERRUPTED),O=r(t,S),C=function(e){return h&&d(h,"normal",e),new m(!0,e)},R=function(e){return k?(o(e),j?O(e[0],e[1],C):O(e[0],e[1])):j?O(e,C):O(e)};if(E)h=e.iterator;else if(A)h=e;else{if(!(b=l(e)))throw f(i(e)+" is not iterable");if(s(b)){for(g=0,y=c(e);y>g;g++)if((x=R(e[g]))&&u(v,x))return x;return new m(!1)}h=p(e,b)}for(w=E?e.next:h.next;!(_=a(w,h)).done;){try{x=R(_.value)}catch(e){d(h,"throw",e)}if("object"==typeof x&&x&&u(v,x))return x}return new m(!1)}},9212:(e,t,n)=>{var r=n(6916),a=n(9670),o=n(8173);e.exports=function(e,t,n){var i,s;a(e);try{if(!(i=o(e,"return"))){if("throw"===t)throw n;return n}i=r(i,e)}catch(e){s=!0,i=e}if("throw"===t)throw n;if(s)throw i;return a(i),n}},3061:(e,t,n)=>{"use strict";var r=n(3383).IteratorPrototype,a=n(30),o=n(9114),i=n(8003),s=n(7497),c=function(){return this};e.exports=function(e,t,n,u){var p=t+" Iterator";return e.prototype=a(r,{next:o(+!u,n)}),i(e,p,!1,!0),s[p]=c,e}},1656:(e,t,n)=>{"use strict";var r=n(2109),a=n(6916),o=n(1913),i=n(6530),s=n(614),c=n(3061),u=n(9518),p=n(7674),l=n(8003),d=n(8880),f=n(8052),m=n(5112),v=n(7497),h=n(3383),b=i.PROPER,g=i.CONFIGURABLE,y=h.IteratorPrototype,x=h.BUGGY_SAFARI_ITERATORS,w=m("iterator"),_="keys",S="values",k="entries",E=function(){return this};e.exports=function(e,t,n,i,m,h,A){c(n,t,i);var j,O,C,R=function(e){if(e===m&&N)return N;if(!x&&e in P)return P[e];switch(e){case _:case S:case k:return function(){return new n(this,e)}}return function(){return new n(this)}},T=t+" Iterator",I=!1,P=e.prototype,U=P[w]||P["@@iterator"]||m&&P[m],N=!x&&U||R(m),D="Array"==t&&P.entries||U;if(D&&(j=u(D.call(new e)))!==Object.prototype&&j.next&&(o||u(j)===y||(p?p(j,y):s(j[w])||f(j,w,E)),l(j,T,!0,!0),o&&(v[T]=E)),b&&m==S&&U&&U.name!==S&&(!o&&g?d(P,"name",S):(I=!0,N=function(){return a(U,this)})),m)if(O={values:R(S),keys:h?N:R(_),entries:R(k)},A)for(C in O)(x||I||!(C in P))&&f(P,C,O[C]);else r({target:t,proto:!0,forced:x||I},O);return o&&!A||P[w]===N||f(P,w,N,{name:m}),v[t]=N,O}},3383:(e,t,n)=>{"use strict";var r,a,o,i=n(7293),s=n(614),c=n(111),u=n(30),p=n(9518),l=n(8052),d=n(5112),f=n(1913),m=d("iterator"),v=!1;[].keys&&("next"in(o=[].keys())?(a=p(p(o)))!==Object.prototype&&(r=a):v=!0),!c(r)||i((function(){var e={};return r[m].call(e)!==e}))?r={}:f&&(r=u(r)),s(r[m])||l(r,m,(function(){return this})),e.exports={IteratorPrototype:r,BUGGY_SAFARI_ITERATORS:v}},7497:e=>{e.exports={}},6244:(e,t,n)=>{var r=n(7466);e.exports=function(e){return r(e.length)}},6339:(e,t,n)=>{var r=n(1702),a=n(7293),o=n(614),i=n(2597),s=n(9781),c=n(6530).CONFIGURABLE,u=n(2788),p=n(9909),l=p.enforce,d=p.get,f=String,m=Object.defineProperty,v=r("".slice),h=r("".replace),b=r([].join),g=s&&!a((function(){return 8!==m((function(){}),"length",{value:8}).length})),y=String(String).split("String"),x=e.exports=function(e,t,n){"Symbol("===v(f(t),0,7)&&(t="["+h(f(t),/^Symbol\(([^)]*)\)/,"$1")+"]"),n&&n.getter&&(t="get "+t),n&&n.setter&&(t="set "+t),(!i(e,"name")||c&&e.name!==t)&&(s?m(e,"name",{value:t,configurable:!0}):e.name=t),g&&n&&i(n,"arity")&&e.length!==n.arity&&m(e,"length",{value:n.arity});try{n&&i(n,"constructor")&&n.constructor?s&&m(e,"prototype",{writable:!1}):e.prototype&&(e.prototype=void 0)}catch(e){}var r=l(e);return i(r,"source")||(r.source=b(y,"string"==typeof t?t:"")),e};Function.prototype.toString=x((function(){return o(this)&&d(this).source||u(this)}),"toString")},4758:e=>{var t=Math.ceil,n=Math.floor;e.exports=Math.trunc||function(e){var r=+e;return(r>0?n:t)(r)}},5948:(e,t,n)=>{var r,a,o,i,s,c=n(7854),u=n(9974),p=n(1236).f,l=n(261).set,d=n(8572),f=n(6833),m=n(1528),v=n(1036),h=n(5268),b=c.MutationObserver||c.WebKitMutationObserver,g=c.document,y=c.process,x=c.Promise,w=p(c,"queueMicrotask"),_=w&&w.value;if(!_){var S=new d,k=function(){var e,t;for(h&&(e=y.domain)&&e.exit();t=S.get();)try{t()}catch(e){throw S.head&&r(),e}e&&e.enter()};f||h||v||!b||!g?!m&&x&&x.resolve?((i=x.resolve(void 0)).constructor=x,s=u(i.then,i),r=function(){s(k)}):h?r=function(){y.nextTick(k)}:(l=u(l,c),r=function(){l(k)}):(a=!0,o=g.createTextNode(""),new b(k).observe(o,{characterData:!0}),r=function(){o.data=a=!a}),_=function(e){S.head||r(),S.add(e)}}e.exports=_},8523:(e,t,n)=>{"use strict";var r=n(9662),a=TypeError,o=function(e){var t,n;this.promise=new e((function(e,r){if(void 0!==t||void 0!==n)throw a("Bad Promise constructor");t=e,n=r})),this.resolve=r(t),this.reject=r(n)};e.exports.f=function(e){return new o(e)}},3929:(e,t,n)=>{var r=n(7850),a=TypeError;e.exports=function(e){if(r(e))throw a("The method doesn't accept regular expressions");return e}},2814:(e,t,n)=>{var r=n(7854),a=n(7293),o=n(1702),i=n(1340),s=n(3111).trim,c=n(1361),u=o("".charAt),p=r.parseFloat,l=r.Symbol,d=l&&l.iterator,f=1/p(c+"-0")!=-1/0||d&&!a((function(){p(Object(d))}));e.exports=f?function(e){var t=s(i(e)),n=p(t);return 0===n&&"-"==u(t,0)?-0:n}:p},3009:(e,t,n)=>{var r=n(7854),a=n(7293),o=n(1702),i=n(1340),s=n(3111).trim,c=n(1361),u=r.parseInt,p=r.Symbol,l=p&&p.iterator,d=/^[+-]?0x/i,f=o(d.exec),m=8!==u(c+"08")||22!==u(c+"0x16")||l&&!a((function(){u(Object(l))}));e.exports=m?function(e,t){var n=s(i(e));return u(n,t>>>0||(f(d,n)?16:10))}:u},30:(e,t,n)=>{var r,a=n(9670),o=n(6048),i=n(748),s=n(3501),c=n(490),u=n(317),p=n(6200)("IE_PROTO"),l=function(){},d=function(e){return"