Statistics
| Branch: | Tag: | Revision:

root / Classes / OpenStackRequest.m @ 623869ee

History | View | Annotate | Download (30 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

    
27

    
28
static NSRecursiveLock *accessDetailsLock = nil;
29

    
30
@implementation OpenStackRequest
31

    
32
@synthesize account, callback, retriedCount;
33

    
34
- (BOOL)isSuccess {
35
	return (200 <= [self responseStatusCode]) && ([self responseStatusCode] <= 299);
36
}
37

    
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];
42
}
43

    
44
- (void)notify {
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];
47
    
48
    NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"];
49

    
50
    NSNotification *observeNotification = [NSNotification notificationWithName:observeName object:nil userInfo:callbackUserInfo];
51
    [[NSNotificationCenter defaultCenter] postNotification:observeNotification];
52

    
53
    NSNotification *callbackNotification = [NSNotification notificationWithName:callbackName object:nil userInfo:callbackUserInfo];
54
    [[NSNotificationCenter defaultCenter] postNotification:callbackNotification];
55
    
56
}
57

    
58
- (NSString *)responseString {
59
    if (retried) {
60
        return [retriedRequest responseString];
61
    } else {
62
        return [super responseString];
63
    }
64
}
65

    
66
- (NSData *)responseData {
67
    if (retried) {
68
        return [retriedRequest responseData];
69
    } else {
70
        return [super responseData];
71
    }
72
}
73

    
74
- (NSDictionary *)responseHeaders {
75
    if (retried) {
76
        return [retriedRequest responseHeaders];
77
    } else {
78
        return [super responseHeaders];
79
    }
80
}
81

    
82
- (int)responseStatusCode {
83
    if (retried) {
84
        return [retriedRequest responseStatusCode];
85
    } else {
86
        return [super responseStatusCode];
87
    }
88
}
89

    
90
- (NSString *)responseStatusMessage {
91
    if (retried) {
92
        return [retriedRequest responseStatusMessage];
93
    } else {
94
        return [super responseStatusMessage];
95
    }
96
}
97

    
98
- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock {
99
    [super setCompletionBlock:aCompletionBlock];
100
    [backupCompletionBlock release];
101
    backupCompletionBlock = [aCompletionBlock copy];
102
}
103

    
104
- (void)setFailedBlock:(ASIBasicBlock)aFailedBlock {
105
    [super setFailedBlock:aFailedBlock];
106
    [backupFailureBlock release];
107
    backupFailureBlock = [aFailedBlock copy];
108
}
109

    
110
+ (RateLimit *)limitForPath:(NSString *)path verb:(NSString *)verb account:(OpenStackAccount *)anAccount {
111
    
112
    // find the lowest requests remaining of all the limits
113

    
114
    RateLimit *lowestLimit = nil;
115
    
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])];
120
            
121
            if (matches > 0) {
122
                if (lowestLimit) {
123
                    if (limit.remaining < lowestLimit.remaining) {
124
                        lowestLimit = limit;
125
                    }
126
                } else {
127
                    lowestLimit = limit;
128
                }
129
            }
130
        }
131
    }
132
    
133
    return lowestLimit;
134
}
135

    
136
#pragma mark -
137
#pragma mark Generic Constructors
138

    
139
+ (void)initialize {
140
	if (self == [OpenStackRequest class]) {
141
		accessDetailsLock = [[NSRecursiveLock alloc] init];
142
	}
143
}
144

    
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;
153
	return request;
