5 // Created by Mike Mayo on 10/8/10.
6 // The OpenStack project is provided under the Apache 2.0 license.
9 #import "OpenStackRequest.h"
11 #import "OpenStackAccount.h"
19 #import "StorageObject.h"
21 #import "BackupSchedule.h"
22 #import "AccountManager.h"
23 #import "GetFlavorsRequest.h"
24 #import "APICallback.h"
25 #import "APILogEntry.h"
28 static NSRecursiveLock *accessDetailsLock = nil;
30 @implementation OpenStackRequest
32 @synthesize account, callback, retriedCount;
35 return (200 <= [self responseStatusCode]) && ([self responseStatusCode] <= 299);
38 - (void)notify:(NSString *)name {
39 NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"];
40 NSNotification *notification = [NSNotification notificationWithName:name object:nil userInfo:callbackUserInfo];
41 [[NSNotificationCenter defaultCenter] postNotification:notification];
45 NSString *observeName = [NSString stringWithFormat:@"%@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, [self.url description]];
46 NSString *callbackName = [NSString stringWithFormat:@"%@ %@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, [self.url description], self.callback.uuid];
48 NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"];
50 NSNotification *observeNotification = [NSNotification notificationWithName:observeName object:nil userInfo:callbackUserInfo];
51 [[NSNotificationCenter defaultCenter] postNotification:observeNotification];
53 NSNotification *callbackNotification = [NSNotification notificationWithName:callbackName object:nil userInfo:callbackUserInfo];
54 [[NSNotificationCenter defaultCenter] postNotification:callbackNotification];
58 - (NSString *)responseString {
60 return [retriedRequest responseString];
62 return [super responseString];
66 - (NSData *)responseData {
68 return [retriedRequest responseData];
70 return [super responseData];
74 - (NSDictionary *)responseHeaders {
76 return [retriedRequest responseHeaders];
78 return [super responseHeaders];
82 - (int)responseStatusCode {
84 return [retriedRequest responseStatusCode];
86 return [super responseStatusCode];
90 - (NSString *)responseStatusMessage {
92 return [retriedRequest responseStatusMessage];
94 return [super responseStatusMessage];
98 - (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock {
99 [super setCompletionBlock:aCompletionBlock];
100 [backupCompletionBlock release];
101 backupCompletionBlock = [aCompletionBlock copy];
104 - (void)setFailedBlock:(ASIBasicBlock)aFailedBlock {
105 [super setFailedBlock:aFailedBlock];
106 [backupFailureBlock release];
107 backupFailureBlock = [aFailedBlock copy];
110 + (RateLimit *)limitForPath:(NSString *)path verb:(NSString *)verb account:(OpenStackAccount *)anAccount {
112 // find the lowest requests remaining of all the limits
114 RateLimit *lowestLimit = nil;
116 for (RateLimit *limit in anAccount.rateLimits) {
117 if ([limit.verb isEqualToString:verb]) {
118 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:limit.regex options:NSRegularExpressionCaseInsensitive error:nil];
119 NSInteger matches = [regex numberOfMatchesInString:path options:0 range:NSMakeRange(0, [path length])];
123 if (limit.remaining < lowestLimit.remaining) {
137 #pragma mark Generic Constructors
140 if (self == [OpenStackRequest class]) {
141 accessDetailsLock = [[NSRecursiveLock alloc] init];
145 + (id)request:(OpenStackAccount *)account method:(NSString *)method url:(NSURL *)url {
146 OpenStackRequest *request = [[[OpenStackRequest alloc] initWithURL:url] autorelease];
147 request.account = account;
148 [request setRequestMethod:method];
149 [request addRequestHeader:@"X-Auth-Token" value:[account authToken]];
150 [request addRequestHeader:@"Content-Type" value:@"application/json"];
151 [request setTimeOutSeconds:60];
152 request.retriedCount = 0;
156 + (id)getSharingAccountsRequest:(OpenStackAccount *)account {
157 NSString *topLevelUrlString = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"GRNetStorageUrlPrefix"];
158 NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@?format=json",topLevelUrlString]];
159 return [OpenStackRequest request:account method:@"GET" url:url];
162 + (id)serversRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path {
163 NSString *now = [[[NSDate date] description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
164 NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?now=%@", account.serversURL, path, now]];
165 return [OpenStackRequest request:account method:method url:url];
168 + (id)filesRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path {
169 NSString *now = [[[NSDate date] description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
172 NSString *urlString = [account.filesURL description];
173 if (account.sharingAccount) {
174 NSRange authUserRange = [urlString rangeOfString:account.username];
175 filesUrl = [NSString stringWithFormat:@"%@%@",
176 [urlString substringToIndex:authUserRange.location],
177 account.sharingAccount];
179 filesUrl = urlString;
182 NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json%@&now=%@",
185 account.shared ? @"&shared=" : @"",
187 return [OpenStackRequest request:account method:method url:url];
190 + (id)cdnRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path {
191 NSString *now = [[[NSDate date] description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
192 NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json&now=%@", account.cdnURL, path, now]];
193 return [OpenStackRequest request:account method:method url:url];
197 #pragma mark Auth Retry
199 - (void)authRetrySucceded:(OpenStackRequest *)retryRequest {
201 if ([self isKindOfClass:[GetFlavorsRequest class]]) {
202 NSLog(@"flavor request");
205 self.account.authToken = [[retryRequest responseHeaders] objectForKey:@"X-Auth-Token"];
206 [self.account persist];
208 // TODO: make this work for GetServersRequest, etc
210 // try the original request again!
212 retriedRequest = [self copy];
213 [retriedRequest addRequestHeader:@"X-Auth-Token" value:self.account.authToken];
215 if (backupCompletionBlock) {
216 [retriedRequest setCompletionBlock:^{
217 backupCompletionBlock();
220 if (backupFailureBlock) {
221 [retriedRequest setFailedBlock:^{
222 backupFailureBlock();
225 [retriedRequest startSynchronous];
228 - (void)authRetryFailed:(OpenStackRequest *)retryRequest {
229 // if it fails due to bad connection, try again?
230 NSLog(@"auth retry failed with status %i", retryRequest.responseStatusCode);
231 NSNotification *notification = [NSNotification notificationWithName:[self.account.manager notificationName:@"authRetryFailed" identifier:0] object:nil userInfo:[NSDictionary dictionaryWithObject:retryRequest forKey:@"request"]];
232 [[NSNotificationCenter defaultCenter] postNotification:notification];
236 #pragma mark ASIHTTPRequest Overrides
237 // overriding to log API calls
239 - (void)requestFinished {
240 //NSLog(@"request finished: %i %@", self.responseStatusCode, self.url);
241 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
242 NSString *loggingLevel = [defaults valueForKey:@"api_logging_level"];
243 if ([loggingLevel isEqualToString:@"all"] || ([loggingLevel isEqualToString:@"errors"] && ![self isSuccess])) {
244 [APILogger log:self];
247 [super requestFinished];
250 - (void)failWithError:(NSError *)theError {
252 if (responseStatusCode == 401 && ![url isEqual:account.provider.authEndpointURL]) {
253 // auth is expired, so get a fresh token
255 OpenStackRequest *retryRequest = [OpenStackRequest authenticationRequest:account];
256 retryRequest.delegate = self;
257 retryRequest.didFinishSelector = @selector(authRetrySucceded:);
258 retryRequest.didFailSelector = @selector(authRetryFailed:);
259 [retryRequest startSynchronous];
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];
272 [defaults setBool:YES forKey:@"already_failed_on_connection"];
273 [defaults synchronize];
276 [super failWithError:theError];
279 #pragma mark - Authentication
281 + (NSString *)apiVersionForURL:(NSURL *)url {
282 NSArray *components = [[url description] componentsSeparatedByString:@"/"];
283 if ([[components lastObject] isEqualToString:@"2.0"]) {
285 } else if ([[components lastObject] isEqualToString:@"v1.1"]) {
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];
297 [request addRequestHeader:@"X-Auth-Key" value:@""];
301 + (void)setupAuthForRequest:(OpenStackRequest *)request account:(OpenStackAccount *)account apiVersion:(NSString *)apiVersion {
303 if ([apiVersion isEqualToString:@"1.0"]) {
305 [OpenStackRequest setupV1AuthForRequest:request account:account apiVersion:apiVersion];
306 account.apiVersion = @"1.0";
308 } else if ([apiVersion isEqualToString:@"1.1"]) {
310 [OpenStackRequest setupV1AuthForRequest:request account:account apiVersion:apiVersion];
311 if (account.projectId) {
312 [request addRequestHeader:@"X-Auth-Project-Id" value:account.projectId];
314 account.apiVersion = @"1.1";
316 } else if ([apiVersion isEqualToString:@"2.0"]) {
318 // TODO: support 2.0 auth
319 account.apiVersion = @"1.1";
323 + (OpenStackRequest *)authenticationRequest:(OpenStackAccount *)account {
325 OpenStackRequest *request = [[[OpenStackRequest alloc] initWithURL:account.provider.authEndpointURL] autorelease];
326 request.account = account;
328 NSString *apiVersion = [OpenStackRequest apiVersionForURL:account.provider.authEndpointURL];
329 [OpenStackRequest setupAuthForRequest:request account:account apiVersion:apiVersion];
334 #pragma mark - Rate Limits
336 + (OpenStackRequest *)getLimitsRequest:(OpenStackAccount *)account {
337 return [OpenStackRequest serversRequest:account method:@"GET" path:@"/limits"];
340 - (NSDictionary *)limits {
341 SBJSON *parser = [[SBJSON alloc] init];
342 NSDictionary *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"limits"];
347 - (NSArray *)rateLimits {
348 NSArray *jsonObjects = [[self limits] objectForKey:@"rate"];
349 NSMutableArray *rateLimits = [[NSMutableArray alloc] initWithCapacity:[jsonObjects count]];
351 for (NSDictionary *dict in jsonObjects) {
352 [rateLimits addObject:[RateLimit fromJSON:dict]];
355 NSArray *result = [NSArray arrayWithArray:rateLimits];
357 [rateLimits release];
361 #pragma mark Collections
363 + (OpenStackRequest *)getServersRequest:(OpenStackAccount *)account {
364 return [OpenStackRequest serversRequest:account method:@"GET" path:@"/servers/detail"];
367 - (NSDictionary *)servers {
368 SBJSON *parser = [[SBJSON alloc] init];
369 NSArray *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"servers"];
370 NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
372 for (int i = 0; i < [jsonObjects count]; i++) {
373 NSDictionary *dict = [jsonObjects objectAtIndex:i];
374 Server *server = [[Server alloc] initWithJSONDict:dict];
375 [objects setObject:server forKey:server.identifier];
383 + (OpenStackRequest *)getServerRequest:(OpenStackAccount *)account serverId:(NSString *)serverId {
384 return [OpenStackRequest serversRequest:account method:@"GET" path:[NSString stringWithFormat:@"/servers/%@", serverId]];
387 + (OpenStackRequest *)getImagesRequest:(OpenStackAccount *)account {
388 return [OpenStackRequest serversRequest:account method:@"GET" path:@"/images/detail"];
391 - (NSDictionary *)images {
392 SBJSON *parser = [[SBJSON alloc] init];
393 NSArray *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"images"];
394 NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
396 for (int i = 0; i < [jsonObjects count]; i++) {
397 NSDictionary *dict = [jsonObjects objectAtIndex:i];
398 Image *image = [[Image alloc] initWithJSONDict:dict];
399 [objects setObject:image forKey:image.identifier];
407 + (OpenStackRequest *)getImageRequest:(OpenStackAccount *)account imageId:(NSString *)imageId {
408 return [OpenStackRequest serversRequest:account method:@"GET" path:[NSString stringWithFormat:@"/images/%@", imageId]];
412 SBJSON *parser = [[SBJSON alloc] init];
413 NSDictionary *dict = [[parser objectWithString:[self responseString]] objectForKey:@"image"];
414 Image *image = [Image fromJSON:dict];
420 + (OpenStackRequest *)getFlavorsRequest:(OpenStackAccount *)account {
421 return [OpenStackRequest serversRequest:account method:@"GET" path:@"/flavors/detail"];
424 - (NSDictionary *)flavors {
425 SBJSON *parser = [[SBJSON alloc] init];
426 NSArray *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"flavors"];
427 NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
429 for (int i = 0; i < [jsonObjects count]; i++) {
430 NSDictionary *dict = [jsonObjects objectAtIndex:i];
431 Flavor *flavor = [Flavor fromJSON:dict];
432 [objects setObject:flavor forKey:flavor.identifier];
439 #pragma mark Server Actions
444 + (OpenStackRequest *)rebootServerRequest:(OpenStackAccount *)account server:(Server *)server type:(NSInteger)type {
445 NSString *body = [NSString stringWithFormat:@"{ \"reboot\": { \"type\": \"%@\" } }", (type == kSoft) ? @"SOFT" : @"HARD"];
446 OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]];
447 NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
448 [request setPostBody:[NSMutableData dataWithData:data]];
452 + (OpenStackRequest *)softRebootServerRequest:(OpenStackAccount *)account server:(Server *)server {
453 return [OpenStackRequest rebootServerRequest:account server:server type:kSoft];
456 + (RateLimit *)softRebootServerLimit:(OpenStackAccount *)account server:(Server *)server {
457 return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
460 + (OpenStackRequest *)hardRebootServerRequest:(OpenStackAccount *)account server:(Server *)server {
461 return [OpenStackRequest rebootServerRequest:account server:server type:kHard];
464 + (RateLimit *)hardRebootServerLimit:(OpenStackAccount *)account server:(Server *)server {
465 return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
468 + (OpenStackRequest *)changeServerAdminPasswordRequest:(OpenStackAccount *)account server:(Server *)server password:(NSString *)password {
469 NSString *body = [NSString stringWithFormat:@"{ \"server\": { \"adminPass\": \"%@\" } }", password];
470 OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/servers/%@", server.identifier]];
471 NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
472 [request setPostBody:[NSMutableData dataWithData:data]];
476 + (RateLimit *)changeServerAdminPasswordLimit:(OpenStackAccount *)account server:(Server *)server {
477 return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@", server.identifier] verb:@"PUT" account:account];
480 + (OpenStackRequest *)renameServerRequest:(OpenStackAccount *)account server:(Server *)server name:(NSString *)name {
481 NSString *body = [NSString stringWithFormat:@"{ \"server\": { \"name\": \"%@\" } }", name];
482 OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/servers/%@", server.identifier]];
483 NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
484 [request setPostBody:[NSMutableData dataWithData:data]];
488 + (RateLimit *)renameServerLimit:(OpenStackAccount *)account server:(Server *)server {
489 return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@", server.identifier] verb:@"PUT" account:account];
492 + (OpenStackRequest *)deleteServerRequest:(OpenStackAccount *)account server:(Server *)server {
493 return [OpenStackRequest serversRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/servers/%@", server.identifier]];
496 + (RateLimit *)deleteServerLimit:(OpenStackAccount *)account server:(Server *)server {
497 return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@", server.identifier] verb:@"DELETE" account:account];
500 + (OpenStackRequest *)createServerRequest:(OpenStackAccount *)account server:(Server *)server {
501 NSString *body = [server toJSON:account.apiVersion];
502 NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/servers", account.serversURL]];
503 NSLog(@"create server: %@", body);
504 OpenStackRequest *request = [OpenStackRequest request:account method:@"POST" url:url];
505 NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
506 [request setPostBody:[NSMutableData dataWithData:data]];
510 + (RateLimit *)createServerLimit:(OpenStackAccount *)account {
511 return [OpenStackRequest limitForPath:@"/servers" verb:@"POST" account:account];
515 SBJSON *parser = [[SBJSON alloc] init];
516 NSDictionary *dict = [[parser objectWithString:[self responseString]] objectForKey:@"server"];
517 Server *server = [Server fromJSON:dict];
522 + (OpenStackRequest *)resizeServerRequest:(OpenStackAccount *)account server:(Server *)server flavor:(Flavor *)flavor {
524 if ([account.apiVersion isEqualToString:@"1.0"]) {
525 body = [NSString stringWithFormat:@"{ \"resize\": { \"flavorId\": %@ } }", flavor.identifier];
527 body = [NSString stringWithFormat:@"{ \"resize\": { \"flavorRef\": %@ } }", flavor.identifier];
529 OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]];
530 NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
531 [request setPostBody:[NSMutableData dataWithData:data]];
535 + (RateLimit *)resizeServerLimit:(OpenStackAccount *)account server:(Server *)server {
536 return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
539 + (OpenStackRequest *)confirmResizeServerRequest:(OpenStackAccount *)account server:(Server *)server {
540 NSString *body = @"{ \"confirmResize\": null }";
541 OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]];
542 NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
543 [request setPostBody:[NSMutableData dataWithData:data]];
547 + (RateLimit *)confirmResizeServerLimit:(OpenStackAccount *)account server:(Server *)server {
548 return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
551 + (OpenStackRequest *)revertResizeServerRequest:(OpenStackAccount *)account server:(Server *)server {
552 NSString *body = @"{ \"revertResize\": null }";
553 OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]];
554 NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
555 [request setPostBody:[NSMutableData dataWithData:data]];
559 + (RateLimit *)revertResizeServerLimit:(OpenStackAccount *)account server:(Server *)server {
560 return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
563 + (OpenStackRequest *)rebuildServerRequest:(OpenStackAccount *)account server:(Server *)server image:(Image *)image {
564 NSString *body = [NSString stringWithFormat:@"{ \"rebuild\": { \"imageId\": %@ } }", image.identifier];
565 OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]];
566 NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
567 [request setPostBody:[NSMutableData dataWithData:data]];
571 + (RateLimit *)rebuildServerLimit:(OpenStackAccount *)account server:(Server *)server {
572 return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
576 + (OpenStackRequest *)getBackupScheduleRequest:(OpenStackAccount *)account server:(Server *)server {
577 return [OpenStackRequest serversRequest:account method:@"GET" path:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier]];
580 + (RateLimit *)getBackupScheduleLimit:(OpenStackAccount *)account server:(Server *)server {
581 return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier] verb:@"GET" account:account];
584 + (OpenStackRequest *)updateBackupScheduleRequest:(OpenStackAccount *)account server:(Server *)server {
585 NSString *body = [NSString stringWithFormat:@"{ \"backupSchedule\": { \"enabled\": true, \"weekly\": \"%@\", \"daily\": \"%@\" } }", server.backupSchedule.weekly, server.backupSchedule.daily];
586 OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier]];
587 NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
588 [request setPostBody:[NSMutableData dataWithData:data]];
592 + (RateLimit *)updateBackupScheduleLimit:(OpenStackAccount *)account server:(Server *)server {
593 return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier] verb:@"POST" account:account];
596 - (BackupSchedule *)backupSchedule {
597 SBJSON *parser = [[SBJSON alloc] init];
598 NSDictionary *dict = [[parser objectWithString:[self responseString]] objectForKey:@"backupSchedule"];
599 BackupSchedule *backupSchedule = [BackupSchedule fromJSON:dict];
601 return backupSchedule;
605 #pragma mark Object Storage Requests
607 + (OpenStackRequest *)getStorageAccountInfoRequest:(OpenStackAccount *)account {
608 return [OpenStackRequest filesRequest:account method:@"HEAD" path:@""];
611 + (OpenStackRequest *)getContainersRequest:(OpenStackAccount *)account {
612 return [OpenStackRequest filesRequest:account method:@"GET" path:@""];
615 - (NSMutableDictionary *)containers {
616 SBJSON *parser = [[SBJSON alloc] init];
617 NSArray *jsonObjects = [parser objectWithString:[self responseString]];
618 NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
620 for (int i = 0; i < [jsonObjects count]; i++) {
621 NSDictionary *dict = [jsonObjects objectAtIndex:i];
622 Container *container = [Container fromJSON:dict];
623 [objects setObject:container forKey:container.name];
630 + (OpenStackRequest *)createContainerRequest:(OpenStackAccount *)account container:(Container *)container {
631 return [OpenStackRequest filesRequest:account method:@"PUT" path:[[NSString stringWithFormat:@"/%@", container.name] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
634 + (OpenStackRequest *)deleteContainerRequest:(OpenStackAccount *)account container:(Container *)container {
635 return [OpenStackRequest filesRequest:account method:@"DELETE" path:[[NSString stringWithFormat:@"/%@", container.name] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
638 + (OpenStackRequest *)getObjectsRequest:(OpenStackAccount *)account container:(Container *)container {
639 return [OpenStackRequest filesRequest:account method:@"GET" path:[[NSString stringWithFormat:@"/%@", container.name] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
642 - (NSMutableDictionary *)objects {
643 SBJSON *parser = [[SBJSON alloc] init];
644 NSArray *jsonObjects = [parser objectWithString:[self responseString]];
645 NSMutableDictionary *objects = [[[NSMutableDictionary alloc] initWithCapacity:[jsonObjects count]] autorelease];
647 for (int i = 0; i < [jsonObjects count]; i++) {
648 NSDictionary *dict = [jsonObjects objectAtIndex:i];
649 StorageObject *object = [StorageObject fromJSON:dict];
650 [objects setObject:object forKey:object.name];
657 + (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
658 return [OpenStackRequest filesRequest:account method:@"HEAD" path:[[NSString stringWithFormat:@"/%@/%@", container.name, object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
661 + (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
662 return [OpenStackRequest filesRequest:account method:@"GET" path:[[NSString stringWithFormat:@"/%@/%@", container.name, object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
665 + (OpenStackRequest *)writeObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
666 NSString *fullPath = object.fullPath;
667 if ([fullPath characterAtIndex:0] == '/') {
668 fullPath = [fullPath substringFromIndex:1];
671 OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"PUT" path:[[NSString stringWithFormat:@"/%@/%@", container.name, fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
672 [request setPostBody:[NSMutableData dataWithData:object.data]];
673 [request.requestHeaders setObject:object.contentType forKey:@"Content-Type"];
677 + (OpenStackRequest *)writeObjectMetadataRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
678 NSString *fullPath = [NSString stringWithString:object.fullPath];
679 if ([fullPath length] && ([fullPath characterAtIndex:0] == '/'))
680 fullPath = [fullPath substringFromIndex:1];
682 NSString *metadataKeyHeaderPrefix;
683 if (![fullPath length])
684 metadataKeyHeaderPrefix = @"X-Container-Meta-";
686 metadataKeyHeaderPrefix = @"X-Object-Meta-";
688 OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"POST" path:[[NSString stringWithFormat:@"/%@/%@", container.name, fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
690 for (NSString *metadataKey in object.metadata) {
691 NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey];
692 [request.requestHeaders setObject:[object.metadata objectForKey:metadataKey] forKey:metadataKeyHeader];
694 if (!account.sharingAccount) {
695 NSString *objectIsPublic = ([object.publicURI length]) ? @"true" : @"false";
696 [request.requestHeaders setObject:objectIsPublic forKey:@"X-Object-Public"];
699 [request.requestHeaders setObject:object.sharing forKey:@"X-Object-Sharing"];
705 + (OpenStackRequest *)deleteObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
706 if ([object.fullPath characterAtIndex:0] == '/') {
707 return [OpenStackRequest filesRequest:account method:@"DELETE" path:[[NSString stringWithFormat:@"/%@%@", container.name, object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
709 return [OpenStackRequest filesRequest:account method:@"DELETE" path:[[NSString stringWithFormat:@"/%@/%@", container.name, object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
713 + (OpenStackRequest *)writeContainerPolicyRequest:(OpenStackAccount *)account container:(Container *)container
715 OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"PUT" path:[[NSString stringWithFormat:@"/%@", container.name] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
717 [request.requestHeaders setObject:container.versioning forKey:@"X-Container-Policy-Versioning"];
718 [request.requestHeaders setObject:[NSString stringWithFormat:@"%lu", container.quota] forKey:@"X-Container-Policy-Quota"];
723 + (OpenStackRequest *)writeAccountMetadataRequest:(OpenStackAccount *)account withAccountInfo:(NSDictionary *)accountInfo
725 OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"POST" path:@""];
727 NSMutableDictionary *groups = [accountInfo objectForKey:@"groups"];
728 for (NSString *groupName in groups) {
729 [request.requestHeaders setObject:[groups objectForKey:groupName] forKey:[NSString stringWithFormat:@"X-Account-Group-%@", groupName]];
731 if ([groups count] == 0)
732 [request.requestHeaders setObject:@"" forKey:@"X-Account-Group-group"];
734 NSMutableDictionary *accountMetadata = [accountInfo objectForKey:@"metadata"];
735 for (NSString *metadataKey in accountMetadata) {
736 [request.requestHeaders setObject:[accountMetadata objectForKey:metadataKey] forKey:[NSString stringWithFormat:@"X-Account-Meta-%@",metadataKey]];
743 #pragma mark Memory Management
745 - (void)releaseBackupBlocksOnMainThread {
746 NSMutableArray *blocks = [NSMutableArray array];
747 if (backupCompletionBlock) {
748 [blocks addObject:backupCompletionBlock];
749 [backupCompletionBlock release];
750 backupCompletionBlock = nil;
752 if (backupFailureBlock) {
753 [blocks addObject:backupFailureBlock];
754 [backupFailureBlock release];
755 backupFailureBlock = nil;
757 [[self class] performSelectorOnMainThread:@selector(releaseBackupBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]];
760 // Always called on main thread
761 + (void)releaseBackupBlocks:(NSArray *)blocks {
762 // Blocks will be released when this method exits
767 [self releaseBackupBlocksOnMainThread];