(no commit message)
authorAndy Bavier <acb@cs.princeton.edu>
Fri, 15 Jan 2010 21:04:51 +0000 (21:04 +0000)
committerAndy Bavier <acb@cs.princeton.edu>
Fri, 15 Jan 2010 21:04:51 +0000 (21:04 +0000)
sfa/managers/aggregate_manager_eucalyptus.py

index f05a36a..fa0f8b2 100644 (file)
+from __future__ import with_statement 
 from sfa.util.faults import *
 from sfa.util.namespace import *
 from sfa.util.rspec import RSpec
 from sfa.server.registry import Registries
 from sfa.plc.nodes import *
+
+import boto
+from boto.ec2.regioninfo import RegionInfo
+from boto.exception import EC2ResponseError
+from ConfigParser import ConfigParser
+from xmlbuilder import XMLBuilder
+from xml.etree import ElementTree as ET
+
 import sys
+import os
+
+# The data structure used to represent a cloud.
+# It contains the cloud name, its ip address, image information,
+# key pairs, and clusters information.
+cloud = {}
+
+##
+# Initialize the aggregate manager by reading a configuration file.
+#
+def init_server():
+    configParser = ConfigParser()
+    configParser.read(['/etc/sfa/eucalyptus_aggregate.conf', 'eucalyptus_aggregate.conf'])
+    if len(configParser.sections()) < 1:
+        print >>sys.stderr, 'No cloud defined in the config file'
+        raise 'Cannot find cloud definition in configuration file.'
+
+    # Only read the first section.
+    cloudSec = configParser.sections()[0]
+    cloud['name'] = cloudSec
+    cloud['access_key'] = configParser.get(cloudSec, 'access_key')
+    cloud['secret_key'] = configParser.get(cloudSec, 'secret_key')
+    cloud['cloud_url']  = configParser.get(cloudSec, 'cloud_url')
+    cloudURL = cloud['cloud_url']
+    if cloudURL.find('https://') >= 0:
+        cloudURL = cloudURL.replace('https://', '')
+    elif cloudURL.find('http://') >= 0:
+        cloudURL = cloudURL.replace('http://', '')
+    (cloud['ip'], parts) = cloudURL.split(':')
+
+##
+# Creates a connection to Eucalytpus. This function is inspired by 
+# the make_connection() in Euca2ools.
+#
+# @return A connection object or None
+#
+def getEucaConnection():
+    global cloud
+    accessKey = cloud['access_key']
+    secretKey = cloud['secret_key']
+    eucaURL   = cloud['cloud_url']
+    useSSL    = False
+    srvPath   = '/'
+    eucaPort  = 8773
+
+    if not accessKey or not secretKey or not eucaURL:
+        print >>sys.stderr, 'Please set ALL of the required environment ' \
+                            'variables by sourcing the eucarc file.'
+        return None
+    
+    # Split the url into parts
+    if eucaURL.find('https://') >= 0:
+        useSSL  = True
+        eucaURL = eucaURL.replace('https://', '')
+    elif eucaURL.find('http://') >= 0:
+        useSSL  = False
+        eucaURL = eucaURL.replace('http://', '')
+    (eucaHost, parts) = eucaURL.split(':')
+    if len(parts) > 1:
+        parts = parts.split('/')
+        eucaPort = int(parts[0])
+        parts = parts[1:]
+        srvPath = '/'.join(parts)
+
+    return boto.connect_ec2(aws_access_key_id=accessKey,
+                            aws_secret_access_key=secretKey,
+                            is_secure=useSSL,
+                            region=RegionInfo(None, 'eucalyptus', eucaHost), 
+                            port=eucaPort,
+                            path=srvPath)
+
+##
+# A class that builds the RSpec for Eucalyptus.
+#
+class EucaRSpecBuilder(object):
+    ##
+    # Initizes a RSpec builder
+    #
+    # @param cloud A dictionary containing data about a 
+    #              cloud (ex. clusters, ip)
+    def __init__(self, cloud):
+        self.eucaRSpec = XMLBuilder()
+        self.cloudInfo = cloud
+
+    ##
+    # Creates the ClusterSpec stanza.
+    #
+    # @param clusters Clusters information.
+    #
+    def __clustersXML(self, clusters):
+        xml = self.eucaRSpec
+        for cluster in clusters:
+            instances = cluster['instances']
+            with xml.ClusterSpec(id=cluster['name'], ip=cluster['ip']):
+                for inst in instances:
+                    with xml.Node(instanceType=inst[0]):
+                        with xml.FreeSlot:
+                            xml << str(inst[1])
+                        with xml.MaxAllow:
+                            xml << str(inst[2])
+                        with xml.NumCore:
+                            xml << str(inst[3])
+                        with xml.Mem:
+                            xml << str(inst[4])
+                        with xml.DiskSpace(unit='GB'):
+                            xml << str(inst[5])
+
+    ##
+    # Creates the Images stanza.
+    #
+    # @param images A list of images in Eucalyptus.
+    #
+    def __imagesXML(self, images):
+        xml = self.eucaRSpec
+        with xml.Images:
+            for image in images:
+                with xml.Image(id=image.id):
+                    with xml.Type:
+                        xml << image.type
+                    with xml.Arch:
+                        xml << image.architecture
+                    with xml.State:
+                        xml << image.state
+                    with xml.location:
+                        xml << image.location
+
+    ##
+    # Creates the KeyPairs stanza.
+    #
+    # @param keypairs A list of key pairs in Eucalyptus.
+    #
+    def __keyPairsXML(self, keypairs):
+        xml = self.eucaRSpec
+        with xml.KeyPairs:
+            for key in keypairs:
+                with xml.Key:
+                    xml << key.name
+
+    ##
+    # Generates the RSpec.
+    #
+    def toXML(self):
+        if not self.cloudInfo:
+            print >>sys.stderr, 'No cloud information'
+            return ''
+
+        xml = self.eucaRSpec
+        cloud = self.cloudInfo
+        with xml.RSpec(name='eucalyptus'):
+            with xml.Capacity:
+                with xml.CloudSpec(id=cloud['name'], ip=cloud['ip']):
+                    self.__keyPairsXML(cloud['keypairs'])
+                    self.__imagesXML(cloud['images'])
+                    self.__clustersXML(cloud['clusters'])
+            with xml.Request:
+                with xml.CloudSpec(id=cloud['name'], ip=cloud['ip']):
+                    with xml.Credential(type='X509'):
+                        xml << 'cred'
+                    with xml.Node(instanceType='m1.small', number='1'):
+                        with xml.Kernel:
+                            xml << 'eki-F26610C6'
+                        with xml.Ramdisk:
+                            xml << ''
+                        with xml.DiskImage:
+                            xml << 'emi-88760F45'
+                        with xml.Key:
+                            xml << 'cortex'
+        return str(xml)
+
+##
+# A parser to parse the output of availability-zones.
+#
+# Note: Only one cluster is supported. If more than one, this will
+#       not work.
+#
+class ZoneResultParser(object):
+    def __init__(self, zones):
+        self.zones = zones
+
+    def parse(self):
+        if len(self.zones) < 3:
+            return
+        clusterList = []
+        cluster = {} 
+        instList = []
+
+        cluster['name'] = self.zones[0].name
+        cluster['ip']   = self.zones[0].state
+
+        for i in range(2, len(self.zones)):
+            currZone = self.zones[i]
+            instType = currZone.name.split()[1]
+
+            stateString = currZone.state.split('/')
+            rscString   = stateString[1].split()
+
+            instFree      = int(stateString[0])
+            instMax       = int(rscString[0])
+            instNumCpu    = int(rscString[1])
+            instRam       = int(rscString[2])
+            instDiskSpace = int(rscString[3])
+
+            instTuple = (instType, instFree, instMax, instNumCpu, instRam, instDiskSpace)
+            instList.append(instTuple)
+        cluster['instances'] = instList
+        clusterList.append(cluster)
+
+        return clusterList
 
 def get_rspec(api, hrn, origin_hrn):
