Revision aa197ee4

b/api/actions.py
25 25
    '''Decorator for functions implementing server actions.
26 26
    `name` is the key in the dict passed by the client.
27 27
    '''
28
    
28

  
29 29
    def decorator(func):
30 30
        server_actions[name] = func
31 31
        return func
......
53 53
    #                       itemNotFound (404),
54 54
    #                       buildInProgress (409),
55 55
    #                       overLimit (413)
56
    
56

  
57 57
    try:
58 58
        password = args['adminPass']
59 59
    except KeyError:
......
72 72
    #                       itemNotFound (404),
73 73
    #                       buildInProgress (409),
74 74
    #                       overLimit (413)
75
    
75

  
76 76
    reboot_type = args.get('type', '')
77 77
    if reboot_type not in ('SOFT', 'HARD'):
78 78
        raise BadRequest('Malformed Request.')
......
84 84
    # Normal Response Code: 202
85 85
    # Error Response Codes: serviceUnavailable (503),
86 86
    #                       itemNotFound (404)
87
    
87

  
88 88
    if args:
89 89
        raise BadRequest('Malformed Request.')
90 90
    startup_instance(vm)
......
95 95
    # Normal Response Code: 202
96 96
    # Error Response Codes: serviceUnavailable (503),
97 97
    #                       itemNotFound (404)
98
    
98

  
99 99
    if args:
100 100
        raise BadRequest('Malformed Request.')
101 101
    shutdown_instance(vm)
......
129 129
    #                       serverCapacityUnavailable (503),
130 130
    #                       overLimit (413),
131 131
    #                       resizeNotAllowed (403)
132
    
132

  
133 133
    raise ServiceUnavailable('Resize not supported.')
134 134

  
135 135
@server_action('confirmResize')
......
145 145
    #                       serverCapacityUnavailable (503),
146 146
    #                       overLimit (413),
147 147
    #                       resizeNotAllowed (403)
148
    
148

  
149 149
    raise ServiceUnavailable('Resize not supported.')
150 150

  
151 151
@server_action('revertResize')
......
184 184
    #                       itemNotFound (404),
185 185
    #                       buildInProgress (409),
186 186
    #                       overLimit (413)
187
    
187

  
188 188
    console_type = args.get('type', '')
189 189
    if console_type != 'vnc':
190 190
        raise BadRequest('Type can only be "vnc".')
......
192 192
    # Use RAPI to get VNC console information for this instance
193 193
    if get_rsapi_state(vm) != 'ACTIVE':
194 194
        raise BadRequest('Server not in ACTIVE state.')
195
    
195

  
196 196
    if settings.TEST:
197 197
        console_data = {'kind': 'vnc', 'host': 'ganeti_node', 'port': 1000}
198 198
    else:
199 199
        console_data = get_instance_console(vm)
200
    
200

  
201 201
    if console_data['kind'] != 'vnc':
202 202
        raise ServiceUnavailable('Could not create a console of requested type.')
203
    
203

  
204 204
    # Let vncauthproxy decide on the source port.
205 205
    # The alternative: static allocation, e.g.
206 206
    # sport = console_data['port'] - 1000
......
208 208
    daddr = console_data['host']
209 209
    dport = console_data['port']
210 210
    password = random_password()
211
    
211

  
212 212
    try:
213 213
        if settings.TEST:
214 214
            fwd = {'source_port': 1234, 'status': 'OK'}
......
219 219

  
220 220
    if fwd['status'] != "OK":
221 221
        raise ServiceUnavailable('Could not allocate VNC console.')
222
    
222

  
223 223
    console = {
224 224
        'type': 'vnc',
225 225
        'host': getfqdn(),
226 226
        'port': fwd['source_port'],
227 227
        'password': password}
228
    
228

  
229 229
    if request.serialization == 'xml':
230 230
        mimetype = 'application/xml'
231 231
        data = render_to_string('console.xml', {'console': console})
232 232
    else:
233 233
        mimetype = 'application/json'
234 234
        data = json.dumps({'console': console})
235
    
235

  
236 236
    return HttpResponse(data, mimetype=mimetype, status=200)
237 237

  
238 238

  
......
246 246
    #                       badMediaType(415),
247 247
    #                       itemNotFound (404),
248 248
    #                       overLimit (413)
249
    
249

  
250 250
    server_id = args.get('serverRef', None)
251 251
    if not server_id:
252 252
        raise BadRequest('Malformed Request.')
......
265 265
    #                       badMediaType(415),
266 266
    #                       itemNotFound (404),
267 267
    #                       overLimit (413)
268
    
268

  
269 269
    server_id = args.get('serverRef', None)
270 270
    if not server_id:
271 271
        raise BadRequest('Malformed Request.')
b/api/fixtures/api_test_data.json
5 5
        "fields": {
6 6
            "cpu": 1,
7 7
            "ram": 1024,
8
            "disk": 20 
8
            "disk": 20
9 9
        }
10 10
    },
11 11
    {
......
14 14
        "fields": {
15 15
            "cpu": 1,
16 16
            "ram": 1024,
17
            "disk": 40 
17
            "disk": 40
18 18
        }
19 19
    },
20 20
    {
......
23 23
        "fields": {
24 24
            "cpu": 1,
25 25
            "ram": 1024,
26
            "disk": 80 
26
            "disk": 80
27 27
        }
28 28
    },
29 29
    {
......
32 32
        "fields": {
33 33
            "cpu": 1,
34 34
            "ram": 2048,
35
            "disk": 20 
35
            "disk": 20
36 36
        }
37 37
    },
38 38
    {
......
41 41
        "fields": {
42 42
            "cpu": 1,
43 43
            "ram": 2048,
44
            "disk": 40 
44
            "disk": 40
45 45
        }
46 46
    },
47 47
    {
......
50 50
        "fields": {
51 51
            "cpu": 1,
52 52
            "ram": 2048,
53
            "disk": 80 
53
            "disk": 80
54 54
        }
55 55
    },
