initialize glance client with ca_ssl_cert
[plstackapi.git] / planetstack / openstack / driver.py
1 import commands
2 import hashlib
3 from planetstack.config import Config
4 from core.models import Controller
5
6 try:
7     from openstack.client import OpenStackClient
8     has_openstack = True
9 except:
10     has_openstack = False
11
12 manager_enabled = Config().api_nova_enabled
13
14 class OpenStackDriver:
15
16     def __init__(self, config = None, client=None):
17         if config:
18             self.config = Config(config)
19         else:
20             self.config = Config()
21
22         if client:
23             self.shell = client
24
25         self.enabled = manager_enabled
26         self.has_openstack = has_openstack
27         self.controller = None
28         self.admin_user = None
29
30     def client_driver(self, caller=None, tenant=None, controller=None):
31         if caller:
32             auth = {'username': caller.email,
33                     'password': hashlib.md5(caller.password).hexdigest()[:6],
34                     'tenant': tenant}
35             client = OpenStackClient(controller=controller, cacert=self.config.nova_ca_ssl_cert, **auth)
36         else:
37             admin_driver = self.admin_driver(tenant=tenant, controller=controller)
38             client = OpenStackClient(tenant=tenant, controller=admin_driver.controller)
39
40         driver = OpenStackDriver(client=client)
41         #driver.admin_user = admin_driver.admin_user
42         #driver.controller = admin_driver.controller
43         return driver
44
45     def admin_driver(self, tenant=None, controller=None):
46         if isinstance(controller, int):
47             controller = Controller.objects.get(id=controller.id)
48         client = OpenStackClient(tenant=tenant, controller=controller, cacert=self.config.nova_ca_ssl_cert)
49         driver = OpenStackDriver(client=client)
50         driver.admin_user = client.keystone.users.find(name=controller.admin_user)
51         driver.controller = controller
52         return driver    
53
54     def create_role(self, name):
55         roles = self.shell.keystone.roles.findall(name=name)
56         roles_title = self.shell.keystone.roles.findall(name=name.title())
57         roles_found = roles + roles_title
58         if not roles_found:
59             role = self.shell.keystone.roles.create(name)
60         else:
61             role = roles_found[0]
62         return role
63
64     def delete_role(self, filter):
65         roles = self.shell.keystone.roles.findall(**filter)
66         for role in roles:
67             self.shell.keystone.roles.delete(role)
68         return 1
69
70     def create_tenant(self, tenant_name, enabled, description):
71         """Create keystone tenant. Suggested fields: name, description, enabled"""  
72         tenants = self.shell.keystone.tenants.findall(name=tenant_name)
73         if not tenants:
74             fields = {'tenant_name': tenant_name, 'enabled': enabled, 
75                       'description': description}  
76             tenant = self.shell.keystone.tenants.create(**fields)
77         else:
78             tenant = tenants[0]
79
80         # always give the admin user the admin role to any tenant created 
81         # by the driver. 
82         self.add_user_role(self.admin_user.id, tenant.id, 'admin')
83         return tenant
84
85     def update_tenant(self, id, **kwds):
86         return self.shell.keystone.tenants.update(id, **kwds)
87
88     def delete_tenant(self, id):
89         ctx = self.shell.nova_db.ctx
90         tenants = self.shell.keystone.tenants.findall(id=id)
91         for tenant in tenants:
92             # nova does not automatically delete the tenant's instances
93             # so we manually delete instances before deleteing the tenant   
94             instances = self.shell.nova_db.instance_get_all_by_filters(ctx,
95                        {'project_id': tenant.id}, 'id', 'asc')
96             client = OpenStackClient(tenant=tenant.name)
97             driver = OpenStackDriver(client=client)
98             for instance in instances:
99                 driver.destroy_instance(instance.id)
100             self.shell.keystone.tenants.delete(tenant)
101         return 1
102
103     def create_user(self, name, email, password, enabled):
104         users = self.shell.keystone.users.findall(email=email)
105         if not users:
106             fields = {'name': name, 'email': email, 'password': password,
107                       'enabled': enabled}
108             user = self.shell.keystone.users.create(**fields)
109         else: 
110             user = users[0]
111         return user
112
113     def delete_user(self, id):
114         users = self.shell.keystone.users.findall(id=id)
115         for user in users:
116             # delete users keys
117             keys = self.shell.nova.keypairs.findall()
118             for key in keys:
119                 self.shell.nova.keypairs.delete(key)
120             self.shell.keystone.users.delete(user)
121         return 1
122
123     def get_admin_role(self):
124         role = None
125         for admin_role_name in ['admin', 'Admin']:
126             roles = self.shell.keystone.roles.findall(name=admin_role_name)
127             if roles:
128                 role = roles[0]
129                 break
130         return role 
131
132     def add_user_role(self, kuser_id, tenant_id, role_name):
133         user = self.shell.keystone.users.find(id=kuser_id)
134         tenant = self.shell.keystone.tenants.find(id=tenant_id)
135         # admin role can be lowercase or title. Look for both
136         role = None
137         if role_name.lower() == 'admin':
138             role = self.get_admin_role()
139         else:
140             # look up non admin role or force exception when admin role isnt found 
141             role = self.shell.keystone.roles.find(name=role_name)                   
142
143         role_found = False
144         user_roles = user.list_roles(tenant.id)
145         for user_role in user_roles:
146             if user_role.name == role.name:
147                 role_found = True
148         if not role_found:
149             tenant.add_user(user, role)
150
151         return 1
152
153     def delete_user_role(self, kuser_id, tenant_id, role_name):
154         user = self.shell.keystone.users.find(id=kuser_id)
155         tenant = self.shell.keystone.tenants.find(id=tenant_id)
156         # admin role can be lowercase or title. Look for both
157         role = None
158         if role_name.lower() == 'admin':
159             role = self.get_admin_role()
160         else:
161             # look up non admin role or force exception when admin role isnt found
162             role = self.shell.keystone.roles.find(name=role_name)
163
164         role_found = False
165         user_roles = user.list_roles(tenant.id)
166         for user_role in user_roles:
167             if user_role.name == role.name:
168                 role_found = True
169         if role_found:
170             tenant.remove_user(user, role)
171
172         return 1 
173
174     def update_user(self, id, fields):
175         if 'password' in fields:
176             self.shell.keystone.users.update_password(id, fields['password'])
177         if 'enabled' in fields:
178             self.shell.keystone.users.update_enabled(id, fields['enabled']) 
179         return 1 
180
181     def create_router(self, name, set_gateway=True):
182         routers = self.shell.quantum.list_routers(name=name)['routers']
183         if routers:
184             router = routers[0]
185         else:
186             router = self.shell.quantum.create_router({'router': {'name': name}})['router']
187         # add router to external network
188         if set_gateway:
189             nets = self.shell.quantum.list_networks()['networks']
190             for net in nets:
191                 if net['router:external'] == True: 
192                     self.shell.quantum.add_gateway_router(router['id'],
193                                                           {'network_id': net['id']})
194         
195         return router
196
197     def delete_router(self, id):
198         routers = self.shell.quantum.list_routers(id=id)['routers']
199         for router in routers:
200             self.shell.quantum.delete_router(router['id'])
201             # remove router form external network
202             #nets = self.shell.quantum.list_networks()['networks']
203             #for net in nets:
204             #    if net['router:external'] == True:
205             #        self.shell.quantum.remove_gateway_router(router['id'])
206
207     def add_router_interface(self, router_id, subnet_id):
208         router = self.shell.quantum.show_router(router_id)['router']
209         subnet = self.shell.quantum.show_subnet(subnet_id)['subnet']
210         if router and subnet:
211             self.shell.quantum.add_interface_router(router_id, {'subnet_id': subnet_id})
212
213     def delete_router_interface(self, router_id, subnet_id):
214         router = self.shell.quantum.show_router(router_id)
215         subnet = self.shell.quantum.show_subnet(subnet_id)
216         if router and subnet:
217             self.shell.quantum.remove_interface_router(router_id, {'subnet_id': subnet_id})
218  
219     def create_network(self, name, shared=False):
220         nets = self.shell.quantum.list_networks(name=name)['networks']
221         if nets: 
222             net = nets[0]
223         else:
224             net = self.shell.quantum.create_network({'network': {'name': name, 'shared': shared}})['network']
225         return net
226  
227     def delete_network(self, id):
228         nets = self.shell.quantum.list_networks()['networks']
229         for net in nets:
230             if net['id'] == id:
231                 # delete_all ports
232                 self.delete_network_ports(net['id'])
233                 # delete all subnets:
234                 for subnet_id in net['subnets']:
235                     self.delete_subnet(subnet_id)
236                 self.shell.quantum.delete_network(net['id'])
237         return 1
238
239     def delete_network_ports(self, network_id):
240         ports = self.shell.quantum.list_ports()['ports']
241         for port in ports:
242             if port['network_id'] == network_id:
243                 self.shell.quantum.delete_port(port['id'])
244         return 1         
245
246     def delete_subnet_ports(self, subnet_id):
247         ports = self.shell.quantum.list_ports()['ports']
248         for port in ports:
249             delete = False
250             for fixed_ip in port['fixed_ips']:
251                 if fixed_ip['subnet_id'] == subnet_id:
252                     delete=True
253                     break
254             if delete:
255                 self.shell.quantum.delete_port(port['id'])
256         return 1
257  
258     def create_subnet(self, name, network_id, cidr_ip, ip_version, start, end):
259         #nets = self.shell.quantum.list_networks(name=network_name)['networks']
260         #if not nets:
261         #    raise Exception, "No such network: %s" % network_name   
262         #net = nets[0]
263
264         subnet = None 
265         subnets = self.shell.quantum.list_subnets()['subnets']
266         for snet in subnets:
267             if snet['cidr'] == cidr_ip and snet['network_id'] == network_id:
268                 subnet = snet
269
270         if not subnet:
271             # HACK: Add metadata route -- Neutron does not reliably supply this
272             metadata_ip = cidr_ip.replace("0/24", "3")
273
274             allocation_pools = [{'start': start, 'end': end}]
275             subnet = {'subnet': {'name': name,
276                                  'network_id': network_id,
277                                  'ip_version': ip_version,
278                                  'cidr': cidr_ip,
279                                  #'dns_nameservers': ['8.8.8.8', '8.8.4.4'],
280                                  'host_routes': [{'destination':'169.254.169.254/32','nexthop':metadata_ip}],
281                                  'gateway_ip': None,
282                                  'allocation_pools': allocation_pools}}
283             subnet = self.shell.quantum.create_subnet(subnet)['subnet']
284             # self.add_external_route(subnet)
285
286         return subnet
287
288     def update_subnet(self, id, fields):
289         return self.shell.quantum.update_subnet(id, fields)
290
291     def delete_subnet(self, id):
292         #return self.shell.quantum.delete_subnet(id=id)
293         # inefficient but fault tolerant
294         subnets = self.shell.quantum.list_subnets()['subnets']
295         for subnet in subnets:
296             if subnet['id'] == id:
297                 self.delete_subnet_ports(subnet['id'])
298                 self.shell.quantum.delete_subnet(id)
299                 self.delete_external_route(subnet)
300         return 1
301
302     def get_external_routes(self):
303         status, output = commands.getstatusoutput('route')
304         routes = output.split('\n')[3:]
305         return routes
306
307     def add_external_route(self, subnet, routes=[]):
308         if not routes:
309             routes = self.get_external_routes()
310  
311         ports = self.shell.quantum.list_ports()['ports']
312
313         gw_ip = subnet['gateway_ip']
314         subnet_id = subnet['id']
315
316         # 1. Find the port associated with the subnet's gateway
317         # 2. Find the router associated with that port
318         # 3. Find the port associated with this router and on the external net
319         # 4. Set up route to the subnet through the port from step 3
320         ip_address = None
321         for port in ports:
322             for fixed_ip in port['fixed_ips']:
323                 if fixed_ip['subnet_id'] == subnet_id and fixed_ip['ip_address'] == gw_ip:
324                     gw_port = port
325                     router_id = gw_port['device_id']
326                     router = self.shell.quantum.show_router(router_id)['router']
327                     if router and router.get('external_gateway_info'):
328                         ext_net = router['external_gateway_info']['network_id']
329                         for port in ports:
330                             if port['device_id'] == router_id and port['network_id'] == ext_net:
331                                 ip_address = port['fixed_ips'][0]['ip_address']
332
333         if ip_address:
334             # check if external route already exists
335             route_exists = False
336             if routes:
337                 for route in routes:
338                     if subnet['cidr'] in route and ip_address in route:
339                         route_exists = True
340             if not route_exists:
341                 cmd = "route add -net %s dev br-ex gw %s" % (subnet['cidr'], ip_address)
342                 s, o = commands.getstatusoutput(cmd)
343                 #print cmd, "\n", s, o
344
345         return 1
346
347     def delete_external_route(self, subnet):
348         ports = self.shell.quantum.list_ports()['ports']
349
350         gw_ip = subnet['gateway_ip']
351         subnet_id = subnet['id']
352
353         # 1. Find the port associated with the subnet's gateway
354         # 2. Find the router associated with that port
355         # 3. Find the port associated with this router and on the external net
356         # 4. Set up route to the subnet through the port from step 3
357         ip_address = None
358         for port in ports:
359             for fixed_ip in port['fixed_ips']:
360                 if fixed_ip['subnet_id'] == subnet_id and fixed_ip['ip_address'] == gw_ip:
361                     gw_port = port
362                     router_id = gw_port['device_id']
363                     router = self.shell.quantum.show_router(router_id)['router']
364                     ext_net = router['external_gateway_info']['network_id']
365                     for port in ports:
366                         if port['device_id'] == router_id and port['network_id'] == ext_net:
367                             ip_address = port['fixed_ips'][0]['ip_address']
368
369         if ip_address:
370             cmd = "route delete -net %s" % (subnet['cidr'])
371             commands.getstatusoutput(cmd)
372              
373         return 1
374     
375     def create_keypair(self, name, public_key):
376         keys = self.shell.nova.keypairs.findall(name=name)
377         if keys:
378             key = keys[0]
379             # update key     
380             if key.public_key != public_key:
381                 self.delete_keypair(key.id)
382                 key = self.shell.nova.keypairs.create(name=name, public_key=public_key)
383         else:
384             key = self.shell.nova.keypairs.create(name=name, public_key=public_key)
385         return key
386
387     def delete_keypair(self, id):
388         keys = self.shell.nova.keypairs.findall(id=id)
389         for key in keys:
390             self.shell.nova.keypairs.delete(key) 
391         return 1
392
393     def get_private_networks(self, tenant=None):
394         if not tenant:
395             tenant = self.shell.nova.tenant
396         tenant = self.shell.keystone.tenants.find(name=tenant)
397         search_opts = {"tenant_id": tenant.id, "shared": False}
398         private_networks = self.shell.quantum.list_networks(**search_opts)
399         return private_networks
400
401     def get_shared_networks(self):
402         search_opts = {"shared": True}
403         shared_networks = self.shell.quantum.list_networks(**search_opts)
404         return shared_networks
405
406     def get_network_subnet(self, network_id):
407         subnet_id = None
408         subnet = None
409         if network_id:
410             os_networks = self.shell.quantum.list_networks(id=network_id)["networks"]
411             if os_networks:
412                 os_network = os_networks[0]
413                 if os_network['subnets']:
414                     subnet_id = os_network['subnets'][0]
415                     os_subnets = self.shell.quantum.list_subnets(id=subnet_id)['subnets']
416                     if os_subnets:
417                         subnet = os_subnets[0]['cidr']
418
419         return (subnet_id, subnet)
420
421     def spawn_instance(self, name, key_name=None, availability_zone=None, hostname=None, image_id=None, security_group=None, pubkeys=[], nics=None, metadata=None, userdata=None, flavor_name=None):
422         if not flavor_name:
423             flavor_name = self.config.nova_default_flavor
424
425         flavor = self.shell.nova.flavors.find(name=flavor_name)
426
427         if not security_group:
428             security_group = self.config.nova_default_security_group
429
430         files = {}
431         #if pubkeys:
432         #    files["/root/.ssh/authorized_keys"] = "\n".join(pubkeys).encode('base64')
433         hints = {}
434         
435         # determine availability zone and compute host 
436         availability_zone_filter = None
437         if availability_zone is None or not availability_zone:
438             availability_zone_filter = 'nova'
439         else: 
440             availability_zone_filter = availability_zone
441         if hostname:
442             availability_zone_filter += ':%s' % hostname
443
444         server = self.shell.nova.servers.create(
445                                             name=name,
446                                             key_name = key_name,
447                                             flavor=flavor.id,
448                                             image=image_id,
449                                             security_group = security_group,
450                                             #files = files,
451                                             scheduler_hints=hints,
452                                             availability_zone=availability_zone_filter,
453                                             nics=nics,
454                                             networks=nics,
455                                             meta=metadata,
456                                             userdata=userdata)
457         return server
458
459     def destroy_instance(self, id):
460         if (self.shell.nova.tenant=="admin"):
461             # findall() is implemented as a list() followed by a python search of the
462             # list. Since findall() doesn't accept "all_tenants", we do this using
463             # list() ourselves. This allows us to delete an instance as admin.
464             servers = self.shell.nova.servers.list(search_opts={"all_tenants": True})
465         else:
466             servers = self.shell.nova.servers.list()
467         for server in servers:
468             if server.id == id:
469                 result=self.shell.nova.servers.delete(server)
470
471     def update_instance_metadata(self, id, metadata):
472         servers = self.shell.nova.servers.findall(id=id)
473         for server in servers:
474             self.shell.nova.servers.set_meta(server, metadata)
475             # note: set_meta() returns a broken Server() object. Don't try to
476             # print it in the shell or it will fail in __repr__.
477
478     def delete_instance_metadata(self, id, metadata):
479         # note: metadata is a dict. Only the keys matter, not the values.
480         servers = self.shell.nova.servers.findall(id=id)
481         for server in servers:
482             self.shell.nova.servers.delete_meta(server, metadata)
483