-
Notifications
You must be signed in to change notification settings - Fork 0
GENI
A GENI test bed needs three entities:
- Aggregate Manager (AM): A server which takes handles resource allocation and management.
- Clearinghouse (CH): A server to provide the experimenter with the needed credentials to send to the AM (certificates and credentials).
- Client: The client sends the experimenters request to the AM. First, the it retrieves the user's certificate and credentials from the CH (if not availabe already). Second, it sends the actual request to the AM.
GENI provides a reference framework for test beds compatible with GENI. AMsoil includes parts of these reference implementations. The GCF includes a clearinghouse which can be run to get certificates and credentials and a client called omni. Please see the Development page or the GENI wiki for more info on using them.
Note: The clearinghouse within the GCF is only a dumb implementation. It issues certificates for experimenters and credentials for slices without checking if those users are actually approved to do so. So the implementation is fine for developing, but not for production mode.
GENI uses URNs for identifying objects. Hence experimenters, slices and slivers have URNs (more info via the GENI wiki). Here the most common objects:
- Experimenter: A human user who uses a client to manage resources via an AM.
- Sliver: A physical or virtual resource. It is the smallest entity which can be addressed by an AM. Example: an IP address, a Virtual Machine, a FlowSpace.
- Slice: A collection of slivers.
The current version of the GENI AM API is 3. The version 3 and the previous version 2 is supported by the GCF and omni.
A major change between version 2 and 3 is that version 3 includes a two-stage commit. See below for more info on states.
AMsoil has a plugin which implements version 3 (see below).
Noteworthy information on the API can be found on the wiki:
Here a quick peek into all the availabe methods:
- GetVersion
- ListResources
- Describe
- Allocate
- Renew
- Provision
- Status
- PerformOperationalAction
- Delete
- Shutdown
Please see the GENI wiki for a detailed description.
Authentication in the GENI AM API is done via certificates. The client (e.g. omni) sends the experimenter's certificate with each request. This way the server can check if the client is really who it claims to be. Certificates are signed by the clearinghouse and the AM needs to check each request against a number of trusted CH root-certificates. The AM does not need all experimenter certificates, it rather checks if the sent experimenter certificate is signed by a trusted entity. Please find more info on the GENI wiki.
Authorization is performed via credentials. Credentials are used to authorize actions (in contrast to certificates which identify and authenticate). They specify the permissions of the experimenter relative to a slice or resource. Please find more info on the GENI wiki.
The GENI AM API version three introduces the concept of allocation state, operational state and operational actions.
Allocation state captures the reservation status of the resource:
First, allocate is called to reserve the slice/sliver for a short period of time (e.g. 10 minutes). During this state no resources are instanciated, they are just blocked from being reserved for others. In the second stage, provision is called which actually instanciates resources. Now the sliver/slice actually takes up resources (e.g. CPU power or disk space). Typically the expiry time of a provisioned slice/sliver is a matter of hours or days (e.g. 3 days). Allocation states include: geni_unallocated
, geni_allocated
and geni_provisioned
. Please find more info on the GENI wiki
Operational state determines the status of the resource in regard to its functional availability:
When a sliver is in geni_provisioned
state, the resource is associated with an operational state (e.g., a virtual machine can be geni_pending_allocation
/ geni_configuring
during the bootup and geni_ready
when the machine is running). GENI predifines a number of these operational states. AMs are only required to implement the OPERATIONAL_STATE_PENDING_ALLOCATION
state. In order to describe the slivers' state, the AM developer can either use the predifined states or define new states. Please find more info on the GENI wiki.
Operational actions are used to trigger a change of the operational state:
The GENI AM API v3 predefines number of operational actions which can be used in the PerformOperationalAction
method. These operational actions trigger a change of state in the sliver. The AM developer is encouraged to use the predefined action names, but can add new states. Please see the GENI wiki for more details.
GENI uses an XML format to specify resources. This format is called RSpec and the GENI v3 RPC plugin assumes that RSpecs are given in RSpec version 3.
The specification of resources fulfills multiple purposes. Hence, there are three different sub-types of RSpec documents (creator->receiver)
:
-
Advertisement (short: ads) Announces which resources/slivers are available and what capabilities they have (AM->client, via the
ListResources
call). -
Request Specifies the wishes of the experimenter/client and includes the parameters needed for allocation/provisioning (client->AM, via the
Allocate
call). -
Manifest Shows the status of a sliver (AM->client, e.g. via the
Status
call)
More information on RSpec3 can be found here and here
There are three entities involved in writing a GENI v3-based aggregate manager. These entities have the following tasks:
- RPC Handler Handles the incoming request and deals with XML-RPC-specific details (e.g. acquiring the client certificate, throwing the correct error codes)
- Delegate (derived from DelegateBase) Mediates between the RPC Handler and the resource manager.
- Resource Manager Handles the resources.
Why so many entities?
We need to decouple the RPC API from the resource management logic. This enables AMsoil-based AMs to implement multiple APIs (e.g. GENI, SFA, OFELIA APIs) without having to re-write everything. The following concept provides the bare minimum to achieve this goal. As a little bonus AMsoil provides a couple of helper methods which make your life easier.
Relationships & responsibilities
The RPC Handler registers itself to the RPC server and handles all incoming requests for GENI AM API v3. After doing some initial processing it passes the request on to the Delegate. Hence, the RPC Handler may be associated with exactly one delegate. Typically, the delegate calls the setDelegate(...)
during its bootstrapping.
The Delegate sits between the RPC Handler and Resource Manager(s). It takes the request from the handler and translates it to the domain-specific interface of the Resource Manager. The delegate may dispatch the calls to one or more Resource Managers. The processing of the handler calls could be done directly in the delegate, but it is strongly discouraged. The actual resource logic should reside in Resource Managers so it can be re-used with other APIs (e.g. GENI AM API v2 or OFELIA API).
The Delegate derives from DelegateBase. The DelegateBase provides a set of methods commonly needed for translating between Resource Managers and the GENI AM API v3. For example, it includes a number of methods which help creating RSpecs and dealing with the XML-schema specifics.
The Resource Manager is a free-form plugin which handles the allocation, provisioning, etc. of resources. There are no constraints on Resource Managers because the interface the manager implements needs to be domain-specific. For example, a virtual machine should have a start()
method, whereas a DHCP lease does not need such a method.
RPC Handler 1:1 Delegate
Delegate 1:n Resource Manager
DelegateBase |> Delegate
Example
Let's say the experimenter uses omni as a client. He specifies his resource wishes and omni sends a request to the Aggregate Manager. The XML-RPC framework takes the request and passes it on to the GENI v3 RPC Plugin. The RPC handler acquires all sorts of information from the request/client and passes all information to the Delegate. The Delegate may take the parameters and change the RSpec to domain-specific value objects which can then be sent to the Resource Manager's methods.
Implementing an AM
Usually, the RPC Handler stays as it is. There are no changes required. First, you would implement the Resource Manager as a new plugin. The methods and value objects can be defined freely to fit the resource type and requirements. Second, you would create a new Delegate to translate the GENI methods, specification language and authentication schemas to your Resource Manager.
As we said above the code of the RPC Handler remains untouched. Your (new) Delegate should only register itself to the handler so it receives the requests from the handler (typically in the Delegate's setup
-method):
delegate = MyGENI3Delegate()
handler = pm.getService('geniv3handler')
handler.setDelegate(delegate)
Note that all values coming from or going back to the handler are Python objects/classes. The handler takes care of the conversion of the XML-RPC values.
As described above, the Delegate mediates between GENI specifics and resource specifics.
Each Delegate shall be derived from the class given by the service geniv3delegatebase
. Please see the classes API documentation for the required methods to be overwritten and which helper methods are available.
The tasks of delegates include (please take a minute to think about each bullet point):
- Translate GENI API methods to the Resource Manager(s) methods
- Translate the RSpecs into value objects which are understood by the Resource Manager (and back).
- Catch all errors thrown by the Resource Manager and re-throw them as GENIv3... errors (see the service
geniv3exceptions
). - Translate from the namespace of GENI (URNs) to the resource manager namespace (e.g. UUIDs or database IDs)
- Specify the needed privileges for authorization
- De-multiplex RSpecs to dispatch to different Resource Managers (if you have multiple resource types in one AM)
Example of a Delegate implementation (only one method shown):
GENIv3DelegateBase = pm.getService('geniv3delegatebase')
geni_ex = pm.getService('geniv3exceptions')
class MyResourceGENI3Delegate(GENIv3DelegateBase): # derive from DelegateBase
# ...
def allocate(self, slice_urn, client_cert, credentials, rspec, end_time=None): # This is one GENI method
# perform authentication and check the privileges
client_urn, client_uuid, client_email = self.auth(client_cert, credentials, slice_urn, ('createsliver',))
rspec = self.lxml_parse_rspec(rspec) # call a helper method to parse the RSpec (incl. validation)
for elm in rspec.getchildren(): # interpret the RSpec XML
if not self.lxml_elm_has_request_prefix(elm, 'myresource'): # do some checking
# throw a GENI-specific error
raise geni_ex.GENIv3BadArgsError("RSpec contains elements/namespaces I dont understand (%s)." % (elm,))
try:
# call a resource manager and make the allocation happen
self._resource_manager.reserve_lease(resource_id_from_rspec, slice_urn, client_uuid, client_email, end_time)
except myresource.MyResourceNotFound as e: # translate the resource manager exceptions to GENI exceptions
raise geni_ex.GENIv3SearchFailedError("The desired my_resource(s) could no be found.")
sliver_status = {'status' : '...omitted...'}
manifest_xml = "<xml>omitted</xml>"
return self.lxml_to_string(manifest_xml), sliver_status # return the required
You are free to add new methods to the Delegate, but please make sure you implement all GENI methods (see API documentation). Also please make yourself familiar with the helper-methods marked with @serviceinterface
.
As seen in the example above the DelegateBase provides a method called auth. This method ensures that the request sender has enough privileges. After authorizing the user, the method extracts the information (e.g. user URN and email if given) and returns it.
Please see the API documentation of this method in the [geniv3rpc] genivtree package. In the API documentation is also a list of possible privileges.
DelegateBase
defines constants for allocation and operational state (e.g. ALLOCATION_STATE_UNALLOCATED
, OPERATIONAL_STATE_READY
). Your delegate implementation is responsible for translating the states of the Resource Manager to the appropriate GENI states (also see the Basics - States section above).
The DelegateBase class provides a set of helper methods which assist in generating RSpecs. These methods are based on lxml. E.g. lxml_parse_rspec(...)
method returns a given RSpec as DOM. The lxml_manifest_root(...)
and lxml_ad_root(...)
methods create a new root element with all the schema's needed. The lxml_xxx_element_maker(...)
methods create a lxml ElementMaker (aka E-factory) for easily creating XML elements.
Note lxml uses full qualified names for element names (including the namespace URI). When you want to check for the name of an element please make sure to include the namespace URI like so: {NS_URI}TagName
(see more info). Either assemble the full qualified name yourself or use the methods lxml_elm_has_request_prefix(elm, prefix)
or lxml_elm_equals_request_tag(elm, prefix, tagname)
.
In each Delegate the get_request_extensions_mapping()
, get_manifest_extensions_mapping()
and get_ad_extensions_mapping
methods should be overwritten. These methods shall return a hash of the form python { 'PREFIX' : 'NAMESPACE_URI'}
, whereby the PREFIX
is an arbitray name which is used to identify the XML namespace URI. Some of the methods above take such a prefix as an argument. This argument is given to create the XML elements with the correct namespace URIs.
Please note that XML/RSpec validation errors show up in AMsoil's log, so please have a close eye on it (also see the Development Process).
Also note, that the validation of the schema downloads the schemas from the given URL. This may take time. While you are developing, it is faster to deactivate the geniv3rpc.rspec_validation
configuration key.
Example:
class MyResourceGENI3Delegate(GENIv3DelegateBase):
# ...
def get_manifest_extensions_mapping(self): # define the prefix
return {'myres' : 'http://example.com/myresource'}
def some_method(self, resources):
manifest = self.lxml_manifest_root() # creates a document with all schemaLocations set (given in get_manifest_extensions_mapping(...))
E = self.lxml_manifest_element_maker('myres') # creates
list = []
for res in resources:
e = E.resource() # create new <resource/> element
e.append(E.myresource(res['id']))
# ... add more info ...
manifest.append(e)
return manifest
Would result in
<?xml version="1.0" ?>
<rspec myres="http://example.com/myresource" type="manifest">
<myres:resource xmlns:myres="http://example.com/myresource">
<myres:myresource>some_id</myres:myresource>
</myres:resource>
</rspec>
The Resource Manager is the workhorse in regard to actual resource handling. Resource Managers are typically implemented as a new plugin per resource type. The tasks of a resource manager are:
- Instanciation of resources
- Manage persistance of reservations and resource state
- Check policies (see the Policy plugin)
- Avoid collisions of resources reservations / Manage availability
- Throw domain-specific errors
The interface of the manager is arbitrary and should be designed according to the needs of the resource type handled. This domain-specific design shall ensure that Resource Managers are flexible enough to cover all resource types and to be re-used throughout different RPC APIs (e.g. GENI AM or OFELIA API).
On one hand the domain-specific interface requires methods/actions to be fitted to the resource type. On the other hand, the values used to specify a resource (and the returned values) are also domain-specific. Usually, the concrete implementation of a Resource Manager would define value objects / parameters to methods which fit the resource type (please also see Persistence). This implies that passing API-specific documents (e.g. RSpecs) is strongly discouraged. On the third hand - haha -, exceptions should also be domain-specific.
Consider the following example:
class BadRM(object):
allocate(self, rspec): # THIS IS BAD: GENI-specific method and GENI-specific specification language.
throw GENIv3NotFound() # BAD BAD BAD!!!
class GoodRM(object):
reserve_myresource(self, characteristic_1, characteristic_2): # THIS IS GOOD: domain-specific method and domain-specific values
throw MyResourceOutOfMemory() # Good.