scalr/fatmouse-phpclient

This package is abandoned and no longer maintained. No replacement package was suggested.

Fatmouse PHP Client

dev-master / 1.0.x-dev 2016-09-19 18:35 UTC

README

FatMouse provides a PHP client for integration with Scalr.

composer install

Tests

Tests are executed in devbox.

Provision devbox

sudo -E fab -f provision/linux/fabfile.py pull retag phpclient

Start Fatmouse

  • localy with docker-compose up
  • or remotely and set FAM_PHPCLIENT_BROKER_URL

Run suite

cd phpclient
./vendor/bin/phpunit -v tests/large

Examples

Initialize PHP Client

<?php
$broker = "amqp://fatmouse-api.scalr-labs.com";
$fatmouse = new Fatmouse\Fatmouse($broker);

Call Server Task. Generic

<?php
use Fatmouse\Errors;

$timeout = 30
$taskName = "register_server";
$params = [
    "server_id" => "cf3a320b-7ac6-4b88-810a-c760a94e1875",
    "env_id" => "7743d825-9792-46bc-827f-285882f58fb2"
];
$asyncResult = $fatmouse->callAsync($taskName, $params);
try {
    $task = $asyncResult->wait();
    var_dump($task->getResult());
}
finally {
    // Acknowledge result to remove it from queue
    $asyncResult->ack();
}

Call Server Task. Sugar

<?php
use Fatmouse\Errors;

try {
    $asyncResult = $fatmouse->registerServer($serverId, $envId);
    try {
        $reg = $asyncResult->wait()->getResult();
        print("Server registered!")
        var_dump($reg->agentConfig);
    }
    finally {
        $asyncResult->ack();
    }  
}
catch (\Exception $e) {
    printf("Server registration failed %s: %s", get_class($e), $e->getMessage());
}

Call Server Task In One Process, Get Result In Other

Process 1:

<?php
$asyncResult = $fatmouse->callAsync($taskName, $params);
$taskId = $asyncResult->getTaskId();
// Then pass $taskId to Process 2

Process 2:

<?php
$asyncResult = $fatmouse->newAsyncResult($taskId, $taskName);
$task = $asyncResult->wait();
try {
    $task->getResult();
}
finally {
    $asyncResult->ack();  
}

Call Scalarizr Task. Generic

<?php
$serverId = "cf3a320b-7ac6-4b88-810a-c760a94e1875";
$taskName = "sys.set_hostname";
$params = ["hostname" => "my.example.com"];
$timeout = 5; 
$result = $fatmouse->callAgentSync($serverId, $taskName, $params, $timeout);

Call Scalarizr Task. Sugar

<?php
$fatmouse->agent($serverId)->sys->setHostname("my.example.com");

// control timeout
$fatmouse->agent($serverId, ['timeout' => 30])->sys->setHostname("my.example.com");

Consume Events

<?php
$callback = function ($event) {
    printf(
        "Received event %s (ID: %s) with payload: %s",
        $event->name, 
        $event->eventId, 
        var_export($event->getPayload(), true)
    );
    // After result was handled, it should be acknowledged to prevent repeating handling
    $event->ack();
}

$fatmouse->initEventConsumer($callback);
$timeout = 1;
while (true) {
    try {
        // poll for event until timeout   
        $fatmouse->consumeEvent($timeout);
    } catch (\Exception $e) {
        print("Event loop error: %s" . $e->getMessage());
    }  
}

Register Scalr Managed Server In FatMouse

Fatmouse should know about each server started and terminated by Scalr.

When Scalr is going to launch new server, it should call registerServer().

Then, process result data:

  • Dump $reg->agentConfig into JSON and inject into cloud server as /etc/scalr/agent.json
  • Edit hostname in $reg->agentConfig['CELERY']['BROKER_URL'] to a public DNS name

To run a Scalr managed server, it's required to install agent on it. It's easy to delegate this to CloudInit. Take the user-data.tpl.sh, replace there %AGENT_JSON% with the JSON data, and pull down the result script as user-data to cloud provider's create server API call. Image you're using should support cloud-init (Ubuntu, Amazon Linux)