154
}
155

    
156
+ (id)serversRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path {
157
    NSString *now = [[[NSDate date] description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
158
	NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?now=%@", account.serversURL, path, now]];
159
    return [OpenStackRequest request:account method:method url:url];
160
}
161

    
162
+ (id)filesRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path {
163
    NSString *now = [[[NSDate date] description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
164
	NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json&now=%@", account.filesURL, path, now]];    
165
    return [OpenStackRequest request:account method:method url:url];
166
}
167

    
168
+ (id)cdnRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path {
169
    NSString *now = [[[NSDate date] description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
170
	NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json&now=%@", account.cdnURL, path, now]];
171
    return [OpenStackRequest request:account method:method url:url];
172
}
173

    
174
#pragma mark -
175
#pragma mark Auth Retry
176

    
177
- (void)authRetrySucceded:(OpenStackRequest *)retryRequest {
178
    
179
    if ([self isKindOfClass:[GetFlavorsRequest class]]) {
180
        NSLog(@"flavor request");
181
    }
182
    
183
    self.account.authToken = [[retryRequest responseHeaders] objectForKey:@"X-Auth-Token"];    
184
    [self.account persist];
185
    
186
    // TODO: make this work for GetServersRequest, etc
187
    
188
    // try the original request again!
189
    retried = YES;
190
    retriedRequest = [self copy];
191
	[retriedRequest addRequestHeader:@"X-Auth-Token" value:self.account.authToken];    
192
    
193
    if (backupCompletionBlock) {
194
        [retriedRequest setCompletionBlock:^{
195
            backupCompletionBlock();
196
        }];
197
    }
198
    if (backupFailureBlock) {
199
        [retriedRequest setFailedBlock:^{
200
            backupFailureBlock();
201
        }];
202
    }
203
    [retriedRequest startSynchronous];     
204
}
205

    
206
- (void)authRetryFailed:(OpenStackRequest *)retryRequest {
207
    // if it fails due to bad connection, try again?
208
    NSLog(@"auth retry failed with status %i", retryRequest.responseStatusCode);
209
    NSNotification *notification = [NSNotification notificationWithName:[self.account.manager notificationName:@"authRetryFailed" identifier:0] object:nil userInfo:[NSDictionary dictionaryWithObject:retryRequest forKey:@"request"]];
210
    [[NSNotificationCenter defaultCenter] postNotification:notification];
211
}
212

    
213
#pragma mark -
214
#pragma mark ASIHTTPRequest Overrides
215
// overriding to log API calls
216

    
217
- (void)requestFinished {
218
    //NSLog(@"request finished: %i %@", self.responseStatusCode, self.url);
219
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
220
    NSString *loggingLevel = [defaults valueForKey:@"api_logging_level"];    
221
    if ([loggingLevel isEqualToString:@"all"] || ([loggingLevel isEqualToString:@"errors"] && ![self isSuccess])) {
222
        [APILogger log:self];
223
    }
224
    
225
    [super requestFinished];
226
}
227

    
228
- (void)failWithError:(NSError *)theError {
229

    
230
    if (responseStatusCode == 401 && ![url isEqual:account.provider.authEndpointURL]) {
231
        // auth is expired, so get a fresh token
232
        if (account) {
233
            OpenStackRequest *retryRequest = [OpenStackRequest authenticationRequest:account];
234
            retryRequest.delegate = self;
235
            retryRequest.didFinishSelector = @selector(authRetrySucceded:);
236
            retryRequest.didFailSelector = @selector(authRetryFailed:);
237
            [retryRequest startSynchronous];
238
        }
239
    } else if (responseStatusCode == 503) {        
240
        NSNotification *notification = [NSNotification notificationWithName:@"serviceUnavailable" object:nil userInfo:nil];
241
        [[NSNotificationCenter defaultCenter] postNotification:notification];        
242
        [super failWithError:theError];
243
    } else if (responseStatusCode == 0) {
244
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
245
        if (![defaults boolForKey:@"already_failed_on_connection"]) {
246
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Connection Error" message:@"Please check your connection or API URL and try again." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
247
            [alert show];
248
            [alert release];
249
        }
250
        [defaults setBool:YES forKey:@"already_failed_on_connection"];
251
        [defaults synchronize];              
252
    }
253

    
254
    [super failWithError:theError];
255
}
256

    
257
#pragma mark - Authentication
258

    
259
+ (NSString *)apiVersionForURL:(NSURL *)url {
260
    NSArray *components = [[url description] componentsSeparatedByString:@"/"];
261
    if ([[components lastObject] isEqualToString:@"2.0"]) {
262
        return @"2.0";
263
    } else if ([[components lastObject] isEqualToString:@"v1.1"]) {
264
        return @"1.1";
265
    } else {
266
        return @"1.0";
267
    }
268
}
269

    
270
+ (void)setupV1AuthForRequest:(OpenStackRequest *)request account:(OpenStackAccount *)account apiVersion:(NSString *)apiVersion {
271
    [request addRequestHeader:@"X-Auth-User" value:account.username];
272
    if (account.apiKey) {
273
        [request addRequestHeader:@"X-Auth-Key" value:account.apiKey];
274
    } else {
275
        [request addRequestHeader:@"X-Auth-Key" value:@""];
276
    }        
277
}
278

    
279
+ (void)setupAuthForRequest:(OpenStackRequest *)request account:(OpenStackAccount *)account apiVersion:(NSString *)apiVersion {
280

    
281
    if ([apiVersion isEqualToString:@"1.0"]) {
282
        
283
        [OpenStackRequest setupV1AuthForRequest:request account:account apiVersion:apiVersion];
284
        account.apiVersion = @"1.0";
285
        
286
    } else if ([apiVersion isEqualToString:@"1.1"]) {
287
        
288
        [OpenStackRequest setupV1AuthForRequest:request account:account apiVersion:apiVersion];
289
        if (account.projectId) {
290
            [request addRequestHeader:@"X-Auth-Project-Id" value:account.projectId];
291
        }
292
        account.apiVersion = @"1.1";
293
        
294
    } else if ([apiVersion isEqualToString:@"2.0"]) {
295
        
296
        // TODO: support 2.0 auth
297
        account.apiVersion = @"1.1";
298
    }
299
}
300

    
301
+ (OpenStackRequest *)authenticationRequest:(OpenStackAccount *)account {
302

    
303
	OpenStackRequest *request = [[[OpenStackRequest alloc] initWithURL:account.provider.authEndpointURL] autorelease];
304
    request.account = account;
305

    
306
    NSString *apiVersion = [OpenStackRequest apiVersionForURL:account.provider.authEndpointURL];
307
    [OpenStackRequest setupAuthForRequest:request account:account apiVersion:apiVersion];
308

    
309
	return request;
310
}
311

    
312
#pragma mark - Rate Limits
313

    
314
+ (OpenStackRequest *)getLimitsRequest:(OpenStackAccount *)account {
315
    return [OpenStackRequest serversRequest:account method:@"GET" path:@"/limits"];
316
}
317

    
318
- (NSDictionary *)limits {
319
    SBJSON *parser = [[SBJSON alloc] init];
320
    NSDictionary *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"limits"];    
321
    [parser release];
322
    return jsonObjects;
323
}
324

    
325
- (NSArray *)rateLimits {
326
    NSArray *jsonObjects = [[self limits] objectForKey:@"rate"];
327
    NSMutableArray *rateLimits = [[NSMutableArray alloc] initWithCapacity:[jsonObjects count]];
328

    
329
    for (NSDictionary *dict in jsonObjects) {
330
        [rateLimits addObject:[RateLimit fromJSON:dict]];
331
    }
332

    
333
    NSArray *result = [NSArray arrayWithArray:rateLimits];
334
    
335
    [rateLimits release];
336
    return result;
337
}
338

    
339
#pragma mark Collections
340

    
341
+ (OpenStackRequest *)getServersRequest:(OpenStackAccount *)account {
342
    return [OpenStackRequest serversRequest:account method:@"GET" path:@"/servers/detail"];
343
}
344

    
345
- (NSDictionary *)servers {
346
    SBJSON *parser = [[SBJSON alloc] init];
347
    NSArray *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"servers"];
348
    NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
349
    
350
    for (int i = 0; i < [jsonObjects count]; i++) {
351
        NSDictionary *dict = [jsonObjects objectAtIndex:i];
352
        Server *server = [[Server alloc] initWithJSONDict:dict];
353
        [objects setObject:server forKey:server.identifier];
354
        [server release];
355
    }
356
    
357
    [parser release];
358
    return objects;
359
}
360

    
361
+ (OpenStackRequest *)getServerRequest:(OpenStackAccount *)account serverId:(NSString *)serverId {
362
    return [OpenStackRequest serversRequest:account method:@"GET" path:[NSString stringWithFormat:@"/servers/%@", serverId]];
363
}
364

    
365
+ (OpenStackRequest *)getImagesRequest:(OpenStackAccount *)account {
366
    return [OpenStackRequest serversRequest:account method:@"GET" path:@"/images/detail"];
367
}
368

    
369
- (NSDictionary *)images {
370
    SBJSON *parser = [[SBJSON alloc] init];
371
    NSArray *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"images"];
372
    NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
373
    
374
    for (int i = 0; i < [jsonObjects count]; i++) {
375
        NSDictionary *dict = [jsonObjects objectAtIndex:i];
376
        Image *image = [[Image alloc] initWithJSONDict:dict];
377
        [objects setObject:image forKey:image.identifier];
378
        [image release];
379
    }
380
    
381
    [parser release];
382
    return objects;
383
}
384

    
385
+ (OpenStackRequest *)getImageRequest:(OpenStackAccount *)account imageId:(NSString *)imageId {
386
    return [OpenStackRequest serversRequest:account method:@"GET" path:[NSString stringWithFormat:@"/images/%@", imageId]];
387
}
388

    
389
- (Image *)image {
390
    SBJSON *parser = [[SBJSON alloc] init];
391
    NSDictionary *dict = [[parser objectWithString:[self responseString]] objectForKey:@"image"];
392
    Image *image = [Image fromJSON:dict];
393
    [parser release];
394
    return image;
395
}
396

    
397

    
398
+ (OpenStackRequest *)getFlavorsRequest:(OpenStackAccount *)account {
399
    return [OpenStackRequest serversRequest:account method:@"GET" path:@"/flavors/detail"];
400
}
401

    
402
- (NSDictionary *)flavors {
403
    SBJSON *parser = [[SBJSON alloc] init];
404
    NSArray *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"flavors"];
405
    NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
406

    
407
    for (int i = 0; i < [jsonObjects count]; i++) {
408
        NSDictionary *dict = [jsonObjects objectAtIndex:i];
409
        Flavor *flavor = [Flavor fromJSON:dict];
410
        [objects setObject:flavor forKey:flavor.identifier];
411
    }
412
    
413
    [parser release];
414
    return objects;
415
}
416

    
417
#pragma mark Server Actions
418

    
419
#define kSoft 0
420
#define kHard 1
421

    
422
+ (OpenStackRequest *)rebootServerRequest:(OpenStackAccount *)account server:(Server *)server type:(NSInteger)type {
423
    NSString *body = [NSString stringWithFormat:@"{ \"reboot\": { \"type\": \"%@\" } }", (type == kSoft) ? @"SOFT" : @"HARD"];
424
    OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]];	
425
    NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
426
    [request setPostBody:[NSMutableData dataWithData:data]];
427
    return request;
428
}
429

    
430
+ (OpenStackRequest *)softRebootServerRequest:(OpenStackAccount *)account server:(Server *)server {
431
    return [OpenStackRequest rebootServerRequest:account server:server type:kSoft];
432
}
433

    
434
+ (RateLimit *)softRebootServerLimit:(OpenStackAccount *)account server:(Server *)server {
435
    return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
436
}
437

    
438
+ (OpenStackRequest *)hardRebootServerRequest:(OpenStackAccount *)account server:(Server *)server {
439
    return [OpenStackRequest rebootServerRequest:account server:server type:kHard];
440
}
441

    
442
+ (RateLimit *)hardRebootServerLimit:(OpenStackAccount *)account server:(Server *)server {
443
    return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
444
}
445

    
446
+ (OpenStackRequest *)changeServerAdminPasswordRequest:(OpenStackAccount *)account server:(Server *)server password:(NSString *)password {
447
	NSString *body = [NSString stringWithFormat:@"{ \"server\": { \"adminPass\": \"%@\" } }", password];
448
    OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/servers/%@", server.identifier]];	
449
	NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
450
	[request setPostBody:[NSMutableData dataWithData:data]];
451
	return request;
452
}
453

    
454
+ (RateLimit *)changeServerAdminPasswordLimit:(OpenStackAccount *)account server:(Server *)server {
455
    return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@", server.identifier] verb:@"PUT" account:account];
456
}
457

    
458
+ (OpenStackRequest *)renameServerRequest:(OpenStackAccount *)account server:(Server *)server name:(NSString *)name {
459
	NSString *body = [NSString stringWithFormat:@"{ \"server\": { \"name\": \"%@\" } }", name];
460
    OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/servers/%@", server.identifier]];	
461
	NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
462
	[request setPostBody:[NSMutableData dataWithData:data]];
463
	return request;
464
}
465

    
466
+ (RateLimit *)renameServerLimit:(OpenStackAccount *)account server:(Server *)server {
467
    return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@", server.identifier] verb:@"PUT" account:account];
468
}
469

    
470
+ (OpenStackRequest *)deleteServerRequest:(OpenStackAccount *)account server:(Server *)server {
471
    return [OpenStackRequest serversRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/servers/%@", server.identifier]];
472
}
473

    
474
+ (RateLimit *)deleteServerLimit:(OpenStackAccount *)account server:(Server *)server {
475
    return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@", server.identifier] verb:@"DELETE" account:account];
476
}
477

    
478
+ (OpenStackRequest *)createServerRequest:(OpenStackAccount *)account server:(Server *)server {
479
	NSString *body = [server toJSON:account.apiVersion];
480
	NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/servers", account.serversURL]];
481
    NSLog(@"create server: %@", body);
482
    OpenStackRequest *request = [OpenStackRequest request:account method:@"POST" url:url];    
483
	NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
484
	[request setPostBody:[NSMutableData dataWithData:data]];
485
	return request;
486
}
487

    
488
+ (RateLimit *)createServerLimit:(OpenStackAccount *)account {
489
    return [OpenStackRequest limitForPath:@"/servers" verb:@"POST" account:account];
490
}
491

    
492
- (Server *)server {
493
    SBJSON *parser = [[SBJSON alloc] init];
494
    NSDictionary *dict = [[parser objectWithString:[self responseString]] objectForKey:@"server"];
495
    Server *server = [Server fromJSON:dict];
496
    [parser release];
497
    return server;
498
}
499

    
500
+ (OpenStackRequest *)resizeServerRequest:(OpenStackAccount *)account server:(Server *)server flavor:(Flavor *)flavor {
501
	NSString *body;
502
    if ([account.apiVersion isEqualToString:@"1.0"]) {
503
        body = [NSString stringWithFormat:@"{ \"resize\": { \"flavorId\": %@ } }", flavor.identifier];
504
    } else {
505
        body = [NSString stringWithFormat:@"{ \"resize\": { \"flavorRef\": %@ } }", flavor.identifier];
506
    }
507
    OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]];	
