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