Resource Brokering

module:ave-broker

The broker is used to allocate resources: Handsets, workspaces, GPS simulators, programmable relays and more. It uses some simple rules for how to treat the client that performs the allocations, depending on what it tries to do. Knowing how the rules work is essential when working with AVE.

Basic Rules of Correct Usage

  • If the allocation attempt raises an exception, check its type. If the type is Busy, that means the wanted equipment exists but is allocated to someone else. Exit your program with status vcsjob.BUSY and let the scheduler that ran your program decide when to try it again:

    import sys
    import vcsjob
    
    from ave.broker            import Broker
    from ave.broker.exceptions import Busy
    
    broker = Broker()
    try:
        handset = broker.get({'type':'handset', 'pretty':'yuga'})
    except Busy:
        # tell the scheduler that the job should be retried sometime later:
        sys.exit(vcsjob.BUSY)
    
  • Client can allocate multiple resources in one call, and if the client needs some resources configured to work together, in a ‘stack’, the client should enclose these resources in a parentheses when invoking the ‘get’ method:

    # ensure workspace is allocated on the same host as the equipment,
    # no need configure workspace in the stack, for workspace, this is a
    # default behavior.
    from ave.broker import Broker
    broker = Broker()
    h, w = broker.get(({'type':'handset'}, {'type':'workspace'}))
    
    # in this case, the client intends to allocate a handset and a relay which
    # were configured to work together in a 'stack'.
    from ave.broker import Broker
    broker = Broker()
    h, r, w = broker.get(({'type':'handset'}, {'type':'relay'},{'type':'workspace'}))
    
    #client allocates two handsets, but these two handsets may be located in different
    #Host PC,
    from ave.broker import Broker
    broker = Broker()
    h1, h2 = broker.get(({'type':'handset'},), ({'type':'handset'},))
    #client allocate  two handsets, and these two handsets were configured
    #to work together in  a 'stack'
    from ave.broker import Broker
    broker = Broker()
    h1, h2 = broker.get({'type':'handset'}, {'type':'handset'})
    # or
    h1, h2 = broker.get(({'type':'handset'}, {'type':'handset'}))
    
    # in this case, client intends to allocate two handsets and one relay, and
    # one of the handsets should work together with the relay.
    from ave.broker import Broker
    broker = Broker()
    h1, r, h2 = broker.get(({'type':'handset'}, {'type':'relay'}), {'type':'handset'})
    
  • If the client disconnects from the broker, the broker will free all of the client’s resources, terminate its session and make the equipment available again. This means that the client does not need to yield resources. Simply terminate the program.

Examples of Incorrect Usage

  • Allocating a handset and a workspace separately can cause them to end up on different hosts. Interaction between them will then not work:

    # handset.push() may raise an exception about the source file not existing
    # in the file system, because the workspace downloaded it on a different
    # host:
    
    broker = Broker()
    handset = broker.get({'type':'handset'})
    workspace = broker.get({'type':'workspace'})
    
    path = workspace.download_git(...)
    handset.push(path+'/some_file', '/system/data')
    
  • The broker disconnects your client if an allocation fails for any reason. The rationale is that a misbehaving client should not be able to loop on the allocation request because that may lead to deadlocks between two clients that fight for the same set of resources:

    import time
    
    from ave.broker            import Broker
    from ave.broker.exceptions import Busy
    
    broker = Broker()
    
    # THE FOLLOWING WILL NOT WORK. the broker disconnects the client on the
    # first failed allocation attempt, after which broker.get() will raise a
    # different exception, telling you that the client's session has been
    # terminated.
    while True:
        try:
            h1 = broker.get({'type':'handset', 'pretty':'yuga'})
            h2 = broker.get({'type':'handset', 'pretty':'togari'})
            break
        except Busy:
            time.sleep(1) # retry
    
  • The client must keep a reference to its Broker instance to prevent the broker from disconnecting the client. Here is a common pitfall that will not work:

    from ave.broker import Broker
    
    def allocate():
        broker = Broker()
        return broker.get({'type':'handset', 'pretty':'yuga'})
        # broker object is garbage collected here
    
    handset = allocate()
    handset.ls('/') # will raise ConnectionRefused
    

API for Regular Clients

class ave.broker.Broker
get(*profiles)

Allocate one or more resources based on profiles.

Parameters:

profiles – One or more dict objects that encode resource profiles. All profiles must have the "type" field set.

Returns:

The same number of resource objects as there were profiles in the request.

Raises:
  • Busy – If the request could not be satisfied because all matching equipment is currently allocated to some other client.
  • NoSuch – If the request could not be satisfied because no match is possible at all. I.e. the broker knows of no such equipment, busy or not.
  • Restarting – If the broker has restarted since the client connected to it. When restarting, the old broker instance remains running to keep track of all sessions it created, but is forbidden to allocate more equipment (because the replacement broker now has that privilege). A client that makes all of its allocation in the close succession is extremely unlikely to ever see this exception.
  • AveException – The parameters could not be validated. E.g. if a profile is malformed.

Example allocations:

b = Broker()

# any android handset on the GTE network together with a workspace:
h,w = b.get(
    {'type':'handset', 'platform':'android', 'gsm.operator':'GTE'},
    {'type':'workspace'}
)