56 56
    {
......
59 59
        "fields": {
60 60
            "cpu": 1,
61 61
            "ram": 4096,
62
            "disk": 20 
62
            "disk": 20
63 63
        }
64 64
    },
65 65
    {
......
68 68
        "fields": {
69 69
            "cpu": 1,
70 70
            "ram": 4096,
71
            "disk": 40 
71
            "disk": 40
72 72
        }
73 73
    },
74 74
    {
......
77 77
        "fields": {
78 78
            "cpu": 1,
79 79
            "ram": 4096,
80
            "disk": 80 
80
            "disk": 80
81 81
        }
82 82
    },
83 83
    {
......
86 86
        "fields": {
87 87
            "cpu": 2,
88 88
            "ram": 1024,
89
            "disk": 20 
89
            "disk": 20
90 90
        }
91 91
    },
92 92
    {
......
95 95
        "fields": {
96 96
            "cpu": 2,
97 97
            "ram": 1024,
98
            "disk": 40 
98
            "disk": 40
99 99
        }
100 100
    },
101 101
    {
......
104 104
        "fields": {
105 105
            "cpu": 2,
106 106
            "ram": 1024,
107
            "disk": 80 
107
            "disk": 80
108 108
        }
109 109
    },
110 110
    {
......
113 113
        "fields": {
114 114
            "cpu": 2,
115 115
            "ram": 2048,
116
            "disk": 20 
116
            "disk": 20
117 117
        }
118 118
    },
119 119
    {
......
122 122
        "fields": {
123 123
            "cpu": 2,
124 124
            "ram": 2048,
125
            "disk": 40 
125
            "disk": 40
126 126
        }
127 127
    },
128 128
    {
......
131 131
        "fields": {
132 132
            "cpu": 2,
133 133
            "ram": 2048,
134
            "disk": 80 
134
            "disk": 80
135 135
        }
136 136
    },
137 137
    {
......
140 140
        "fields": {
141 141
            "cpu": 2,
142 142
            "ram": 4096,
143
            "disk": 20 
143
            "disk": 20
144 144
        }
145 145
    },
146 146
    {
......
149 149
        "fields": {
150 150
            "cpu": 2,
151 151
            "ram": 4096,
152
            "disk": 40 
152
            "disk": 40
153 153
        }
154 154
    },
155 155
    {
......
158 158
        "fields": {
159 159
            "cpu": 2,
160 160
            "ram": 4096,
161
            "disk": 80 
161
            "disk": 80
162 162
        }
163 163
    },
164 164
    {
......
167 167
        "fields": {
168 168
            "cpu": 4,
169 169
            "ram": 1024,
170
            "disk": 20 
170
            "disk": 20
171 171
        }
172 172
    },
173 173
    {
......
176 176
        "fields": {
177 177
            "cpu": 4,
178 178
            "ram": 1024,
179
            "disk": 40 
179
            "disk": 40
180 180
        }
181 181
    },
182 182
    {
......
185 185
        "fields": {
186 186
            "cpu": 4,
187 187
            "ram": 1024,
188
            "disk": 80 
188
            "disk": 80
189 189
        }
190 190
    },
191 191
    {
......
194 194
        "fields": {
195 195
            "cpu": 4,
196 196
            "ram": 2048,
197
            "disk": 20 
197
            "disk": 20
198 198
        }
199 199
    },
200 200
    {
......
203 203
        "fields": {
204 204
            "cpu": 4,
205 205
            "ram": 2048,
206
            "disk": 40 
206
            "disk": 40
207 207
        }
208 208
    },
209 209
    {
......
212 212
        "fields": {
213 213
            "cpu": 4,
214 214
            "ram": 2048,
215
            "disk": 80 
215
            "disk": 80
216 216
        }
217 217
    },
218 218
    {
......
221 221
        "fields": {
222 222
            "cpu": 4,
223 223
            "ram": 4096,
224
            "disk": 20 
224
            "disk": 20
225 225
        }
226 226
    },
227 227
    {
......
230 230
        "fields": {
231 231
            "cpu": 4,
232 232
            "ram": 4096,
233
            "disk": 40 
233
            "disk": 40
234 234
        }
235 235
    },
236 236
    {
......
239 239
        "fields": {
240 240
            "cpu": 4,
241 241
            "ram": 4096,
242
            "disk": 80 
242
            "disk": 80
243 243
        }
244 244
    },
245 245
    {
......
333 333
            "sourcevm": 1001
334 334
        }
335 335
    }
336
] 
336
]
b/api/flavors.py
35 35
    #                       unauthorized (401),
36 36
    #                       badRequest (400),
37 37
    #                       overLimit (413)
38
    
38

  
39 39
    all_flavors = Flavor.objects.all()
40 40
    flavors = [flavor_to_dict(flavor, detail) for flavor in all_flavors]
41
    
41

  
42 42
    if request.serialization == 'xml':
43 43
        data = render_to_string('list_flavors.xml', {'flavors': flavors, 'detail': detail})
44 44
    else:
45 45
        data = json.dumps({'flavors': {'values': flavors}})
46
    
46

  
47 47
    return HttpResponse(data, status=200)
48 48

  
49 49
@api_method('GET')
......
55 55
    #                       badRequest (400),
56 56
    #                       itemNotFound (404),
57 57
    #                       overLimit (413)
58
    
58

  
59 59
    flavor = get_flavor(flavor_id)
60 60
    flavordict = flavor_to_dict(flavor, detail=True)
61
    
61

  
62 62
    if request.serialization == 'xml':
63 63
        data = render_to_string('flavor.xml', {'flavor': flavordict})
64 64
    else:
65 65
        data = json.dumps({'flavor': flavordict})
66
    
66

  
67 67
    return HttpResponse(data, status=200)
b/api/images.py
66 66
        d['progress'] = 100 if image.state == 'ACTIVE' else 0
67 67
        if image.sourcevm:
68 68
            d['serverRef'] = image.sourcevm.id
69
        
69

  
70 70
        metadata = {}
71 71
        for meta in ImageMetadata.objects.filter(image=image):
