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