Your IP : 3.17.156.114
Current Path : /opt/webdir/lib/ |
|
Current File : //opt/webdir/lib/bxProvider.pm |
# provider options
#
package bxProvider;
use strict;
use warnings;
use Moose;
use File::Basename qw( dirname basename );
use File::Path qw(remove_tree rmtree mkpath);
use File::Spec::Functions;
use Data::Dumper;
use JSON;
use Output;
use Pool;
use Host;
use SSHAuthUser;
use bxDaemon;
# basic path for site
has 'base', is => 'ro', default => '/opt/webdir/providers';
has 'provider', is => 'ro', lazy => 1, builder => 'provider_options', predicate => 'has_options';
has 'name', is => 'ro', lazy => 1, builder => 'provider_name', predicate => 'has_name';
has 'debug', is => 'ro', isa => 'Int', default => 0;
has 'logfile', is => 'ro', isa => 'Str', default => '/opt/webdir/logs/providers.debug';
# bulid
sub BUILD {
my $self = shift;
die "Need to specify at least one of 'provider', 'name'!"
unless $self->has_options || $self->has_name;
}
# execute plugin with defined option and return hash
sub executeProviderCmd {
my ($exec, $opt) = @_;
my $message_p = (caller(0))[3];
my $message_t = __PACKAGE__;
# test available actions
my $provider_execute = qq($exec $opt);
my $provider_json = "";
open(my $ph, "$provider_execute |")
or die "Cannot execute $provider_execute: $!";
while(my $line = <$ph>){
$provider_json .= $line;
}
close $ph;
if($provider_json =~ /^$/){
return Output->new(
error => 1,
message => "$message_p: Command \`$provider_execute\` return empty string",
);
}
my $json_to_hash = from_json($provider_json);
if (exists $json_to_hash->{'error'}){
return Output->new(
error => $json_to_hash->{'error'},
message => "cmd=\`$provider_execute\` return ".$json_to_hash->{'error_message'},
)
}
return Output->new(
error => 0,
data => ['json', $json_to_hash],
);
}
# save status info to task file
sub saveStatus{
my ($file, $status, $message) = @_;
my $message_p = (caller(0))[3];
my $message_t = __PACKAGE__;
open (my $fh, '>', $file)
or return Output->new(
error => 1,
message => "$message_p: Cannot open $file: $!",
);
print $fh "$status: $message";
close $fh;
}
# create name based on provider options
sub provider_name {
my $self = shift;
my $provider_options = $self->provider;
return $provider_options->{'name'};
}
# get all provider option by it's name
sub provider_options {
my $self = shift;
my $provider_name = $self->name;
my $provider_base = $self->base;
my $provider_options = {
name => $provider_name,
status => 'not_exists', # define exist or not directory with provider data
config => 'not_exists', # defined exist config file or not
files => {
holder => catfile($provider_base, $provider_name),
config => undef, # config file for provider
execute => undef, # execute script
},
options => { #
help => 0, # print supported actions
init => 0, # action that used when pool created, manager host setup
configs => 0, # list host configuration name
order => 0, # request for configuration that user choose
order_status => 0, # status of request
},
};
my $provider_execdir = catfile($provider_options->{'files'}->{'holder'}, 'bin');
my $provider_confdir = catfile($provider_options->{'files'}->{'holder'}, 'etc');
$provider_options->{'files'}->{'execute'} = catfile($provider_execdir, $provider_name);
$provider_options->{'files'}->{'config'} = catfile($provider_confdir, $provider_name.'.conf');
if (! -x $provider_options->{'files'}->{'execute'}){ return $provider_options; }
$provider_options->{'status'} = 'exists';
if (-f $provider_options->{'files'}->{'config'}){
$provider_options->{'config'} = 'exists';
}
# test available actions
my $provider_answer = executeProviderCmd(
$provider_options->{'files'}->{'execute'},
'help',
);
if($provider_answer->is_error){ return $provider_options; }
my $json_to_hash = $provider_answer->get_data->[1];
if(defined $json_to_hash->{'options'}){
foreach my $cmd (@{$json_to_hash->{'options'}}){
if (defined $provider_options->{'options'}->{$cmd}){
$provider_options->{'options'}->{$cmd} = 1;
}
}
}
if(defined $json_to_hash->{'status'}){
$provider_options->{'status'} = $json_to_hash->{'status'};
}
return $provider_options;
}
# unpack archive to defined folder
sub unpack {
my ($self, $folder, $archive) = @_;
my $message_p = (caller(0))[3];
my $message_t = __PACKAGE__;
if(!$folder){
return Output->new(
error => 1,
message => "$message_t: path/to/folder is mandatory option"
);
}
if(!$archive){
return Output->new(
error => 1,
message => "$message_t: path/to/archive is mandatory option"
);
}
if(! -f $archive){
return Output->new(
error => 1,
message => "$message_t: not found $archive"
);
}
if (! -d $folder){
mkdir $folder;
}
if ($archive =~ /\.(tar\.gz|tar\.bz2|zip)$/){
my $archive_type = $1;
my $unpack_cmd = '';
if ($archive_type =~ /^tar\.gz$/){
$unpack_cmd = qq(tar xzf $archive -C $folder);
}elsif($archive_type =~ /^tar\.bz2$/){
$unpack_cmd = qq(tar xzf $archive -C $folder);
}elsif($archive_type =~ /^zip$/){
$unpack_cmd = qq(unzip $archive -d $folder);
}
system("$unpack_cmd 1>/dev/null 2>&1") == 0
or return Output->new(
error => 1,
message => "$message_t: cmd=\`$unpack_cmd\` return error: $!"
);
return Output->new(
error => 0,
message => "$message_t: unpack $archive to $folder",
);
}else{
return Output->new(
error => 1,
message => "$message_t: not supported archive (only support tar.gz, tar.bz2, zip)"
);
}
}
# create initial directories for provider and copy its files
sub installProvider {
my ($self, $archive) = @_;
my $message_p = (caller(0))[3];
my $message_t = __PACKAGE__;
my $debug = $self->debug;
my $logOutput = Output->new(error => 0, logfile => $self->logfile);
my $provider_options = $self->provider;
my $provider_name = $self->name;
my $provider_base = $self->base;
if ($provider_options->{'status'} !~ /^not_exists$/){
return Output->new(
error => 1,
message => "$message_t: Provider $provider_name already exist on the host"
);
}
# create directories
foreach my $d (
$provider_base,
$provider_options->{'files'}->{'holder'},
dirname($provider_options->{'files'}->{'execute'}),
dirname($provider_options->{'files'}->{'config'}),
){
if(! -d $d){
mkdir $d;
if($debug){ $logOutput->log_data("$message_p: mkdir $d"); }
}
}
if ($archive){
my $unpack_archive = $self->unpack($provider_options->{'files'}->{'holder'}, $archive);
if ($unpack_archive->is_error){ return $unpack_archive; }
if($debug){ $logOutput->log_data("$message_p: unpack $archive to ".$provider_options->{'files'}->{'holder'}); }
}
my $chown_cmd = qq(chown -R bitrix.bitrix $provider_options->{'files'}->{'holder'});
system($chown_cmd) == 0
or return Output->new(
error => 1,
message => "$message_t: Cannot change access on ".$provider_options->{'files'}->{'holder'},
);
return Output->new(
error => 0,
message => "$message_t: Provider data created, you can found it in ".$provider_options->{'files'}->{'holder'},
);
}
# delete folder for provider
sub uninstallProvider {
my ($self) = @_;
my $message_p = (caller(0))[3];
my $message_t = __PACKAGE__;
my $debug = $self->debug;
my $logOutput = Output->new(error => 0, logfile => $self->logfile);
my $provider_options = $self->provider;
my $provider_name = $self->name;
if ($provider_options->{'status'} =~ /^not_exists$/){
return Output->new(
error => 1,
message => "$message_t: Provider $provider_name not exist on the host"
);
}
my $provider_directory = $provider_options->{'files'}->{'holder'};
remove_tree( $provider_options, {error => \my $err} );
if (@$err) {
my $error_message = "";
for my $diag (@$err) {
my ($file, $message) = %$diag;
if ($file eq '') {
$error_message .= "general error: $message\n";
}else{
$error_message .= "problem unlinking $file: $message\n";
}
}
return Output->new(
error => 1,
message => "$message_t: $error_message"
);
}
return Output->new(
error => 0,
message => "$message_t: Provider data deleted from ".$provider_options->{'files'}->{'holder'},
);
}
# get provider status
sub optionsProvider {
my ($self) = @_;
my $message_p = (caller(0))[3];
my $message_t = __PACKAGE__;
my $debug = $self->debug;
my $logOutput = Output->new(error => 0, logfile => $self->logfile);
my $provider_options = $self->provider;
my $provider_name = $self->name;
if ($provider_options->{'status'} =~ /^not_exists$/){
return Output->new(
error => 1,
message => "$message_p: Provider $provider_name not exist on the host"
);
}
my $output = { $provider_name => $provider_options };
return Output->new(
error => 0,
data => ["provider_options", $output],
);
}
# get provider status
sub configsProvider {
my ($self) = @_;
my $message_p = (caller(0))[3];
my $message_t = __PACKAGE__;
my $debug = $self->debug;
my $logOutput = Output->new(error => 0, logfile => $self->logfile);
my $provider_options = $self->provider;
my $provider_name = $self->name;
if ($provider_options->{'status'} =~ /^(not_exists|disabled)$/){
return Output->new(
error => 1,
message => "$message_p: Provider $provider_name $1 on the host"
);
}
if($provider_options->{'options'}->{'configs'} == 0){
return Output->new(
error => 1,
message => "$message_p: Provider $provider_name doesn't support configs option"
);
}
# test available actions
my $provider_answer = executeProviderCmd(
$provider_options->{'files'}->{'execute'},
'configs',
);
if($provider_answer->is_error){ return $provider_answer; }
my $provider_configs = $provider_answer->get_data->[1];
return Output->new(
error => 0,
data => ["provider_configs", {$provider_name => $provider_configs}],
);
}
# order config from provider
sub order2Provider {
my ($self,$config_id) = @_;
my $message_p = (caller(0))[3];
my $message_t = __PACKAGE__;
my $debug = $self->debug;
my $logOutput = Output->new(error => 0, logfile => $self->logfile);
if(not defined $config_id){
return Output->new(
error => 1,
message => "$message_p: You must provide config_id for order"
);
}
my $provider_options = $self->provider;
my $provider_name = $self->name;
my $provider_tasks = catfile($provider_options->{'files'}->{'holder'},'tasks');
if ($provider_options->{'status'} =~ /^(not_exists|disabled)$/){
return Output->new(
error => 1,
message => "$message_p: Provider $provider_name $1 on the host"
);
}
if($provider_options->{'options'}->{'order'} == 0){
return Output->new(
error => 1,
message => "$message_p: Provider $provider_name doesn't support order option"
);
}
# test available actions
my $provider_answer = executeProviderCmd(
$provider_options->{'files'}->{'execute'},
"order $config_id",
);
if($provider_answer->is_error){ return $provider_answer; }
if(! -d $provider_tasks){
mkpath($provider_tasks,0,0770);
}
my $provider_order = $provider_answer->get_data->[1];
my $task_id = $provider_order->{'task_id'};
if($task_id =~ /^$/){
return Output->new(
error => 1,
message => "$message_p: Provider $provider_name doesn't return task_id"
);
}
my $task_file = catfile($provider_tasks, $task_id);
open (my $th, '>', $task_file)
or return Output->new(
error => 1,
message => "$message_p: Cannot save $task_file: $!"
);
close $th;
return Output->new(
error => 0,
data => ["provider_order", {$provider_name => $provider_order}],
);
}
sub order_status2Provider {
my ($self,$task_id) = @_;
my $message_p = (caller(0))[3];
my $message_t = __PACKAGE__;
my $debug = $self->debug;
my $logOutput = Output->new(error => 0, logfile => $self->logfile);
if(not defined $task_id){
return Output->new(
error => 1,
message => "$message_p: You must provide task_id for status request"
);
}
my $provider_options = $self->provider;
my $provider_name = $self->name;
my $provider_tasks = catfile($provider_options->{'files'}->{'holder'},'tasks');
if ($provider_options->{'status'} =~ /^(not_exists|disabled)$/){
return Output->new(
error => 1,
message => "$message_p: Provider $provider_name $1 on the host"
);
}
if($provider_options->{'options'}->{'order_status'} == 0){
return Output->new(
error => 1,
message => "$message_p: Provider $provider_name doesn't support order_status option"
);
}
# test available actions
my $provider_answer = executeProviderCmd(
$provider_options->{'files'}->{'execute'},
"order_status $task_id",
);
# save error to task info
if($provider_answer->is_error){ return $provider_answer; }
my $provider_order_status = $provider_answer->get_data->[1];
my $status = $provider_order_status->{'status'};
if($status =~ /^$/){
return Output->new(
error => 1,
message => "$message_p: Provider $provider_name doesn't return status for task_id=$task_id"
);
}
# test if we already try to add host to the pool
my $task_file = catfile($provider_tasks, $task_id);
if (-f $task_file){
open (my $th, '<', $task_file)
or return Output->new(
error => 1,
message => "Cannot open $task_file: $!",
);
my $server_status = "";
while(my $line = <$th>){
$server_status .= $line;
}
close $th;
if ($server_status =~ /^error:\s*(.+)$/){
$provider_order_status->{'status'} = "error";
$provider_order_status->{'message'}= "$1";
$provider_order_status->{'error'} = 1;
}elsif($server_status =~ /^complete/){
$provider_order_status->{'status'} = "complete";
}
}
return Output->new(
error => 0,
data => ["provider_order", {$provider_name => $provider_order_status}],
);
}
# test task_id and start adding server to the pool
sub order_to_hostProvider {
my ($self,$task_id) = @_;
my $message_p = (caller(0))[3];
my $message_t = __PACKAGE__;
my $debug = $self->debug;
my $logOutput = Output->new(error => 0, logfile => $self->logfile);
if(not defined $task_id){
return Output->new(
error => 1,
message => "$message_p: You must provide task_id for status request"
);
}
my $provider_options = $self->provider;
my $provider_name = $self->name;
my $provider_tasks = catfile($provider_options->{'files'}->{'holder'},'tasks');
if ($provider_options->{'status'} =~ /^(not_exists|disabled)$/){
return Output->new(
error => 1,
message => "$message_p: Provider $provider_name $1 on the host"
);
}
if($provider_options->{'options'}->{'order_status'} == 0){
return Output->new(
error => 1,
message => "$message_p: Provider $provider_name doesn't support order_status option"
);
}
# test available actions
my $provider_answer = executeProviderCmd(
$provider_options->{'files'}->{'execute'},
"order_status $task_id",
);
# save error to task info
if($provider_answer->is_error){ return $provider_answer; }
my $provider_order_status = $provider_answer->get_data->[1];
my $status = $provider_order_status->{'status'};
if($status =~ /^$/){
return Output->new(
error => 1,
message => "$message_p: Provider $provider_name doesn't return status for task_id=$task_id"
);
}
# if remote server return that server is ready for client
if ($status =~ /^finished$/){
my $server_address = $provider_order_status->{'server'};
my $server_password = ($provider_order_status->{'server_password'})?
$provider_order_status->{'server_password'}: "";
# use task_file, to avoid re-adding server to the pool
my $task_file = catfile($provider_tasks, $task_id);
my $server_status = "";
# it is an impossible situation, but suddenly
if(! -f $task_file){
return Output->new(
error => 1,
message => "$message_p: Not found saved task_id=$task_id on this server",
);
}
# test if user already add server to the pool
open(my $th, '<', $task_file)
or return Output->new(
error => 1,
message => "$message_p: Cannot open file=$task_file:$!"
);
while(my $line = <$th>){
$server_status .= $line;
}
close $th;
# test if task already complete
if ($server_status =~ /^(complete|error)/){
return Output->new(
error => 1,
message => "$message_p: task_id=$task_id already $server_status",
);
# we still did not do anything to add to the server pool
}elsif($server_status =~ /^$/){
#1. copy ssh key to host, if defined server_password
if($server_password !~ /^$/){
# get ssh key from pool configuration
my $po = Pool->new(debug=>$debug);
my $get_ssh_key = $po->get_ssh_key();
if ($get_ssh_key->is_error){
my $ss = saveStatus($task_file, "error", $get_ssh_key->get_message());
return $get_ssh_key;
}
# copy ssh key to the server
my $ssh = SSHAuthUser->new(
sship => $server_address,
sshkey => $get_ssh_key->get_data->[1],
oldpass => $server_password,
);
my $copy_ssh_key = $ssh->copy_ssh_key();
if ($copy_ssh_key->is_error){
my $ss = saveStatus($task_file, "error", $copy_ssh_key->get_message());
return $copy_ssh_key;
}
}
# add host to ansible group file and create host configuration file
my $host = Host->new(host => $server_address, ip => $server_address);
my $createHost = $host->createHost();
if ($createHost->is_error){
my $ss = saveStatus($task_file, "error", $createHost->get_message());
return $createHost
}
# save information that host added to group and order complete totally
my $ss = saveStatus($task_file, "complete", "Server=$server_address is added to the pool");
return Output->new(
error => 0,
message => "Server=$server_address is added to the pool",
);
}
# status == in_process
}elsif($status == "in_progress"){
return Output->new(
error => 0,
data => ["provider_order", {$provider_name => $provider_order_status}]
)
}else{
return Output->new(
error => 1,
message => "Unknown status=$status for $provider_name",
);
}
}
# get list of all tasks that order servers|VPS
sub listOrders4Provider {
my $self = shift;
my $message_p = (caller(0))[3];
my $message_t = __PACKAGE__;
my $debug = $self->debug;
my $logOutput = Output->new(error => 0, logfile => $self->logfile);
my $provider_options = $self->provider;
my $provider_name = $self->name;
my $provider_tasks = catfile($provider_options->{'files'}->{'holder'},'tasks');
if ($provider_options->{'status'} =~ /^(not_exists|disabled)$/){
return Output->new(
error => 1,
message => "$message_p: Provider $provider_name $1 on the host"
);
}
if($provider_options->{'options'}->{'order_status'} == 0){
return Output->new(
error => 1,
message => "$message_p: Provider $provider_name doesn't support order_status option"
);
}
opendir(my $td, $provider_tasks)
or return Output->new(
error => 1,
message => "$message_t: Cannot open dircetory ".$provider_tasks.": $!",
);
my $output;
while(my $task_id = readdir($td)){
next if ($task_id =~ /^\.\.?$/);
my $fn = catfile($provider_tasks, $task_id);
my $task_modify = (stat($fn))[9];
if ( -f $fn){
my $task_status = $self->order_status2Provider($task_id);
if ($task_status->is_error){
$output->{$provider_name}->{$task_id} = {
error => $task_status->is_error,
message => $task_status->get_message,
mtime => $task_modify,
};
}else{
my $task_data = $task_status->get_data->[1]->{$provider_name};
my $task_stat = $task_data->{'status'};
my $task_err = ($task_data->{'error'})? $task_data->{'error'}: 0;
my $task_msg = ($task_data->{'message'})? $task_data->{'message'}: "";
$output->{$provider_name}->{$task_id} = {
error => $task_err,
message => $task_msg,
status => $task_stat,
mtime => $task_modify,
};
}
}
}
return Output->new(
error => 0,
data => ["provider_order_list", $output],
);
}
1;