72 72
            metadata[meta.meta_key] = meta.meta_value
73
        
73

  
74 74
        if metadata:
75 75
            d['metadata'] = {'values': metadata}
76
    
76

  
77 77
    return d
78 78

  
79 79
def metadata_to_dict(image):
......
89 89
    #                       unauthorized (401),
90 90
    #                       badRequest (400),
91 91
    #                       overLimit (413)
92
    
92

  
93 93
    since = isoparse(request.GET.get('changes-since'))
94
    
94

  
95 95
    if since:
96 96
        avail_images = Image.objects.filter(owner=request.user, updated__gte=since)
97 97
        if not avail_images:
98 98
            return HttpResponse(status=304)
99 99
    else:
100 100
        avail_images = Image.objects.filter(owner=request.user)
101
    
101

  
102 102
    images = [image_to_dict(image, detail) for image in avail_images]
103
    
103

  
104 104
    if request.serialization == 'xml':
105 105
        data = render_to_string('list_images.xml', {'images': images, 'detail': detail})
106 106
    else:
107 107
        data = json.dumps({'images': {'values': images}})
108
    
108

  
109 109
    return HttpResponse(data, status=200)
110 110

  
111 111
@api_method('POST')
......
122 122
    #                       resizeNotAllowed (403),
123 123
    #                       backupOrResizeInProgress (409),
124 124
    #                       overLimit (413)
125
    
125

  
126 126
    req = get_request_dict(request)
127
    
127

  
128 128
    try:
129 129
        d = req['image']
130 130
        server_id = d['serverRef']
131 131
        name = d['name']
132 132
    except (KeyError, ValueError):
133 133
        raise BadRequest('Malformed request.')
134
    
134

  
135 135
    owner = request.user
136 136
    vm = get_vm(server_id, owner)
137 137
    image = Image.objects.create(name=name, owner=owner, sourcevm=vm)
138
    
138

  
139 139
    imagedict = image_to_dict(image)
140 140
    if request.serialization == 'xml':
141 141
        data = render_to_string('image.xml', {'image': imagedict})
142 142
    else:
143 143
        data = json.dumps({'image': imagedict})
144
    
144

  
145 145
    return HttpResponse(data, status=202)
146 146

  
147 147
@api_method('GET')
......
153 153
    #                       badRequest (400),
154 154
    #                       itemNotFound (404),
155 155
    #                       overLimit (413)
156
    
156

  
157 157
    image = get_image(image_id, request.user)
158 158
    imagedict = image_to_dict(image)
159
    
159

  
160 160
    if request.serialization == 'xml':
161 161
        data = render_to_string('image.xml', {'image': imagedict})
162 162
    else:
163 163
        data = json.dumps({'image': imagedict})
164
    
164

  
165 165
    return HttpResponse(data, status=200)
166 166

  
167 167
@api_method('DELETE')
......
172 172
    #                       unauthorized (401),
173 173
    #                       itemNotFound (404),
174 174
    #                       overLimit (413)
175
    
175

  
176 176
    image = get_image(image_id, request.user)
177 177
    image.delete()
178 178
    return HttpResponse(status=204)
......
231 231
    #                       itemNotFound (404),
232 232
    #                       badRequest (400),
233 233
    #                       overLimit (413)
234
    
234

  
235 235
    image = get_image(image_id, request.user)
236 236
    meta = get_image_meta(image, key)
237 237
    return render_meta(request, meta, status=200)
......
274 274
    #                       buildInProgress (409),
275 275
    #                       badMediaType(415),
276 276
    #                       overLimit (413),
277
    
277

  
278 278
    image = get_image(image_id, request.user)
279 279
    meta = get_image_meta(image, key)
280 280
    meta.delete()
b/api/middleware.py
75 75
        #caching of results
76 76
        response['Vary'] = self.auth_token
77 77
        return response
78
        
78

  
b/api/networks.py
59 59
    #                       unauthorized (401),
60 60
    #                       badRequest (400),
61 61
    #                       overLimit (413)
62
    
62

  
63 63
    since = isoparse(request.GET.get('changes-since'))
64
    
64

  
65 65
    if since:
66 66
        user_networks = Network.objects.filter(owner=request.user, updated__gte=since)
67 67
        if not user_networks:
68 68
            return HttpResponse(status=304)
69 69
    else:
70 70
        user_networks = Network.objects.filter(owner=request.user)
71
    
71

  
72 72
    networks = [network_to_dict(network, detail) for network in user_networks]
73
    
73

  
74 74
    if request.serialization == 'xml':
75 75
        data = render_to_string('list_networks.xml', {'networks': networks, 'detail': detail})
76 76
    else:
77 77
        data = json.dumps({'networks': {'values': networks}})
78
    
78

  
79 79
    return HttpResponse(data, status=200)
80 80

  
81 81
@api_method('POST')
......
87 87
    #                       badMediaType(415),
88 88
    #                       badRequest (400),
89 89
    #                       overLimit (413)
90
    
90

  
91 91
    req = get_request_dict(request)
92
    
92

  
93 93
    try:
94 94
        d = req['network']
95 95
        name = d['name']
96 96
    except (KeyError, ValueError):
97 97
        raise BadRequest('Malformed request.')
98
    
98

  
99 99
    network = Network.objects.create(name=name, owner=request.user)
100 100
    networkdict = network_to_dict(network)
101 101
    return render_network(request, networkdict, status=202)
......
109 109
    #                       badRequest (400),
110 110
    #                       itemNotFound (404),
111 111
    #                       overLimit (413)
112
    
112

  
113 113
    net = get_network(network_id, request.user)
114 114
    netdict = network_to_dict(net)
115 115
    return render_network(request, netdict)
......
124 124
    #                       badMediaType(415),
125 125
    #                       itemNotFound (404),
126 126
    #                       overLimit (413)
127
    
127

  
128 128
    req = get_request_dict(request)
129 129

  
130 130
    try:
......
146 146
    #                       itemNotFound (404),
147 147
    #                       unauthorized (401),
148 148
    #                       overLimit (413)