508
	NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
509
	[request setPostBody:[NSMutableData dataWithData:data]];
510
	return request;
511
}
512

    
513
+ (RateLimit *)resizeServerLimit:(OpenStackAccount *)account server:(Server *)server {
514
    return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
515
}
516

    
517
+ (OpenStackRequest *)confirmResizeServerRequest:(OpenStackAccount *)account server:(Server *)server {
518
	NSString *body = @"{ \"confirmResize\": null }";
519
    OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]];	
520
	NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
521
	[request setPostBody:[NSMutableData dataWithData:data]];
522
	return request;
523
}
524

    
525
+ (RateLimit *)confirmResizeServerLimit:(OpenStackAccount *)account server:(Server *)server {
526
    return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
527
}
528

    
529
+ (OpenStackRequest *)revertResizeServerRequest:(OpenStackAccount *)account server:(Server *)server {
530
	NSString *body = @"{ \"revertResize\": null }";
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 *)revertResizeServerLimit:(OpenStackAccount *)account server:(Server *)server {
538
    return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
539
}
540

    
541
+ (OpenStackRequest *)rebuildServerRequest:(OpenStackAccount *)account server:(Server *)server image:(Image *)image {
542
	NSString *body = [NSString stringWithFormat:@"{ \"rebuild\": { \"imageId\": %@ } }", image.identifier];
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 *)rebuildServerLimit:(OpenStackAccount *)account server:(Server *)server {
550
    return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
551
}
552

    
553

    
554
+ (OpenStackRequest *)getBackupScheduleRequest:(OpenStackAccount *)account server:(Server *)server {    
555
    return [OpenStackRequest serversRequest:account method:@"GET" path:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier]];
