Statistics
| Branch: | Tag: | Revision:

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