149
    
149

  
150 150
    net = get_network(network_id, request.user)
151 151
    net.delete()
152 152
    return HttpResponse(status=204)
......
157 157
    req = get_request_dict(request)
158 158
    if len(req) != 1:
159 159
        raise BadRequest('Malformed request.')
160
    
160

  
161 161
    key = req.keys()[0]
162 162
    val = req[key]
163
    
163

  
164 164
    try:
165 165
        assert isinstance(val, dict)
166 166
        return network_actions[key](request, net, req[key])
b/api/servers.py
88 88
        d['created'] = isoformat(vm.created)
89 89
        d['flavorRef'] = vm.flavor.id
90 90
        d['imageRef'] = vm.sourceimage.id
91
        
91

  
92 92
        metadata = metadata_to_dict(vm)
93 93
        if metadata:
94 94
            d['metadata'] = {'values': metadata}
95
        
95

  
96 96
        addresses = [address_to_dict(vm.ipfour, vm.ipsix)]
97 97
        addresses.extend({'id': str(network.id), 'values': []} for network in vm.network_set.all())
98 98
        d['addresses'] = {'values': addresses}
......
105 105
    else:
106 106
        data = json.dumps({'server': server})
107 107
    return HttpResponse(data, status=status)
108
    
108

  
109 109

  
110 110
@api_method('GET')
111 111
def list_servers(request, detail=False):
......
115 115
    #                       unauthorized (401),
116 116
    #                       badRequest (400),
117 117
    #                       overLimit (413)
118
    
118

  
119 119
    since = isoparse(request.GET.get('changes-since'))
120
    
120

  
121 121
    if since:
122 122
        user_vms = VirtualMachine.objects.filter(owner=request.user, updated__gte=since)
123 123
        if not user_vms:
......
125 125
    else:
126 126
        user_vms = VirtualMachine.objects.filter(owner=request.user, deleted=False)
127 127
    servers = [vm_to_dict(server, detail) for server in user_vms]
128
    
128

  
129 129
    if request.serialization == 'xml':
130 130
        data = render_to_string('list_servers.xml', {'servers': servers, 'detail': detail})
131 131
    else:
132 132
        data = json.dumps({'servers': {'values': servers}})
133
    
133

  
134 134
    return HttpResponse(data, status=200)
135 135

  
136 136
@api_method('POST')
......
144 144
    #                       badRequest (400),
145 145
    #                       serverCapacityUnavailable (503),
146 146
    #                       overLimit (413)
147
    
147

  
148 148
    req = get_request_dict(request)
149
    
149

  
150 150
    try:
151 151
        server = req['server']
152 152
        name = server['name']
......
156 156
        flavor_id = server['flavorRef']
157 157
    except (KeyError, AssertionError):
158 158
        raise BadRequest('Malformed request.')
159
    
159

  
160 160
    image = get_image(image_id, request.user)
161 161
    flavor = get_flavor(flavor_id)
162
    
162

  
163 163
    # We must save the VM instance now, so that it gets a valid vm.backend_id.
164 164
    vm = VirtualMachine.objects.create(
165 165
        name=name,
......
168 168
        ipfour='0.0.0.0',
169 169
        ipsix='::1',
170 170
        flavor=flavor)
171
    
171

  
172 172
    password = random_password()
173
                
173

  
174 174
    try:
175 175
        create_instance(vm, flavor, password)
176 176
    except GanetiApiError:
177 177
        vm.delete()
178 178
        raise ServiceUnavailable('Could not create server.')
179
        
179

  
180 180
    for key, val in metadata.items():
181 181
        VirtualMachineMetadata.objects.create(meta_key=key, meta_value=val, vm=vm)
182
    
182

  
183 183
    logging.info('created vm with %s cpus, %s ram and %s storage',
184 184
                    flavor.cpu, flavor.ram, flavor.disk)
185
    
185

  
186 186
    server = vm_to_dict(vm, detail=True)
187 187
    server['status'] = 'BUILD'
188 188
    server['adminPass'] = password
......
197 197
    #                       badRequest (400),
198 198
    #                       itemNotFound (404),
199 199
    #                       overLimit (413)
200
    
200

  
201 201
    vm = get_vm(server_id, request.user)
202 202
    server = vm_to_dict(vm, detail=True)
203 203
    return render_server(request, server)
......
213 213
    #                       itemNotFound (404),
214 214
    #                       buildInProgress (409),
215 215
    #                       overLimit (413)
216
    
216

  
217 217
    req = get_request_dict(request)
218
    
218

  
219 219
    try:
220 220
        name = req['server']['name']
221 221
    except (TypeError, KeyError):
222 222
        raise BadRequest('Malformed request.')
223
    
223

  
224 224
    vm = get_vm(server_id, request.user)
225 225
    vm.name = name
226 226
    vm.save()
227
    
227

  
228 228
    return HttpResponse(status=204)
229 229

  
230 230
@api_method('DELETE')
......
237 237
    #                       unauthorized (401),
238 238
    #                       buildInProgress (409),
239 239
    #                       overLimit (413)
240
    
240

  
241 241
    vm = get_vm(server_id, request.user)
242 242
    delete_instance(vm)
243 243
    return HttpResponse(status=204)
......
248 248
    req = get_request_dict(request)
249 249
    if len(req) != 1:
250 250
        raise BadRequest('Malformed request.')
251
    
251

  
252 252
    key = req.keys()[0]
253 253
    val = req[key]
254
    
254

  
255 255
    try:
256 256
        assert isinstance(val, dict)
257 257
        return server_actions[key](request, vm, req[key])
......
268 268
    #                       unauthorized (401),
269 269
    #                       badRequest (400),
270 270
    #                       overLimit (413)
271
    
271

  
272 272
    vm = get_vm(server_id, request.user)
273 273
    addresses = [address_to_dict(vm.ipfour, vm.ipsix)]
274
    
274

  
275 275
    if request.serialization == 'xml':
276 276
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
277 277
    else:
278 278
        data = json.dumps({'addresses': {'values': addresses}})
