root / Classes / OpenStackRequest.m @ 54fd5c36
History | View | Annotate | Download (36 kB)
1 |
// |
---|---|
2 |
// OpenStackRequest.m |
3 |
// OpenStack |
4 |
// |
5 |
// Created by Mike Mayo on 10/8/10. |
6 |
// The OpenStack project is provided under the Apache 2.0 license. |
7 |
// |
8 |
|
9 |
#import "OpenStackRequest.h" |
10 |
#import "Provider.h" |
11 |
#import "OpenStackAccount.h" |
12 |
#import "Server.h" |
13 |
#import "Image.h" |
14 |
#import "Flavor.h" |
15 |
#import "APILogger.h" |
16 |
#import "JSON.h" |
17 |
#import "RateLimit.h" |
18 |
#import "Container.h" |
19 |
#import "StorageObject.h" |
20 |
#import "Folder.h" |
21 |
#import "BackupSchedule.h" |
22 |
#import "AccountManager.h" |
23 |
#import "GetFlavorsRequest.h" |
24 |
#import "APICallback.h" |
25 |
#import "APILogEntry.h" |
26 |
#import "NSString+Conveniences.h" |
27 |
|
28 |
|
29 |
static NSRecursiveLock *accessDetailsLock = nil; |
30 |
|
31 |
@implementation OpenStackRequest |
32 |
|
33 |
@synthesize account, callback, retriedCount; |
34 |
|
35 |
- (BOOL)isSuccess { |
36 |
return (200 <= [self responseStatusCode]) && ([self responseStatusCode] <= 299); |
37 |
} |
38 |
|
39 |
- (void)notify:(NSString *)name { |
40 |
NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"]; |
41 |
NSNotification *notification = [NSNotification notificationWithName:name object:nil userInfo:callbackUserInfo]; |
42 |
[[NSNotificationCenter defaultCenter] postNotification:notification]; |
43 |
} |
44 |
|
45 |
- (void)notify { |
46 |
NSString *observeName = [NSString stringWithFormat:@"%@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, [self.url description]]; |
47 |
NSString *callbackName = [NSString stringWithFormat:@"%@ %@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, [self.url description], self.callback.uuid]; |
48 |
|
49 |
NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"]; |
50 |
|
51 |
NSNotification *observeNotification = [NSNotification notificationWithName:observeName object:nil userInfo:callbackUserInfo]; |
52 |
[[NSNotificationCenter defaultCenter] postNotification:observeNotification]; |
53 |
|
54 |
NSNotification *callbackNotification = [NSNotification notificationWithName:callbackName object:nil userInfo:callbackUserInfo]; |
55 |
[[NSNotificationCenter defaultCenter] postNotification:callbackNotification]; |
56 |
|
57 |
} |
58 |
|
59 |
- (NSString *)responseString { |
60 |
if (retried) { |
61 |
return [retriedRequest responseString]; |
62 |
} else { |
63 |
return [super responseString]; |
64 |
} |
65 |
} |
66 |
|
67 |
- (NSData *)responseData { |
68 |
if (retried) { |
69 |
return [retriedRequest responseData]; |
70 |
} else { |
71 |
return [super responseData]; |
72 |
} |
73 |
} |
74 |
|
75 |
- (NSDictionary *)responseHeaders { |
76 |
if (retried) { |
77 |
return [retriedRequest responseHeaders]; |
78 |
} else { |
79 |
return [super responseHeaders]; |
80 |
} |
81 |
} |
82 |
|
83 |
- (int)responseStatusCode { |
84 |
if (retried) { |
85 |
return [retriedRequest responseStatusCode]; |
86 |
} else { |
87 |
return [super responseStatusCode]; |
88 |
} |
89 |
} |
90 |
|
91 |
- (NSString *)responseStatusMessage { |
92 |
if (retried) { |
93 |
return [retriedRequest responseStatusMessage]; |
94 |
} else { |
95 |
return [super responseStatusMessage]; |
96 |
} |
97 |
} |
98 |
|
99 |
- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock { |
100 |
[super setCompletionBlock:aCompletionBlock]; |
101 |
[backupCompletionBlock release]; |
102 |
backupCompletionBlock = [aCompletionBlock copy]; |
103 |
} |
104 |
|
105 |
- (void)setFailedBlock:(ASIBasicBlock)aFailedBlock { |
106 |
[super setFailedBlock:aFailedBlock]; |
107 |
[backupFailureBlock release]; |
108 |
backupFailureBlock = [aFailedBlock copy]; |
109 |
} |
110 |
|
111 |
+ (RateLimit *)limitForPath:(NSString *)path verb:(NSString *)verb account:(OpenStackAccount *)anAccount { |
112 |
|
113 |
// find the lowest requests remaining of all the limits |
114 |
|
115 |
RateLimit *lowestLimit = nil; |
116 |
|
117 |
for (RateLimit *limit in anAccount.rateLimits) { |
118 |
if ([limit.verb isEqualToString:verb]) { |
119 |
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:limit.regex options:NSRegularExpressionCaseInsensitive error:nil]; |
120 |
NSInteger matches = [regex numberOfMatchesInString:path options:0 range:NSMakeRange(0, [path length])]; |
121 |
|
122 |
if (matches > 0) { |
123 |
if (lowestLimit) { |
124 |
if (limit.remaining < lowestLimit.remaining) { |
125 |
lowestLimit = limit; |
126 |
} |
127 |
} else { |
128 |
lowestLimit = limit; |
129 |
} |
130 |
} |
131 |
} |
132 |
} |
133 |
|
134 |
return lowestLimit; |
135 |
} |
136 |
|
137 |
#pragma mark - |
138 |
#pragma mark Generic Constructors |
139 |
|
140 |
+ (void)initialize { |
141 |
if (self == [OpenStackRequest class]) { |
142 |
accessDetailsLock = [[NSRecursiveLock alloc] init]; |
143 |
} |
144 |
} |
145 |
|
146 |
+ (id)request:(OpenStackAccount *)account method:(NSString *)method url:(NSURL *)url { |
147 |
OpenStackRequest *request = [[[OpenStackRequest alloc] initWithURL:url] autorelease]; |
148 |
request.account = account; |
149 |
[request setRequestMethod:method]; |
150 |
[request addRequestHeader:@"X-Auth-Token" value:[account authToken]]; |
151 |
[request addRequestHeader:@"Content-Type" value:@"application/json"]; |
152 |
[request setTimeOutSeconds:60]; |
153 |
[request setNumberOfTimesToRetryOnTimeout:5]; |
154 |
request.retriedCount = 0; |
155 |
return request; |
156 |
} |
157 |
|
158 |
+ (id)getSharingAccountsRequest:(OpenStackAccount *)account { |
159 |
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@?format=json",account.provider.authEndpointURL]]; |
160 |
return [OpenStackRequest request:account method:@"GET" url:url]; |
161 |
} |
162 |
|
163 |
+ (id)serversRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path { |
164 |
NSString *now = [[[NSDate date] description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; |
165 |
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?now=%@", account.serversURL, path, now]]; |
166 |
return [OpenStackRequest request:account method:method url:url]; |
167 |
} |
168 |
|
169 |
+ (id)filesRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path { |
170 |
NSString *now = [[[NSDate date] description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; |
171 |
|
172 |
NSString *filesUrl; |
173 |
NSString *urlString = [account.filesURL description]; |
174 |
if (account.sharingAccount) { |
175 |
NSRange authUserRange = [urlString rangeOfString:account.username]; |
176 |
filesUrl = [NSString stringWithFormat:@"%@%@", |
177 |
[urlString substringToIndex:authUserRange.location], |
178 |
account.sharingAccount]; |
179 |
} else |
180 |
filesUrl = urlString; |
181 |
|
182 |
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json%@&now=%@", |
183 |
filesUrl, |
184 |
path, |
185 |
account.shared ? @"&shared=" : @"", |
186 |
now]]; |
187 |
|
188 |
return [OpenStackRequest request:account method:method url:url]; |
189 |
} |
190 |
|
191 |
+ (id)cdnRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path { |
192 |
NSString *now = [[[NSDate date] description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; |
193 |
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json&now=%@", account.cdnURL, path, now]]; |
194 |
return [OpenStackRequest request:account method:method url:url]; |
195 |
} |
196 |
|
197 |
|
198 |
#pragma mark - |
199 |
#pragma mark Auth Retry |
200 |
|
201 |
- (void)authRetrySucceded:(OpenStackRequest *)retryRequest { |
202 |
if ([self isKindOfClass:[GetFlavorsRequest class]]) { |
203 |
NSLog(@"flavor request"); |
204 |
} |
205 |
self.account.authToken = [[retryRequest responseHeaders] objectForKey:@"X-Auth-Token"]; |
206 |
[self.account persist]; |
207 |
|
208 |
// TODO: make this work for GetServersRequest, etc |
209 |
|
210 |
// try the original request again! |
211 |
retried = YES; |
212 |
retriedRequest = [self copy]; |
213 |
|
214 |
[retriedRequest addRequestHeader:@"X-Auth-Token" value:self.account.authToken]; |
215 |
|
216 |
if (backupCompletionBlock) { |
217 |
[retriedRequest setCompletionBlock:^{ |
218 |
backupCompletionBlock(); |
219 |
}]; |
220 |
} |
221 |
if (backupFailureBlock) { |
222 |
[retriedRequest setFailedBlock:^{ |
223 |
backupFailureBlock(); |
224 |
}]; |
225 |
} |
226 |
|
227 |
[retriedRequest startSynchronous]; |
228 |
} |
229 |
|
230 |
- (void)authRetryFailed:(OpenStackRequest *)retryRequest { |
231 |
// if it fails due to bad connection, try again? |
232 |
NSNotification *notification = [NSNotification notificationWithName:[self.account.manager notificationName:@"authRetryFailed" identifier:0] object:nil userInfo:[NSDictionary dictionaryWithObject:retryRequest forKey:@"request"]]; |
233 |
[[NSNotificationCenter defaultCenter] postNotification:notification]; |
234 |
} |
235 |
|
236 |
#pragma mark - |
237 |
#pragma mark ASIHTTPRequest Overrides |
238 |
// overriding to log API calls |
239 |
|
240 |
- (void)requestFinished { |
241 |
//NSLog(@"request finished: %i %@", self.responseStatusCode, self.url); |
242 |
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; |
243 |
NSString *loggingLevel = [defaults valueForKey:@"api_logging_level"]; |
244 |
if ([loggingLevel isEqualToString:@"all"] || ([loggingLevel isEqualToString:@"errors"] && ![self isSuccess])) { |
245 |
[APILogger log:self]; |
246 |
} |
247 |
|
248 |
[super requestFinished]; |
249 |
} |
250 |
|
251 |
- (void)failWithError:(NSError *)theError { |
252 |
if (responseStatusCode == 401 && ![url isEqual:account.provider.authEndpointURL]) { |
253 |
// auth is expired, so get a fresh token |
254 |
if (account && ![account.provider isGRNet]) { |
255 |
OpenStackRequest *retryRequest = [OpenStackRequest authenticationRequest:account]; |
256 |
retryRequest.delegate = self; |
257 |
retryRequest.didFinishSelector = @selector(authRetrySucceded:); |
258 |
retryRequest.didFailSelector = @selector(authRetryFailed:); |
259 |
[retryRequest startSynchronous]; |
260 |
} |
261 |
} else if (responseStatusCode == 503) { |
262 |
NSNotification *notification = [NSNotification notificationWithName:@"serviceUnavailable" object:nil userInfo:nil]; |
263 |
[[NSNotificationCenter defaultCenter] postNotification:notification]; |
264 |
[super failWithError:theError]; |
265 |
} else if (responseStatusCode == 0) { |
266 |
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; |
267 |
if (![defaults boolForKey:@"already_failed_on_connection"]) { |
268 |
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Connection Error" message:@"Please check your connection or API URL and try again." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; |
269 |
[alert show]; |
270 |
[alert release]; |
271 |
} |
272 |
[defaults setBool:YES forKey:@"already_failed_on_connection"]; |
273 |
[defaults synchronize]; |
274 |
} |
275 |
|
276 |
[super failWithError:theError]; |
277 |
} |
278 |
|
279 |
#pragma mark - Authentication |
280 |
|
281 |
+ (NSString *)apiVersionForURL:(NSURL *)url { |
282 |
NSArray *components = [[url description] componentsSeparatedByString:@"/"]; |
283 |
if ([[components lastObject] isEqualToString:@"2.0"]) { |
284 |
return @"2.0"; |
285 |
} else if ([[components lastObject] isEqualToString:@"v1.1"]) { |
286 |
return @"1.1"; |
287 |
} else { |
288 |
return @"1.0"; |
289 |
} |
290 |
} |
291 |
|
292 |
+ (void)setupV1AuthForRequest:(OpenStackRequest *)request account:(OpenStackAccount *)account apiVersion:(NSString *)apiVersion { |
293 |
[request addRequestHeader:@"X-Auth-User" value:account.username]; |
294 |
if (account.apiKey) { |
295 |
[request addRequestHeader:@"X-Auth-Key" value:account.apiKey]; |
296 |
} else { |
297 |
[request addRequestHeader:@"X-Auth-Key" value:@""]; |
298 |
} |
299 |
} |
300 |
|
301 |
+ (void)setupAuthForRequest:(OpenStackRequest *)request account:(OpenStackAccount *)account apiVersion:(NSString *)apiVersion { |
302 |
|
303 |
if ([apiVersion isEqualToString:@"1.0"]) { |
304 |
|
305 |
[OpenStackRequest setupV1AuthForRequest:request account:account apiVersion:apiVersion]; |
306 |
account.apiVersion = @"1.0"; |
307 |
|
308 |
} else if ([apiVersion isEqualToString:@"1.1"]) { |
309 |
|
310 |
[OpenStackRequest setupV1AuthForRequest:request account:account apiVersion:apiVersion]; |
311 |
if (account.projectId) { |
312 |
[request addRequestHeader:@"X-Auth-Project-Id" value:account.projectId]; |
313 |
} |
314 |
account.apiVersion = @"1.1"; |
315 |
|
316 |
} else if ([apiVersion isEqualToString:@"2.0"]) { |
317 |
|
318 |
// TODO: support 2.0 auth |
319 |
account.apiVersion = @"1.1"; |
320 |
} |
321 |
} |
322 |
|
323 |
|
324 |
|
325 |
+ (OpenStackRequest *)authenticationRequest:(OpenStackAccount *)account { |
326 |
|
327 |
OpenStackRequest *request = [[[OpenStackRequest alloc] initWithURL:account.provider.authEndpointURL] autorelease]; |
328 |
request.account = account; |
329 |
|
330 |
NSString *apiVersion = [OpenStackRequest apiVersionForURL:account.provider.authEndpointURL]; |
331 |
[OpenStackRequest setupAuthForRequest:request account:account apiVersion:apiVersion]; |
332 |
|
333 |
return request; |
334 |
} |
335 |
|
336 |
#pragma mark - Rate Limits |
337 |
|
338 |
+ (OpenStackRequest *)getLimitsRequest:(OpenStackAccount *)account { |
339 |
return [OpenStackRequest serversRequest:account method:@"GET" path:@"/limits"]; |
340 |
} |
341 |
|
342 |
- (NSDictionary *)limits { |
343 |
SBJSON *parser = [[SBJSON alloc] init]; |
344 |
NSDictionary *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"limits"]; |
345 |
[parser release]; |
346 |
return jsonObjects; |
347 |
} |
348 |
|
349 |
- (NSArray *)rateLimits { |
350 |
NSArray *jsonObjects = [[self limits] objectForKey:@"rate"]; |
351 |
NSMutableArray *rateLimits = [[NSMutableArray alloc] initWithCapacity:[jsonObjects count]]; |
352 |
|
353 |
for (NSDictionary *dict in jsonObjects) { |
354 |
[rateLimits addObject:[RateLimit fromJSON:dict]]; |
355 |
} |
356 |
|
357 |
NSArray *result = [NSArray arrayWithArray:rateLimits]; |
358 |
|
359 |
[rateLimits release]; |
360 |
return result; |
361 |
} |
362 |
|
363 |
#pragma mark Collections |
364 |
|
365 |
+ (OpenStackRequest *)getServersRequest:(OpenStackAccount *)account { |
366 |
return [OpenStackRequest serversRequest:account method:@"GET" path:@"/servers/detail"]; |
367 |
} |
368 |
|
369 |
- (NSDictionary *)servers { |
370 |
SBJSON *parser = [[SBJSON alloc] init]; |
371 |
NSArray *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"servers"]; |
372 |
NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]]; |
373 |
|
374 |
for (int i = 0; i < [jsonObjects count]; i++) { |
375 |
NSDictionary *dict = [jsonObjects objectAtIndex:i]; |
376 |
Server *server = [[Server alloc] initWithJSONDict:dict]; |
377 |
[objects setObject:server forKey:server.identifier]; |
378 |
[server release]; |
379 |
} |
380 |
|
381 |
[parser release]; |
382 |
return objects; |
383 |
} |
384 |
|
385 |
+ (OpenStackRequest *)getServerRequest:(OpenStackAccount *)account serverId:(NSString *)serverId { |
386 |
return [OpenStackRequest serversRequest:account method:@"GET" path:[NSString stringWithFormat:@"/servers/%@", serverId]]; |
387 |
} |
388 |
|
389 |
+ (OpenStackRequest *)getImagesRequest:(OpenStackAccount *)account { |
390 |
return [OpenStackRequest serversRequest:account method:@"GET" path:@"/images/detail"]; |
391 |
} |
392 |
|
393 |
- (NSDictionary *)images { |
394 |
SBJSON *parser = [[SBJSON alloc] init]; |
395 |
NSArray *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"images"]; |
396 |
NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]]; |
397 |
|
398 |
for (int i = 0; i < [jsonObjects count]; i++) { |
399 |
NSDictionary *dict = [jsonObjects objectAtIndex:i]; |
400 |
Image *image = [[Image alloc] initWithJSONDict:dict]; |
401 |
[objects setObject:image forKey:image.identifier]; |
402 |
[image release]; |
403 |
} |
404 |
|
405 |
[parser release]; |
406 |
return objects; |
407 |
} |
408 |
|
409 |
+ (OpenStackRequest *)getImageRequest:(OpenStackAccount *)account imageId:(NSString *)imageId { |
410 |
return [OpenStackRequest serversRequest:account method:@"GET" path:[NSString stringWithFormat:@"/images/%@", imageId]]; |
411 |
} |
412 |
|
413 |
- (Image *)image { |
414 |
SBJSON *parser = [[SBJSON alloc] init]; |
415 |
NSDictionary *dict = [[parser objectWithString:[self responseString]] objectForKey:@"image"]; |
416 |
Image *image = [Image fromJSON:dict]; |
417 |
[parser release]; |
418 |
return image; |
419 |
} |
420 |
|
421 |
|
422 |
+ (OpenStackRequest *)getFlavorsRequest:(OpenStackAccount *)account { |
423 |
return [OpenStackRequest serversRequest:account method:@"GET" path:@"/flavors/detail"]; |
424 |
} |
425 |
|
426 |
- (NSDictionary *)flavors { |
427 |
SBJSON *parser = [[SBJSON alloc] init]; |
428 |
NSArray *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"flavors"]; |
429 |
NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]]; |
430 |
|
431 |
for (int i = 0; i < [jsonObjects count]; i++) { |
432 |
NSDictionary *dict = [jsonObjects objectAtIndex:i]; |
433 |
Flavor *flavor = [Flavor fromJSON:dict]; |
434 |
[objects setObject:flavor forKey:flavor.identifier]; |
435 |
} |
436 |
|
437 |
[parser release]; |
438 |
return objects; |
439 |
} |
440 |
|
441 |
#pragma mark Server Actions |
442 |
|
443 |
#define kSoft 0 |
444 |
#define kHard 1 |
445 |
|
446 |
+ (OpenStackRequest *)rebootServerRequest:(OpenStackAccount *)account server:(Server *)server type:(NSInteger)type { |
447 |
NSString *body = [NSString stringWithFormat:@"{ \"reboot\": { \"type\": \"%@\" } }", (type == kSoft) ? @"SOFT" : @"HARD"]; |
448 |
OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]]; |
449 |
NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding]; |
450 |
[request setPostBody:[NSMutableData dataWithData:data]]; |
451 |
return request; |
452 |
} |
453 |
|
454 |
+ (OpenStackRequest *)softRebootServerRequest:(OpenStackAccount *)account server:(Server *)server { |
455 |
return [OpenStackRequest rebootServerRequest:account server:server type:kSoft]; |
456 |
} |
457 |
|
458 |
+ (RateLimit *)softRebootServerLimit:(OpenStackAccount *)account server:(Server *)server { |
459 |
return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account]; |
460 |
} |
461 |
|
462 |
+ (OpenStackRequest *)hardRebootServerRequest:(OpenStackAccount *)account server:(Server *)server { |
463 |
return [OpenStackRequest rebootServerRequest:account server:server type:kHard]; |
464 |
} |
465 |
|
466 |
+ (RateLimit *)hardRebootServerLimit:(OpenStackAccount *)account server:(Server *)server { |
467 |
return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account]; |
468 |
} |
469 |
|
470 |
+ (OpenStackRequest *)changeServerAdminPasswordRequest:(OpenStackAccount *)account server:(Server *)server password:(NSString *)password { |
471 |
NSString *body = [NSString stringWithFormat:@"{ \"server\": { \"adminPass\": \"%@\" } }", password]; |
472 |
OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/servers/%@", server.identifier]]; |
473 |
NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding]; |
474 |
[request setPostBody:[NSMutableData dataWithData:data]]; |
475 |
return request; |
476 |
} |
477 |
|
478 |
+ (RateLimit *)changeServerAdminPasswordLimit:(OpenStackAccount *)account server:(Server *)server { |
479 |
return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@", server.identifier] verb:@"PUT" account:account]; |
480 |
} |
481 |
|
482 |
+ (OpenStackRequest *)renameServerRequest:(OpenStackAccount *)account server:(Server *)server name:(NSString *)name { |
483 |
NSString *body = [NSString stringWithFormat:@"{ \"server\": { \"name\": \"%@\" } }", name]; |
484 |
OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/servers/%@", server.identifier]]; |
485 |
NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding]; |
486 |
[request setPostBody:[NSMutableData dataWithData:data]]; |
487 |
return request; |
488 |
} |
489 |
|
490 |
+ (RateLimit *)renameServerLimit:(OpenStackAccount *)account server:(Server *)server { |
491 |
return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@", server.identifier] verb:@"PUT" account:account]; |
492 |
} |
493 |
|
494 |
+ (OpenStackRequest *)deleteServerRequest:(OpenStackAccount *)account server:(Server *)server { |
495 |
return [OpenStackRequest serversRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/servers/%@", server.identifier]]; |
496 |
} |
497 |
|
498 |
+ (RateLimit *)deleteServerLimit:(OpenStackAccount *)account server:(Server *)server { |
499 |
return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@", server.identifier] verb:@"DELETE" account:account]; |
500 |
} |
501 |
|
502 |
+ (OpenStackRequest *)createServerRequest:(OpenStackAccount *)account server:(Server *)server { |
503 |
NSString *body = [server toJSON:account.apiVersion]; |
504 |
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/servers", account.serversURL]]; |
505 |
NSLog(@"create server: %@", body); |
506 |
OpenStackRequest *request = [OpenStackRequest request:account method:@"POST" url:url]; |
507 |
NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding]; |
508 |
[request setPostBody:[NSMutableData dataWithData:data]]; |
509 |
return request; |
510 |
} |
511 |
|
512 |
+ (RateLimit *)createServerLimit:(OpenStackAccount *)account { |
513 |
return [OpenStackRequest limitForPath:@"/servers" verb:@"POST" account:account]; |
514 |
} |
515 |
|
516 |
- (Server *)server { |
517 |
SBJSON *parser = [[SBJSON alloc] init]; |
518 |
NSDictionary *dict = [[parser objectWithString:[self responseString]] objectForKey:@"server"]; |
519 |
Server *server = [Server fromJSON:dict]; |
520 |
[parser release]; |
521 |
return server; |
522 |
} |
523 |
|
524 |
+ (OpenStackRequest *)resizeServerRequest:(OpenStackAccount *)account server:(Server *)server flavor:(Flavor *)flavor { |
525 |
NSString *body; |
526 |
if ([account.apiVersion isEqualToString:@"1.0"]) { |
527 |
body = [NSString stringWithFormat:@"{ \"resize\": { \"flavorId\": %@ } }", flavor.identifier]; |
528 |
} else { |
529 |
body = [NSString stringWithFormat:@"{ \"resize\": { \"flavorRef\": %@ } }", flavor.identifier]; |
530 |
} |
531 |
OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]]; |
532 |
NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding]; |
533 |
[request setPostBody:[NSMutableData dataWithData:data]]; |
534 |
return request; |
535 |
} |
536 |
|
537 |
+ (RateLimit *)resizeServerLimit:(OpenStackAccount *)account server:(Server *)server { |
538 |
return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account]; |
539 |
} |
540 |
|
541 |
+ (OpenStackRequest *)confirmResizeServerRequest:(OpenStackAccount *)account server:(Server *)server { |
542 |
NSString *body = @"{ \"confirmResize\": null }"; |
543 |
OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]]; |
544 |
NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding]; |
545 |
[request setPostBody:[NSMutableData dataWithData:data]]; |
546 |
return request; |
547 |
} |
548 |
|
549 |
+ (RateLimit *)confirmResizeServerLimit:(OpenStackAccount *)account server:(Server *)server { |
550 |
return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account]; |
551 |
} |
552 |
|
553 |
+ (OpenStackRequest *)revertResizeServerRequest:(OpenStackAccount *)account server:(Server *)server { |
554 |
NSString *body = @"{ \"revertResize\": null }"; |
555 |
OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]]; |
556 |
NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding]; |
557 |
[request setPostBody:[NSMutableData dataWithData:data]]; |
558 |
return request; |
559 |
} |
560 |
|
561 |
+ (RateLimit *)revertResizeServerLimit:(OpenStackAccount *)account server:(Server *)server { |
562 |
return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account]; |
563 |
} |
564 |
|
565 |
+ (OpenStackRequest *)rebuildServerRequest:(OpenStackAccount *)account server:(Server *)server image:(Image *)image { |
566 |
NSString *body = [NSString stringWithFormat:@"{ \"rebuild\": { \"imageId\": %@ } }", image.identifier]; |
567 |
OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]]; |
568 |
NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding]; |
569 |
[request setPostBody:[NSMutableData dataWithData:data]]; |
570 |
return request; |
571 |
} |
572 |
|
573 |
+ (RateLimit *)rebuildServerLimit:(OpenStackAccount *)account server:(Server *)server { |
574 |
return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account]; |
575 |
} |
576 |
|
577 |
|
578 |
+ (OpenStackRequest *)getBackupScheduleRequest:(OpenStackAccount *)account server:(Server *)server { |
579 |
return [OpenStackRequest serversRequest:account method:@"GET" path:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier]]; |
580 |
} |
581 |
|
582 |
+ (RateLimit *)getBackupScheduleLimit:(OpenStackAccount *)account server:(Server *)server { |
583 |
return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier] verb:@"GET" account:account]; |
584 |
} |
585 |
|
586 |
+ (OpenStackRequest *)updateBackupScheduleRequest:(OpenStackAccount *)account server:(Server *)server { |
587 |
NSString *body = [NSString stringWithFormat:@"{ \"backupSchedule\": { \"enabled\": true, \"weekly\": \"%@\", \"daily\": \"%@\" } }", server.backupSchedule.weekly, server.backupSchedule.daily]; |
588 |
OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier]]; |
589 |
NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding]; |
590 |
[request setPostBody:[NSMutableData dataWithData:data]]; |
591 |
return request; |
592 |
} |
593 |
|
594 |
+ (RateLimit *)updateBackupScheduleLimit:(OpenStackAccount *)account server:(Server *)server { |
595 |
return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier] verb:@"POST" account:account]; |
596 |
} |
597 |
|
598 |
- (BackupSchedule *)backupSchedule { |
599 |
SBJSON *parser = [[SBJSON alloc] init]; |
600 |
NSDictionary *dict = [[parser objectWithString:[self responseString]] objectForKey:@"backupSchedule"]; |
601 |
BackupSchedule *backupSchedule = [BackupSchedule fromJSON:dict]; |
602 |
[parser release]; |
603 |
return backupSchedule; |
604 |
} |
605 |
|
606 |
#pragma mark - |
607 |
#pragma mark Object Storage Requests |
608 |
|
609 |
+ (OpenStackRequest *)getStorageAccountInfoRequest:(OpenStackAccount *)account { |
610 |
return [OpenStackRequest filesRequest:account method:@"HEAD" path:@""]; |
611 |
} |
612 |
|
613 |
+ (OpenStackRequest *)getContainersRequest:(OpenStackAccount *)account { |
614 |
return [OpenStackRequest filesRequest:account method:@"GET" path:@""]; |
615 |
} |
616 |
|
617 |
- (NSMutableDictionary *)containers { |
618 |
SBJSON *parser = [[SBJSON alloc] init]; |
619 |
NSArray *jsonObjects = [parser objectWithString:[self responseString]]; |
620 |
NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]]; |
621 |
|
622 |
for (int i = 0; i < [jsonObjects count]; i++) { |
623 |
NSDictionary *dict = [jsonObjects objectAtIndex:i]; |
624 |
Container *container = [Container fromJSON:dict]; |
625 |
[objects setObject:container forKey:container.name]; |
626 |
} |
627 |
|
628 |
[parser release]; |
629 |
return objects; |
630 |
} |
631 |
|
632 |
+ (OpenStackRequest *)createContainerRequest:(OpenStackAccount *)account container:(Container *)container { |
633 |
return [OpenStackRequest filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]]; |
634 |
} |
635 |
|
636 |
+ (OpenStackRequest *)deleteContainerRequest:(OpenStackAccount *)account container:(Container *)container { |
637 |
return [OpenStackRequest filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]]; |
638 |
} |
639 |
|
640 |
+ (OpenStackRequest *)getObjectsRequest:(OpenStackAccount *)account container:(Container *)container { |
641 |
return [OpenStackRequest filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]]; |
642 |
} |
643 |
|
644 |
- (NSMutableDictionary *)objects { |
645 |
SBJSON *parser = [[SBJSON alloc] init]; |
646 |
NSArray *jsonObjects = [parser objectWithString:[self responseString]]; |
647 |
|
648 |
NSMutableDictionary *objects = [[[NSMutableDictionary alloc] initWithCapacity:[jsonObjects count]] autorelease]; |
649 |
|
650 |
for (int i = 0; i < [jsonObjects count]; i++) { |
651 |
NSDictionary *dict = [jsonObjects objectAtIndex:i]; |
652 |
StorageObject *object = [StorageObject fromJSON:dict]; |
653 |
[objects setObject:object forKey:object.name]; |
654 |
} |
655 |
|
656 |
[parser release]; |
657 |
return objects; |
658 |
} |
659 |
|
660 |
+ (OpenStackRequest *)getContainerInfoRequest:(OpenStackAccount *)account container:(Container *)container { |
661 |
return [OpenStackRequest filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@",[NSString encodeToPercentEscape:container.name]]]; |
662 |
} |
663 |
|
664 |
+ (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object { |
665 |
NSString *objectFullPath = object.fullPath; |
666 |
if ([objectFullPath hasPrefix:@"/"]) |
667 |
objectFullPath = [objectFullPath substringFromIndex:1]; |
668 |
return [OpenStackRequest filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; |
669 |
} |
670 |
|
671 |
+ (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account |
672 |
container:(Container *)container |
673 |
object:(StorageObject *)object |
674 |
version:(NSString *)version { |
675 |
OpenStackRequest *request = [OpenStackRequest getObjectInfoRequest:account container:container object:object]; |
676 |
request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@",request.url.description, version]]; |
677 |
return request; |
678 |
} |
679 |
|
680 |
+ (OpenStackRequest *)getObjectVersionsRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object { |
681 |
OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; |
682 |
|
683 |
request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=list",request.url.description]]; |
684 |
return request; |
685 |
} |
686 |
|
687 |
+ (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object { |
688 |
return [OpenStackRequest filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; |
689 |
} |
690 |
|
691 |
+ (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account |
692 |
container:(Container *)container |
693 |
object:(StorageObject *)object |
694 |
version:(NSString *)version { |
695 |
OpenStackRequest *request = [OpenStackRequest getObjectRequest:account container:container object:object]; |
696 |
if (version) { |
697 |
request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@",request.url.description, version]]; |
698 |
} |
699 |
return request; |
700 |
} |
701 |
|
702 |
+ (OpenStackRequest *)writeObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object { |
703 |
NSString *fullPath = object.fullPath; |
704 |
if ([fullPath characterAtIndex:0] == '/') { |
705 |
fullPath = [fullPath substringFromIndex:1]; |
706 |
} |
707 |
|
708 |
OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; |
709 |
|
710 |
if (object.sharing) |
711 |
[request.requestHeaders setObject:object.sharing forKey:@"X-Object-Sharing"]; |
712 |
|
713 |
NSString *metadataKeyHeaderPrefix; |
714 |
if ([fullPath length] == 0) |
715 |
metadataKeyHeaderPrefix = @"X-Container-Meta-"; |
716 |
else |
717 |
metadataKeyHeaderPrefix = @"X-Object-Meta-"; |
718 |
|
719 |
for (NSString *metadataKey in object.metadata) { |
720 |
NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey]; |
721 |
metadataKeyHeader = [NSString encodeToPercentEscape:metadataKeyHeader]; |
722 |
NSString *metadataValue = [NSString encodeToPercentEscape:[object.metadata objectForKey:metadataKey]]; |
723 |
[request.requestHeaders setObject:metadataValue forKey:metadataKeyHeader]; |
724 |
} |
725 |
|
726 |
[request setPostBody:[NSMutableData dataWithData:object.data]]; |
727 |
[request.requestHeaders setObject:object.contentType forKey:@"Content-Type"]; |
728 |
return request; |
729 |
} |
730 |
|
731 |
+ (OpenStackRequest *)writeObjectMetadataRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object { |
732 |
NSString *fullPath = object.fullPath; |
733 |
if ([fullPath length] != 0 && [fullPath characterAtIndex:0] == '/') { |
734 |
fullPath = [fullPath substringFromIndex:1]; |
735 |
} |
736 |
|
737 |
NSString *metadataKeyHeaderPrefix; |
738 |
if ([fullPath length] == 0) |
739 |
metadataKeyHeaderPrefix = @"X-Container-Meta-"; |
740 |
else |
741 |
metadataKeyHeaderPrefix = @"X-Object-Meta-"; |
742 |
|
743 |
OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"POST" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; |
744 |
|
745 |
for (NSString *metadataKey in object.metadata) { |
746 |
NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey]; |
747 |
metadataKeyHeader = [NSString encodeToPercentEscape:metadataKeyHeader]; |
748 |
NSString *metadataValue = [NSString encodeToPercentEscape:[object.metadata objectForKey:metadataKey]]; |
749 |
[request.requestHeaders setObject:metadataValue forKey:metadataKeyHeader]; |
750 |
} |
751 |
if (!account.sharingAccount) { |
752 |
NSString *objectIsPublic = ([object.publicURI length] > 0) ? @"true" : @"false"; |
753 |
[request.requestHeaders setObject:objectIsPublic forKey:@"X-Object-Public"]; |
754 |
|
755 |
if (object.sharing) { |
756 |
NSString *urlEncodedSharingString = [NSString encodeToPercentEscape:object.sharing]; |
757 |
[request.requestHeaders setObject:urlEncodedSharingString forKey:@"X-Object-Sharing"]; |
758 |
} |
759 |
} |
760 |
return request; |
761 |
} |
762 |
|
763 |
|
764 |
+ (OpenStackRequest *)deleteObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object { |
765 |
if ([object.fullPath characterAtIndex:0] == '/') { |
766 |
return [OpenStackRequest filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; |
767 |
} else { |
768 |
return [OpenStackRequest filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; |
769 |
} |
770 |
} |
771 |
|
772 |
+ (OpenStackRequest *)writeContainerPolicyRequest:(OpenStackAccount *)account container:(Container *)container |
773 |
{ |
774 |
OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]]; |
775 |
|
776 |
[request.requestHeaders setObject:container.versioning forKey:@"X-Container-Policy-Versioning"]; |
777 |
[request.requestHeaders setObject:[NSString stringWithFormat:@"%u", container.quota] forKey:@"X-Container-Policy-Quota"]; |
778 |
|
779 |
return request; |
780 |
} |
781 |
|
782 |
+ (OpenStackRequest *)writeAccountMetadataRequest:(OpenStackAccount *)account withAccountInfo:(NSDictionary *)accountInfo |
783 |
{ |
784 |
OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"POST" path:@""]; |
785 |
|
786 |
NSMutableDictionary *groups = [accountInfo objectForKey:@"groups"]; |
787 |
for (NSString *groupName in groups) { |
788 |
NSString *group = [NSString encodeToPercentEscape:[groups objectForKey:groupName]]; |
789 |
groupName = [NSString encodeToPercentEscape:groupName]; |
790 |
[request.requestHeaders setObject:group forKey:[NSString stringWithFormat:@"X-Account-Group-%@", groupName]]; |
791 |
} |
792 |
if ([groups count] == 0) |
793 |
[request.requestHeaders setObject:@"" forKey:@"X-Account-Group-group"]; |
794 |
|
795 |
NSMutableDictionary *accountMetadata = [accountInfo objectForKey:@"metadata"]; |
796 |
for (NSString *metadataKey in accountMetadata) { |
797 |
NSString *metadataValue = [NSString encodeToPercentEscape:[accountMetadata objectForKey:metadataKey]]; |
798 |
metadataKey = [NSString encodeToPercentEscape:[accountMetadata objectForKey:metadataKey]]; |
799 |
[request.requestHeaders setObject:metadataValue forKey:[NSString stringWithFormat:@"X-Account-Meta-%@",metadataKey]]; |
800 |
} |
801 |
|
802 |
return request; |
803 |
} |
804 |
|
805 |
|
806 |
#pragma mark - |
807 |
#pragma mark Memory Management |
808 |
|
809 |
- (void)releaseBackupBlocksOnMainThread { |
810 |
NSMutableArray *blocks = [NSMutableArray array]; |
811 |
if (backupCompletionBlock) { |
812 |
[blocks addObject:backupCompletionBlock]; |
813 |
[backupCompletionBlock release]; |
814 |
backupCompletionBlock = nil; |
815 |
} |
816 |
if (backupFailureBlock) { |
817 |
[blocks addObject:backupFailureBlock]; |
818 |
[backupFailureBlock release]; |
819 |
backupFailureBlock = nil; |
820 |
} |
821 |
[[self class] performSelectorOnMainThread:@selector(releaseBackupBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]]; |
822 |
} |
823 |
|
824 |
// Always called on main thread |
825 |
+ (void)releaseBackupBlocks:(NSArray *)blocks { |
826 |
// Blocks will be released when this method exits |
827 |
} |
828 |
|
829 |
- (void)dealloc { |
830 |
[account release]; |
831 |
[self releaseBackupBlocksOnMainThread]; |
832 |
[super dealloc]; |
833 |
} |
834 |
|
835 |
@end |