I wrote the following code to launch the AWS Instance. It works fine. I am able to launch AWS instance. But I need to wait, until the instance's status check is 2/2 or the instance is ready to be invoked.
public function launch_instance() {
$isInstanceLaunched = array('status' => false);
try {
if(!empty($this->ec2Client)) {
/**
* 1) EbsOptimized : Indicates whether the instance is optimized for EBS I/O.
* This optimization provides dedicated throughput to Amazon EBS and an optimized
* configuration stack to provide optimal EBS I/O performance. This optimization isn't
* available with all instance types. Additional usage charges apply when using an
* EBS-optimized instance.
*/
$result = $this->ec2Client->runInstances([
'AdditionalInfo' => 'Launched from API',
'DisableApiTermination' => false,
'ImageId' => 'ami-8xaxxxe8', // REQUIRED
'InstanceInitiatedShutdownBehavior' => 'stop',
'InstanceType' => 't2.micro',
'MaxCount' => 1, // REQUIRED
'MinCount' => 1, // REQUIRED,
'EbsOptimized' => false, // SEE COMMENT 1
'KeyName' => 'TestCloudKey',
'Monitoring' => [
'Enabled' => true // REQUIRED
],
'SecurityGroups' => ['TestGroup']
]);
if($result) {
$metadata = $result->get('#metadata');
$statusCode = $metadata['statusCode'];
//Verify if the code is 200
if($statusCode == 200) {
$instanceData = $result->get('Instances');
$instanceID = $instanceData[0]['InstanceId'];
// Give a name to the launched instance
$tagCreated = $this->ec2Client->createTags([
'Resources' => [$instanceID],
'Tags' => [
[
'Key' => 'Name',
'Value' => 'Created from API'
]
]
]);
$instance = $this->ec2Client->describeInstances([
'InstanceIds' => [$instanceID]
]);
$instanceInfo = $instance->get('Reservations')[0]['Instances'];
// Get the Public IP of the instance
$instancePublicIP = $instanceInfo[0]['PublicIpAddress'];
// Get the Public DNS of the instance
$instancePublicDNS = $instanceInfo[0]['PublicDnsName'];
// Get the instance state
$instanceState = $instanceInfo[0]['State']['Name'];
$instanceS = $this->ec2Client->describeInstanceStatus([
'InstanceIds' => [$instanceID]
]);
try {
$waiterName = 'InstanceRunning';
$waiterOptions = ['InstanceId' => $instanceID];
$waiter = $this->ec2Client->getWaiter($waiterName,$waiterOptions);
// Initiate the waiter and retrieve a promise.
$promise = $waiter->promise();
// Call methods when the promise is resolved.
$promise
->then(function () {
// Waiter Completed
})
->otherwise(function (\Exception $e) {
echo "Waiter failed: " . $e . "\n";
});
// Block until the waiter completes or fails. Note that this might throw
// a RuntimeException if the waiter fails.
$promise->wait();
}catch(RuntimeException $runTimeException) {
$isInstanceLaunched['side-information'] = $runTimeException->getMessage();
}
$isInstanceLaunched['aws-response'] = $result;
$isInstanceLaunched['instance-public-ip'] = $instancePublicIP;
$isInstanceLaunched['status'] = true;
}
}
}else {
$isInstanceLaunched['message'] = 'EC2 client not initialized. Call init_ec2 to initialize the client';
}
}catch(Ec2Exception $ec2Exception){
$isInstanceLaunched['message'] = $ec2Exception->getMessage().'-- FILE --'.$ec2Exception->getFile().'-- LINE --'.$ec2Exception->getLine();
}catch(Exception $exc) {
$isInstanceLaunched['message'] = $exc->getMessage().'-- FILE -- '.$exc->getFile().'-- LINE -- '.$exc->getLine();
}
return $isInstanceLaunched;
}
I have used the waiter named InstanceRunning but that doesn't help. How do I know that the instance is ready to be invoked or the status check is 2/2?
have you try to replace
$waiter = $this->ec2Client->getWaiter($waiterName,$waiterOptions);
with
$this->ec2Client->waitUntil($waiterName, $waiterOptions);
$result = $ec2->describeInstances(array(
'InstanceIds' => array($instanceId),
));
I did not test for some time but it used to work for me
You need to use two waiters: first "InstanceRunning" and then "InstanceStatusOk"
I wrote the following code to check if the instance is ready to be invoked. I do a polling to check if the statusCheck is ok.
/**
* The following block checks if the instance is ready to be invoked
* or StatusCheck of the instance equals 2/2
* Loop will return:
* - If the status check received is 'ok'
* - If it exceeds _amazon_client::MAX_ATTEMPTS_TO_EC2_SCHECK
*/
$statusCheckAttempt = 0;
do {
// Sleep for _amazon_client::POLLING_SECONDS
sleep(_amazon_client::POLLING_SECONDS);
// Query the instance status
$instanceS = $this->ec2Client->describeInstanceStatus([
'InstanceIds' => [$instanceID]
]);
$statusCheck = $instanceS->get('InstanceStatuses')[0]['InstanceStatus']['Status'];
$statusCheckAttempt++;
}while($statusCheck != 'ok' ||
$statusCheckAttempt >= _amazon_client::MAX_ATTEMPTS_TO_EC2_SCHECK);
Note: When the instance has just been launched, $instanceS->get('InstanceStatuses')[0] returns an empty array. So I wait some seconds, before I could actually get the status.
Please suggest if there are better methods to achieve this.
Related
Array
(
[error] => WRONG_AUTH_TYPE
[error_description] => Current authorization type is denied for this method
)
i´m trying to create an event that triggers when a call starts and it should work for all users, not the ones with admin previleges but even i that have admin privileges get this error so i guess it does not have anything to do with it?
this is the error i get when i run my event handler, i´m new to this so i don´t actually know how the authorization works, i´ve read the documentation but haven´t reached any solutions yet.
handler.php:
<?php
require_once (__DIR__.'/crest.php');
class AuthSimple
{
const AUTH_TYPE = 'demo_simple';
const AUTH_PARAM_NAME = 'secret_word';
const AUTH_PARAM_VALUE = 'MySuperSecurePassword123456';
public static function onRestCheckAuth(array $query, $scope, &$res)
{
if(array_key_exists(static::AUTH_PARAM_NAME, $query))
{
$res = array('error' => 'INVALID_CREDENTIALS', 'error_description' => 'Invalid request credentials');
return false;
}
return null;
}
}
$data = file_get_contents('https://gotmink.bitrix24.com.br/rest/4755/uj3p50inofwg6eqm/');
echo '<PRE>';
$result = CRest::call(
'event.bind',
[
'event' => 'OnVoximplantCallInit',
'handler' => 'https://testebitrix24.herokuapp.com/handler.php',
'auth_type' => '4755',
'event_type' => 'online'
]
);
print_r($result);
echo '</PRE>';
?>
Is there a way to make this work..
I'm trying to publish a 'PING' request to a device, and then subscribe to another channel and wait for the 'PONG' response. Here's the code:
public function ping(Request $request)
{
$device = Device::find($request->id);
if ($device) {
//Listen on laravel_device_DEVICEID
$listen_topic = 'laravel_device_' . $device->device_id;
$result = [];
$mqtt = MQTT::connection();
$mqtt->subscribe($listen_topic, function (string $topic, string $message) use ($mqtt, &$result) {
$result['topic'] = $topic;
$result['message'] = $message;
$mqtt->interrupt();
}, 0);
$mqtt->loop(true, true);
//Submit PING message
$topic = $device->device_id;
$message = json_encode([
'type' => 'ping',
'device_id' => $device->device_id
]);
$mqtt = MQTT::connection();
$mqtt->publish($topic, $message);
$device->last_ping_response = 2;
$device->save();
}
}
I'm so free to copy my response from the original question in the php-mqtt/laravel-client repository.
Yes, it is possible and a fairly common thing to do as well. The pattern is called RPC (Remote Procedure Call).
Your code already looks quite good, the only issue is the order of subscribe(), publish() and loop(). Subscribing first is cruicial, since you do not want to miss a response before your subscription goes through. This part is correct already. But you may start the loop only after publishing the request, otherwise the publish() is never reached (because the loop does not exit by itself, only when it receives a message).
Here is the updated piece of code of yours, with some best practices added in. I assumed the response message is what you want to save as $device->last_ping_response:
use PhpMqtt\Client\MqttClient;
use PhpMqtt\Client\Exceptions\MqttClientException;
public function ping(Request $request)
{
$device = Device::find($request->id);
// Exit early if we have no device to ping. Doing it this way round,
// we save one level of identation for the rest of the code.
if ($device === null) {
return;
}
// Prepare all data before connecting to the broker. This way, we do not waste
// time preparing data while already connected.
$subscribeTopic = "laravel_device_{$device->device_id}";
$publishTopic = $device->device_id;
$request = json_encode([
'type' => 'ping',
'device_id' => $device->device_id,
]);
try {
$mqtt = MQTT::connection();
// Ensure we are ready to receive a response to the request
// we are going to send.
$mqtt->subscribe(
$subscribeTopic,
function (string $topic, string $message) use ($mqtt, $device) {
// Update the device based on the response.
$device->last_ping_response = $message;
$device->save();
$mqtt->interrupt();
},
MqttClient::QOS_AT_LEAST_ONCE
);
// Here we register a timeout using a loop event handler.
// The event handler is passed the elapsed time
// the loop runs already (in seconds).
// We do this because in case the receiver of our request is offline,
// we would be stuck in a loop forever.
$mqtt->registerLoopEventHandler(
function function (MqttClient $client, float $elapsedTime) {
// After 10 seconds, we quit the loop.
if ($elapsedTime > 10) {
$client->interrupt();
}
}
);
// Send the request. We use QoS 1 to have guaranteed delivery.
$mqtt->publish($publishTopic, $request, MqttClient::QOS_AT_LEAST_ONCE);
// Wait for a response. This will either return when we receive
// a response on the subscribed topic, or after 10 seconds
// due to our registered loop event handler.
$mqtt->loop();
// Always disconnect gracefully.
$mqtt->disconnect();
} catch (MqttClientException $e) {
\Log::error("Pinging device failed.", [
'device' => $device->id,
'exception' => $e->getMessage(),
]);
}
}
I have a list of Job IDs to check their status. So, I'm simply looping through all the Job IDs to get their status on Media Convert.
function get_aws_job_id_status($job_id)
{
$result = [];
$client = \App::make('aws')->createClient('MediaConvert', [
// 'profile' => 'default',
// 'version' => '2017-08-29',
'region' => 'region',
'endpoint' => "endpoint"
]);
try {
$result = $client->getJob([
'Id' => $job_id,
]);
return $result;
} catch (AwsException $e) {
return $result;
}
}
I'm using the above function inside the loop to get the status.
Referred to AWS Docs and Stackoverflow, but still, when I don't find a record for the given Job ID, it returns "NotFoundException" error that is not going in catch block and breaking the loop. Is there any way to handle that exception so I can continue the loop?
I believe you need to call Aws\MediaConvert\Exception\MediaConvertException and catch for MediaConvert specific errors. I don't see any of your use statements but I assume the code would look something like the following.
Note I am catching for all MediaConvert client errors, but I believe you could specifically call out the NotFoundException by doing Aws\MediaConvert\Exception\MediaConvertException\NotFoundException
use Aws\MediaConvert\MediaConvertClient;
use Aws\Exception\AwsException;
use Aws\MediaConvert\Exception\MediaConvertException as MediaConvertError;
function get_aws_job_id_status($job_id)
{
$result = [];
$client = \App::make('aws')->createClient('MediaConvert', [
// 'profile' => 'default',
// 'version' => '2017-08-29',
'region' => 'region',
'endpoint' => "endpoint"
]);
try {
$result = $client->getJob([
'Id' => $job_id,
]);
return $result;
} catch (MediaConvertError $e) {
/*Oh no, the job id provided ca not be found.
Let us log the job id and the message and return it back up to the main application
Note this assumes the main application is iterating through a list of JobIDs and
can handle this and log that job id was not found and will not have the normal Job
JSON structure.
*/
$error = array("Id"=>$job_id, "Message"=>"Job Id Not found");
$result = json_encode($error);
return $result;
}
}
Also keep in mind that if you are polling for job status's you may be throttled if your list grows too big. You would need to catch for a TooManyRequestsException [1] and try the poll with a back off threshold [2].
The most scalable solution is to use CloudWatch Events and track jobs based the STATUS_UPDATE, COMPLETE and ERROR status. [3]
[1] https://docs.aws.amazon.com/aws-sdk-php/v3/api/class-Aws.MediaConvert.Exception.MediaConvertException.html
[2] https://docs.aws.amazon.com/general/latest/gr/api-retries.html
[3] https://docs.aws.amazon.com/mediaconvert/latest/ug/monitoring-overview.html
Is there any way to mock response and request in Guzzle?
I have a class which sends some request and I want to test.
In Guzzle doc I found a way how can I mock response and request separately. But how can I combine them?
Because, If use history stack, guzzle trying to send a real request.
And visa verse, when I mock response handler can't test request.
class MyClass {
public function __construct($guzzleClient) {
$this->client = $guzzleClient;
}
public function registerUser($name, $lang)
{
$body = ['name' => $name, 'lang' = $lang, 'state' => 'online'];
$response = $this->sendRequest('PUT', '/users', ['body' => $body];
return $response->getStatusCode() == 201;
}
protected function sendRequest($method, $resource, array $options = [])
{
try {
$response = $this->client->request($method, $resource, $options);
} catch (BadResponseException $e) {
$response = $e->getResponse();
}
$this->response = $response;
return $response;
}
}
Test:
class MyClassTest {
//....
public function testRegisterUser()
{
$guzzleMock = new \GuzzleHttp\Handler\MockHandler([
new \GuzzleHttp\Psr7\Response(201, [], 'user created response'),
]);
$guzzleClient = new \GuzzleHttp\Client(['handler' => $guzzleMock]);
$myClass = new MyClass($guzzleClient);
/**
* But how can I check that request contains all fields that I put in the body? Or if I add some extra header?
*/
$this->assertTrue($myClass->registerUser('John Doe', 'en'));
}
//...
}
#Alex Blex was very close.
Solution:
$container = [];
$history = \GuzzleHttp\Middleware::history($container);
$guzzleMock = new \GuzzleHttp\Handler\MockHandler([
new \GuzzleHttp\Psr7\Response(201, [], 'user created response'),
]);
$stack = \GuzzleHttp\HandlerStack::create($guzzleMock);
$stack->push($history);
$guzzleClient = new \GuzzleHttp\Client(['handler' => $stack]);
First of all, you don't mock requests. The requests are the real ones you are going to use in production. The mock handler is actually a stack, so you can push multiple handlers there:
$container = [];
$history = \GuzzleHttp\Middleware::history($container);
$stack = \GuzzleHttp\Handler\MockHandler::createWithMiddleware([
new \GuzzleHttp\Psr7\Response(201, [], 'user created response'),
]);
$stack->push($history);
$guzzleClient = new \GuzzleHttp\Client(['handler' => $stack]);
After you run your tests, $container will have all transactions for you to assert. In your particular test - a single transaction. You are interested in $container[0]['request'], since $container[0]['response'] will contain your canned response, so there is nothing to assert really.
I have a function that looks like:
public function downloadProjectFolder($projectId, $taskToken){
// Download the project directory if it isn't on the server
if(is_dir('/path/to/folder/') === false){
$manager = $this->instantiateS3TransferObject($projectId, $taskToken);
$promise = $manager->promise();
$promise->wait();
}
else{
return 'Project Folder Already Exists';
}
}
The above method downloads a folder onto my server from AWS S3 if it doesn't already exist on the local machine. The actual S3 Transfer object (from the AWS PHP SDK V3 library - which in itself is mostly abstracted from Guzzle PHP) is instantiated by the below function:
private function instantiateS3TransferObject($projectId, $taskToken){
$lastDatetime = time();
return new \Aws\S3\Transfer($this->myS3ClientObject, 's3://mys3bucket/destination/url',
'/path/to/local/directory/', array(
'base_dir' => 'destination/url',
'before' => function()use($projectId, $taskToken, &$lastDatetime){
$currentDatetime = time();
if(($currentDatetime - $lastDatetime) >= 30){
$postParams = array(
'project_id' => $projectId,
'task_token' => $taskToken
);
$this->curl->post($postParams, 'http://url/endpoint');
$lastDatetime = $currentDatetime;
}
}
)
);
}
The above essentially starts my folder download and hits an custom endpoint every 30 seconds asynchronously.
How would I mock out the \Aws\S3\Transfer object in this case so that it includes the promise() method on return and that method in turn returns the wait() method?
Not much you can do about the time since it is a native function and cannot be mocked. You can slightly refactor it for the sake of testability to something like:
class TimeGetter
{
public function getTime()
{
return time();
}
}
and then use as
$currentDatetime = $this->timeGetter->getTime();
// instead of $currentDatetime = time();
So you can mock it later, and return whatever time you need to test your functionality.
Neither you can create a mock for \Aws\S3\Transfer, since you explicitly create a new instance in instantiateS3TransferObject.
For the rest of the code you will need to mock both Guzzle and curl. The very rough approximation based on the code snippet in the question:
// First Guzzle, but order doesn't matter:
$mock = new MockHandler([
// first expected request
function($_self, RequestInterface $request, array $options) {
// assert $request values meet expectations
// and return response or exception you expect from S3
return new Response(200, ['X-Foo' => 'Bar']);
},
// second expected request, if any, is either instance of Response, Exception, or a function which returns one of them
// third expected request, etc...
]);
$handler = HandlerStack::create($mock);
// then pass it to the class under test
// assuming it is public:
$cut->myS3ClientObject = new Client(['handler' => $handler]);
// Now curl:
$curlMock = $this->getMockBuilder('\whatever\curl\class\you\use')
->disableOriginalConstructor()
->setMethods(['post'])
->getMock();
$curlMock
->expects($this->once()) // twice, exact, etc ...
->method('post')
->with(
$this->equalTo(['project_id' => 'expected_project_id', 'task_token' => 'expected_token' ]),
$this->equalTo('http://url/endpoint')
);
//again, assuming it is public
$cut->curl = $curlMock;
// then actually execute the method you are testing:
$cut-> downloadProjectFolder('expected_project_id', 'expected_token');
You can read more how to test Guzzle in official docs.