resource_waiter.js (5123B)
1 /** 2 * Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"). You 5 * may not use this file except in compliance with the License. A copy of 6 * the License is located at 7 * 8 * http://aws.amazon.com/apache2.0/ 9 * 10 * or in the "license" file accompanying this file. This file is 11 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 * ANY KIND, either express or implied. See the License for the specific 13 * language governing permissions and limitations under the License. 14 */ 15 16 var AWS = require('./core'); 17 var inherit = AWS.util.inherit; 18 var jmespath = require('jmespath'); 19 20 /** 21 * @api private 22 */ 23 function CHECK_ACCEPTORS(resp) { 24 var waiter = resp.request._waiter; 25 var acceptors = waiter.config.acceptors; 26 var acceptorMatched = false; 27 var state = 'retry'; 28 29 acceptors.forEach(function(acceptor) { 30 if (!acceptorMatched) { 31 var matcher = waiter.matchers[acceptor.matcher]; 32 if (matcher && matcher(resp, acceptor.expected, acceptor.argument)) { 33 acceptorMatched = true; 34 state = acceptor.state; 35 } 36 } 37 }); 38 39 if (!acceptorMatched && resp.error) state = 'failure'; 40 41 if (state === 'success') { 42 waiter.setSuccess(resp); 43 } else { 44 waiter.setError(resp, state === 'retry'); 45 } 46 } 47 48 /** 49 * @api private 50 */ 51 AWS.ResourceWaiter = inherit({ 52 /** 53 * Waits for a given state on a service object 54 * @param service [Service] the service object to wait on 55 * @param state [String] the state (defined in waiter configuration) to wait 56 * for. 57 * @example Create a waiter for running EC2 instances 58 * var ec2 = new AWS.EC2; 59 * var waiter = new AWS.ResourceWaiter(ec2, 'instanceRunning'); 60 */ 61 constructor: function constructor(service, state) { 62 this.service = service; 63 this.state = state; 64 this.loadWaiterConfig(this.state); 65 }, 66 67 service: null, 68 69 state: null, 70 71 config: null, 72 73 matchers: { 74 path: function(resp, expected, argument) { 75 var result = jmespath.search(resp.data, argument); 76 return jmespath.strictDeepEqual(result,expected); 77 }, 78 79 pathAll: function(resp, expected, argument) { 80 var results = jmespath.search(resp.data, argument); 81 if (!Array.isArray(results)) results = [results]; 82 var numResults = results.length; 83 if (!numResults) return false; 84 for (var ind = 0 ; ind < numResults; ind++) { 85 if (!jmespath.strictDeepEqual(results[ind], expected)) { 86 return false; 87 } 88 } 89 return true; 90 }, 91 92 pathAny: function(resp, expected, argument) { 93 var results = jmespath.search(resp.data, argument); 94 if (!Array.isArray(results)) results = [results]; 95 var numResults = results.length; 96 for (var ind = 0 ; ind < numResults; ind++) { 97 if (jmespath.strictDeepEqual(results[ind], expected)) { 98 return true; 99 } 100 } 101 return false; 102 }, 103 104 status: function(resp, expected) { 105 var statusCode = resp.httpResponse.statusCode; 106 return (typeof statusCode === 'number') && (statusCode === expected); 107 }, 108 109 error: function(resp, expected) { 110 if (typeof expected === 'string' && resp.error) { 111 return expected === resp.error.code; 112 } 113 // if expected is not string, can be boolean indicating presence of error 114 return expected === !!resp.error; 115 } 116 }, 117 118 listeners: new AWS.SequentialExecutor().addNamedListeners(function(add) { 119 add('RETRY_CHECK', 'retry', function(resp) { 120 var waiter = resp.request._waiter; 121 if (resp.error && resp.error.code === 'ResourceNotReady') { 122 resp.error.retryDelay = (waiter.config.delay || 0) * 1000; 123 } 124 }); 125 126 add('CHECK_OUTPUT', 'extractData', CHECK_ACCEPTORS); 127 128 add('CHECK_ERROR', 'extractError', CHECK_ACCEPTORS); 129 }), 130 131 /** 132 * @return [AWS.Request] 133 */ 134 wait: function wait(params, callback) { 135 if (typeof params === 'function') { 136 callback = params; params = undefined; 137 } 138 139 var request = this.service.makeRequest(this.config.operation, params); 140 request._waiter = this; 141 request.response.maxRetries = this.config.maxAttempts; 142 request.addListeners(this.listeners); 143 144 if (callback) request.send(callback); 145 return request; 146 }, 147 148 setSuccess: function setSuccess(resp) { 149 resp.error = null; 150 resp.data = resp.data || {}; 151 resp.request.removeAllListeners('extractData'); 152 }, 153 154 setError: function setError(resp, retryable) { 155 resp.data = null; 156 resp.error = AWS.util.error(resp.error || new Error(), { 157 code: 'ResourceNotReady', 158 message: 'Resource is not in the state ' + this.state, 159 retryable: retryable 160 }); 161 }, 162 163 /** 164 * Loads waiter configuration from API configuration 165 * 166 * @api private 167 */ 168 loadWaiterConfig: function loadWaiterConfig(state) { 169 if (!this.service.api.waiters[state]) { 170 throw new AWS.util.error(new Error(), { 171 code: 'StateNotFoundError', 172 message: 'State ' + state + ' not found.' 173 }); 174 } 175 176 this.config = this.service.api.waiters[state]; 177 } 178 });