geni aggregate interface
authorTony Mack <tmack@cs.princeton.edu>
Mon, 5 Jan 2009 20:14:01 +0000 (20:14 +0000)
committerTony Mack <tmack@cs.princeton.edu>
Mon, 5 Jan 2009 20:14:01 +0000 (20:14 +0000)
plc/aggregate.py

index f24d752..cfc00ee 100644 (file)
-##
-# Aggregate is a GeniServer that implements the Slice interface at PLC
-
-import tempfile
 import os
-import time
 import sys
-
-from util.hierarchy import Hierarchy
-from util.trustedroot import TrustedRootList
-from util.cert import Keypair, Certificate
-from util.gid import GID
-from util.geniserver import GeniServer
-from util.record import GeniRecord
-from util.genitable import GeniTable
-from util.geniticket import Ticket
-from util.excep import *
-from util.misc import *
-
-##
-# Aggregate class extends GeniServer class
-
-class Aggregate(GeniServer):
-    ##
-    # Create a new aggregate object.
-    #
-    # @param ip the ip address to listen on
-    # @param port the port to listen on
-    # @param key_file private key filename of registry
-    # @param cert_file certificate filename containing public key (could be a GID file)
-
-    def __init__(self, ip, port, key_file, cert_file):
-        GeniServer.__init__(self, ip, port, key_file, cert_file)
-
-        # get PL account settings from config module
-        self.pl_auth = get_pl_auth()
-
-        # connect to planetlab
-        if "Url" in self.pl_auth:
-            self.connect_remote_shell()
-        else:
-            self.connect_local_shell()
-
-    ##
-    # Connect to a remote shell via XMLRPC
-
-    def connect_remote_shell(self):
-        import remoteshell
-        self.shell = remoteshell.RemoteShell()
-
-    ##
-    # Connect to a local shell via local API functions
-
-    def connect_local_shell(self):
-        import PLC.Shell
-        self.shell = PLC.Shell.Shell(globals = globals())
-
-    ##
-    # Register the server RPCs for the slice interface
-
-    def register_functions(self):
-        GeniServer.register_functions(self)
-        self.server.register_function(self.create_slice)
-        self.server.register_function(self.get_ticket)
-        self.server.register_function(self.redeem_ticket)
-        self.server.register_function(self.start_slice)
-        self.server.register_function(self.stop_slice)
-        self.server.register_function(self.reset_slice)
-        self.server.register_function(self.delete_slice)
-        self.server.register_function(self.get_slice_resources)
-        self.server.register_function(self.list_slices)
-        self.server.register_function(self.list_nodes)
-
-    ##
-    # Given an authority name, return the information for that authority. This
-    # is basically a stub that calls the hierarchy module.
-    #
-    # @param auth_hrn human readable name of authority
-
-    def get_auth_info(self, auth_hrn):
-        return AuthHierarchy.get_auth_info(auth_hrn)
-
-    ##
-    # Given an authority name, return the database table for that authority. If
-    # the database table does not exist, then one will be automatically
-    # created.
-    #
-    # @param auth_name human readable name of authority
-
-    def get_auth_table(self, auth_name):
-        auth_info = self.get_auth_info(auth_name)
-
-        table = GeniTable(hrn=auth_name,
-                          cninfo=auth_info.get_dbinfo())
-
-        # if the table doesn't exist, then it means we haven't put any records
-        # into this authority yet.
-
-        if not table.exists():
-            report.trace("Registry: creating table for authority " + auth_name)
-            table.create()
-
-        return table
-
-    ##
-    # Verify that an authority belongs to this registry. This is basically left
-    # up to the implementation of the hierarchy module. If the specified name
-    # does not belong to this registry, an exception is thrown indicating the
-    # caller should contact someone else.
-    #
-    # @param auth_name human readable name of authority
-
-    def verify_auth_belongs_to_me(self, name):
-        # get_auth_info will throw an exception if the authority does not
-        # exist
-        self.get_auth_info(name)
-
-    ##
-    # Verify that an object belongs to this registry. By extension, this implies
-    # that the authority that owns the object belongs to this registry. If the
-    # object does not belong to this registry, then an exception is thrown.
-    #
-    # @param name human readable name of object
-
-    def verify_object_belongs_to_me(self, name):
-        auth_name = get_authority(name)
-        if not auth_name:
-            # the root authority belongs to the registry by default?
-            # TODO: is this true?
-            return
-        self.verify_auth_belongs_to_me(auth_name)
-
-    ##
-    # Verify that the object_gid that was specified in the credential allows
-    # permission to the object 'name'. This is done by a simple prefix test.
-    # For example, an object_gid for planetlab.us.arizona would match the
-    # objects planetlab.us.arizona.slice1 and planetlab.us.arizona.
-    #
-    # @param name human readable name to test
-
-    def verify_object_permission(self, name):
-        object_hrn = self.object_gid.get_hrn()
-        if object_hrn == name:
-            return
-        if name.startswith(object_hrn + "."):
-            return
-        raise PermissionError(name)
-
-    ##
-    # Convert a PLC record into the slice information that will be stored in
-    # a ticket. There are two parts to this information: attributes and
-    # rspec.
-    #
-    # Attributes are non-resource items, such as keys and the initscript
-    # Rspec is a set of resource specifications
-    #
-    # @param record a record object
-    #
-    # @return a tuple (attrs, rspec) of dictionaries
-
-    def record_to_slice_info(self, record):
-
-        # get the user keys from the slice
-        keys = []
-        persons = self.shell.GetPersons(self.pl_auth, record.pl_info['person_ids'])
-        for person in persons:
-            person_keys = self.shell.GetKeys(self.pl_auth, person["key_ids"])
-            for person_key in person_keys:
-                keys = keys + [person_key['key']]
-
-        attributes={}
-        attributes['name'] = record.pl_info['name']
-        attributes['keys'] = keys
-        attributes['instantiation'] = record.pl_info['instantiation']
-        attributes['vref'] = 'default'
-        attributes['timestamp'] = time.time()
-        attributes['slice_id'] = record.pl_info['slice_id']
-
-        rspec = {}
-
-        # get the PLC attributes and separate them into slice attributes and
-        # rspec attributes
-        filter = {}
-        filter['slice_id'] = record.pl_info['slice_id']
-        plc_attrs = self.shell.GetSliceAttributes(self.pl_auth, filter)
-        for attr in plc_attrs:
-            name = attr['name']
-
-            # initscripts: lookup the contents of the initscript and store it
-            # in the ticket attributes
-            if (name == "initscript"):
-                filter={'name': attr['value']}
-                initscripts = self.shell.GetInitScripts(self.pl_auth, filter)
-                if initscripts:
-                    attributes['initscript'] = initscripts[0]['script']
-            else:
-                rspec[name] = attr['value']
-
-        return (attributes, rspec)
-
-    ##
-    # create_slice: Create (instantiate) a slice. 
-    #
-    # @param cred credential string
-    # @param name name of the slice to retrieve a ticket for
-    # @param rspec resource specification dictionary
-    #
-    # @return the string representation of a ticket object
-
-    def create_slice(self, cred, name, rspec):
-        self.decode_authentication(cred, "createslice")
-        slicename = hrn_to_pl_slicename(self.object_gid.get_hrn())
-        # TODO: create a slice
-
-    ##
-    # get_ticket: Retrieve a ticket. 
-    #
-    # This operation is currently implemented on PLC only (see SFA,
-    # engineering decisions); it is not implemented on components.
-    #
-    # The ticket is filled in with information from the PLC database. This
-    # information includes resources, and attributes such as user keys and
-    # initscripts.
-    #
-    # @param cred credential string
-    # @param name name of the slice to retrieve a ticket for
-    # @param rspec resource specification dictionary
-    #
-    # @return the string representation of a ticket object
-
-    def get_ticket(self, cred, name, rspec):
-        self.decode_authentication(cred, "getticket")
-
-        self.verify_object_belongs_to_me(name)
-
-        self.verify_object_permission(name)
-
-        auth_hrn = get_authority(name)
-        auth_info = self.get_auth_info(auth_hrn)
-
-        records = self.resolve_raw("slice", name, must_exist=True)
-        record = records[0]
-
-        object_gid = record.get_gid_object()
-        new_ticket = Ticket(subject = object_gid.get_subject())
-        new_ticket.set_gid_caller(self.client_gid)
-        new_ticket.set_gid_object(object_gid)
-        new_ticket.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
-        new_ticket.set_pubkey(object_gid.get_pubkey())
-
-        self.fill_record_info(record)
-
-        (attributes, rspec) = self.record_to_slice_info(record)
-
-        new_ticket.set_attributes(attributes)
-        new_ticket.set_rspec(rspec)
-
-        new_ticket.set_parent(AuthHierarchy.get_auth_ticket(auth_hrn))
-
-        new_ticket.encode()
-        new_ticket.sign()
-
-        return new_ticket.save_to_string(save_parents=True)
-
-    ##
-    # redeem_ticket: Redeem a ticket.
-    #
-    # Not supported at a PLC aggregate.
-    #
-    # @param ...not sure...
-
-    def redeem_ticket(self, whatever):
-        return anything
-
-    ##
-    # stop_slice: Stop a slice.
-    #
-    # @param cred a credential identifying the caller (callerGID) and the slice
-    #     (objectGID)
-
-    def stop_slice(self, cred_str):
-        self.decode_authentication(cred_str, "stopslice")
-        slicename = hrn_to_pl_slicename(self.object_gid.get_hrn())
-        # TODO: stop the slice
-
-    ##
-    # start_slice: Start a slice.
-    #
-    # @param cred a credential identifying the caller (callerGID) and the slice
-    #     (objectGID)
-
-    def start_slice(self, cred_str):
-        self.decode_authentication(cred_str, "startslice")
-        slicename = hrn_to_pl_slicename(self.object_gid.get_hrn())
-        # TODO: start the slice
-
-    ##
-    # reset_slice: Reset a slice.
-    #
-    # @param cred a credential identifying the caller (callerGID) and the slice
-    #     (objectGID)
-
-    def reset_slice(self, cred_str):
-        self.decode_authentication(cred_str, "resetslice")
-        slicename = hrn_to_pl_slicename(self.object_gid.get_hrn())
-        # TODO: reset the slice
-
-    ##
-    # delete_slice: Delete a slice.
-    #
-    # @param cred a credential identifying the caller (callerGID) and the slice
-    #     (objectGID)
-
-    def delete_slice(self, cred_str):
-        self.decode_authentication(cred_str, "deleteslice")
-        slicename = hrn_to_pl_slicename(self.object_gid.get_hrn())
-        # TODO: delete the slice
-
-    ##
-    # get_resources: Get resources allocated to slice
-    #
-    # @param cred a credential identifying the caller (callerGID) and the slice
-    #     (objectGID)
-
-    def get_slice_resources(self, cred_str):
-        self.decode_authentication(cred_str, "getsliceresources")
-        slicename = hrn_to_pl_slicename(self.object_gid.get_hrn())
-        # TODO: get resources allocated to slice
-
-    ##
-    # list_slices: List hosted slices.
-    #
-    # @param cred a credential identifying the caller (callerGID)
-
-    def list_slices(self, cred_str):
-        self.decode_authentication(cred_str, "listslices")
-        # TODO: list hosted slices
-
-    ##
-    # list_nodes: List available nodes.
-    #
-    # @param cred a credential identifying the caller (callerGID)
-
-    def list_nodes(self, cred_str):
-        self.decode_authentication(cred_str, "listnodes")
-        # TODO: list available nodes
-
-
+import datetime
+import time
+import xmlrpclib
+
+from geniserver import *
+from excep import *
+from misc import *
+from config import Config
+
+conf = Config()
+basedir = conf.GENI_BASE_DIR + os.sep 
+server_basedir = basedir + os.sep + "plc" + os.sep 
+agg_hrn = conf.GENI_INTERFACE_HRN
+
+class Aggregate:
+
+    hrn = None
+    aggregate_file = None
+    components_file = None
+    slices_file = None 
+    components_ttl = None
+    components = []
+    slices = []        
+    policies = {}
+    timestamp = None
+    threshold = None   
+    server = None
+     
+
+    def __init__(self, hrn = agg_hrn, components_ttl = 1):
+        self.hrn = hrn
+       self.components_file = os.sep.join([server_basedir, 'components', hrn + '.comp'])
+       self.slices_file = os.sep.join([server_basedir, 'components', hrn + '.slices'])
+       self.timestamp_file = os.sep.join([server_basedir, 'components', hrn + '.timestamp']) 
+       self.components_ttl = components_ttl
+
+    def connect(self):
+       """
+       Connect to the plc api interface. First attempt to impor thte shell, if that fails
+       try to connect to the xmlrpc server.
+       """
+       self.auth = {'Username': conf.GENI_PLC_USER,
+                     'AuthMethod': 'password',
+                     'AuthString': conf.GENI_PLC_PASSWORD}
+
+       try:
+           # try to import PLC.Shell directly
+           sys.path.append(conf.GENI_PLC_SHELL_PATH) 
+           import PLC.Shell
+           self.shell = PLC.Shell.Shell(globals())
+           self.shell.AuthCheck()
+       except ImportError:
+            # connect to plc api via xmlrpc
+           plc_host = conf.GENI_PLC_HOST
+           plc_port = conf.GENI_PLC_PORT
+           plc_api_path = conf.GENI_PLC_API_PATH                    
+           url = "https://%(plc_host)s:%(plc_port)s/%(plc_api_path)s/" % locals()
+            self.auth = {'Username': conf.GENI_PLC_USER,
+                        'AuthMethod': 'password',
+                        'AuthString': conf.GENI_PLC_PASSWORD} 
+
+           self.shell = xmlrpclib.Server(url, verbose = 0, allow_none = True) 
+           self.shell.AuthCheck(self.auth) 
+
+    def hostname_to_hrn(self, login_base, hostname):
+       """
+       Convert hrn to plantelab name.
+       """
+        genihostname = "_".join(hostname.split("."))
+        return ".".join([self.hrn, login_base, genihostname])
+
+    def slicename_to_hrn(self, slicename):
+       """
+       Convert hrn to planetlab name.
+       """
+        slicename = slicename.replace("_", ".")
+        return ".".join([self.hrn, slicename])
+
+    def refresh_components(self):
+       """
+       Update the cached list of nodes and slices.
+       """
+       print "refreshing"      
+       # resolve component hostnames 
+       nodes = self.shell.GetNodes(self.auth, {}, ['hostname', 'site_id'])
+       
+       # resolve slices
+       slices = self.shell.GetSlices(self.auth, {}, ['name', 'site_id'])
+   
+       # resolve site login_bases
+       site_ids = [node['site_id'] for node in nodes]
+       sites = self.shell.GetSites(self.auth, site_ids, ['site_id', 'login_base'])
+       site_dict = {}
+       for site in sites:
+           site_dict[site['site_id']] = site['login_base']
+
+       # convert plc names to geni hrn
+       self.components = [self.hostname_to_hrn(site_dict[node['site_id']], node['hostname']) for node in nodes]
+       self.slices = [self.slicename_to_hrn(slice['name']) for slice in slices]
+               
+       # update timestamp and threshold
+       self.timestamp = datetime.datetime.now()
+       delta = datetime.timedelta(hours=self.components_ttl)
+       self.threshold = self.timestamp + delta 
+       
+       f = open(self.components_file, 'w')
+       f.write(str(self.components))
+       f.close()
+       f = open(self.slices_file, 'w')
+       f.write(str(self.slices))
+       f.close()
+       f = open(self.timestamp_file, 'w')
+       f.write(str(self.threshold))
+       f.close()
+    def load_components(self):
+       """
+       Read cached list of nodes and slices.
+       """
+       print "loading"
+       # Read component list from cached file 
+       if os.path.exists(self.components_file):
+           f = open(self.components_file, 'r')
+           self.components = eval(f.read())
+           f.close()
+       
+       if os.path.exists(self.slices_file):
+            f = open(self.components_file, 'r')
+            self.slices = eval(f.read())
+            f.close()
+
+       time_format = "%Y-%m-%d %H:%M:%S"
+       if os.path.exists(self.timestamp_file):
+           f = open(self.timestamp_file, 'r')
+           timestamp = str(f.read()).split(".")[0]
+           self.timestamp = datetime.datetime.fromtimestamp(time.mktime(time.strptime(timestamp, time_format)))
+           delta = datetime.timedelta(hours=self.components_ttl)
+            self.threshold = self.timestamp + delta
+           f.close()   
+
+    def get_components(self):
+       """
+       Return a list of components at this aggregate.
+       """
+       # Reload components list
+       now = datetime.datetime.now()
+       #self.load_components()
+       if not self.threshold or not self.timestamp or now > self.threshold:
+           self.refresh_components()
+       elif now < self.threshold and not self.components: 
+           self.load_components()
+       return self.components
+   
+     
+    def get_slices(self):
+       """
+       Return a list of instnatiated slices at this aggregate.
+       """
+       now = datetime.datetime.now()
+       #self.load_components()
+       if not self.threshold or not self.timestamp or now > self.threshold:
+           self.refresh_components()
+       elif now < self.threshold and not self.slices:
+           self.load_components()
+       return self.slices
+
+    def get_rspec(self, hrn, type):
+       #rspec = Rspec()
+       if type in ['node']:
+           nodes = self.shell.GetNodes(self.auth)
+       elif type in ['slice']:
+           slices = self.shell.GetSlices(self.auth)
+       elif type in ['aggregate']:
+           pass
+
+    get_slice_rspec = get_rspec(hrn, 'slice')
+    get_node_rspec = get_rspec(hrn, 'node')
+    get_aggregate_rspec = get_rpsec(hrn, 'aggregate')          
+
+    def get_resources(self, slice_hrn):
+       """
+       Return the current rspec for the specified slice.
+       """
+       slicename = hrn_to_plcslicename(slice_hrn)
+       rspec = self.get_slice_rspec(slicename)
+        
+       return rspec
+    def create_slice(self, slice_hrn, rspec):
+       """
+       Instantiate the specified slice according to whats defined in the rspec.
+       """
+       slicename = hrn_to_plcslicename(slice_hrn)
+       #spec = Rspec(rspec)
+       #components = spec.components()
+       #shell.AddSliceToNodes(self.auth, slicename, components)
+       return 1
+       
+    def delete_slice_(self, slice_hrn):
+       """
+       Remove this slice from all components it was previouly associated with and 
+       free up the resources it was using.
+       """
+       slicename = hrn_to_plcslicename(slice_hrn)
+       rspec = self.get_resources(slice_hrn)
+       components = rspec.components()
+       shell.DeleteSliceFromNodes(self.auth, slicename, components)
+       return 1
+
+    def start_slice(self, slice_hrn):
+       """
+       Stop the slice at plc.
+       """
+       slicename = hrn_to_plcslicename(slice_hrn)
+       slices = self.shell.GetSlices(self.auth, {'name': slicename}, ['slice_id'])
+       if not slices:
+           raise RecordNotFound(slice_hrn)
+       slice_id = slices[0]
+       atrribtes = self.shell.GetSliceAttributes({'slice_id': slice_id, 'name': 'enabled'}, ['slice_attribute_id'])
+       attribute_id = attreibutes[0] 
+       self.shell.UpdateSliceAttribute(self.auth, attribute_id, "1" })
+       return 1
+
+    def stop_slice(self, slice_hrn):
+       """
+       Stop the slice at plc
+       """
+       slicename = hrn_to_plcslicename(slice_hrn)
+       slices = self.shell.GetSlices(self.auth, {'name': slicename}, ['slice_id'])
+        if not slices:
+            raise RecordNotFound(slice_hrn)
+        slice_id = slices[0]
+        atrribtes = self.shell.GetSliceAttributes({'slice_id': slice_id, 'name': 'enabled'}, ['slice_attribute_id'])
+        attribute_id = attreibutes[0]
+       self.shell.UpdateSliceAttribute(self.auth, attribute_id, "0"})
+       return 1
+
+    def reset_slice(self, slice_hrn):
+       """
+       Reset the slice
+       """
+       slicename = hrn_to_plcslicename(slice_hrn)
+       return 1
+
+    def get_policy(self):
+       """
+       Return this aggregates policy as an rspec
+       """
+       rspec = get_aggregate_rspec(self.hrn):
+       return rspec
+       
+