Revision 64938cb0
b/api/actions.py | ||
---|---|---|
10 | 10 |
from django.utils import simplejson as json |
11 | 11 |
|
12 | 12 |
from synnefo.api.faults import BadRequest, ServiceUnavailable |
13 |
from synnefo.api.util import random_password, get_vm, get_nic
|
|
13 |
from synnefo.api.util import random_password, get_vm |
|
14 | 14 |
from synnefo.util.vapclient import request_forwarding as request_vnc_forwarding |
15 |
from synnefo.logic.backend import (reboot_instance, startup_instance, shutdown_instance, |
|
16 |
get_instance_console) |
|
15 |
from synnefo.logic import backend |
|
17 | 16 |
from synnefo.logic.utils import get_rsapi_state |
18 | 17 |
|
19 | 18 |
|
... | ... | |
76 | 75 |
reboot_type = args.get('type', '') |
77 | 76 |
if reboot_type not in ('SOFT', 'HARD'): |
78 | 77 |
raise BadRequest('Malformed Request.') |
79 |
reboot_instance(vm, reboot_type.lower()) |
|
78 |
backend.reboot_instance(vm, reboot_type.lower())
|
|
80 | 79 |
return HttpResponse(status=202) |
81 | 80 |
|
82 | 81 |
@server_action('start') |
... | ... | |
87 | 86 |
|
88 | 87 |
if args: |
89 | 88 |
raise BadRequest('Malformed Request.') |
90 |
startup_instance(vm) |
|
89 |
backend.startup_instance(vm)
|
|
91 | 90 |
return HttpResponse(status=202) |
92 | 91 |
|
93 | 92 |
@server_action('shutdown') |
... | ... | |
98 | 97 |
|
99 | 98 |
if args: |
100 | 99 |
raise BadRequest('Malformed Request.') |
101 |
shutdown_instance(vm) |
|
100 |
backend.shutdown_instance(vm)
|
|
102 | 101 |
return HttpResponse(status=202) |
103 | 102 |
|
104 | 103 |
@server_action('rebuild') |
... | ... | |
196 | 195 |
if settings.TEST: |
197 | 196 |
console_data = {'kind': 'vnc', 'host': 'ganeti_node', 'port': 1000} |
198 | 197 |
else: |
199 |
console_data = get_instance_console(vm) |
|
198 |
console_data = backend.get_instance_console(vm)
|
|
200 | 199 |
|
201 | 200 |
if console_data['kind'] != 'vnc': |
202 | 201 |
raise ServiceUnavailable('Could not create a console of requested type.') |
... | ... | |
251 | 250 |
if not server_id: |
252 | 251 |
raise BadRequest('Malformed Request.') |
253 | 252 |
vm = get_vm(server_id, request.user) |
254 |
vm.nics.create(network=net)
|
|
253 |
backend.connect_to_network(vm, net)
|
|
255 | 254 |
vm.save() |
256 | 255 |
net.save() |
257 | 256 |
return HttpResponse(status=202) |
... | ... | |
271 | 270 |
if not server_id: |
272 | 271 |
raise BadRequest('Malformed Request.') |
273 | 272 |
vm = get_vm(server_id, request.user) |
274 |
nic = get_nic(vm, net) |
|
275 |
nic.delete() |
|
273 |
backend.disconnect_from_network(vm, net) |
|
276 | 274 |
vm.save() |
277 | 275 |
net.save() |
278 | 276 |
return HttpResponse(status=202) |
b/api/faults.py | ||
---|---|---|
28 | 28 |
class BuildInProgress(Fault): |
29 | 29 |
code = 409 |
30 | 30 |
|
31 |
class OverLimit(Fault): |
|
32 |
code = 413 |
|
33 |
|
|
31 | 34 |
class ServiceUnavailable(Fault): |
32 | 35 |
code = 503 |
b/api/networks.py | ||
---|---|---|
1 | 1 |
from synnefo.api.actions import network_actions |
2 | 2 |
from synnefo.api.common import method_not_allowed |
3 |
from synnefo.api.faults import BadRequest, Unauthorized |
|
3 |
from synnefo.api.faults import BadRequest, OverLimit, Unauthorized
|
|
4 | 4 |
from synnefo.api.util import (isoformat, isoparse, get_network, |
5 | 5 |
get_request_dict, api_method) |
6 | 6 |
from synnefo.db.models import Network |
7 |
from synnefo.logic import backend |
|
7 | 8 |
|
8 | 9 |
from django.conf.urls.defaults import patterns |
9 | 10 |
from django.db.models import Q |
... | ... | |
104 | 105 |
raise BadRequest('Malformed request.') |
105 | 106 |
|
106 | 107 |
network = Network.objects.create(name=name, owner=request.user, state='ACTIVE') |
108 |
if not backend.create_network(network): |
|
109 |
network.delete() |
|
110 |
raise OverLimit('Maximum number of networks reached.') |
|
111 |
|
|
107 | 112 |
networkdict = network_to_dict(network) |
108 | 113 |
return render_network(request, networkdict, status=202) |
109 | 114 |
|
... | ... | |
157 | 162 |
if network_id in ('1', 'public'): |
158 | 163 |
raise Unauthorized('Can not delete the public network.') |
159 | 164 |
net = get_network(network_id, request.user) |
160 |
net.nics.all().delete() |
|
161 |
for vm in net.machines.all(): |
|
162 |
vm.save() |
|
163 |
net.state = 'DELETED' |
|
164 |
net.save() |
|
165 |
backend.delete_network(net) |
|
165 | 166 |
return HttpResponse(status=204) |
166 | 167 |
|
167 | 168 |
@api_method('POST') |
b/api/servers.py | ||
---|---|---|
77 | 77 |
'mac': nic.mac, |
78 | 78 |
'firewallProfile': nic.firewall_profile} |
79 | 79 |
if nic.ipv4 or nic.ipv6: |
80 |
d['values'] = [{'version': 4, 'addr': nic.ipv4}, {'version': 6, 'addr': nic.ipv6}] |
|
80 |
d['values'] = [ |
|
81 |
{'version': 4, 'addr': nic.ipv4 or ''}, |
|
82 |
{'version': 6, 'addr': nic.ipv6 or ''}] |
|
81 | 83 |
return d |
82 | 84 |
|
83 | 85 |
def metadata_to_dict(vm): |
b/api/tests.py | ||
---|---|---|
895 | 895 |
|
896 | 896 |
class GetNetworkDetails(BaseTestCase): |
897 | 897 |
SERVERS = 5 |
898 |
NETWORKS = 1 |
|
899 |
|
|
898 |
|
|
900 | 899 |
def test_get_network_details(self): |
900 |
name = 'net' |
|
901 |
self.create_network(name) |
|
902 |
|
|
901 | 903 |
servers = VirtualMachine.objects.all() |
902 |
network = Network.objects.all()[0]
|
|
904 |
network = Network.objects.all()[1]
|
|
903 | 905 |
|
904 | 906 |
net = self.get_network_details(network.id) |
905 |
self.assertEqual(net['name'], network.name)
|
|
907 |
self.assertEqual(net['name'], name) |
|
906 | 908 |
self.assertEqual(net['servers']['values'], []) |
907 | 909 |
|
908 | 910 |
server_id = choice(servers).id |
909 | 911 |
self.add_to_network(network.id, server_id) |
910 | 912 |
net = self.get_network_details(network.id) |
911 | 913 |
self.assertEqual(net['name'], network.name) |
912 |
self.assertEqual(net['servers']['values'], [server_id]) |
|
913 | 914 |
|
914 | 915 |
|
915 | 916 |
class UpdateNetworkName(BaseTestCase): |
... | ... | |
930 | 931 |
|
931 | 932 |
|
932 | 933 |
class DeleteNetwork(BaseTestCase): |
933 |
NETWORKS = 5 |
|
934 |
|
|
935 | 934 |
def test_delete_network(self): |
935 |
for i in range(5): |
|
936 |
self.create_network('net%d' % i) |
|
937 |
|
|
936 | 938 |
networks = self.list_networks() |
937 | 939 |
network = choice(networks) |
938 | 940 |
while network['id'] == 1: |
... | ... | |
949 | 951 |
|
950 | 952 |
class NetworkActions(BaseTestCase): |
951 | 953 |
SERVERS = 20 |
952 |
NETWORKS = 1 |
|
953 | 954 |
|
954 | 955 |
def test_add_remove_server(self): |
956 |
self.create_network('net') |
|
957 |
|
|
955 | 958 |
server_ids = [vm.id for vm in VirtualMachine.objects.all()] |
956 |
network = self.list_networks(detail=True)[0]
|
|
959 |
network = self.list_networks(detail=True)[1]
|
|
957 | 960 |
network_id = network['id'] |
958 | 961 |
|
959 | 962 |
to_add = set(sample(server_ids, 10)) |
960 | 963 |
for server_id in to_add: |
961 | 964 |
self.add_to_network(network_id, server_id) |
962 |
net = self.get_network_details(network_id) |
|
963 |
self.assertTrue(server_id in net['servers']['values']) |
|
964 |
|
|
965 |
net = self.get_network_details(network_id) |
|
966 |
self.assertEqual(set(net['servers']['values']), to_add) |
|
967 |
|
|
965 |
|
|
968 | 966 |
to_remove = set(sample(to_add, 5)) |
969 | 967 |
for server_id in to_remove: |
970 | 968 |
self.remove_from_network(network_id, server_id) |
971 |
net = self.get_network_details(network_id) |
|
972 |
self.assertTrue(server_id not in net['servers']['values']) |
|
973 |
|
|
974 |
net = self.get_network_details(network_id) |
|
975 |
self.assertEqual(set(net['servers']['values']), to_add - to_remove) |
|
976 |
|
b/db/migrations/0006_auto__add_networklink.py | ||
---|---|---|
1 |
# encoding: utf-8 |
|
2 |
import datetime |
|
3 |
from south.db import db |
|
4 |
from south.v2 import SchemaMigration |
|
5 |
from django.db import models |
|
6 |
|
|
7 |
class Migration(SchemaMigration): |
|
8 |
|
|
9 |
def forwards(self, orm): |
|
10 |
|
|
11 |
# Adding model 'NetworkLink' |
|
12 |
db.create_table('db_networklink', ( |
|
13 |
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
|
14 |
('network', self.gf('django.db.models.fields.related.OneToOneField')(related_name='link', unique=True, null=True, to=orm['db.Network'])), |
|
15 |
('index', self.gf('django.db.models.fields.IntegerField')()), |
|
16 |
('name', self.gf('django.db.models.fields.CharField')(max_length=255)), |
|
17 |
('available', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), |
|
18 |
)) |
|
19 |
db.send_create_signal('db', ['NetworkLink']) |
|
20 |
|
|
21 |
|
|
22 |
def backwards(self, orm): |
|
23 |
|
|
24 |
# Deleting model 'NetworkLink' |
|
25 |
db.delete_table('db_networklink') |
|
26 |
|
|
27 |
|
|
28 |
models = { |
|
29 |
'db.debit': { |
|
30 |
'Meta': {'object_name': 'Debit'}, |
|
31 |
'description': ('django.db.models.fields.TextField', [], {}), |
|
32 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
33 |
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']"}), |
|
34 |
'vm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.VirtualMachine']"}), |
|
35 |
'when': ('django.db.models.fields.DateTimeField', [], {}) |
|
36 |
}, |
|
37 |
'db.disk': { |
|
38 |
'Meta': {'object_name': 'Disk'}, |
|
39 |
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
|
40 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
41 |
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
|
42 |
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']", 'null': 'True', 'blank': 'True'}), |
|
43 |
'size': ('django.db.models.fields.PositiveIntegerField', [], {}), |
|
44 |
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), |
|
45 |
'vm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.VirtualMachine']", 'null': 'True', 'blank': 'True'}) |
|
46 |
}, |
|
47 |
'db.flavor': { |
|
48 |
'Meta': {'unique_together': "(('cpu', 'ram', 'disk'),)", 'object_name': 'Flavor'}, |
|
49 |
'cpu': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
|
50 |
'disk': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
|
51 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
52 |
'ram': ('django.db.models.fields.IntegerField', [], {'default': '0'}) |
|
53 |
}, |
|
54 |
'db.flavorcost': { |
|
55 |
'Meta': {'object_name': 'FlavorCost'}, |
|
56 |
'cost_active': ('django.db.models.fields.PositiveIntegerField', [], {}), |
|
57 |
'cost_inactive': ('django.db.models.fields.PositiveIntegerField', [], {}), |
|
58 |
'effective_from': ('django.db.models.fields.DateTimeField', [], {}), |
|
59 |
'flavor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.Flavor']"}), |
|
60 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) |
|
61 |
}, |
|
62 |
'db.image': { |
|
63 |
'Meta': {'object_name': 'Image'}, |
|
64 |
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
|
65 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
66 |
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
|
67 |
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']", 'null': 'True', 'blank': 'True'}), |
|
68 |
'sourcevm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.VirtualMachine']", 'null': 'True'}), |
|
69 |
'state': ('django.db.models.fields.CharField', [], {'max_length': '30'}), |
|
70 |
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
|
71 |
}, |
|
72 |
'db.imagemetadata': { |
|
73 |
'Meta': {'object_name': 'ImageMetadata'}, |
|
74 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
75 |
'image': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.Image']"}), |
|
76 |
'meta_key': ('django.db.models.fields.CharField', [], {'max_length': '50'}), |
|
77 |
'meta_value': ('django.db.models.fields.CharField', [], {'max_length': '500'}) |
|
78 |
}, |
|
79 |
'db.limit': { |
|
80 |
'Meta': {'object_name': 'Limit'}, |
|
81 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
82 |
'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}), |
|
83 |
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']"}), |
|
84 |
'value': ('django.db.models.fields.IntegerField', [], {}) |
|
85 |
}, |
|
86 |
'db.network': { |
|
87 |
'Meta': {'object_name': 'Network'}, |
|
88 |
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
|
89 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
90 |
'machines': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['db.VirtualMachine']", 'through': "orm['db.NetworkInterface']", 'symmetrical': 'False'}), |
|
91 |
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
|
92 |
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']", 'null': 'True'}), |
|
93 |
'public': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), |
|
94 |
'state': ('django.db.models.fields.CharField', [], {'max_length': '30'}), |
|
95 |
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
|
96 |
}, |
|
97 |
'db.networkinterface': { |
|
98 |
'Meta': {'object_name': 'NetworkInterface'}, |
|
99 |
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
|
100 |
'firewall_profile': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}), |
|
101 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
102 |
'index': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), |
|
103 |
'ipv4': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True'}), |
|
104 |
'ipv6': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), |
|
105 |
'mac': ('django.db.models.fields.CharField', [], {'max_length': '17', 'null': 'True'}), |
|
106 |
'machine': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nics'", 'to': "orm['db.VirtualMachine']"}), |
|
107 |
'network': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nics'", 'to': "orm['db.Network']"}), |
|
108 |
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
|
109 |
}, |
|
110 |
'db.networklink': { |
|
111 |
'Meta': {'object_name': 'NetworkLink'}, |
|
112 |
'available': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), |
|
113 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
114 |
'index': ('django.db.models.fields.IntegerField', [], {}), |
|
115 |
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
|
116 |
'network': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'link'", 'unique': 'True', 'null': 'True', 'to': "orm['db.Network']"}) |
|
117 |
}, |
|
118 |
'db.synnefouser': { |
|
119 |
'Meta': {'object_name': 'SynnefoUser'}, |
|
120 |
'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), |
|
121 |
'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
|
122 |
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
|
123 |
'credit': ('django.db.models.fields.IntegerField', [], {}), |
|
124 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
125 |
'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), |
|
126 |
'realname': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), |
|
127 |
'type': ('django.db.models.fields.CharField', [], {'max_length': '30'}), |
|
128 |
'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), |
|
129 |
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
|
130 |
}, |
|
131 |
'db.virtualmachine': { |
|
132 |
'Meta': {'object_name': 'VirtualMachine'}, |
|
133 |
'action': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}), |
|
134 |
'backendjobid': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), |
|
135 |
'backendjobstatus': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}), |
|
136 |
'backendlogmsg': ('django.db.models.fields.TextField', [], {'null': 'True'}), |
|
137 |
'backendopcode': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}), |
|
138 |
'charged': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 5, 29, 12, 32, 13, 412198)'}), |
|
139 |
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
|
140 |
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), |
|
141 |
'flavor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.Flavor']"}), |
|
142 |
'hostid': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
|
143 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
144 |
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
|
145 |
'operstate': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}), |
|
146 |
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']"}), |
|
147 |
'sourceimage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.Image']"}), |
|
148 |
'suspended': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), |
|
149 |
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
|
150 |
}, |
|
151 |
'db.virtualmachinegroup': { |
|
152 |
'Meta': {'object_name': 'VirtualMachineGroup'}, |
|
153 |
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
|
154 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
155 |
'machines': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['db.VirtualMachine']", 'symmetrical': 'False'}), |
|
156 |
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
|
157 |
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']"}), |
|
158 |
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
|
159 |
}, |
|
160 |
'db.virtualmachinemetadata': { |
|
161 |
'Meta': {'object_name': 'VirtualMachineMetadata'}, |
|
162 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
163 |
'meta_key': ('django.db.models.fields.CharField', [], {'max_length': '50'}), |
|
164 |
'meta_value': ('django.db.models.fields.CharField', [], {'max_length': '500'}), |
|
165 |
'vm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.VirtualMachine']"}) |
|
166 |
} |
|
167 |
} |
|
168 |
|
|
169 |
complete_apps = ['db'] |
b/db/models.py | ||
---|---|---|
401 | 401 |
ipv4 = models.IPAddressField(null=True) |
402 | 402 |
ipv6 = models.CharField(max_length=100, null=True) |
403 | 403 |
firewall_profile = models.CharField(choices=FIREWALL_PROFILES, max_length=30, null=True) |
404 |
|
|
405 |
|
|
406 |
class NetworkLink(models.Model): |
|
407 |
network = models.OneToOneField(Network, null=True, related_name='link') |
|
408 |
index = models.IntegerField() |
|
409 |
name = models.CharField(max_length=255) |
|
410 |
available = models.BooleanField(default=True) |
b/logic/backend.py | ||
---|---|---|
5 | 5 |
# |
6 | 6 |
|
7 | 7 |
from django.conf import settings |
8 |
from synnefo.db.models import VirtualMachine |
|
8 |
from synnefo.db.models import VirtualMachine, Network, NetworkLink
|
|
9 | 9 |
from synnefo.logic import utils |
10 | 10 |
from synnefo.util.rapi import GanetiRapiClient |
11 | 11 |
|
... | ... | |
52 | 52 |
detailing the NIC configuration of a VM instance. |
53 | 53 |
|
54 | 54 |
Update the state of the VM in the DB accordingly. |
55 |
|
|
56 | 55 |
""" |
57 |
|
|
58 |
# For the time being, we can only update the ipfour field, |
|
59 |
# based on the IPv4 address of the first NIC |
|
60 |
if len(nics) > 0: |
|
61 |
ipv4 = nics[0]['ip'] |
|
62 |
if ipv4 == '': |
|
63 |
ipv4 = '0.0.0.0' |
|
64 |
vm.ipfour = ipv4 |
|
56 |
|
|
57 |
vm.nics.all().delete() |
|
58 |
for i, nic in enumerate(nics): |
|
59 |
if i == 0: |
|
60 |
net = Network.objects.filter(public=True)[0] |
|
61 |
else: |
|
62 |
link = NetworkLink.objects.get(name=nic['link']) |
|
63 |
net = link.network |
|
64 |
|
|
65 |
vm.nics.create( |
|
66 |
network=net, |
|
67 |
index=i, |
|
68 |
mac=nic.get('mac', ''), |
|
69 |
ipv4=nic.get('ip', '')) |
|
65 | 70 |
vm.save() |
66 | 71 |
|
67 | 72 |
|
... | ... | |
96 | 101 |
|
97 | 102 |
def create_instance(vm, flavor, password): |
98 | 103 |
# FIXME: `password` must be passed to the Ganeti OS provider via CreateInstance() |
104 |
|
|
105 |
nic = {'ip': 'pool', 'mode': 'routed', 'link': settings.GANETI_PUBLIC_LINK} |
|
106 |
|
|
99 | 107 |
return rapi.CreateInstance( |
100 | 108 |
mode='create', |
101 | 109 |
name=vm.backend_id, |
102 | 110 |
disk_template='plain', |
103 | 111 |
disks=[{"size": 2000}], #FIXME: Always ask for a 2GB disk for now |
104 |
nics=[{}],
|
|
112 |
nics=[nic],
|
|
105 | 113 |
os='debootstrap+default', #TODO: select OS from imageRef |
106 | 114 |
ip_check=False, |
107 | 115 |
name_check=False, |
... | ... | |
111 | 119 |
|
112 | 120 |
def delete_instance(vm): |
113 | 121 |
start_action(vm, 'DESTROY') |
114 |
rapi.DeleteInstance(vm.backend_id) |
|
122 |
rapi.DeleteInstance(vm.backend_id, dry_run=settings.TEST)
|
|
115 | 123 |
vm.nics.all().delete() |
116 | 124 |
|
117 | 125 |
|
118 | 126 |
def reboot_instance(vm, reboot_type): |
119 | 127 |
assert reboot_type in ('soft', 'hard') |
120 |
rapi.RebootInstance(vm.backend_id, reboot_type) |
|
128 |
rapi.RebootInstance(vm.backend_id, reboot_type, dry_run=settings.TEST)
|
|
121 | 129 |
|
122 | 130 |
|
123 | 131 |
def startup_instance(vm): |
124 | 132 |
start_action(vm, 'START') |
125 |
rapi.StartupInstance(vm.backend_id) |
|
133 |
rapi.StartupInstance(vm.backend_id, dry_run=settings.TEST)
|
|
126 | 134 |
|
127 | 135 |
|
128 | 136 |
def shutdown_instance(vm): |
129 | 137 |
start_action(vm, 'STOP') |
130 |
rapi.ShutdownInstance(vm.backend_id) |
|
138 |
rapi.ShutdownInstance(vm.backend_id, dry_run=settings.TEST)
|
|
131 | 139 |
|
132 | 140 |
|
133 | 141 |
def get_instance_console(vm): |
134 | 142 |
return rapi.GetInstanceConsole(vm.backend_id) |
143 |
|
|
144 |
|
|
145 |
def create_network_link(): |
|
146 |
try: |
|
147 |
last = NetworkLink.objects.order_by('-index')[0] |
|
148 |
index = last.index + 1 |
|
149 |
except IndexError: |
|
150 |
index = 1 |
|
151 |
|
|
152 |
if index <= settings.GANETI_MAX_LINK_NUMBER: |
|
153 |
name = '%s%d' % (settings.GANETI_LINK_PREFIX, index) |
|
154 |
return NetworkLink.objects.create(index=index, name=name, available=True) |
|
155 |
return None # All link slots are filled |
|
156 |
|
|
157 |
def create_network(net): |
|
158 |
try: |
|
159 |
link = NetworkLink.objects.filter(available=True)[0] |
|
160 |
except IndexError: |
|
161 |
link = create_network_link() |
|
162 |
if not link: |
|
163 |
return False |
|
164 |
link.network = net |
|
165 |
link.available = False |
|
166 |
link.save() |
|
167 |
return True |
|
168 |
|
|
169 |
def delete_network(net): |
|
170 |
link = net.link |
|
171 |
link.available = True |
|
172 |
link.netowrk = False |
|
173 |
link.save() |
|
174 |
|
|
175 |
for vm in net.machines.all(): |
|
176 |
disconnect_from_network(vm, net) |
|
177 |
vm.save() |
|
178 |
net.state = 'DELETED' |
|
179 |
net.save() |
|
180 |
|
|
181 |
def connect_to_network(vm, net): |
|
182 |
nic = {'mode': 'bridged', 'link': net.link.name} |
|
183 |
rapi.ModifyInstance(vm.backend_id, nics=[('add', nic)], dry_run=settings.TEST) |
|
184 |
|
|
185 |
def disconnect_from_network(vm, net): |
|
186 |
nics = vm.nics.order_by('index')[1:] # Skip the public network |
|
187 |
ops = [('remove', {})] * len(nics) |
|
188 |
for nic in nics: |
|
189 |
if nic.network == net: |
|
190 |
continue |
|
191 |
ops.append(('add', { |
|
192 |
'mode': 'bridged', |
|
193 |
'link': nic.network.link.name, |
|
194 |
'mac': nic.mac})) |
|
195 |
for op in ops: |
|
196 |
rapi.ModifyInstance(vm.backend_id, nics=[op], dry_run=settings.TEST) |
b/logic/tests.py | ||
---|---|---|
147 | 147 |
} |
148 | 148 |
vm = VirtualMachine.objects.get(pk=30000) |
149 | 149 |
backend.process_net_status(vm, msg['nics']) |
150 |
self.assertEquals(vm.ipfour, '192.168.33.1')
|
|
150 |
self.assertEquals(vm.nics.all()[0].ipv4, '192.168.33.1')
|
|
151 | 151 |
|
152 | 152 |
def test_set_empty_ipv4(self): |
153 | 153 |
"""Test reception of a net status notification with no IPv4 assigned""" |
... | ... | |
159 | 159 |
} |
160 | 160 |
vm = VirtualMachine.objects.get(pk=30000) |
161 | 161 |
backend.process_net_status(vm, msg['nics']) |
162 |
self.assertEquals(vm.ipfour, '0.0.0.0') |
|
162 |
self.assertEquals(vm.nics.all()[0].ipv4, '') |
b/settings.py.dist | ||
---|---|---|
239 | 239 |
# work after this many hours after 2011/05/10 |
240 | 240 |
AUTH_TOKEN_DURATION = 30 * 24 |
241 | 241 |
|
242 |
GANETI_PUBLIC_LINK = 'snf_public' |
|
243 |
GANETI_LINK_PREFIX = 'prv' |
|
244 |
GANETI_MAX_LINK_NUMBER = 100 |
Also available in: Unified diff