556
}
557

    
558
+ (RateLimit *)getBackupScheduleLimit:(OpenStackAccount *)account server:(Server *)server {
559
    return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier] verb:@"GET" account:account];
560
}
561

    
562
+ (OpenStackRequest *)updateBackupScheduleRequest:(OpenStackAccount *)account server:(Server *)server {
563
	NSString *body = [NSString stringWithFormat:@"{ \"backupSchedule\": { \"enabled\": true, \"weekly\": \"%@\", \"daily\": \"%@\" } }", server.backupSchedule.weekly, server.backupSchedule.daily];
564
    OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier]];	
565
	NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
566
	[request setPostBody:[NSMutableData dataWithData:data]];
567
	return request;
568
}
569

    
570
+ (RateLimit *)updateBackupScheduleLimit:(OpenStackAccount *)account server:(Server *)server {
571
    return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier] verb:@"POST" account:account];
572
}
573

    
574
- (BackupSchedule *)backupSchedule {
575
    SBJSON *parser = [[SBJSON alloc] init];
576
    NSDictionary *dict = [[parser objectWithString:[self responseString]] objectForKey:@"backupSchedule"];
577
    BackupSchedule *backupSchedule = [BackupSchedule fromJSON:dict];
578
    [parser release];
579
    return backupSchedule;
580
}
581

    
582
#pragma mark -
583
#pragma mark Object Storage Requests
584

    
585
+ (OpenStackRequest *)getStorageAccountInfoRequest:(OpenStackAccount *)account {
586
    return [OpenStackRequest filesRequest:account method:@"HEAD" path:@""];
587
}
588

    
589
+ (OpenStackRequest *)getContainersRequest:(OpenStackAccount *)account {
590
    return [OpenStackRequest filesRequest:account method:@"GET" path:@""];
591
}
592

    
593
- (NSMutableDictionary *)containers {
594
    SBJSON *parser = [[SBJSON alloc] init];
595
    NSArray *jsonObjects = [parser objectWithString:[self responseString]];
596
    NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
597
    
598
    for (int i = 0; i < [jsonObjects count]; i++) {
599
        NSDictionary *dict = [jsonObjects objectAtIndex:i];
600
        Container *container = [Container fromJSON:dict];
601
        [objects setObject:container forKey:container.name];
602
    }
603
    
604
    [parser release];
605
    return objects;
606
}
607

    
608
+ (OpenStackRequest *)createContainerRequest:(OpenStackAccount *)account container:(Container *)container {    
609
    return [OpenStackRequest filesRequest:account method:@"PUT" path:[[NSString stringWithFormat:@"/%@", container.name] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
610
}
611

    
612
+ (OpenStackRequest *)deleteContainerRequest:(OpenStackAccount *)account container:(Container *)container {
613
    return [OpenStackRequest filesRequest:account method:@"DELETE" path:[[NSString stringWithFormat:@"/%@", container.name] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
614
}
615

    
616
+ (OpenStackRequest *)getObjectsRequest:(OpenStackAccount *)account container:(Container *)container {
617
    return [OpenStackRequest filesRequest:account method:@"GET" path:[[NSString stringWithFormat:@"/%@", container.name] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];    
618
}
619

    
620
- (NSMutableDictionary *)objects {
621
    SBJSON *parser = [[SBJSON alloc] init];
622
    NSArray *jsonObjects = [parser objectWithString:[self responseString]];
623
    NSMutableDictionary *objects = [[[NSMutableDictionary alloc] initWithCapacity:[jsonObjects count]] autorelease];
624
    
625
    for (int i = 0; i < [jsonObjects count]; i++) {
626
        NSDictionary *dict = [jsonObjects objectAtIndex:i];
627
        StorageObject *object = [StorageObject fromJSON:dict];
628
        [objects setObject:object forKey:object.name];
629
    }
630
    
631
    [parser release];
632
    return objects;
633
}
634

    
635
+ (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
636
    return [OpenStackRequest filesRequest:account method:@"HEAD" path:[[NSString stringWithFormat:@"/%@/%@", container.name, object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];    
637
}
638

    
639
+ (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
640
    return [OpenStackRequest filesRequest:account method:@"GET" path:[[NSString stringWithFormat:@"/%@/%@", container.name, object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];    
641
}
642

    
643
+ (OpenStackRequest *)writeObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
644
    NSString *fullPath = object.fullPath;
645
    if ([fullPath characterAtIndex:0] == '/') {
646
        fullPath = [fullPath substringFromIndex:1];
647
    }
648
    
649
    OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"PUT" path:[[NSString stringWithFormat:@"/%@/%@", container.name, fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];    
650
	[request setPostBody:[NSMutableData dataWithData:object.data]];
651
    [request.requestHeaders setObject:object.contentType forKey:@"Content-Type"];
652
	return request;
653
}
654

    
655
+ (OpenStackRequest *)writeObjectMetadataRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
656
    NSString *fullPath = [NSString stringWithString:object.fullPath];
657
    if ([fullPath length] && ([fullPath characterAtIndex:0] == '/'))
658
        fullPath = [fullPath substringFromIndex:1];
659

    
660
    NSString *metadataKeyHeaderPrefix;
661
    if (![fullPath length])
662
        metadataKeyHeaderPrefix = @"X-Container-Meta-";
663
    else
664
        metadataKeyHeaderPrefix = @"X-Object-Meta-";
665
        
666
    OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"POST" path:[[NSString stringWithFormat:@"/%@/%@", container.name, fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 
667
    
668
    for (NSString *metadataKey in object.metadata) {
669
        NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey]; 
670
        [request.requestHeaders setObject:[object.metadata objectForKey:metadataKey] forKey:metadataKeyHeader];
671
    }
672
    
673
    NSString *objectIsPublic = ([object.publicURI length]) ? @"true" : @"false";
674
    [request.requestHeaders setObject:objectIsPublic forKey:@"X-Object-Public"];
675
    return request;
676
}
677

    
678
+ (OpenStackRequest *)deleteObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
679
    if ([object.fullPath characterAtIndex:0] == '/') {
680
        return [OpenStackRequest filesRequest:account method:@"DELETE" path:[[NSString stringWithFormat:@"/%@%@", container.name, object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
681
    } else {
682
        return [OpenStackRequest filesRequest:account method:@"DELETE" path:[[NSString stringWithFormat:@"/%@/%@", container.name, object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
683
    }
684
}
685

    
686
#pragma mark -
687
#pragma mark Memory Management
688

    
689
- (void)releaseBackupBlocksOnMainThread {
690
	NSMutableArray *blocks = [NSMutableArray array];
691
	if (backupCompletionBlock) {
692
		[blocks addObject:backupCompletionBlock];
693
		[backupCompletionBlock release];
694
		backupCompletionBlock = nil;
695
	}
696
	if (backupFailureBlock) {
697
		[blocks addObject:backupFailureBlock];
698
		[backupFailureBlock release];
699
		backupFailureBlock = nil;
700
	}
701
	[[self class] performSelectorOnMainThread:@selector(releaseBackupBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]];
702
}
703

    
704
// Always called on main thread
705
+ (void)releaseBackupBlocks:(NSArray *)blocks {
706
	// Blocks will be released when this method exits
707
}
708

    
709
- (void)dealloc {
710
    [account release];
711
    [self releaseBackupBlocksOnMainThread];
712
    [super dealloc];
713
}
714

    
715
@end