wiki:FeddPluginCalls

Plug-in Interfaces

Access controllers respond to a series of standard calls from the experiment controller to create local allocations. These are described in conceptual detail elsewhere on this site and in a document about the design of the ProtoGENI plug-in. This section discussed the calls and their encoding for developers interested in reading and extending existing plug-in code, developers will likely want to configure and play with the skeleton plug-in when you have completed this page (or before reading it, depending on how one's neurons are arranged).

The interfaces are all described in xsd and SOAP, and these are easy enough to convert into python data structures once you know how. Below we describe the semantics of each call, and the tools available to plug-in writers to assist in implementation. These tools are available when plug-ins derive from federation.access, as federation.skeleton_access is.

Each python method in the plug-in that corresponds to a plug-in interface is called with the same parameters:

  • the request: a python dict representing the parameters to the call as encoded in SOAP
  • the calling fid: a fedid object holding the caller's identity

Each method should return the python dict encoding the response or raise a service_error containing information about the error. The service_error class contains error codes (in either the XSD or python definitions.

Many requests and responses include attribute/value pairs set by the experiment controller to plug-ins that understand them. They are not commonly used but do represent a lightweight extension mechanism. If a plug-in adds one to a sub-experiment description or topology element, the experiment controller generally passes it through transparently.

Initialization

This is not an interface from the experiment controller, but we discuss it to mention the tools available for implementors. These include routines to save state and interpret the standard parts of the access database, including specializing for testbed dependent data. The method read_access(file, local_info_parser) takes the file to read and a function that takes the string following the access attribute and returns the local testbed attributes, stored into the self.access dict, keyed by the three-name being authorized. The ___init___() routine of federation.skeleton_access includes an example of this.

The base class also creates self.auth, which is an authorizer object. This object keeps track of the authorization attributes associated with the three-level names and fedids that the plug-in knows. When reading the authorization database, the base class assigns the attributes given by the attribute field of the database to the relevant names. Generally this will an access attribute tp three-level names allowed to make allocations. Other attributes can be added by the plug-in, and the authorizer used to control acces to them. The skeleton plug-in includes code to do ths.

In addition, the initializer of the federation.access base class parses standard parameters from [access] section from the configuration file. The fields and corresponding class instance attributes are:

cert_file
The certificate file that contains the fedid that identifies this access controller. It is stored in self.cert_file.
cert_pw
Password for the certificate file, if any. Stored in self.cert_pw.
trusted_certs
Certificates used to validate the signatures of other fedd principals. Generally unused. Stored in self.trusted_certs.

Those first three attributes are initialized by the [access] section, if given, or [globals], if not.

access_state
The file storing persistent state. Stored in self.state_file.
certdir
Directory to store local certificates for allocations in. Stored in self.certdir.
project_priority
Affects the standard access lookup routines by preferring matches on project if true. Stored in self.project_priority, though most implementations will not need to access it.
create_debug
Intended to keep the plug-in from making any persistent changes to the facility. Plug-in writers are expected to honor it. It is stored in self.create_debug.
leave_tmpfiles
Intended to tell the plug-in not to delete temporary files. Plug-in writers are expected to honor it. Stored in self.cleanup, negated; that is if leave_tempfiles is false in the configuration self.cleanup is true. self.cleanup defaults to true.
type
Distinguishes between sub-types of the plug-in. Stored in self.access_type; semantics are up to the plug-in writer.
log_level
The level of log entries to pass to the logger. Any of the values in the standard python logging choices are acceptable. It is only stored as internal state of self.log, a logger allocated by the initializer.

Finally, the default initializer allocates a Lock to be acquired when accessing persistent state and reads that state from self.state_file.

The plug-in initializer is largely plug-in depenent, but must also set the methods to be called for each interface call in the self.soap_services and self.xmlrpcservices dicts.

RequestAccess

The RequestAccess command asks the testbed to map the three-level name on the call into local permissions and to do any bookkeeping associated with later attachment of resources to that allocation. Specifically, a fedid must be created. Requests from this allocation back to the central experiment controller will be made under that principal/fedid. Such requests gather configuration information or parameters passed between sub-experiments during creation. The per-allocation fedid allows fine-grained control of access to thie information at the experiment controller, as well as the ability to name (and therefore request operation on) the various allocations made on this plug-in.

The plug-in should add an authorization attribute to the creator of the allocation that indicates that the creator can manipulate the allocation via any of the other commands. Existing plug-ins do this by adding an attribute with the same value as the allocation's fedid to the authorization object.

A RequestAccess message has the following format:

request = {
    # list of strings of the form user:name project:name (these will be replaced by ABAC credentials in fedd 3.1)
    'credential': [ 'user:u', 'project:p' ]
    # Requested services
    'service': [
        # Each of these has an identifier, service name, server (optional), and a list of attributes as service parameters
        { 'id': '0001', 'name': 'project_export', 'visibility': 'export', 'fedAttr: [ { 'attribute': 'att_name', 'value': 'val1'} ] }
    ],
}

The credentials are opaquely and correctly handled by the access controller base class. Service requests encode the various services that the facility will be asked for in this allocation.

There are also some scheduling fields that are for future expansion. The DETER experiment controller never includes them.

The response message has the form:

response = {
    'allocID': { 'fedid': fedidobj },
    'service': [
        # The meta service 'export_project' in the request has become 2 actual service descriptions with the same ID as the request 
        { 'id': '0001', 'name': 'SMB', 'visibility': 'export', 'fedAttr: [ { 'attribute': 'att_name', 'value': 'val1'} ] } ] },
        { 'id': '0001', 'name': 'userconf', 'visibility': 'export', 'fedAttr: [ { 'attribute': 'att_name', 'value': 'val1'} ] },
     ]
}

The response includes the fedid of the allocation and the actual services exported. Note that in the example the single export_project request has become 2 specific service exports. See the [sources:fedd/federation/emulab_access.py emulab plug-in] for an example of how services are decoded and managed.

StartSegment

This is the operation where the plug-in has the most to do. The resources described in the segment description are attached to the allocation and configured to provide the various services, including interconnection with other testbeds.

The request has the format:

request = {
    'allocID' { 'fedid': fedid_obj },
    'segmentdescription': {
        'topdldescription': { 
             # a topdl description as a dict (see below)
        },
    },
    'service': [
        # The meta service 'export_project' in the request has become 2 actual service descriptions with the same ID as the request 
        { 'id': '0001', 'name': 'SMB', 'visibility': 'export', 'fedAttr: [ { 'attribute': 'att_name', 'value': 'val1'} ] } ] },
        { 'id': '0001', 'name': 'userconf', 'visibility': 'export', 'fedAttr: [ { 'attribute': 'att_name', 'value': 'val1'} ] },
     ],
     'connection': [ 
        {
        'type': 'transit',
        'portal': 'hostname',
        'peer': 'remote_hostname',
        'parameter': [
                  { 
                      # The parameter
                      'name': 'ssh_port', 
                      # Where to get it (local part of a url)
                      'key': 'djfahlshl/val',
                      # where to get it (host running the synchronized store)
                      'store': 'https://users.isi.deterlab.net:23235',
                      # Whether we need to send or receive the value
                      'type': 'output',
                   },
             ]
        }
    ],
}

To get the topdl representation out of the (potentially large and complex) dict that describes it, the simplest mechanism is to use the topdl.topology_from_startsegment function:

top = topdl.topology_from_startsegment(request)

That is done in all the existing plug-in code. The various plug-ins walk, alter, and write topologies in several ways, and that code is a good guide to them. The topdl source is also available.

The connection information explains how the connectivity is established between the sub-experiments. Now there are two ways to establish connections - ssh tunnels and direct transit connections. The ssh connection implies a portal in this experiment (it will be in the topology description) that will connect to the peer. Generally the port on which to communicate will be a parameter as it is here. Parameters are set using the SetValue interface, which is described below. There are a few conventional parameters:

peer
The DNS name or IP address of the other side of the connection
ssh_port
The port to contact ssh on the peer
vlan_id
For federants that are connected to transit federants, e.g., dragon, this is the VLAN ID to use in the connection

For most federants the import_store_info member of federation/access.py will import the necessary parameters correctly. For those outputting values, the export_store_info member of federation/emulab_access.py is a good starting point.

A plug-in should export its connection parameters as soon as it can determine them, and import connection parameters as late as it can in the experiment creation process.

The service parameters are the same format, and for services exported from this sub-experiment often the same content, as those returned from a RequestAccess request.

The connectivity and service information is particularly useful if the service and connectivity set-up is being done using the DETER federation kit. While other systems for providing this connectivity are fine, the federation kit provides a robust implementation that runs across several Unix based platforms. The Emulab plug-in is a good place to look at the details of configuring the federation kit.

In addition to configuring and establishing connectivity, the plug-in must convert the topology description into an embeddable topology. The topdl source includes several conversions into different formats, including emulab ns2 and ProtoGENI RSpecs (v1.0).

While there is considerable support available, StartSegment is the operation that is the most tricky for the plug-in developer.

The response has the format:

response = {
    # Same as in the request
    'allocID': { 'fedid': fedid_object },
    'allocationLog': 'a log of the allocation from the plug-in's perspective.  This may be edited or complete.',
    'segmentdescription' : { 'topdldescription': # Topdl description 
         },
    # Description of the allocation
    'embedding': [ { 'topname': 'name_from_topdl', 'physname': 'testbed_name', 'testbed': 'testbed_id_from_topdl' }, #... 
         ],
    # Generic attribute value pairs, rarely used.
    'fedAttr' : [ { 'attribute': 'a1', 'value': 'v1'}, {'attribute': 'a2', 'value: 'v2' } ],
}

The allocation log is a plug-in's eye view of the allocation. It is output by the experiment controller and can be viewed by the user using fedd_spewlog.py.

The segment description that is returned may be annotated with more detail about the segment as constructed. For example specifics of the hardware on which an element was instantiated could be filled in.

The embedding provides a way for the experiment controller to map elements from the global experiment description into a name in the plug-in's name space. Currently this is available to the user from the fedd_ftopo.py command and is used in ad hoc ways. In a future release this information will be useful in executing remote operations from the experiment controller.

TerminateSegment

This operation basically undoes the resource assignemnt and configuration accomplished by StartSegment. The sub-experiment resources are released and the connectivity and services terminated. Local facility rules on cleaning the resources and restoring them to use by others apply.

The request message looks like:

request = {
    'allocID': { 'fedid': fedid_obj },
    'force': True,
}

AllocID identifies the sub-experiment to terminate. If force is True, remove resources even if they are in the middle of being allocate or if the plug-in would otherwise not do so.

The response messgae looks like:

response = {
    'allocID': { 'fedid': fedid_obj },
    'deallocationLog': 'A long string',
}

The allocID tells which segment has been destroyed. A plug-in can also send the deallocation log to the experiment controller and on to the user. fedd_terminate.py provides an option to display or save these logs, if plug-ins send them.

ReleaseAccess

This reverses a successful RequestAccess. Remove the unique fedid for this allocation, and unbind the local resource information from it. After this call returns there will no longer be an allocation.

The request and response message are the same:

request_or_response = {
    'allocID': { 'fedid': fedid_obj }
}

allocID identifies either the allocation to release or that has been released.

Using SetValue and GetValue

The experiment controller acts as an exchange point where access controllers can exchange information they need to interconnect. Specifically the parameters in the connection structures of a StartSegment request are exchanged through the experiment controller.

A SetValue request has the form:

request = {
    'name': key_from_connection_parameter,
    'value': value_to_set,
}

Values are strings and the name is a unique key (generated by the experiment controller) that identifies the variable. Access is contolled for each variable.

The response is

request = {
    'name': key_from_connection_parameter,
    'value': value_set,
    'proof': An_ABAC_proof_of_access
}

GetValue requests are similarly formatted:

request = {
    'name': key_from_connection_parameter,
    # If wait is true, the call will block until some access controller has set the value
    'wait': boolean,
}

and the response is

response = {
    'name': key_from_connection_parameter,
    # This is optional.  If wait was not set in the request and the value is unset, this field will not be in the response
    'value': value,
    'proof': An_ABAC_proof_of_access
}

Requesting GetValue with the wait parameter set blocks the caller until the value is set. Commonly, access controllers will set their output parameters using multiple SetValue? calls and then wait for values from others using blocking GetValue calls.

Next

Developers may want to proceed to configure and play with the skeleton plug-in.

Last modified 7 years ago Last modified on Jul 16, 2012 3:13:53 PM