279
    
279

  
280 280
    return HttpResponse(data, status=200)
281 281

  
282 282
@api_method('GET')
......
288 288
    #                       badRequest (400),
289 289
    #                       itemNotFound (404),
290 290
    #                       overLimit (413)
291
    
291

  
292 292
    vm = get_vm(server_id, request.user)
293 293
    if network_id != 'public':
294 294
        raise ItemNotFound('Unknown network.')
295
    
295

  
296 296
    address = address_to_dict(vm.ipfour, vm.ipsix)
297
    
297

  
298 298
    if request.serialization == 'xml':
299 299
        data = render_to_string('address.xml', {'address': address})
300 300
    else:
301 301
        data = json.dumps({'network': address})
302
    
302

  
303 303
    return HttpResponse(data, status=200)
304 304

  
305 305
@api_method('GET')
......
333 333
        assert isinstance(metadata, dict)
334 334
    except (KeyError, AssertionError):
335 335
        raise BadRequest('Malformed request.')
336
    
336

  
337 337
    updated = {}
338
    
338

  
339 339
    for key, val in metadata.items():
340 340
        try:
341 341
            meta = VirtualMachineMetadata.objects.get(meta_key=key, vm=vm)
......
344 344
            updated[key] = val
345 345
        except VirtualMachineMetadata.DoesNotExist:
346 346
            pass    # Ignore non-existent metadata
347
    
347

  
348 348
    return render_metadata(request, updated, status=201)
349 349

  
350 350
@api_method('GET')
......
356 356
    #                       itemNotFound (404),
357 357
    #                       badRequest (400),
358 358
    #                       overLimit (413)
359
    
359

  
360 360
    vm = get_vm(server_id, request.user)
361 361
    meta = get_vm_meta(vm, key)
362 362
    return render_meta(request, meta, status=200)
......
382 382
        assert key in metadict
383 383
    except (KeyError, AssertionError):
384 384
        raise BadRequest('Malformed request.')
385
    
385

  
386 386
    meta, created = VirtualMachineMetadata.objects.get_or_create(meta_key=key, vm=vm)
387 387
    meta.meta_value = metadict[key]
388 388
    meta.save()
......
399 399
    #                       buildInProgress (409),
400 400
    #                       badMediaType(415),
401 401
    #                       overLimit (413),
402
    
402

  
403 403
    vm = get_vm(server_id, request.user)
404 404
    meta = get_vm_meta(vm, key)
405 405
    meta.delete()
b/api/templates/list_flavors.xml
1 1
{% spaceless %}
2 2
<?xml version="1.0" encoding="UTF-8"?>
3 3
<flavors xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom">
4
  {% for flavor in flavors %}  
4
  {% for flavor in flavors %}
5 5
  <flavor id="{{ flavor.id}}" name="{{ flavor.name }}"{% if detail %} ram="{{ flavor.ram }}" cpu="{{ flavor.cpu }}" disk="{{ flavor.disk }}"{% endif %}>
6 6
  </flavor>
7 7
  {% endfor %}
b/api/templates/server.xml
20 20
        {% for key, val in server.metadata.values.items %}<meta key="{{ key }}">{{ val }}</meta>{% endfor %}
21 21
    </metadata>
22 22
    {% endif %}
23
    
23

  
24 24
    <addresses>
25 25
        {% for network in server.addresses.values %}
26 26
        <network id="{{ network.id }}">
b/api/templates/version_details.atom
9 9
    <uri>http://ocean.grnet.gr/</uri>
10 10
  </author>
11 11
  <link rel="self" href="http://ocean.grnet.gr/"/>
12
  
12

  
13 13
  <entry>
14 14
    <id>http://servers.api.openstack.org/{{ version.id }}/</id>
15 15
    <title type="text">Version {{ version.id }}</title>
......
17 17
    {% for link in version.links %}
18 18
      <link rel="{{ link.rel }}" {% if link.type %}type="{{ link.type }}" {% endif %}href="{{ link.href }}"/>
19 19
    {% endfor %}
20
    <content type="text">Version {{ version.id }} {{ version.status }} ({{ version.updated }})</content>   
20
    <content type="text">Version {{ version.id }} {{ version.status }} ({{ version.updated }})</content>
21 21
  </entry>
22 22
</feed>
23 23
{% endspaceless %}
b/api/templates/versions_list.atom
9 9
    <uri>http://ocean.grnet.gr/</uri>
10 10
  </author>
11 11
  <link rel="self" href="http://ocean.grnet.gr/"/>
12
  
12

  
13 13
  {% for version in versions %}
14 14
  <entry>
15 15
    <id>http://servers.api.openstack.org/{{ version.id }}/</id>
b/api/tests.py
40 40

  
41 41
    def test_api_version(self):
42 42
        """Check API version."""
43
        
43

  
44 44
        response = self.client.get('/api/v1.1/')
45 45
        self.assertEqual(response.status_code, 200)
46 46
        api_version = json.loads(response.content)['version']
......
49 49

  
50 50
    def test_server_list(self):
51 51
        """Test if the expected list of servers is returned."""
52
        
52

  
53 53
        response = self.client.get('/api/v1.1/servers')
54 54
        vms_from_api = json.loads(response.content)['servers']['values']
55 55
        vms_from_db = VirtualMachine.objects.filter(deleted=False)
......
62 62

  
63 63
    def test_server_details(self):
64 64
        """Test if the expected server is returned."""
65
        
65

  
66 66
        response = self.client.get('/api/v1.1/servers/%d' % self.test_server_id)
67 67
        vm_from_api = json.loads(response.content)['server']
68 68
        vm_from_db = VirtualMachine.objects.get(id=self.test_server_id)
......
76 76

  
77 77
    def test_servers_details(self):
78 78
        """Test if the servers details are returned."""
79
        
79

  
80 80
        response = self.client.get('/api/v1.1/servers/detail')
81 81

  
82 82
        # Make sure both DB and API responses are sorted by id,
......
110 110

  
111 111
    def test_wrong_server(self):