-    xml = """<?xml version="1.0"?>
-<RSpec name="eucalyptus">
-</RSpec>"""
-    return xml
+    global cloud
+    conn = getEucaConnection()
+
+    if not conn:
+        print >>sys.stderr, 'Error: Cannot make a connection to the cloud'
+        return ''
+
+    try:
+        # Zones
+        zones = conn.get_all_zones(['verbose'])
+        p = ZoneResultParser(zones)
+        clusters = p.parse()
+        cloud['clusters'] = clusters
+        
+        # Images
+        images = conn.get_all_images()
+        cloud['images'] = images
+
+        # Key Pairs
+        keyPairs = conn.get_all_key_pairs()
+        cloud['keypairs'] = keyPairs
+    except EC2ResponseError:
+        errTree = ET.fromstring(EC2ResponseError.body)
+        errMsgE = errTree.find('.//Message')
+        print >>sys.stderr, errMsgE.text
+
+    rspec = EucaRSpecBuilder(cloud).toXML()
+
+    return rspec
 
 """
 Hook called via 'sfi.py create'
@@ -18,10 +260,13 @@ def create_slice(api, hrn, xml):
     return True
 
 def main():
-    r = RSpec()
-    r.parseFile(sys.argv[1])
-    rspec = r.toDict()
-    create_slice(None,'plc',rspec)
+    #r = RSpec()
+    #r.parseFile(sys.argv[1])
+    #rspec = r.toDict()
+    #create_slice(None,'plc',rspec)
+    rspec = get_rspec('euca', 'hrn:euca', 'oring_hrn')
+    print rspec
 
 if __name__ == "__main__":
     main()
+