0b20d79a9a1a34c2c339f90bbd9e73935f656f09
[plstackapi.git] / planetstack / openstack / manager.py
1 w
2 import os
3 #os.environ.setdefault("DJANGO_SETTINGS_MODULE", "planetstack.settings")
4 import string
5 import random
6 import hashlib
7 from datetime import datetime
8
9 from netaddr import IPAddress, IPNetwork
10 from planetstack import settings
11 from django.core import management
12 from core.models import * 
13 from planetstack.config import Config
14 try:
15     from openstack.client import OpenStackClient
16     from openstack.driver import OpenStackDriver
17     has_openstack = True
18 except:
19     has_openstack = False
20
21 manager_enabled = Config().api_nova_enabled
22
23
24 def random_string(size=6):
25     return ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(size))
26
27 def require_enabled(callable):
28     def wrapper(*args, **kwds):
29         if manager_enabled and has_openstack:
30             return callable(*args, **kwds)
31         else:
32             return None
33     return wrapper
34
35
36 class OpenStackManager:
37
38     def __init__(self, auth={}, caller=None):
39         self.client = None
40         self.driver = None
41         self.caller = None
42         self.has_openstack = has_openstack       
43         self.enabled = manager_enabled
44
45         if has_openstack and manager_enabled:
46             if auth:
47                 try:
48                     self.init_user(auth, caller)
49                 except:
50                     # if this fails then it meanse the caller doesn't have a
51                     # role at the slice's tenant. if the caller is an admin
52                     # just use the admin client/manager.
53                     if caller and caller.is_admin: 
54                         self.init_admin()
55                     else: raise
56             else:
57                 self.init_admin()
58
59     @require_enabled 
60     def init_caller(self, caller, tenant):
61         auth = {'username': caller.email,
62                 'password': hashlib.md5(caller.password).hexdigest()[:6],
63                 'tenant': tenant}
64         self.client = OpenStackClient(**auth)
65         self.driver = OpenStackDriver(client=self.client)
66         self.caller = caller                 
67     
68     @require_enabled
69     def init_admin(self, tenant=None):
70         # use the admin credentials 
71         self.client = OpenStackClient(tenant=tenant)
72         self.driver = OpenStackDriver(client=self.client)
73         self.caller = self.driver.admin_user
74         self.caller.kuser_id = self.caller.id 
75
76     @require_enabled
77     def save_role(self, role):
78         if not role.role:
79             keystone_role = self.driver.create_role(role.role_type)
80             role.role = keystone_role.id
81
82     @require_enabled
83     def delete_role(self, role):
84         if role.role:
85             self.driver.delete_role({'id': role.role})
86
87     @require_enabled
88     def save_key(self, key, name):
89         key_fields = {'name': name,
90                       'public_key': key}
91         nova_key = self.driver.create_keypair(**key_fields)
92
93     @require_enabled
94     def delete_key(self, key):
95         if key.nkey_id:
96             self.driver.delete_keypair(key.nkey_id)
97
98     @require_enabled
99     def save_user(self, user):
100         name = user.email[:user.email.find('@')]
101         user_fields = {'name': name,
102                        'email': user.email,
103                        'password': hashlib.md5(user.password).hexdigest()[:6],
104                        'enabled': True}
105         if not user.kuser_id:
106             keystone_user = self.driver.create_user(**user_fields)
107             user.kuser_id = keystone_user.id
108         else:
109             self.driver.update_user(user.kuser_id, user_fields)     
110
111         if user.site:
112             self.driver.add_user_role(user.kuser_id, user.site.tenant_id, 'user')
113             if user.is_admin:
114                 self.driver.add_user_role(user.kuser_id, user.site.tenant_id, 'admin')
115             else:
116                 # may have admin role so attempt to remove it
117                 self.driver.delete_user_role(user.kuser_id, user.site.tenant_id, 'admin')
118
119         if user.public_key:
120             self.init_caller(user, user.site.login_base)
121             self.save_key(user.public_key, user.keyname)
122             self.init_admin()
123
124         user.save()
125         user.enacted = datetime.now()
126         user.save(update_fields=['enacted'])
127   
128     @require_enabled
129     def delete_user(self, user):
130         if user.kuser_id:
131             self.driver.delete_user(user.kuser_id)        
132     
133     @require_enabled
134     def save_site(self, site, add_role=True):
135         if not site.tenant_id:
136             tenant = self.driver.create_tenant(tenant_name=site.login_base,
137                                                description=site.name,
138                                                enabled=site.enabled)
139             site.tenant_id = tenant.id
140             # give caller an admin role at the tenant they've created
141             self.driver.add_user_role(self.caller.kuser_id, tenant.id, 'admin')
142
143         # update the record
144         if site.id and site.tenant_id:
145             self.driver.update_tenant(site.tenant_id,
146                                       description=site.name,
147                                       enabled=site.enabled)
148
149         # commit the updated record
150         site.save()
151         site.enacted = datetime.now()
152         site.save(update_fields=['enacted']) # enusre enacted > updated  
153         
154
155     @require_enabled
156     def delete_site(self, site):
157         if site.tenant_id:
158             self.driver.delete_tenant(site.tenant_id)
159                
160     @require_enabled
161     def save_site_privilege(self, site_priv):
162         if site_priv.user.kuser_id and site_priv.site.tenant_id:
163             self.driver.add_user_role(site_priv.user.kuser_id,
164                                       site_priv.site.tenant_id,
165                                       site_priv.role.role_type)
166         site_priv.enacted = datetime.now()
167         site_priv.save(update_fields=['enacted'])
168
169     
170     @require_enabled
171     def delete_site_privilege(self, site_priv):
172         self.driver.delete_user_role(site_priv.user.kuser_id, 
173                                      site_priv.site.tenant_id, 
174                                      site_priv.role.role_type)
175
176     @require_enabled
177     def save_slice(self, slice):
178         if not slice.tenant_id:
179             nova_fields = {'tenant_name': slice.name,
180                    'description': slice.description,
181                    'enabled': slice.enabled}
182             tenant = self.driver.create_tenant(**nova_fields)
183             slice.tenant_id = tenant.id
184
185             # give caller an admin role at the tenant they've created
186             self.driver.add_user_role(self.caller.kuser_id, tenant.id, 'admin')
187
188             # refresh credentials using this tenant
189             self.driver.shell.connect(username=self.driver.shell.keystone.username,
190                                       password=self.driver.shell.keystone.password,
191                                       tenant=tenant.name)
192
193             # create network
194             network = self.driver.create_network(slice.name)
195             slice.network_id = network['id']
196
197             # create router
198             router = self.driver.create_router(slice.name)
199             slice.router_id = router['id']
200
201             # create subnet
202             next_subnet = self.get_next_subnet()
203             cidr = str(next_subnet.cidr)
204             ip_version = next_subnet.version
205             start = str(next_subnet[2])
206             end = str(next_subnet[-2]) 
207             subnet = self.driver.create_subnet(name=slice.name,
208                                                network_id = network['id'],
209                                                cidr_ip = cidr,
210                                                ip_version = ip_version,
211                                                start = start,
212                                                end = end)
213             slice.subnet_id = subnet['id']
214             # add subnet as interface to slice's router
215             self.driver.add_router_interface(router['id'], subnet['id'])
216             # add external route
217             self.driver.add_external_route(subnet)
218
219
220         if slice.id and slice.tenant_id:
221             self.driver.update_tenant(slice.tenant_id,
222                                       description=slice.description,
223                                       enabled=slice.enabled)   
224
225         slice.save()
226         slice.enacted = datetime.now()
227         slice.save(update_fields=['enacted']) 
228
229     @require_enabled
230     def delete_slice(self, slice):
231         if slice.tenant_id:
232             self._delete_slice(slice.tenant_id, slice.network_id, 
233                                slice.router_id, slice.subnet_id)
234     @require_enabled
235     def _delete_slice(self, tenant_id, network_id, router_id, subnet_id):
236         self.driver.delete_router_interface(slice.router_id, slice.subnet_id)
237         self.driver.delete_subnet(slice.subnet_id)
238         self.driver.delete_router(slice.router_id)
239         self.driver.delete_network(slice.network_id)
240         self.driver.delete_tenant(slice.tenant_id)
241         # delete external route
242         subnet = None
243         subnets = self.driver.shell.quantum.list_subnets()['subnets']
244         for snet in subnets:
245             if snet['id'] == slice.subnet_id:
246                 subnet = snet
247         if subnet:
248             self.driver.delete_external_route(subnet) 
249
250     
251     @require_enabled
252     def save_slice_membership(self, slice_memb):
253         if slice_memb.user.kuser_id and slice_memb.slice.tenant_id:
254             self.driver.add_user_role(slice_memb.user.kuser_id,
255                                       slice_memb.slice.tenant_id,
256                                       slice_memb.role.role_type)
257         slice_memb.enacted = datetime.now()
258         slice_memb.save(update_fields=['enacted'])
259
260
261     @require_enabled
262     def delete_slice_membership(self, slice_memb):
263         self.driver.delete_user_role(slice_memb.user.kuser_id,
264                                      slice_memb.slice.tenant_id,
265                                      slice_memb.role.role_type)
266
267
268     @require_enabled
269     def get_next_subnet(self):
270         # limit ourself to 10.0.x.x for now
271         valid_subnet = lambda net: net.startswith('10.0')  
272         subnets = self.driver.shell.quantum.list_subnets()['subnets']
273         ints = [int(IPNetwork(subnet['cidr']).ip) for subnet in subnets \
274                 if valid_subnet(subnet['cidr'])] 
275         ints.sort()
276         last_ip = IPAddress(ints[-1])
277         last_network = IPNetwork(str(last_ip) + "/24")
278         next_network = IPNetwork(str(IPAddress(last_network) + last_network.size) + "/24")
279         return next_network
280
281     @require_enabled
282     def save_subnet(self, subnet):    
283         if not subnet.subnet_id:
284             quantum_subnet = self.driver.create_subnet(name= subnet.slice.name,
285                                           network_id=subnet.slice.network_id,
286                                           cidr_ip = subnet.cidr,
287                                           ip_version=subnet.ip_version,
288                                           start = subnet.start,
289                                           end = subnet.end)
290             subnet.subnet_id = quantum_subnet['id']
291             # add subnet as interface to slice's router
292             self.driver.add_router_interface(subnet.slice.router_id, subnet.subnet_id)
293             #add_route = 'route add -net %s dev br-ex gw 10.100.0.5' % self.cidr
294             #commands.getstatusoutput(add_route)
295
296     
297     @require_enabled
298     def delete_subnet(self, subnet):
299         if subnet.subnet_id:
300             self.driver.delete_router_interface(subnet.slice.router_id, subnet.subnet_id)
301             self.driver.delete_subnet(subnet.subnet_id)
302             #del_route = 'route del -net %s' % self.cidr
303             #commands.getstatusoutput(del_route)
304
305     @require_enabled
306     def save_sliver(self, sliver):
307         if not sliver.instance_id:
308             slice_memberships = SliceMembership.objects.filter(slice=sliver.slice)
309             pubkeys = [sm.user.public_key for sm in slice_memberships if sm.user.public_key]
310             pubkeys.append(sliver.creator.public_key) 
311             instance = self.driver.spawn_instance(name=sliver.name,
312                                    key_name = sliver.creator.keyname,
313                                    image_id = sliver.image.image_id,
314                                    hostname = sliver.node.name,
315                                    pubkeys = pubkeys )
316             sliver.instance_id = instance.id
317             sliver.instance_name = getattr(instance, 'OS-EXT-SRV-ATTR:instance_name')
318
319         if sliver.instance_id and ("numberCores" in sliver.changed_fields):
320             self.driver.update_instance_metadata(sliver.instance_id, {"cpu_cores": str(sliver.numberCores)})
321
322         sliver.save()
323         sliver.enacted = datetime.now()
324         sliver.save(update_fields=['enacted'])
325
326     @require_enabled
327     def delete_sliver(self, sliver):
328         if sliver.instance_id:
329             self.driver.destroy_instance(sliver.instance_id) 
330     
331
332     def refresh_nodes(self):
333         # collect local nodes
334         nodes = Node.objects.all()
335         nodes_dict = {}
336         for node in nodes:
337             if 'viccidev10' not in node.name:
338                 nodes_dict[node.name] = node 
339         
340         deployment = Deployment.objects.filter(name='VICCI')[0]
341         login_bases = ['princeton', 'stanford', 'gt', 'uw', 'mpisws']
342         sites = Site.objects.filter(login_base__in=login_bases)
343         # collect nova nodes:
344         compute_nodes = self.client.nova.hypervisors.list()
345
346         compute_nodes_dict = {}
347         for compute_node in compute_nodes:
348             compute_nodes_dict[compute_node.hypervisor_hostname] = compute_node
349
350         # add new nodes:
351         new_node_names = set(compute_nodes_dict.keys()).difference(nodes_dict.keys())
352         i = 0
353         max = len(sites)
354         for name in new_node_names:
355             if i == max:
356                 i = 0
357             site = sites[i]
358             node = Node(name=compute_nodes_dict[name].hypervisor_hostname,
359                         site=site,
360                         deployment=deployment)
361             node.save()
362             i+=1
363
364         # remove old nodes
365         old_node_names = set(nodes_dict.keys()).difference(compute_nodes_dict.keys())
366         Node.objects.filter(name__in=old_node_names).delete()
367
368     def refresh_images(self):
369         from core.models.image import Image
370         # collect local images
371         images = Image.objects.all()
372         images_dict = {}    
373         for image in images:
374             images_dict[image.name] = image
375
376         # collect glance images
377         glance_images = self.client.glance.get_images()
378         glance_images_dict = {}
379         for glance_image in glance_images:
380             glance_images_dict[glance_image['name']] = glance_image
381
382         # add new images
383         new_image_names = set(glance_images_dict.keys()).difference(images_dict.keys())
384         for name in new_image_names:
385             image = Image(image_id=glance_images_dict[name]['id'],
386                           name=glance_images_dict[name]['name'],
387                           disk_format=glance_images_dict[name]['disk_format'],
388                           container_format=glance_images_dict[name]['container_format'])
389             image.save()
390
391         # remove old images
392         old_image_names = set(images_dict.keys()).difference(glance_images_dict.keys())
393         Image.objects.filter(name__in=old_image_names).delete()
394
395