112 112
        """Test 404 response if server does not exist."""
113
        
113

  
114 114
        response = self.client.get('/api/v1.1/servers/%d' % self.test_wrong_server_id)
115 115
        self.assertEqual(response.status_code, 404)
116 116

  
117 117
    def test_create_server_empty(self):
118 118
        """Test if the create server call returns a 400 badRequest if
119 119
           no attributes are specified."""
120
        
120

  
121 121
        response = self.client.post('/api/v1.1/servers', {})
122 122
        self.assertEqual(response.status_code, 400)
123 123

  
124 124
    def test_create_server(self):
125 125
        """Test if the create server call returns the expected response
126 126
           if a valid request has been speficied."""
127
        
127

  
128 128
        request = {
129 129
                    "server": {
130 130
                        "name": "new-server-test",
......
145 145

  
146 146
    def test_server_polling(self):
147 147
        """Test if the server polling works as expected."""
148
        
148

  
149 149
        response = self.client.get('/api/v1.1/servers/detail')
150 150
        vms_from_api_initial = json.loads(response.content)['servers']['values']
151 151
        ts = mktime(parsedate(response['Date']))
......
165 165
                        "personality": []
166 166
                    }
167 167
        }
168
        
168

  
169 169
        path = '/api/v1.1/servers'
170 170
        response = self.client.post(path, json.dumps(request), content_type='application/json')
171 171
        self.assertEqual(response.status_code, 202)
......
178 178

  
179 179
    def test_reboot_server(self):
180 180
        """Test if the specified server is rebooted."""
181
        
182 181
        request = {'reboot': {'type': 'HARD'}}
183 182
        path = '/api/v1.1/servers/%d/action' % self.test_server_id
184 183
        response = self.client.post(path, json.dumps(request), content_type='application/json')
......
190 189

  
191 190
    def test_shutdown_server(self):
192 191
        """Test if the specified server is shutdown."""
193
        
192

  
194 193
        request = {'shutdown': {}}
195 194
        path = '/api/v1.1/servers/%d/action' % self.test_server_id
196 195
        response = self.client.post(path, json.dumps(request), content_type='application/json')
......
202 201

  
203 202
    def test_start_server(self):
204 203
        """Test if the specified server is started."""
205
        
204

  
206 205
        request = {'start': {}}
207 206
        path = '/api/v1.1/servers/%d/action' % self.test_server_id
208 207
        response = self.client.post(path, json.dumps(request), content_type='application/json')
......
222 221

  
223 222
    def test_flavor_list(self):
224 223
        """Test if the expected list of flavors is returned by."""
225
        
224

  
226 225
        response = self.client.get('/api/v1.1/flavors')
227 226
        flavors_from_api = json.loads(response.content)['flavors']['values']
228 227
        flavors_from_db = Flavor.objects.all()
......
235 234

  
236 235
    def test_flavors_details(self):
237 236
        """Test if the flavors details are returned."""
238
        
237

  
239 238
        response = self.client.get('/api/v1.1/flavors/detail')
240 239
        flavors_from_db = Flavor.objects.all()
241 240
        flavors_from_api = json.loads(response.content)['flavors']['values']
......
264 263

  
265 264
    def test_flavor_details(self):
266 265
        """Test if the expected flavor is returned."""
267
        
266

  
268 267
        response = self.client.get('/api/v1.1/flavors/%d' % self.test_flavor_id)
269 268
        flavor_from_api = json.loads(response.content)['flavor']
270 269
        flavor_from_db = Flavor.objects.get(id=self.test_flavor_id)
......
277 276

  
278 277
    def test_wrong_flavor(self):
279 278
        """Test 404 result when requesting a flavor that does not exist."""
280
        
279

  
281 280
        response = self.client.get('/api/v1.1/flavors/%d' % self.test_wrong_flavor_id)
282 281
        self.assertTrue(response.status_code in [404, 503])
283 282

  
284 283
    def test_image_list(self):
285 284
        """Test if the expected list of images is returned by the API."""
286
        
285

  
287 286
        response = self.client.get('/api/v1.1/images')
288 287
        images_from_api = json.loads(response.content)['images']['values']
289 288
        images_from_db = Image.objects.all()
......
296 295

  
297 296
    def test_wrong_image(self):
298 297
        """Test 404 result if a non existent image is requested."""
299
        
298

  
300 299
        response = self.client.get('/api/v1.1/images/%d' % self.test_wrong_image_id)
301 300
        self.assertEqual(response.status_code, 404)
302 301

  
303 302
    def test_server_metadata(self):
304 303
        """Test server's metadata (add, edit)."""
305
        
304

  
306 305
        key = 'name'
307 306
        request = {'meta': {key: 'a fancy name'}}
308
        
307

  
309 308
        path = '/api/v1.1/servers/%d/meta/%s' % (self.test_server_id, key)
310 309
        response = self.client.put(path, json.dumps(request), content_type='application/json')
311 310
        self.assertEqual(response.status_code, 201)
......
375 374
        self.callable = callable
376 375
        self.args = args
377 376
        self.kwargs = kwargs
378
    
377

  
379 378
    def __enter__(self):
380 379
        self.value = self.callable(*self.args, **self.kwargs)
381 380
        return self.value
382
    
381

  
383 382
    def __exit__(self, type, value, tb):
384 383
        assert self.value == self.callable(*self.args, **self.kwargs)
385 384

  
......
392 391
    SERVER_METADATA = 0
393 392
    IMAGE_METADATA = 0
394 393
    NETWORKS = 0
395
    
394

  
396 395
    def setUp(self):
397 396
        self.client = AaiClient()
398 397
        create_users(self.USERS)
......
402 401
        create_servers(self.SERVERS)
403 402
        create_server_metadata(self.SERVER_METADATA)
404 403
        create_networks(self.NETWORKS)
405
    
404

  
406 405
    def assertFault(self, response, status_code, name):
407 406
        self.assertEqual(response.status_code, status_code)
408 407
        fault = json.loads(response.content)
