Untitled

mail@pastecode.io avatar
unknown
plain_text
2 years ago
6.8 kB
1
Indexable
Never
<?php

namespace App\Jobs;

use App\Events\InstanceEvent;
use App\Models\Instance;
use App\Models\Project;
use App\Models\PublicIpv4;
use App\Models\User;
use Confirm\ZabbixApi\ZabbixApi;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
use Popson\CloudStack\Clients\UserClient;
use Popson\CloudStack\Model\AsyncJob;
use Popson\CloudStack\Model\VirtualMachine;
use Popson\CloudStack\Services\User\AddressApiService;
use Popson\CloudStack\Services\User\AsyncJobApiService;
use Popson\CloudStack\Services\User\FirewallApiService;
use Popson\CloudStack\Services\User\NatApiService;
use Popson\CloudStack\Services\User\VirtualMachineApiService;

class AssignIpAddressJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Batchable;

    protected User $user;
    protected PublicIpv4 $ipv4;
    protected AsyncJob $virtualMachineJob;
    protected string $networkType;    
    protected string $isSourceIpUsed;    

    public int|float $timeout = 60 * 15; // 15 minutes
    public bool $failOnTimeout = true;

    public function __construct(User $user, PublicIpv4 $ipv4, AsyncJob $virtualMachineJob, string $networkType, $isSourceIpUsed)
    {
        $this->user = $user;
        $this->ipv4 = $ipv4;
        $this->virtualMachineJob = $virtualMachineJob;
        $this->networkType = $networkType;
        $this->isSourceIpUsed = $isSourceIpUsed;
    }

    public function handle()
    {
        // PublicIpv4 Migrated from PublicIpv4::query() to Polymorphic Relation
        // So instance_id replaced with public_ipv4able_id
        $instance = Instance::query()->findOrFail($this->ipv4->public_ipv4able_id);
        $project = Project::find($this->ipv4->project_id);

        $os_family = $instance?->os_version_offering?->osVersion?->os?->family??'Linux';


        app()->singleton(UserClient::class, function () {
            return new UserClient(config('services.cloudstack'), $this->user);
        });

        $asyncJobApiService = app(AsyncJobApiService::class);
        $firewallApiService = app(FirewallApiService::class);
        $addressApiService = app(AddressApiService::class);
        $natApiService = app(NatApiService::class); 
        $virtualMachineApiService = app(VirtualMachineApiService::class); 

        $vm = $this->waitForTheVmToBeCreated($asyncJobApiService);
        
        if ($vm->password_enabled) {
            $instance->update([
                'password_enabled' => true,
                'encrypted_password' => Crypt::encryptString($vm->password ?? '<password not set>'),
            ]);
        }

        if(empty($this->ipv4->ipv4_id)){
            event(new InstanceEvent($this->user->id, 'vm-ready', "Instance is ready..."));
            return;
        }

        $this->waitForIpv4GetAllocated($this->ipv4, $addressApiService); 

        if($this->networkType == 'Isolated' && $this->isSourceIpUsed){
            $this->batch()->add(new CreateFirewallRuleJob($this->user, $this->ipv4->ipv4_id, $os_family, $instance));
        }

        Log::debug('Strategy:: '. $this->ipv4->strategy);
        $ipv4 = $this->ipv4;
        $user = $this->user;

        $this->batch()->add((function() use($project, $natApiService, $vm, $firewallApiService, $ipv4, $user, $asyncJobApiService){
            sleep(30);
            if ($ipv4->strategy === 'static') {   
                $natApiService->enableStaticNat([
                    'ipaddressid' => $ipv4->ipv4_id,
                    'virtualmachineid' => $ipv4->public_ipv4able_id,
                    'networkid' => $ipv4->network_id,
                    'projectid' => $project->id,
                    'domainid' => $project->account->domain_id,
                    'account' => $project->account->username,
                    'vmguestip' => $vm->nic?->first()->ipaddress,
                ]);
                event(new InstanceEvent($user->id, 'vm-ready', "Static nat enabled successfully!")); 

            } else {

                foreach ([80, 443, 22] as $port) {
                    $rule = [
                        'ipaddressid' => $ipv4->ipv4_id,
                        'privateport' => $port,
                        'publicport' => $port,
                        'virtualmachineid' => $ipv4->public_ipv4able_id,
                        'networkid' => $ipv4->network_id,
                        'openfirewall' => false,
                        'privateendport' => $port,
                        'publicendport' => $port,
                    ];
        
                    $job = $firewallApiService->createPortForwardingRule($rule + ['protocol' => 'tcp']);
                    $this->followJob($asyncJobApiService, $job);

                    $job = $firewallApiService->createPortForwardingRule($rule + ['protocol' => 'udp']);
                    $this->followJob($asyncJobApiService, $job);
                }
                
                event(new InstanceEvent($user->id, 'vm-ready', "Port forwarding rules created successfully!"));
            }

        }));
        
    }

    protected function followJob(AsyncJobApiService $asyncJobApiService, AsyncJob $job)
    {
        $retries = 10;

        while ($retries-- > 0) {
            $result = $asyncJobApiService->queryAsyncJobResult(['jobid' => $job->jobId]);

            if (!$result->finished()) sleep(3);
            else return;
        }

        $this->release(30);
    }

    protected function waitForTheVmToBeCreated(mixed $asyncJobApiService): VirtualMachine
    {
        // wait for the vm to start
        while (true) {
            /** @var \Popson\CloudStack\Model\AsyncJobResult $result */
            $result = $asyncJobApiService->queryAsyncJobResult(['jobid' => $this->virtualMachineJob->jobId]);

            if ($result->finished()) {
                return new VirtualMachine($result->job_result['virtualmachine'] ?? []);
            }

            sleep(5);
        }
    }

    private function waitForIpv4GetAllocated(PublicIpv4 $ipv4, AddressApiService $addressApiService)
    {
        while (true) {
            $publicIpAddresses = $addressApiService->listPublicIpAddresses([
                'id' => $ipv4->ipv4_id,
                'allocatedonly' => false
            ]);

            /** @var \Popson\CloudStack\Model\PublicIpAddress $publicIpAddress */
            $publicIpAddress = $publicIpAddresses->first();

            if ($publicIpAddress->state === 'Allocated') {
                return;
            }

            sleep(2);
        }
    }
}