fix new sfa packaging
[sfa.git] / sfa / openstack / osaggregate.py
1
2 import os
3 import socket
4 import base64
5 import string
6 import random    
7 from collections import defaultdict
8 from nova.exception import ImageNotFound
9 from nova.api.ec2.cloud import CloudController
10 from sfa.util.faults import SfaAPIError
11 from sfa.rspecs.rspec import RSpec
12 from sfa.rspecs.elements.hardware_type import HardwareType
13 from sfa.rspecs.elements.node import Node
14 from sfa.rspecs.elements.sliver import Sliver
15 from sfa.rspecs.elements.login import Login
16 from sfa.rspecs.elements.disk_image import DiskImage
17 from sfa.rspecs.elements.services import Services
18 from sfa.util.xrn import Xrn
19 from sfa.util.osxrn import OSXrn
20 from sfa.rspecs.version_manager import VersionManager
21 from sfa.openstack.image import ImageManager
22 from sfa.openstack.security_group import SecurityGroup
23 from sfa.util.sfalogging import logger
24
25 def instance_to_sliver(instance, slice_xrn=None):
26     # should include?
27     # * instance.image_ref
28     # * instance.kernel_id
29     # * instance.ramdisk_id
30     import nova.db.sqlalchemy.models
31     name=None
32     type=None
33     sliver_id = None
34     if isinstance(instance, dict):
35         # this is an isntance type dict
36         name = instance['name']
37         type = instance['name']
38     elif isinstance(instance, nova.db.sqlalchemy.models.Instance):
39         # this is an object that describes a running instance
40         name = instance.display_name
41         type = instance.instance_type.name
42     else:
43         raise SfaAPIError("instnace must be an instance_type dict or" + \
44                            " a nova.db.sqlalchemy.models.Instance object")
45     if slice_xrn:
46         xrn = Xrn(slice_xrn, 'slice')
47         sliver_id = xrn.get_sliver_id(instance.project_id, instance.hostname, instance.id)
48
49     sliver = Sliver({'slice_id': sliver_id,
50                      'name': name,
51                      'type':  type,
52                      'tags': []})
53     return sliver
54     
55
56 def ec2_id(id=None, type=None):
57     ec2_id = None
58     if type == 'ovf':
59         type = 'ami'   
60     if id and type:
61         ec2_id = CloudController.image_ec2_id(id, type)        
62     return ec2_id
63
64
65 class OSAggregate:
66
67     def __init__(self, driver):
68         self.driver = driver
69
70     def get_rspec(self, slice_xrn=None, version=None, options={}):
71         version_manager = VersionManager()
72         version = version_manager.get_version(version)
73         if not slice_xrn:
74             rspec_version = version_manager._get_version(version.type, version.version, 'ad')
75             nodes = self.get_aggregate_nodes()
76         else:
77             rspec_version = version_manager._get_version(version.type, version.version, 'manifest')
78             nodes = self.get_slice_nodes(slice_xrn)
79         rspec = RSpec(version=rspec_version, user_options=options)
80         rspec.version.add_nodes(nodes)
81         return rspec.toxml()
82
83     def get_slice_nodes(self, slice_xrn):
84         image_manager = ImageManager(self.driver)
85         name = OSXrn(xrn = slice_xrn).name
86         instances = self.driver.shell.db.instance_get_all_by_project(name)
87         rspec_nodes = []
88         for instance in instances:
89             rspec_node = Node()
90             xrn = OSXrn(instance.hostname, 'node')
91             rspec_node['component_id'] = xrn.urn
92             rspec_node['component_name'] = xrn.name
93             rspec_node['component_manager_id'] = Xrn(self.driver.hrn, 'authority+cm').get_urn()   
94             sliver = instance_to_sliver(instance)
95             disk_image = image_manager.get_disk_image(instance.image_ref)
96             sliver['disk_images'] = [disk_image.to_rspec_object()]
97             rspec_node['slivers'] = [sliver]
98             rspec_nodes.append(rspec_node)
99         return rspec_nodes
100
101     def get_aggregate_nodes(self):
102                 
103         zones = self.driver.shell.db.zone_get_all()
104         if not zones:
105             zones = ['cloud']
106         else:
107             zones = [zone.name for zone in zones]
108
109         # available sliver/instance/vm types
110         instances = self.driver.shell.db.instance_type_get_all().values()
111         # available images
112         image_manager = ImageManager(self.driver)
113         disk_images = image_manager.get_available_disk_images()
114         disk_image_objects = [image.to_rspec_object() \
115                                for image in disk_images]  
116         rspec_nodes = []
117         for zone in zones:
118             rspec_node = Node()
119             xrn = OSXrn(zone, 'node')
120             rspec_node['component_id'] = xrn.urn
121             rspec_node['component_name'] = xrn.name
122             rspec_node['component_manager_id'] = Xrn(self.driver.hrn, 'authority+cm').get_urn()
123             rspec_node['exclusive'] = 'false'
124             rspec_node['hardware_types'] = [HardwareType({'name': 'plos-pc'}),
125                                                 HardwareType({'name': 'pc'})]
126             slivers = []
127             for instance in instances:
128                 sliver = instance_to_sliver(instance)
129                 sliver['disk_images'] = disk_image_objects
130                 slivers.append(sliver)
131         
132             rspec_node['slivers'] = slivers
133             rspec_nodes.append(rspec_node) 
134
135         return rspec_nodes 
136
137
138     def create_project(self, slicename, users, options={}):
139         """
140         Create the slice if it doesn't alredy exist. Create user
141         accounts that don't already exist   
142         """
143         from nova.exception import ProjectNotFound, UserNotFound
144         for user in users:
145             username = Xrn(user['urn']).get_leaf()
146             try:
147                 self.driver.shell.auth_manager.get_user(username)
148             except nova.exception.UserNotFound:
149                 self.driver.shell.auth_manager.create_user(username)
150             self.verify_user_keys(username, user['keys'], options)
151
152         try:
153             slice = self.driver.shell.auth_manager.get_project(slicename)
154         except ProjectNotFound:
155             # assume that the first user is the project manager
156             proj_manager = Xrn(users[0]['urn']).get_leaf()
157             self.driver.shell.auth_manager.create_project(slicename, proj_manager) 
158
159     def verify_user_keys(self, username, keys, options={}):
160         """
161         Add requested keys.
162         """
163         append = options.get('append', True)    
164         existing_keys = self.driver.shell.db.key_pair_get_all_by_user(username)
165         existing_pub_keys = [key.public_key for key in existing_keys]
166         removed_pub_keys = set(existing_pub_keys).difference(keys)
167         added_pub_keys = set(keys).difference(existing_pub_keys)
168         pubkeys = []
169         # add new keys
170         for public_key in added_pub_keys:
171             key = {}
172             key['user_id'] = username
173             key['name'] =  username
174             key['public_key'] = public_key
175             self.driver.shell.db.key_pair_create(key)
176
177         # remove old keys
178         if not append:
179             for key in existing_keys:
180                 if key.public_key in removed_pub_keys:
181                     self.driver.shell.db.key_pair_destroy(username, key.name)
182
183
184     def create_security_group(self, slicename, fw_rules=[]):
185         # use default group by default
186         group_name = 'default' 
187         if isinstance(fw_rules, list) and fw_rules:
188             # Each sliver get's its own security group.
189             # Keep security group names unique by appending some random
190             # characters on end.
191             random_name = "".join([random.choice(string.letters+string.digits)
192                                            for i in xrange(6)])
193             group_name = slicename + random_name 
194             security_group = SecurityGroup(self.driver)
195             security_group.create_security_group(group_name)
196             for rule in fw_rules:
197                 security_group.add_rule_to_group(group_name, 
198                                              protocol = rule.get('protocol'), 
199                                              cidr_ip = rule.get('cidr_ip'), 
200                                              port_range = rule.get('port_range'), 
201                                              icmp_type_code = rule.get('icmp_type_code'))
202         return group_name
203
204     def add_rule_to_security_group(self, group_name, **kwds):
205         security_group = SecurityGroup(self.driver)
206         security_group.add_rule_to_group(group_name=group_name, 
207                                          protocol=kwds.get('protocol'), 
208                                          cidr_ip =kwds.get('cidr_ip'), 
209                                          icmp_type_code = kwds.get('icmp_type_code'))
210
211  
212     def reserve_instance(self, image_id, kernel_id, ramdisk_id, \
213                          instance_type, key_name, user_data, group_name):
214         conn  = self.driver.euca_shell.get_euca_connection()
215         logger.info('Reserving an instance: image: %s, kernel: ' \
216                     '%s, ramdisk: %s, type: %s, key: %s' % \
217                     (image_id, kernel_id, ramdisk_id,
218                     instance_type, key_name))
219         try:
220             reservation = conn.run_instances(image_id=image_id,
221                                              kernel_id=kernel_id,
222                                              ramdisk_id=ramdisk_id,
223                                              instance_type=instance_type,
224                                              key_name=key_name,
225                                              user_data = user_data,
226                                              security_groups=[group_name])
227                                              #placement=zone,
228                                              #min_count=min_count,
229                                              #max_count=max_count,           
230                                               
231         except Exception, err:
232             logger.log_exc(err)
233     
234                
235     def run_instances(self, slicename, rspec, keyname, pubkeys):
236         """
237         Create the security groups and instances. 
238         """
239         # the default image to use for instnaces that dont
240         # explicitly request an image.
241         # Just choose the first available image for now.
242         image_manager = ImageManager(self.driver)
243         available_images = image_manager.get_available_disk_images()
244         default_image_id = None
245         default_aki_id  = None
246         default_ari_id = None
247         default_image = available_images[0]
248         default_image_id = ec2_id(default_image.id, default_image.container_format)  
249         default_aki_id = ec2_id(default_image.kernel_id, 'aki')  
250         default_ari_id = ec2_id(default_image.ramdisk_id, 'ari')
251
252         # get requested slivers
253         rspec = RSpec(rspec)
254         user_data = "\n".join(pubkeys)
255         requested_instances = defaultdict(list)
256         # iterate over clouds/zones/nodes
257         for node in rspec.version.get_nodes_with_slivers():
258             instance_types = node.get('slivers', [])
259             if isinstance(instance_types, list):
260                 # iterate over sliver/instance types
261                 for instance_type in instance_types:
262                     fw_rules = instance_type.get('fw_rules', [])
263                     group_name = self.create_security_group(slicename, fw_rules)
264                     ami_id = default_image_id
265                     aki_id = default_aki_id
266                     ari_id = default_ari_id
267                     req_image = instance_type.get('disk_images')
268                     if req_image and isinstance(req_image, list):
269                         req_image_name = req_image[0]['name']
270                         disk_image = image_manager.get_disk_image(name=req_image_name)
271                         if disk_image:
272                             ami_id = ec2_id(disk_image.id, disk_image.container_format)
273                             aki_id = ec2_id(disk_image.kernel_id, 'aki')
274                             ari_id = ec2_id(disk_image.ramdisk_id, 'ari')
275                     # start the instance
276                     self.reserve_instance(image_id=ami_id, 
277                                           kernel_id=aki_id, 
278                                           ramdisk_id=ari_id, 
279                                           instance_type=instance_type['name'], 
280                                           key_name=keyname, 
281                                           user_data=user_data, 
282                                           group_name=group_name)
283
284
285     def delete_instances(self, project_name):
286         instances = self.driver.shell.db.instance_get_all_by_project(project_name)
287         security_group_manager = SecurityGroup(self.driver)
288         for instance in instances:
289             # deleate this instance's security groups
290             for security_group in instance.security_groups:
291                 # dont delete the default security group
292                 if security_group.name != 'default': 
293                     security_group_manager.delete_security_group(security_group.name)
294             # destroy instance
295             self.driver.shell.db.instance_destroy(instance.id)
296         return 1
297
298     def stop_instances(self, project_name):
299         instances = self.driver.shell.db.instance_get_all_by_project(project_name)
300         for instance in instances:
301             self.driver.shell.db.instance_stop(instance.id)
302         return 1
303
304     def update_instances(self, project_name):
305         pass