<?php
// When Scalr has launched new server
$asyncResult = $fatmouse->registerServer($serverId, $envId);
$reg = $asyncResult->wait()->getResult();

// agent.json data
var_dump($reg->agentConfig);

When Scalr has terminated server, it should call deregisterServer(). Task has no result value

<?php
// When Scalr terminates server
$fatmouse->deregisterServer();

Execute Workflows

If you're unfamiliar with workflows, read an introduction

To initialize server Scalr should run init workflow.

<?php
use \Fatmouse\Errors;

$asyncResult = $fatmouse->workflows()->init($params);
$init = $asyncResult->wait();
try {
    var_dump($init->getResult()); // output workflow result
    if ($init->isHalfCompleted()) {
        foreach ($init->failedTasks as $task) {
            printf('Task "%s" %s(%s) error: %s', 
                $task->title,
                $task->name,
                $task->taskId,
                $task->getException()->getMessage()
            );
        }
    }
catch (Errors\ServerException $e) {
    // handle error
    printf('Workflow %s(%s) error: %s',
        $init->getName(),
        $init->getTaskId(),
        $e->getMessage()
    );
} finally {
    $asyncResult->ack(); // Acknowlenge result and remove it from queue
}

Call Agent

It's possible to call agent tasks directly without workflows. It's useful for getting various OS information, logs, metrics directly from Scalr in a RPC fashion.

This example will get server CPU statistic

<?php
use Fatmouse\Agent\Fact;

$serverId = 'd20dc26c-6220-4df8-996a-92f09d710524';
$stat = $fatmouse->agent($serverId)->sys->getFact(Fact::STAT_CPU);
var_dump($stat);

/* Output
array(4) {
  ["nice"]=>
  int(0)
  ["user"]=>
  int(8416)
  ["system"]=>
  int(6754)
  ["idle"]=>
  int(147309)
}
*/

Receive Events

FatMouse fires various events, that should be listened and handled.

Handle Server Reboot

<?php
use Fatmouse\Events;

$callback = function ($event) {
    $pay = $event->getPayload();
    if ($pay instanceof Events\ServerRebooted)) {
        // Server was rebooted
        printf(
            'Server %s was rebooted at %s, and new boot_id is: %s', 
            $pay->serverId, 
            $event->firedAt, 
            $pay->bootId
        );
    } else {
        // default event handler
    }
    // acknowledge event, so it'll be never received by other workers
    // all received events should be acknowledged, or they will come again.
    $event->ack();  
}

$timeout = 1;
$fatmouse->initEventConsumer($callback);
while (true) {
    $fatmouse->consumeEvent($timeout);
}

Handle Orchestration Workflow Progress

<?php
use Fatmouse\Agent;
use Fatmouse\Events;

$asyncResult = $fatmouse->workflows()->orchestration(...);
$workflowId = $asyncResult->getTaskId();

$callback = function ($event) {
    $pay = $ev->getPayload();
    // track scripts and chef tasks status
    if ($pay instanceof Events\TaskFinished
        && $pay->workflow 
        && $pay->workflow->taskId == $workflowId
        && in_array($pay->task->name, [
              Agent\TaskName::EXECUTE, 
              Agent\TaskName::CHEF_SOLO, 
              Agent\TaskName::CHEF_CLIENT
           ])
        ) {
        // Push notification to js
        //
        // Example output:
        // [orchestration] task chef.solo(deploy application) on 83888...4f9368a COMPLETED
        // [orchestration] task execute(reload crontab) on d9f82a12...740a28 COMPLETED
        // [orchestration] task execute(danger staff) on d9f82a12...740a28 FAILED
        // E: Command exited with non-zero code 2
        
        $js->pushStatus(sprintf(
            '[%s] task %(%s) on server %s %s',
            $pay->task->workflow->name,
            $pay->task->name,
            $pay->task->title,
            $pay->serverId,
            strtoupper($pay->task->status)
        ));
        if ($pay->task->isFailed()) {
            $js->pushStatus(sprintf(
                'error: %s', 
                $pay->task->getException()->getMessage()
            ));
        }
        $event->ack();  // acknowledge event!
    }
}