409 408
        self.assertEqual(fault.keys(), [name])
410
    
409

  
411 410
    def assertBadRequest(self, response):
412 411
        self.assertFault(response, 400, 'badRequest')
413 412

  
414 413
    def assertItemNotFound(self, response):
415 414
        self.assertFault(response, 404, 'itemNotFound')
416
    
417
    
415

  
416

  
418 417
    def list_images(self, detail=False):
419 418
        path = '/api/v1.1/images'
420 419
        if detail:
......
425 424
        self.assertEqual(reply.keys(), ['images'])
426 425
        self.assertEqual(reply['images'].keys(), ['values'])
427 426
        return reply['images']['values']
428
    
427

  
429 428
    def list_metadata(self, path):
430 429
        response = self.client.get(path)
431 430
        self.assertTrue(response.status_code in (200, 203))
......
433 432
        self.assertEqual(reply.keys(), ['metadata'])
434 433
        self.assertEqual(reply['metadata'].keys(), ['values'])
435 434
        return reply['metadata']['values']
436
    
435

  
437 436
    def list_server_metadata(self, server_id):
438 437
        path = '/api/v1.1/servers/%d/meta' % server_id
439 438
        return self.list_metadata(path)
440
    
439

  
441 440
    def list_image_metadata(self, image_id):
442 441
        path = '/api/v1.1/images/%d/meta' % image_id
443 442
        return self.list_metadata(path)
444
    
443

  
445 444
    def update_metadata(self, path, metadata):
446 445
        data = json.dumps({'metadata': metadata})
447 446
        response = self.client.post(path, data, content_type='application/json')
......
449 448
        reply = json.loads(response.content)
450 449
        self.assertEqual(reply.keys(), ['metadata'])
451 450
        return reply['metadata']
452
    
451

  
453 452
    def update_server_metadata(self, server_id, metadata):
454 453
        path = '/api/v1.1/servers/%d/meta' % server_id
455 454
        return self.update_metadata(path, metadata)
456
    
455

  
457 456
    def update_image_metadata(self, image_id, metadata):
458 457
        path = '/api/v1.1/images/%d/meta' % image_id
459 458
        return self.update_metadata(path, metadata)
460
    
459

  
461 460
    def create_server_meta(self, server_id, meta):
462 461
        key = meta.keys()[0]
463 462
        path = '/api/v1.1/servers/%d/meta/%s' % (server_id, key)
......
468 467
        self.assertEqual(reply.keys(), ['meta'])
469 468
        response_meta = reply['meta']
470 469
        self.assertEqual(response_meta, meta)
471
    
470

  
472 471
    def get_all_server_metadata(self):
473 472
        metadata = defaultdict(dict)
474 473
        for m in VirtualMachineMetadata.objects.all():
475 474
            metadata[m.vm.id][m.meta_key] = m.meta_value
476 475
        return metadata
477
    
476

  
478 477
    def get_all_image_metadata(self):
479 478
        metadata = defaultdict(dict)
480 479
        for m in ImageMetadata.objects.all():
481 480
            metadata[m.image.id][m.meta_key] = m.meta_value
482 481
        return metadata
483
    
482

  
484 483
    def list_networks(self, detail=False):
485 484
        path = '/api/v1.1/networks'
486 485
        if detail:
......
491 490
        self.assertEqual(reply.keys(), ['networks'])
492 491
        self.assertEqual(reply['networks'].keys(), ['values'])
493 492
        return reply['networks']['values']
494
    
493

  
495 494
    def create_network(self, name):
496 495
        path = '/api/v1.1/networks'
497 496
        data = json.dumps({'network': {'name': name}})
......
500 499
        reply = json.loads(response.content)
501 500
        self.assertEqual(reply.keys(), ['network'])
502 501
        return reply
503
    
502

  
504 503
    def get_network_details(self, network_id):
505 504
        path = '/api/v1.1/networks/%d' % network_id
506 505
        response = self.client.get(path)
......
508 507
        reply = json.loads(response.content)
509 508
        self.assertEqual(reply.keys(), ['network'])
510 509
        return reply['network']
511
    
510

  
512 511
    def update_network_name(self, network_id, new_name):
513 512
        path = '/api/v1.1/networks/%d' % network_id
514 513
        data = json.dumps({'network': {'name': new_name}})
515 514
        response = self.client.put(path, data, content_type='application/json')
516 515
        self.assertEqual(response.status_code, 204)
517
    
516

  
518 517
    def delete_network(self, network_id):
519 518
        path = '/api/v1.1/networks/%d' % network_id
520 519
        response = self.client.delete(path)
521 520
        self.assertEqual(response.status_code, 204)
522
    
521

  
523 522
    def add_to_network(self, network_id, server_id):
524 523
        path = '/api/v1.1/networks/%d/action' % network_id
525 524
        data = json.dumps({'add': {'serverRef': server_id}})
526 525
        response = self.client.post(path, data, content_type='application/json')
527 526
        self.assertEqual(response.status_code, 202)
528
    
527

  
529 528
    def remove_from_network(self, network_id, server_id):
530 529
        path = '/api/v1.1/networks/%d/action' % network_id
531 530
        data = json.dumps({'remove': {'serverRef': server_id}})
......
535 534

  
536 535
def popdict(l, **kwargs):
537 536
    """Pops a dict from list `l` based on the predicates given as `kwargs`."""
538
    
537

  
539 538
    for i in range(len(l)):
540 539
        item = l[i]
541 540
        match = True
......
551 550

  
552 551
class ListImages(BaseTestCase):
553 552
    IMAGES = 10
554
    
553

  
555 554
    def test_list_images(self):
556 555
        images = self.list_images()
557 556
        keys = set(['id', 'name'])
......
562 561
            self.assertEqual(image['id'], img.id)
563 562
            self.assertEqual(image['name'], img.name)
564 563
        self.assertEqual(images, [])
565
    
564

  
566 565
    def test_list_images_detail(self):
567 566
        images = self.list_images(detail=True)
568 567
        keys = set(['id', 'name', 'updated', 'created', 'status', 'progress'])