# a Hayabusa handset running FirefoxOS of a specific label, together
# with a programmable relay that has been wired to cut the USB and
# battery connections of the handset, plus a workspace:
h,r,w = b.get(
    {
        'type':'handset',
        'platform':'firefox',
        'sw.label':'ICS-BLUE-FF-130516-1131'
    },
    {'type':'relay', 'circuits':['usb.pc.vcc', 'handset.battery']},
    {'type':'workspace'}
)

If the broker cannot satisfy the request and a forwarding rule is set to point to another broker on the network, then the request will be passed on and tried there instead. This chain can be arbitrarily long.

API for Administrative Clients

Connecting as administrator gives the client new capabilities and removes some. The administrator role is supposed to be used by web frontends and the like, not by regular programs.

class ave.broker.Broker(authkey)
Parameters:authkey – An authentication string that must match the value of the "admin" field in .ave/config/authkeys.json.
get(*profiles)
Raises ConnectionClosed:
 The client is not allowed to allocate resources while logged in as administrator.
list_allocations_all()
Returns:A list of profiles representing the combined allocations of all clients.
list_collateral_all()
Returns:A list of profiles representing the collateral of the combined allocations of all clients.

Collateral is any equipment that is part of a stack that the client has taken some other resource from, but not the equipment itself. It is an internal book keeping mechanism that secures that different clients cannot accidentally get resources that will interfere with each other when manipulated.

start_sharing()

If the broker has a sharing rule, this call will force it to start sharing its equipment over the network.

stop_sharing()

Stop sharing equipment with another broker.

list_shares()

List all brokers that share equipment with this broker.

drop_share(address)
Parameters:address – A (host, port) tuple.

Causes the broker to disconnect another broker that shares equipment with it. The call has no effect if no such broker is connected. Note that sharing brokers are by default configured to try to reconnect when they loose the connection. This call is only useful for diagnostics.

drop_all_shares()

Causes the broker to disconnect all brokers that share equipment with it. Note that sharing brokers are by default configured to try to reconnect when they loose the connection. This call is only useful for diagnostics.

list_equipment(profile=None)

Returns a list of equipment that matches the profile. The equipment does not have to be available for allocation.

list_available(profile=None)

Returns a list of equipment that matches the profile and is available for allocation.

list_stacks()

Returns a list of the broker’s configured equipment stacks. Stacking determines what equipment can be allocated together. (Workspaces can always be allocated together with equipment and is not listed in the stacks.)

Starting and Stopping the Broker

On systems that use Upstart, the broker and other AVE services will be started by init on system boot. On other systems, the user has to start the broker manually:

ave-broker --start

The broker can be restarted without affecting clients and their sessions:

ave-broker --restart

Stopping the broker terminates all open sessions and disconnects all clients:

ave-broker --stop

Configuration Files

The files are located under $home/.ave/config, where the value of $home is read from /etc/ave/user.

Changes to the files take effect when the broker is restarted:

# change the config files
ave-broker --restart

Note

Some changes require a full stop and start of the broker: Port numbers and changes to authentication keys:

ave-broker --stop
# change the config files
ave-broker --start

broker.json

The smallest valid configuration is just the empty dictionary:

{}

A broker that is configured to forward failed allocations to another broker uses the remote rule (always use port 4000 unless you know what you are doing):

{
    "remote": {
        "host"  : "hostname",
        "port"  : 4000,
        "policy": "forward"
    }
}

A broker that is configured to share its equipment with another broker also uses the remote rule, but with slightly different settings. The authkey field must match the share value of the remote broker’s authkeys.json file:

{
    "remote": {
        "host"   : "hostname",
        "port"   : 4000,
        "policy" : "share",
        "authkey": "sharing key of the other broker"
    }
}

A broker that is configured to handle stacked equipment can add the stacks field. Each entry must be a list of profiles that contain the type field and a field that uniquely identifies an instance of that kind of equipment. In the following example, handsets (identified by serial number) are stacked against relays (identified by symbolic ID’s):

{
    "stacks":[
        [{"type":"relay","uid":"a"}, {"type":"handset","serial":"CB5A1JYXRQ"}],
        [{"type":"relay","uid":"b"}, {"type":"handset","serial":"CB511VD8AF"}],
        [{"type":"relay","uid":"c"}, {"type":"handset","serial":"CB5A1M7CTP"}]
    ]
}

A broker configuration can contain both remote and stacks rules.

Note

Although JSON is great for comfortable configuration handling, it is a format with some limitations:

  • It is not possible to put comments in the files. Hopefully this document is so smashingly fantastic that this is not a problem...
  • If the same field occurs more than once, the Python JSON parser simply keeps the last entry and silently overwrites whatever was already there. E.g. it is not possible to use the remote rule twice, but the broker will not complain about it because the JSON parser doesn’t.

authkeys.json

Certain adiministrative functions in the broker are protected by authentication keys to prevent accidental usage of functionality that are not meant to be used by regular clients. These are keys are generated automatically when AVE is installed but can be changed to more memorable values:

{
    "admin": "Some random stuff... 1234!",
    "share": "please stick to printable characters, ok?"
}

In the API documentation above, functions that require use of the admin key have been put under their own section.