2872aa335c16b059d966780e026abcf8ea7aa9cd
[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'] = 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, group_name, fw_rules=[]):
185         security_group = SecurityGroup(self.driver)
186         security_group.create_security_group(group_name)
187         if not fw_rules:
188             # open port 22 by default
189             security_group.add_rule_to_group(group_name,
190                                              protocol='tcp',
191                                              cidr_ip = '0.0.0.0/0',
192                                              port_range='22',
193                                              icmp_type_code="-1")
194         elif isinstance(fw_rules, list): 
195             for rule in fw_rules:
196                 security_group.add_rule_to_group(group_name, 
197                                              protocol = rule.get('protocol'), 
198                                              cidr_ip = rule.get('cidr_ip'), 
199                                              port_range = rule.get('port_range'), 
200                                              icmp_type_code = rule.get('icmp_type_code'))
201
202     def add_rule_to_security_group(self, group_name, **kwds):
203         security_group = SecurityGroup(self.driver)
204         security_group.add_rule_to_group(group_name=group_name, 
205                                          protocol=kwds.get('protocol'), 
206                                          cidr_ip =kwds.get('cidr_ip'), 
207                                          icmp_type_code = kwds.get('icmp_type_code'))
208
209  
210     def reserve_instance(self, image_id, kernel_id, ramdisk_id, \
211                          instance_type, key_name, user_data, group_name):
212         conn  = self.driver.euca_shell.get_euca_connection()
213         logger.info('Reserving an instance: image: %s, kernel: ' \
214                     '%s, ramdisk: %s, type: %s, key: %s' % \
215                     (image_id, kernel_id, ramdisk_id,
216                     instance_type, key_name))
217         try:
218             reservation = conn.run_instances(image_id=image_id,
219                                              kernel_id=kernel_id,
220                                              ramdisk_id=ramdisk_id,
221                                              instance_type=instance_type,
222                                              key_name=key_name,
223                                              user_data = user_data,
224                                              security_groups=[group_name])
225                                              #placement=zone,
226                                              #min_count=min_count,
227                                              #max_count=max_count,           
228                                               
229         except Exception, err:
230             logger.log_exc(err)
231     
232                
233     def run_instances(self, slicename, rspec, keyname, pubkeys):
234         """
235         Create the security groups and instances. 
236         """
237         # the default image to use for instnaces that dont
238         # explicitly request an image.
239         # Just choose the first available image for now.
240         image_manager = ImageManager(self.driver)
241         available_images = image_manager.get_available_disk_images()
242         default_image_id = None
243         default_aki_id  = None
244         default_ari_id = None
245         default_image = available_images[0]
246         default_image_id = ec2_id(default_image.id, default_image.container_format)  
247         default_aki_id = ec2_id(default_image.kernel_id, 'aki')  
248         default_ari_id = ec2_id(default_image.ramdisk_id, 'ari')
249
250         # get requested slivers
251         rspec = RSpec(rspec)
252         user_data = "\n".join(pubkeys)
253         requested_instances = defaultdict(list)
254         # iterate over clouds/zones/nodes
255         for node in rspec.version.get_nodes_with_slivers():
256             instance_types = node.get('slivers', [])
257             if isinstance(instance_types, list):
258                 # iterate over sliver/instance types
259                 for instance_type in instance_types:
260                     fw_rules = instance_type.get('fw_rules', [])
261                     # Each sliver get's its own security group.  
262                     # Keep security group names unique by appending some random 
263                     # characters on end.
264                     random_name = "".join([random.choice(string.letters+string.digits) 
265                                            for i in xrange(6)])
266                     group_name = slicename + random_name
267                     self.create_security_group(group_name, fw_rules)
268                     ami_id = default_image_id
269                     aki_id = default_aki_id
270                     ari_id = default_ari_id
271                     req_image = instance_type.get('disk_images')
272                     if req_image and isinstance(req_image, list):
273                         req_image_name = req_image[0]['name']
274                         disk_image = image_manager.get_disk_image(name=req_image_name)
275                         if disk_image:
276                             ami_id = ec2_id(disk_image.id, disk_image.container_format)
277                             aki_id = ec2_id(disk_image.kernel_id, 'aki')
278                             ari_id = ec2_id(disk_image.ramdisk_id, 'ari')
279                     # start the instance
280                     self.reserve_instance(image_id=ami_id, 
281                                           kernel_id=aki_id, 
282                                           ramdisk_id=ari_id, 
283                                           instance_type=instance_type['name'], 
284                                           key_name=keyname, 
285                                           user_data=user_data, 
286                                           group_name=group_name)
287
288
289     def delete_instances(self, project_name):
290         instances = self.driver.shell.db.instance_get_all_by_project(project_name)
291         security_group_manager = SecurityGroup(self.driver)
292         for instance in instances:
293             # deleate this instance's security groups
294             for security_group in instance.security_groups:
295                 # dont delete the default security group
296                 if security_group.name != 'default': 
297                     security_group_manager.delete_security_group(security_group.name)
298             # destroy instance
299             self.driver.shell.db.instance_destroy(instance.id)
300         return 1
301
302     def stop_instances(self, project_name):
303         instances = self.driver.shell.db.instance_get_all_by_project(project_name)
304         for instance in instances:
305             self.driver.shell.db.instance_stop(instance.id)
306         return 1
307
308     def update_instances(self, project_name):
309         pass