updated calls to accept/support urn format where necessary
[sfa.git] / sfa / managers / aggregate_manager_eucalyptus.py
1 from __future__ import with_statement 
2 from sfa.util.faults import *
3 from sfa.util.namespace import *
4 from sfa.util.rspec import RSpec
5 from sfa.server.registry import Registries
6 from sfa.plc.nodes import *
7
8 import boto
9 from boto.ec2.regioninfo import RegionInfo
10 from boto.exception import EC2ResponseError
11 from ConfigParser import ConfigParser
12 from xmlbuilder import XMLBuilder
13 from xml.etree import ElementTree as ET
14
15 import sys
16 import os
17
18 # The data structure used to represent a cloud.
19 # It contains the cloud name, its ip address, image information,
20 # key pairs, and clusters information.
21 cloud = {}
22
23 ##
24 # Initialize the aggregate manager by reading a configuration file.
25 #
26 def init_server():
27     configParser = ConfigParser()
28     configParser.read(['/etc/sfa/eucalyptus_aggregate.conf', 'eucalyptus_aggregate.conf'])
29     if len(configParser.sections()) < 1:
30         print >>sys.stderr, 'No cloud defined in the config file'
31         raise 'Cannot find cloud definition in configuration file.'
32
33     # Only read the first section.
34     cloudSec = configParser.sections()[0]
35     cloud['name'] = cloudSec
36     cloud['access_key'] = configParser.get(cloudSec, 'access_key')
37     cloud['secret_key'] = configParser.get(cloudSec, 'secret_key')
38     cloud['cloud_url']  = configParser.get(cloudSec, 'cloud_url')
39     cloudURL = cloud['cloud_url']
40     if cloudURL.find('https://') >= 0:
41         cloudURL = cloudURL.replace('https://', '')
42     elif cloudURL.find('http://') >= 0:
43         cloudURL = cloudURL.replace('http://', '')
44     (cloud['ip'], parts) = cloudURL.split(':')
45
46 ##
47 # Creates a connection to Eucalytpus. This function is inspired by 
48 # the make_connection() in Euca2ools.
49 #
50 # @return A connection object or None
51 #
52 def getEucaConnection():
53     global cloud
54     accessKey = cloud['access_key']
55     secretKey = cloud['secret_key']
56     eucaURL   = cloud['cloud_url']
57     useSSL    = False
58     srvPath   = '/'
59     eucaPort  = 8773
60
61     if not accessKey or not secretKey or not eucaURL:
62         print >>sys.stderr, 'Please set ALL of the required environment ' \
63                             'variables by sourcing the eucarc file.'
64         return None
65     
66     # Split the url into parts
67     if eucaURL.find('https://') >= 0:
68         useSSL  = True
69         eucaURL = eucaURL.replace('https://', '')
70     elif eucaURL.find('http://') >= 0:
71         useSSL  = False
72         eucaURL = eucaURL.replace('http://', '')
73     (eucaHost, parts) = eucaURL.split(':')
74     if len(parts) > 1:
75         parts = parts.split('/')
76         eucaPort = int(parts[0])
77         parts = parts[1:]
78         srvPath = '/'.join(parts)
79
80     return boto.connect_ec2(aws_access_key_id=accessKey,
81                             aws_secret_access_key=secretKey,
82                             is_secure=useSSL,
83                             region=RegionInfo(None, 'eucalyptus', eucaHost), 
84                             port=eucaPort,
85                             path=srvPath)
86
87 ##
88 # A class that builds the RSpec for Eucalyptus.
89 #
90 class EucaRSpecBuilder(object):
91     ##
92     # Initizes a RSpec builder
93     #
94     # @param cloud A dictionary containing data about a 
95     #              cloud (ex. clusters, ip)
96     def __init__(self, cloud):
97         self.eucaRSpec = XMLBuilder()
98         self.cloudInfo = cloud
99
100     ##
101     # Creates the ClusterSpec stanza.
102     #
103     # @param clusters Clusters information.
104     #
105     def __clustersXML(self, clusters):
106         xml = self.eucaRSpec
107         for cluster in clusters:
108             instances = cluster['instances']
109             with xml.ClusterSpec(id=cluster['name'], ip=cluster['ip']):
110                 for inst in instances:
111                     with xml.Node(instanceType=inst[0]):
112                         with xml.FreeSlot:
113                             xml << str(inst[1])
114                         with xml.MaxAllow:
115                             xml << str(inst[2])
116                         with xml.NumCore:
117                             xml << str(inst[3])
118                         with xml.Mem:
119                             xml << str(inst[4])
120                         with xml.DiskSpace(unit='GB'):
121                             xml << str(inst[5])
122
123     ##
124     # Creates the Images stanza.
125     #
126     # @param images A list of images in Eucalyptus.
127     #
128     def __imagesXML(self, images):
129         xml = self.eucaRSpec
130         with xml.Images:
131             for image in images:
132                 with xml.Image(id=image.id):
133                     with xml.Type:
134                         xml << image.type
135                     with xml.Arch:
136                         xml << image.architecture
137                     with xml.State:
138                         xml << image.state
139                     with xml.location:
140                         xml << image.location
141
142     ##
143     # Creates the KeyPairs stanza.
144     #
145     # @param keypairs A list of key pairs in Eucalyptus.
146     #
147     def __keyPairsXML(self, keypairs):
148         xml = self.eucaRSpec
149         with xml.KeyPairs:
150             for key in keypairs:
151                 with xml.Key:
152                     xml << key.name
153
154     ##
155     # Generates the RSpec.
156     #
157     def toXML(self):
158         if not self.cloudInfo:
159             print >>sys.stderr, 'No cloud information'
160             return ''
161
162         xml = self.eucaRSpec
163         cloud = self.cloudInfo
164         with xml.RSpec(name='eucalyptus'):
165             with xml.Capacity:
166                 with xml.CloudSpec(id=cloud['name'], ip=cloud['ip']):
167                     self.__keyPairsXML(cloud['keypairs'])
168                     self.__imagesXML(cloud['images'])
169                     self.__clustersXML(cloud['clusters'])
170             with xml.Request:
171                 with xml.CloudSpec(id=cloud['name'], ip=cloud['ip']):
172                     with xml.Credential(type='X509'):
173                         xml << 'cred'
174                     with xml.Node(instanceType='m1.small', number='1'):
175                         with xml.Kernel:
176                             xml << 'eki-F26610C6'
177                         with xml.Ramdisk:
178                             xml << ''
179                         with xml.DiskImage:
180                             xml << 'emi-88760F45'
181                         with xml.Key:
182                             xml << 'cortex'
183         return str(xml)
184
185 ##
186 # A parser to parse the output of availability-zones.
187 #
188 # Note: Only one cluster is supported. If more than one, this will
189 #       not work.
190 #
191 class ZoneResultParser(object):
192     def __init__(self, zones):
193         self.zones = zones
194
195     def parse(self):
196         if len(self.zones) < 3:
197             return
198         clusterList = []
199         cluster = {} 
200         instList = []
201
202         cluster['name'] = self.zones[0].name
203         cluster['ip']   = self.zones[0].state
204
205         for i in range(2, len(self.zones)):
206             currZone = self.zones[i]
207             instType = currZone.name.split()[1]
208
209             stateString = currZone.state.split('/')
210             rscString   = stateString[1].split()
211
212             instFree      = int(stateString[0])
213             instMax       = int(rscString[0])
214             instNumCpu    = int(rscString[1])
215             instRam       = int(rscString[2])
216             instDiskSpace = int(rscString[3])
217
218             instTuple = (instType, instFree, instMax, instNumCpu, instRam, instDiskSpace)
219             instList.append(instTuple)
220         cluster['instances'] = instList
221         clusterList.append(cluster)
222
223         return clusterList
224
225 def get_rspec(api, xrn, origin_hrn):
226     global cloud
227     hrn = urn_to_hrn(xrn)[0]
228     conn = getEucaConnection()
229
230     if not conn:
231         print >>sys.stderr, 'Error: Cannot make a connection to the cloud'
232         return ''
233
234     try:
235         # Zones
236         zones = conn.get_all_zones(['verbose'])
237         p = ZoneResultParser(zones)
238         clusters = p.parse()
239         cloud['clusters'] = clusters
240         
241         # Images
242         images = conn.get_all_images()
243         cloud['images'] = images
244
245         # Key Pairs
246         keyPairs = conn.get_all_key_pairs()
247         cloud['keypairs'] = keyPairs
248     except EC2ResponseError:
249         errTree = ET.fromstring(EC2ResponseError.body)
250         errMsgE = errTree.find('.//Message')
251         print >>sys.stderr, errMsgE.text
252
253     rspec = EucaRSpecBuilder(cloud).toXML()
254
255     return rspec
256
257 """
258 Hook called via 'sfi.py create'
259 """
260 def create_slice(api, xrn, xml):
261     hrn = urn_to_hrn(xrn)[0]
262     return True
263
264 def main():
265     #r = RSpec()
266     #r.parseFile(sys.argv[1])
267     #rspec = r.toDict()
268     #create_slice(None,'plc',rspec)
269     rspec = get_rspec('euca', 'hrn:euca', 'oring_hrn')
270     print rspec
271
272 if __name__ == "__main__":
273     main()
274