$timeout = 1;
$fatmouse->initEventConsumer($callback);
while (true) {
    $fatmouse->consumeEvent($timeout);
}

Handle Workflow Finished Event

<?php
use Fatmouse\Events;
use Fatmouse\Errors;
use Fatmouse\Workflows;

$callback = function ($event) {
    $pay = $event->getPayload();
    if ($pay instanceof Events\WorkflowFinished) {
        try {
            $pay->workflow->throwForState();
            
            if ($pay->workflow instanceof Workflows\AgentUpdate) {
                // agent updated. proceed to orchestration(HostInit)
            }
            elseif ($pay->workflow instanceof Workflows\Init) {
                // initialization completed
                $pay->workflow->getResult(); // HostUp like data
            } 
            elseif ($pay->workflow instanceof Workflows\Orchestration) {
                // orchestration completed
            }
            elseif ($pay->workflow instanceof Workflows\Volume) {
                // storage volume created, attached and configured
            }
        }
        catch (Errors\ServerException $e) {
            // handle workflow error
            printf('Workflow %s(%s) error: %s',
                $pay->workflow->name,
                $pay->workflow->taskId,
                $e->getMessage()
            );
        }
        
        $event->ack();  // acknowledge event!
    }
}

$timeout = 1;
$fatmouse->initEventConsumer($callback);
while (true) {
    $fatmouse->consumeEvent($timeout);
}

The Complete Server Initialization Process (HostInit -> HostUp)

1. Register New Server.

<?php
$reg = $fatmouse->registerServer($serverId)->wait()->getResult();

2. Launch Server In a Cloud

Launch server in a cloud and pass $reg->agentConfig to server agent in a user-data script. More about server registration

3. Wait For AuthAgent Task Completion

Receive task_finished event from authenticate_server task. Read how to receive events

<?php
use Fatmouse\Events;
use Fatmouse\Errors;
use Fatmouse\Tasks;


$callback = function ($event) {
    $pay = $event->getPayload();
    if ($pay instanceof Events\TaskFinished
        && $pay->task instanceof Tasks\AuthenticateServer
        ) {
        try {
            $pay->task->throwForState();
            printf(
                'Server %s successfully authenticated',
                $pay->task->serverId
            );
        }
        catch (Errors\ServerException $e) {
            printf('Server %s authentication error: %s',
                $pay->task->serverId,
                $e->getMessage())
        }
        $event->ack();  // acknowledge event!
    }
    
}

4. Orchestrate HostInit

Create HostInit event object and execute orchestration workflow.

<?php
// TODO: TBD $orc format
$ar = $fatmouse->workflows()->orchestration($orc);
$result = $ar->wait()->getResult();

See how to setup callback to [handle workflow result](#Handle Workflow Finished Event) asynchronously. The same is valid for next steps.

5. Execute "init" workflow

Execute "init" workflow to initialize server. (Phase between HostInitResponse and BeforeHostUp)

<?php
use \Fatmouse\Types;

$init = new Types\InitializationParams()
    ->withHostName($hostname)
    ->andVolume($ephemeral_D)
    ->andVolume($ebs_E)
    ->andChefAction(new Types\Orchestration\ChefSoloAction()
        ->withJsonAttributes($jsonAttributes)
        ->andCookbooks(new Types\Orchestration\CookbookUrlSource()
            ->withUrl($httpsCookbooksTarGz)));
 
$ar = $fatmouse->workflows()->init($init)
$result = $ar->wait()->getResult();

$init and $result contain data similar to HostInitResponse / HostUp messages

6. Orchestrate BeforeHostUp

Create BeforeHostUp event object and execute orchestration workflow.

Server is running! Scalr adds server to DNS, and execute internal HostUp routines

7. Orchestrate HostUp

Create HostUp event object and execute orchestration workflow. orchestration workflow.