......
580 579
class ListServerMetadata(BaseTestCase):
581 580
    SERVERS = 5
582 581
    SERVER_METADATA = 100
583
    
582

  
584 583
    def test_list_metadata(self):
585 584
        with AssertInvariant(self.get_all_server_metadata) as metadata:
586 585
            for vm in VirtualMachine.objects.all():
587 586
                response_metadata = self.list_server_metadata(vm.id)
588 587
                self.assertEqual(response_metadata, metadata[vm.id])
589
    
588

  
590 589
    def test_invalid_server(self):
591 590
        with AssertInvariant(self.get_all_server_metadata):
592 591
            response = self.client.get('/api/v1.1/servers/0/meta')
......
595 594

  
596 595
class UpdateServerMetadata(BaseTestCase):
597 596
    SERVER_METADATA = 10
598
    
597

  
599 598
    def test_update_metadata(self):
600 599
        metadata = self.get_all_server_metadata()
601 600
        server_id = choice(metadata.keys())
......
606 605
        self.assertEqual(response_metadata, new_metadata)
607 606
        metadata[server_id].update(new_metadata)
608 607
        self.assertEqual(metadata, self.get_all_server_metadata())
609
    
608

  
610 609
    def test_does_not_create(self):
611 610
        with AssertInvariant(self.get_all_server_metadata) as metadata:
612 611
            server_id = choice(metadata.keys())
613 612
            new_metadata = {'Foo': 'Bar'}
614 613
            response_metadata = self.update_server_metadata(server_id, new_metadata)
615 614
            self.assertEqual(response_metadata, {})
616
    
615

  
617 616
    def test_invalid_data(self):
618 617
        with AssertInvariant(self.get_all_server_metadata) as metadata:
619 618
            server_id = choice(metadata.keys())
620 619
            path = '/api/v1.1/servers/%d/meta' % server_id
621 620
            response = self.client.post(path, 'metadata', content_type='application/json')
622 621
            self.assertBadRequest(response)
623
    
622

  
624 623
    def test_invalid_server(self):
625 624
        with AssertInvariant(self.get_all_server_metadata):
626 625
            path = '/api/v1.1/servers/0/meta'
......
632 631
class GetServerMetadataItem(BaseTestCase):
633 632
    SERVERS = 5
634 633
    SERVER_METADATA = 100
635
    
634

  
636 635
    def test_get_metadata_item(self):
637 636
        with AssertInvariant(self.get_all_server_metadata) as metadata:
638 637
            server_id = choice(metadata.keys())
......
642 641
            self.assertTrue(response.status_code in (200, 203))
643 642
            reply = json.loads(response.content)
644 643
            self.assertEqual(reply['meta'], {key: metadata[server_id][key]})
645
    
644

  
646 645
    def test_invalid_key(self):
647 646
        with AssertInvariant(self.get_all_server_metadata) as metadata:
648 647
            server_id = choice(metadata.keys())
649 648
            response = self.client.get('/api/v1.1/servers/%d/meta/foo' % server_id)
650 649
            self.assertItemNotFound(response)
651
    
650

  
652 651
    def test_invalid_server(self):
653 652
        with AssertInvariant(self.get_all_server_metadata):
654 653
            response = self.client.get('/api/v1.1/servers/0/meta/foo')
......
657 656

  
658 657
class CreateServerMetadataItem(BaseTestCase):
659 658
    SERVER_METADATA = 10
660
    
659

  
661 660
    def test_create_metadata(self):
662 661
        metadata = self.get_all_server_metadata()
663 662
        server_id = choice(metadata.keys())
......
665 664
        self.create_server_meta(server_id, meta)
666 665
        metadata[server_id].update(meta)
667 666
        self.assertEqual(metadata, self.get_all_server_metadata())
668
    
667

  
669 668
    def test_update_metadata(self):
670 669
        metadata = self.get_all_server_metadata()
671 670
        server_id = choice(metadata.keys())
......
674 673
        self.create_server_meta(server_id, meta)
675 674
        metadata[server_id].update(meta)
676 675
        self.assertEqual(metadata, self.get_all_server_metadata())
677
    
676

  
678 677
    def test_invalid_server(self):
679 678
        with AssertInvariant(self.get_all_server_metadata):
680 679
            path = '/api/v1.1/servers/0/meta/foo'
681 680
            data = json.dumps({'meta': {'foo': 'bar'}})
682 681
            response = self.client.put(path, data, content_type='application/json')
683 682
            self.assertItemNotFound(response)
684
    
683

  
685 684
    def test_invalid_key(self):
686 685
        with AssertInvariant(self.get_all_server_metadata) as metadata:
687 686
            server_id = choice(metadata.keys())
......
689 688
            data = json.dumps({'meta': {'foo': 'bar'}})
690 689
            response = self.client.put(path, data, content_type='application/json')
691 690
            self.assertBadRequest(response)
692
    
691

  
693 692
    def test_invalid_data(self):
694 693
        with AssertInvariant(self.get_all_server_metadata) as metadata:
695 694
            server_id = choice(metadata.keys())
......
700 699

  
701 700
class DeleteServerMetadataItem(BaseTestCase):
702 701
    SERVER_METADATA = 10
703
    
702

  
704 703
    def test_delete_metadata(self):
705 704
        metadata = self.get_all_server_metadata()
706 705
        server_id = choice(metadata.keys())
......
710 709
        self.assertEqual(response.status_code, 204)
711 710
        metadata[server_id].pop(key)
712 711
        self.assertEqual(metadata, self.get_all_server_metadata())
713
    
712

  
714 713
    def test_invalid_server(self):
715 714
        with AssertInvariant(self.get_all_server_metadata):
716 715
            response = self.client.delete('/api/v1.1/servers/9/meta/Key1')
717 716
            self.assertItemNotFound(response)
718
    
717

  
719 718
    def test_invalid_key(self):
720 719
        with AssertInvariant(self.get_all_server_metadata) as metadata:
721 720
            server_id = choice(metadata.keys())
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff