Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
namespace Aws;
3
 
4
use Aws\Exception\AwsException;
5
use GuzzleHttp\Promise\Coroutine;
6
use GuzzleHttp\Promise\PromisorInterface;
7
use GuzzleHttp\Promise\RejectedPromise;
8
 
9
/**
10
 * "Waiters" are associated with an AWS resource (e.g., EC2 instance), and poll
11
 * that resource and until it is in a particular state.
12
 
13
 * The Waiter object produces a promise that is either a.) resolved once the
14
 * waiting conditions are met, or b.) rejected if the waiting conditions cannot
15
 * be met or has exceeded the number of allowed attempts at meeting the
16
 * conditions. You can use waiters in a blocking or non-blocking way, depending
17
 * on whether you call wait() on the promise.
18
 
19
 * The configuration for the waiter must include information about the operation
20
 * and the conditions for wait completion.
21
 */
22
class Waiter implements PromisorInterface
23
{
24
    /** @var AwsClientInterface Client used to execute each attempt. */
25
    private $client;
26
 
27
    /** @var string Name of the waiter. */
28
    private $name;
29
 
30
    /** @var array Params to use with each attempt operation. */
31
    private $args;
32
 
33
    /** @var array Waiter configuration. */
34
    private $config;
35
 
36
    /** @var array Default configuration options. */
37
    private static $defaults = ['initDelay' => 0, 'before' => null];
38
 
39
    /** @var array Required configuration options. */
40
    private static $required = [
41
        'acceptors',
42
        'delay',
43
        'maxAttempts',
44
        'operation',
45
    ];
46
 
47
    /**
48
     * The array of configuration options include:
49
     *
50
     * - acceptors: (array) Array of acceptor options
51
     * - delay: (int) Number of seconds to delay between attempts
52
     * - maxAttempts: (int) Maximum number of attempts before failing
53
     * - operation: (string) Name of the API operation to use for polling
54
     * - before: (callable) Invoked before attempts. Accepts command and tries.
55
     *
56
     * @param AwsClientInterface $client Client used to execute commands.
57
     * @param string             $name   Waiter name.
58
     * @param array              $args   Command arguments.
59
     * @param array              $config Waiter config that overrides defaults.
60
     *
61
     * @throws \InvalidArgumentException if the configuration is incomplete.
62
     */
63
    public function __construct(
64
        AwsClientInterface $client,
65
        $name,
66
        array $args = [],
67
        array $config = []
68
    ) {
69
        $this->client = $client;
70
        $this->name = $name;
71
        $this->args = $args;
72
 
73
        // Prepare and validate config.
74
        $this->config = $config + self::$defaults;
75
        foreach (self::$required as $key) {
76
            if (!isset($this->config[$key])) {
77
                throw new \InvalidArgumentException(
78
                    'The provided waiter configuration was incomplete.'
79
                );
80
            }
81
        }
82
        if ($this->config['before'] && !is_callable($this->config['before'])) {
83
            throw new \InvalidArgumentException(
84
                'The provided "before" callback is not callable.'
85
            );
86
        }
87
    }
88
 
89
    /**
90
     * @return Coroutine
91
     */
92
    public function promise()
93
    {
94
        return Coroutine::of(function () {
95
            $name = $this->config['operation'];
96
            for ($state = 'retry', $attempt = 1; $state === 'retry'; $attempt++) {
97
                // Execute the operation.
98
                $args = $this->getArgsForAttempt($attempt);
99
                $command = $this->client->getCommand($name, $args);
100
                try {
101
                    if ($this->config['before']) {
102
                        $this->config['before']($command, $attempt);
103
                    }
104
                    $result = (yield $this->client->executeAsync($command));
105
                } catch (AwsException $e) {
106
                    $result = $e;
107
                }
108
 
109
                // Determine the waiter's state and what to do next.
110
                $state = $this->determineState($result);
111
                if ($state === 'success') {
112
                    yield $command;
113
                } elseif ($state === 'failed') {
114
                    $msg = "The {$this->name} waiter entered a failure state.";
115
                    if ($result instanceof \Exception) {
116
                        $msg .= ' Reason: ' . $result->getMessage();
117
                    }
118
                    yield new RejectedPromise(new \RuntimeException($msg));
119
                } elseif ($state === 'retry'
120
                    && $attempt >= $this->config['maxAttempts']
121
                ) {
122
                    $state = 'failed';
123
                    yield new RejectedPromise(new \RuntimeException(
124
                        "The {$this->name} waiter failed after attempt #{$attempt}."
125
                    ));
126
                }
127
            }
128
        });
129
    }
130
 
131
    /**
132
     * Gets the operation arguments for the attempt, including the delay.
133
     *
134
     * @param $attempt Number of the current attempt.
135
     *
136
     * @return mixed integer
137
     */
138
    private function getArgsForAttempt($attempt)
139
    {
140
        $args = $this->args;
141
 
142
        // Determine the delay.
143
        $delay = ($attempt === 1)
144
            ? $this->config['initDelay']
145
            : $this->config['delay'];
146
        if (is_callable($delay)) {
147
            $delay = $delay($attempt);
148
        }
149
 
150
        // Set the delay. (Note: handlers except delay in milliseconds.)
151
        if (!isset($args['@http'])) {
152
            $args['@http'] = [];
153
        }
154
        $args['@http']['delay'] = $delay * 1000;
155
 
156
        return $args;
157
    }
158
 
159
    /**
160
     * Determines the state of the waiter attempt, based on the result of
161
     * polling the resource. A waiter can have the state of "success", "failed",
162
     * or "retry".
163
     *
164
     * @param mixed $result
165
     *
166
     * @return string Will be "success", "failed", or "retry"
167
     */
168
    private function determineState($result)
169
    {
170
        foreach ($this->config['acceptors'] as $acceptor) {
171
            $matcher = 'matches' . ucfirst($acceptor['matcher']);
172
            if ($this->{$matcher}($result, $acceptor)) {
173
                return $acceptor['state'];
174
            }
175
        }
176
 
177
        return $result instanceof \Exception ? 'failed' : 'retry';
178
    }
179
 
180
    /**
181
     * @param Result $result   Result or exception.
182
     * @param array  $acceptor Acceptor configuration being checked.
183
     *
184
     * @return bool
185
     */
186
    private function matchesPath($result, array $acceptor)
187
    {
188
        return !($result instanceof ResultInterface)
189
            ? false
190
            : $acceptor['expected'] == $result->search($acceptor['argument']);
191
    }
192
 
193
    /**
194
     * @param Result $result   Result or exception.
195
     * @param array  $acceptor Acceptor configuration being checked.
196
     *
197
     * @return bool
198
     */
199
    private function matchesPathAll($result, array $acceptor)
200
    {
201
        if (!($result instanceof ResultInterface)) {
202
            return false;
203
        }
204
 
205
        $actuals = $result->search($acceptor['argument']) ?: [];
206
        foreach ($actuals as $actual) {
207
            if ($actual != $acceptor['expected']) {
208
                return false;
209
            }
210
        }
211
 
212
        return true;
213
    }
214
 
215
    /**
216
     * @param Result $result   Result or exception.
217
     * @param array  $acceptor Acceptor configuration being checked.
218
     *
219
     * @return bool
220
     */
221
    private function matchesPathAny($result, array $acceptor)
222
    {
223
        if (!($result instanceof ResultInterface)) {
224
            return false;
225
        }
226
 
227
        $actuals = $result->search($acceptor['argument']) ?: [];
228
        return in_array($acceptor['expected'], $actuals);
229
    }
230
 
231
    /**
232
     * @param Result $result   Result or exception.
233
     * @param array  $acceptor Acceptor configuration being checked.
234
     *
235
     * @return bool
236
     */
237
    private function matchesStatus($result, array $acceptor)
238
    {
239
        if ($result instanceof ResultInterface) {
240
            return $acceptor['expected'] == $result['@metadata']['statusCode'];
241
        }
242
 
243
        if ($result instanceof AwsException && $response = $result->getResponse()) {
244
            return $acceptor['expected'] == $response->getStatusCode();
245
        }
246
 
247
        return false;
248
    }
249
 
250
    /**
251
     * @param Result $result   Result or exception.
252
     * @param array  $acceptor Acceptor configuration being checked.
253
     *
254
     * @return bool
255
     */
256
    private function matchesError($result, array $acceptor)
257
    {
258
        if ($result instanceof AwsException) {
259
            return $result->isConnectionError()
260
                || $result->getAwsErrorCode() == $acceptor['expected'];
261
        }
262
 
263
        return false;
264
    }
265
}