Blog

Archive for the ‘Cloud Compute’ Category

SMP: Queued Locks – Part 9

Posted on: May 30th, 2012 by stephenbroeker No Comments

I’m wrapping up a nine-part series on Symmetric Multi Processors (SMP). Following up on #8 the Random Back-off Problem which focused on a solution to isolating locks so that a cache line can only hold a single lock, I’ll conclude with another solution that completely solves the Thundering Herd problem in an optimal manner: Queued Locks.

I’ve shown you how a 32 bit cpu commonly uses an 8-word cache line.  And we only want a single lock (or word) on a cache line.  We can use the rest of the cache line to implement a Queued Lock.

Queued Locks

The Queued Lock algorithm essentually consists of having a cpu add itself to the end of a queue when it is waiting for a lock (this gives us fairness).  The cpu then does a tight spin on a private cache line, waiting for the lock to be released (this gives us optimality).  When the lock is released, the holding cpu picks the first cpu off of the queue (this is fair) and pokes the private cache line of that cpu to wake it up (optimal).

Single Cache

Here is the Queued Lock data structure that resides on a single cache line:

struct queued_lock
{
    int  mutex; // locks this data structure.

    int  value; // 0 = free, 1 = locked

    char  queue[24];
};

This data structure can thus handle a 25 cpu system.  The queue field contains cpu numbers.  A zero value indicates that the entry is free.

Private Wait Array

Here is the Private Wait Array:

struct private_wait
{
    int  value;

    int  fill[7];
};

struct private_wait private_wait_array[25];

This array is indexed by the cpu number.

Queued Lock Algorithms

Here is the Queued Lock lock algorithm:

queued_lock_lock (struct queued_lock *lock)
{

Now that I've laid out various scenarios and solutions, have I missed anything? What solutions would you add?
    while (test_and_set(lock->mutex) == 1)    // lock the data structure
        ;

    if (lock->value == 0)    // lock is not set, take it
    {
        lock->value = 1;
        lock->mutex = 0;    // unlock the data structure
    }
    else    // lock is set, add myself to the queue, and wait
    {
        for (index = 0; ; index++)
        {
            assert(index < 24)

            if (lock->queue[index] == 0)
                break;
        }

        lock->queue[index] = my_cpu_number + 1;
        private_wait_array[my_cpu_number]->value = 0;
        lock->mutex = 0;    // unlock the data structure

        while (private_wait_array[my_cpu_number]->value == 0)
            ;
    }
}

And finally here is the Queued Lock unlock algorithm:

queued_lock_unlock (struct queued_lock *lock)
{
    while (test_and_set(lock->mutex) == 1)    // lock the data structure
        ;

    if (lock->queue[0] == 0)  // no one is waiting
    {
        lock->value = 0;
    }
    else  // somebody is waiting, poke them
    {
        cpu = lock->queue[0] - 1;
        memmove(&lock->queue[0], &lock->queue[1], 23);
        private_wait_array[cpu]->value = 1;
    }

    lock->mutex = 0;    // unlock the data structure
}

SMP: Random Backoff Solution – Part 8

Posted on: May 23rd, 2012 by stephenbroeker No Comments

Last time I presented a solution to the Thundering Herd problem: the Random Backoff, but noted a problem with solution.  It is quite possible that when a cpu releases a contentious lock, waiting cpus will not wake up immediately.  That is, it could take up to a second before a waiting lock tries to obtain the now-free lock.  This solution is thus not optimal.

As a follow-up I’d like to present a SMP refinement that I believe solves this problem: isolating and separating all locks so that there is a single lock per cache line.  Why do we do this?

Isolated & Separated Locks

Consider two SMP locks: L1 and L2, both on a single cache line.  Note: on a 32 bit cpu, a word is 32 bits and a cache line is commonly 8 words, and a single cache line could thus hold 8 locks.  If a cpu locks L1, then its cached copy of L1 is updated and the cache copy of L1 for the other cpus is invalidated.  A waiting cpu will thus have to get the up to date copy of L1 from main memory.  This is an expensive operation in time and bus traffic.  If L2 is also on the same cache line, then L1 traffic will invalidate L2 cache copies.  The L2 cache invalidates are not necessary for L2 cache coheriency and thus adversly effect system performance.

If we seperate L1 and L2 and place them on different cache lines, then L1 activity does not effect L2 activity and visa versa.

Memory Wasted

The downside of this solution is that memory is wasted.  Optimally (as far as memory is concerned) we could pack 8 locks into a single cache line.  So for every lock we are wasting 7 words.  If we consider Moore’s Law and how the price of memory chips is constantly dropping, I think this solution is worth the price.

So how much does this solution help?  Turns out that cache line isolation does not completely solve this problem.  It improves the situation, but does not completely elliminate it.  So in my next post, I will present another solution to the SMP Random Backoff problem.

SMP: Thundering Herd Solution – Part 7

Posted on: May 16th, 2012 by stephenbroeker No Comments

In Part 6 I presented Symmetric Multi-Processor’s (smp) Thundering Herd problem; it’s the result of a lack of fairness in the mutex algorithm.  This problem can occur when multiple cpus are using a lock under high contention. Next I’d like to share one of the smp solutions we used at Pyramid to solve this problem.

SMP Solution 1: Random Backoff

You may recall the mutex algorithm as:

mutex (void *addr)
{
    while (test_and_set(addr) == 1)
        ;
}

The problem is that when multiple cpus are waiting for a single lock, the while loop is too predictable.  It lacks variation.  What we need is a random period before the next call to call test_and_set().  We thus create a Random Backoff array.  The size of this array should be large relative to the number of cpus.  In the case of 24 cpus, we use an array size of 1024.  We then initialize the array entries to random numbers (in micro-seconds) with an appropriate upper bound of 1 second.

So the mutex algorithm now becomes:

mutex (void *addr)
{
    int  index;
    extern int random_backoff[];

    while (test_and_set(addr) == 1)
    {
        index = random() % 1000000;
        usec = random_backoff[index];
        usleep(usec);
    }
}

The particular use of random() is not required, any type of random number generater will do.  What is important is how the random number generater is seeded.  Each cpu must use a different seed.  I recommend using the cpu number multiplied by the current date.

Notice how we index the array with a random number.  If we did not did this, then indexing the array could become predictable.

Contentious Locks

There is a problem with this smp solution though.  It is quite possible that when a cpu releases a contentious lock, waiting cpus will not wake up immediately.  That is, it could take up to a second before a waiting lock trys to obtain the now free lock.  Thus the Random Backoff solution is not optimal.

In my next blog, I will present solutions and refinements to this situation.

SMP: Thundering Herd Problem – Part 6

Posted on: May 9th, 2012 by stephenbroeker No Comments

My last five blogs posts have presented:

  1. The Symmetric Multi Processor (SMP) scaling problem.
  2. The solution as presented by Pyramid Technology.
  3. The user space SMP problem.
  4. The various lock primitives.
  5. Debugging locks.

Now, let’s explore something called the Thundering Herd Problem. If you recall, a previous blog presented the mutex algorithm as:

mutex (void *addr)
{
    while (test_and_set(addr) == 1)
        ;
}

Now consider the scenerio where Process 1 (P1) is holding a lock and two other processes (P2 and P3) are trying to obtain the same lock.  If P2 has waiting for a long time and P3 has been waiting for a small amount of time, then there is nothing in the mutext algorithm that enforces fairness.  That is, once P1 releases the lock, P3 can immediately obtain it, even though P2 has been waiting for a much longer time.

Now extend this problem to a 12 CPU SMP system.  One cpu could hold a lock and 11 cpu’s could be waiting on the same lock.  And extend this problem to a 24 CPU SMP system.  One cpu could hold a lock and 23 cpu’s could be waiting on the same lock.

In this case, when a cpu releases a lock, possibly 23 other cpus could all try to get the same lock.  This mass attempt to obtain a single lock results in a non linear increase in bus and cache traffic.  The Thundering Herd is the 23 other cpus.  And the problem is, only one cpu is going to obtain the lock.  The other 23 cpus go back into a wait state and the process continues.  Since fairness is not built into this algorithm, there is no gaurantee that a particular cpu will obtain the lock in a reletively fair fashon.

Are there locks that suffer from this kind of contention?  Does the Thundering Herd problem occur in a real engineering envirnoment?  Or, is all of this just an interesting theory?

At Pyramid we actually ran into this problem.  In the Unix kernel, there a number of locks that experience this kind of contention.  And we experienced the Thundering Herd problem when we expanded our cachsis from 12 cpus to 24 cpus.  We actually ran into Lock Timeouts (30 seconds) where no single cpu was holding the lock for an excessive amount of time.  The problem was that the mutex algorithm was not fair, a waiting cpu was starved for attention.

In my next post I will present solutions to this problem.

SMP: Debugging the System Part 5

Posted on: May 2nd, 2012 by stephenbroeker No Comments

My last four blogs posts have presented:

  1. Symmetric Multi Processor (SMP) scaling problem.
  2. Solution as presented by Pyramid Technology.
  3. User space SMP problem.
  4. Various lock primitives.

It’s logical to now explore how to debug an SMP system. But first I need to present the classic locking problem as defined by Dijkstra.

Classic Locking Problem

Consider two processes: P1 and P2; and two locks: L1 and L2.  Now consider the following locking scenerio: P1 locks L1 (P1.L1) and then L2 (P2.L1); P2 locks L2 (P2.L2) and then L1 (P2.L1).

If the locks are aquired in the order: (1: P1.L1, 2: P2.L2, 3: P1.L2, 4: P2,L1), then steps 3 and 4 will never complete.  This scenerio is called a deadlock in that both processes are stuck waiting for locks that are held by each other.

So how do we deal with a SMP deadlock?  There are essentually two ways to deal with deadlocks.  Method 1: try to detect when a deadlock has occured.  Method 2: prevent deadlocks from occuring.

The first method is somewhat problomatic in that you have to keep track of all of the locks in the system and periodically monitor them for a deadlock.

The second method is much easier to implement.  Pyramid dealt with this problem by adding code to detect when a deadlock was immenant.  To wit, a positive, non-zero level was assigned to each lock and the lock protocol was modified to ensure that a lock could only be aquired if the lock level was increased.

Now let us apply lock levels to the Dijkstra lock problem.  L1 will now be defined as L1.5 (lock 1 with level 5) and L2 will now be defined as L2.10 (lock 1 with level 10).  If the locks are aquired in the order: (1: P1.L1, 2: P2.L2, 3: P1.L2, 4: P2,L1), then step 4 will generate a Lock Level Violation since P2 is holding L2 (which has a level of 10) and P2 is trying to lock L1 (which has a level of 5) – you can only increase the lock levels.

Handling SMP Interrupts

There is another problem that we have to deal with: interrupts.  If a process is holding a lock and interrupts occur, then it will look like the process is holding the lock for a long time.

This problem is dealt with by adding an interrupt level to the definition of a lock.  In addition, the lock protocol is expanded by requiring that the cpu interrupt level be set to the lock interrupt level.  Setting the cpu interrupt level blocks interrupts at a lower level.  System interrupts are defined by levels, for example: 0 = none, 1 = tty, 2 = network, 3 = disk, etc.  The higher the interrupt level, the higher the priority.  Higher level interrupts block out lower level interrupts.  Thus, setting the interrupt level of a cpu to priority X, blocks all interrupts lower than and including level X.

Defining the Locking Protocol

So a lock is now defined as: (Name, Level, Interrupt).  And the locking protocol is now defined as:

1)  If the new lock level is less than the current lock level, then generate a Lock Level Protocol Violation.

2)  If the new lock interrupt priority is less than the current cpu interrupt priority, then generate a Lock Interrupt Protocol Violation.

Next up? SMP’s Thundering Herd Problem.

Openstack Swift TempAuth Module

Posted on: March 28th, 2012 by stephenbroeker No Comments

Last time I presented a Swift REST API example. Now I’ll explain the Openstack Swift Test Authentication and Authorization System (tempauth). This is an excellent authentication module for Swift All In One (SAIO) and for development work.

Add Tempauth to Openstack Swift Proxy Server

The first thing that you will need to do is to add tempauth to the Proxy Server configuration. So make sure that the following is in proxy-server.conf:

[pipeline:main]
pipeline = catch_errors cache tempauth proxy-server

[app:proxy-server]
account_autocreate = true

[filter:tempauth]
use = egg:swift#tempauth
user_admin_admin = admin .admin .reseller_admin
user_test_tester = testing .admin
user_test2_tester2 = testing2 .admin
user_test_tester3 = testing3

The configuration lines under the [filter:tempauth] section that begin with “user_”, define a users login, password, and privileges. These lines are of the form: user_<login1>_<login2> = <password> <privileges>

Four Users, Four Permissions

This configuration enables tempauth and creates the following four users, each with different permissions.

1) login = admin:admin
password = admin
privileges = .admin, .reseller_admin

2) login = test:tester
password = testing
privileges = .admin

3) login = test2:tester2
password = testing2
privileges = .admin

4) login = test:tester3
password = testing3
privileges = None

Openstack Swift Privileges

So what do the privileges mean?

1) admin
Admin users can do anything within their account.

2) reseller_admin
Reseller Admin users can do anything to any account.

3) None
Non-Admin users can only perform operations per container based on the container’s X-Container-Read and

X-Container-Write ACLs.

To allow a “user” to read the objects in container, then set the container header “X-Container-Read: .r:user”.

To allow a “user” to list the contents of a container, then set the container header “X-Container-Read: .rlistings”.

To allow a “user” to read and list the objects in container, then set the container header

“X-Container-Read: .r:user, .rlistings”.

To allow anyone to write to a container, then set the container header “X-Container-Write: .r:*”.

For complete ACL details, check out Openstack Swift dev documentation.

When a Non-Admin user is created, then the only way to create X-Container-Read and X-Container-Write headers is via a Reseller Admin user.

Or, do you have another solution?

Swift REST API Example

Posted on: March 14th, 2012 by stephenbroeker No Comments

Swift REST API Example

Last post I explained REST interfaces, and promised to use a Swift REST API as an example.   This application programming interface (API) supports the following operations.

1)  Swift REST API Authorization

GET Authorization

————————

This operation is used to obtain an authorization token and URL for a  given user login and password.   This token and URL are then used in any subsequent operations.

  • URL Data: None.
  • Required Request Headers:
    • X-Auth-User = user login.
    • X-Auth-Key = user password.
  • Optional Request Headers: None.
  • Parameters: None.
  • Response Headers:
    • Content-Length = always zero.
    • Date = current date.
    • X-Auth-Token = authorization token, input for subsequent operations.
    • X-Storage-Token = storage token, input for subsequent operations.
    • X-Storage-Url = storage URL, input for subsequent operations.
  • HTTP Data: None.

2) Swift REST API Account

DELETE Account
————————

Mark an account as deleted.   Swift REST API will clean up the account as time permits.

  • URL Data: None.
  • Required Request Headers:
    • X-Auth-Token = authorization token.
  • Optional Request Headers: None.
  • Parameters: None.
  • Response Headers:
    • Content-Length = always zero.
    • Content-Type = http data content type.
    • Date = current date.
  • HTTP Data: None.

GET Account
————————

Get the list of containers in an account.

  • URL Data: None.
  • Required Request Headers:
    • X-Auth-Token = authorization token.
  • Optional Request Headers: None.
  • Parameters:
    • format=type : return http data in “json” or “xml” format.
    • limit=number : limit the number of returned containers.
    • marker=filter : provide a filter. The returned list of containers will start after “filter”.
  • Response Headers:
    • Accept-Ranges = always “bytes”. This header will eventually comply with the http “range” header.
    • Content-Length = the number of bytes in http data.
    • Content-Type = http data content type.
    • Date = current date.
    • X-Account-Bytes-Used = the number of bytes used in the account. Keep in mind that this field is updated in a lazy fashion.
    • X-Account-Container-Count = the number of containers in the account.
    • X-Account-Object-Count = the number of objects in the account. Keep in mind that this field is updated in a lazy fashion.
  • HTTP Data: The list of containers for the account.

HEAD Account
————————

Get account statistics.

  • URL Data: None.
  • Required Request Headers:
    • X-Auth-Token = authorization token.
  • Optional Request Headers: None.
  • Parameters: None.
  • Response Headers:
    • Accept-Ranges = always “bytes”. This header will eventually comply with the http “range” header.
    • Content-Length = always zero.
    • Content-Type = http data content type.
    • Date = current date.
    • X-Account-Bytes-Used = the number of bytes used in the account. Keep in mind that this field is updated in a lazy fashion.
    • X-Account-Container-Count = the number of containers in the account.
    • X-Account-Object-Count = the number of objects in the account. Keep in mind that this field is updated in a lazy fashion.
  • HTTP Data: None.

POST Account
————————

  • Post meta-data to an account.
  • URL Data: None.
  • Required Request Headers:
    • X-Auth-Token = authorization token.
  • Optional Request Headers:
    • X-Account-Meta-* = the user can create a account meta-data header. Such headers are of the form: “key value”.   The resulting account header will be: “X-Account-Meta-key: value”
  • Parameters: None.
  • Response Headers:
    • Content-Length = always zero.
    • Content-Type = http data content type.
    • Date = current date.
  • HTTP Data: None.

PUT Account
————————

Create an account.

  • URL Data: None.
  • Required Request Headers:
    • X-Auth-Token = authorization token.
  • Optional Request Headers:
    • X-Account-Meta-* = the user can create a account meta-data header. Such headers are of the form: “key value”.   The resulting account header will be: “X-Account-Meta-key: value”
  • Parameters: None.
  • Response Headers:
    • Content-Length = always zero.
    • Content-Type = http data content type.
    • Date = current date.
  • HTTP Data: None.

3) Swift REST API Container

DELETE Container
————————

Mark a container as deleted. Swift will clean up the container as time permits.

  • URL Data: Container name.
  • Required Request Headers:
    • X-Auth-Token = authorization token.
  • Optional Request Headers: None.
  • Parameters: None.
  • Response Headers:
    • Content-Length = always zero.
    • Content-Type = http data content type.
    • Date = current date.
  • HTTP Data: None.

GET Container
————————

Get the list of objects in a container.

  • URL Data: Container name.
  • Required Request Headers:
    • X-Auth-Token = authorization token.
  • Optional Request Headers: None.
  • Parameters:
    • delimiter=char: apply a character as a “delimiter” to the list of objects.
    • format=type : return http data in “json” or “xml” format.
    • limit=number : limit the number of returned objects.
    • marker=filter : provide a filter. The returned list of objects will start after “filter”.
    • path=string : for object names with embedded slashes (/).
    • prefix=string : The returned list of objects will start with “string”.
  • Response Headers:
    • Accept-Ranges = always “bytes”. This header will eventually comply with the http “range” eader.
    • Content-Length = the number of bytes in http data.
    • Content-Type = http data content type.
    • Date = current date.
    • X-Container-Bytes-Used = the number of bytes used in the container.
    • X-Container-Object-Count = the number of objects in the container.
  • HTTP Data: The list of objects for the container.

HEAD Container
————————

Get container statistics.

  • URL Data: Container name.
  • Required Request Headers:
    • X-Auth-Token = authorization token.
  • Optional Request Headers: None.
  • Parameters: None.
  • Response Headers:
    • Accept-Ranges = always “bytes”. This header will eventually comply with the http “range” header.
    • Content-Length = always zero.
    • Content-Type = http data content type.
    • Date = current date.
    • X-Container-Bytes-Used = the number of bytes used in the container.
    • X-Container-Object-Count = the number of objects in the container.
  • HTTP Data: None.

POST Container
————————

  • Post meta-data to a container.
  • URL Data: Container name.
  • Required Request Headers:
    • X-Auth-Token = authorization token.
  • Optional Request Headers:
    • X-Container-Meta-* = the user can create a container meta-data header. Such headers are of the form: “key value”. The resulting container header will be: “X-Container-Meta-key: value”
  • Parameters: None.
  • Response Headers:
    • Content-Length = always zero.
    • Content-Type = http data content type.
    • Date = current date.
  • HTTP Data: None.

PUT Container
————————

Create a container.

  • URL Data: Container name.
  • Required Request Headers:
    • X-Auth-Token = authorization token.
  • Optional Request Headers:
    • X-Account-Meta-* = the user can create a account meta-data header. Such headers are of the form: “key value”.   The resulting account header will be: “X-Account-Meta-key: value”
  • Parameters: None.
  • Response Headers:
    • Content-Length = always zero.
    • Content-Type = http data content type.
    • Date = current date.
  • HTTP Data: None.

4) Swift REST API Object

COPY Object
————————

Copy an object from one container to another.

  • URL Data: src_container/src_object.
  • Required Request Headers:
    • X-Auth-Token = authorization token.
    • Destination = /<dest_container>/<dest_object>
  • Optional Request Headers: None.
  • Parameters: None.
  • Response Headers:
    • Content-Length = always zero.
    • Content-Type = http data content type.
    • Date = current date.
    • Etag = checksum of new object data.
    • Last-Modified = the data that the source object was last changed.
    • X-Copied-From = source container and object.
    • X-Object-Meta-* = user defined meta data.
  • HTTP Data: None.

DELETE Object
————————

Delete an object from a container.

  • URL Data: container/object.
  • Required Request Headers:
    • X-Auth-Token = authorization token.
  • Optional Request Headers: None.
  • Parameters: None.
  • Response Headers:
    • Content-Length = always zero.
    • Content-Type = http data content type.
    • Date = current date.
  • HTTP Data: None.

GET Object
————————

Get an object from a container.

  • URL Data: container/object.
  • Required Request Headers:
    • X-Auth-Token = authorization token.
  • Optional Request Headers:
    • If-Match: etag = return the object data if there is an etag match.
    • If-Modified-Since: date = return the object data if it was modified since “data”.
    • If-None-Match: etag = return the object data if there is no etag match.
    • If-Unmodified-Since: date = return the object data if it was unmodified since “data”.
    • Range: bytes = return the specified object byte range.
  • Parameters: None.
  • Response Headers:
    • Content-Length = number of bytes in http data.
    • Content-Type = http data content type.
    • Date = current date.
    • Etag = checksum of object data.
    • Last-Modified = the data that the object was last changed.
    • X-Object-Meta-* = user defined meta data.
  • HTTP Data: Object data.

POST Object
————————

Post meta-data to an object.

  • URL Data: container/object.
  • Required Request Headers:
    • X-Auth-Token = authorization token.
  • Optional Request Headers:
    • X-Object-Meta-* = the user can create an object meta-data header. Such headers are of the form: “key value”.   The resulting object header will be: “X-Object-Meta-key: value”
    • X-Delete-After: seconds = delete the object after “seconds”.
    • X-Delete-At: seconds = delete the object after current time + “seconds”.
  • Parameters: None.
  • Response Headers:
    • Content-Length = always zero.
    • Content-Type = http data content type.
    • Date = current date.
  • HTTP Data: None.

PUT Object
————————

Create an object in a container.

  • URL Data: container/object.
  • Required Request Headers:
    • X-Auth-Token = authorization token.
  • Optional Request Headers:
    • X-Object-Manifest = indicates that this object is a manifest, that is, contains an ordered list of data objects.   This header is used to support objects larger than  4GB.
    • X-Object-Meta-* = the user can create an object meta-data header. Such headers are of the form: “key value”.   The resulting object header will be: “X-Object-Meta-key: value”
    • Transfer-Encoding = use transfer encoding.
  • Parameters: None.
  • Response Headers:
    • Content-Length = always zero.
    • Content-Type = http data content type.
    • Date = current date.
    • Etag = checksum of object data.
    • Last-Modified = the data that the object was last changed.
    • X-Object-Meta-* = user defined meta data.
  • HTTP Data: None.

What would you add to this Swift REST API example?

REST Interfaces Explained

Posted on: March 7th, 2012 by stephenbroeker No Comments

REST (REpresentational State Transfer) is an interface type that was created in 2000 by Roy Fielding in a doctoral dissertation. It is based on HTTP, so a REST interface is basically composed of GET, PUT, POST, HEAD, and DELETE operations.  I’ll explain REST Interfaces in this post, and follow-up with a Swift REST API example.

REST Interface HTTP Components

First, let’s look at each operation as defined by the HTTP components:

  1. Method
    The operations typically are: COPY, DELETE, GET, HEAD, PUT, and POST.
    The server has complete freedom in defining these operations.
    Typically though, these operations are defined as follows:
    COPY – create a copy of an item.
    • DELETE – delete an item.
    • GET – read an item.
    • HEAD – read item meta-data.
    • PUT – create an item.
    • POST – write item meta-data.
  2. URL
    The URL is commonly of the form:
    <http>://<ip><port>/<version>/<auth>/<parameters>
    Where:

    • <http> = “http” or “https”.
    • <ip> = ip address.
    • <port> = port number.
    • <version> = version string.
    • <auth> = authorization token.
    • <data> = operation specific data.
    • <parameters> = options.
      Parameters begins with a “?”.   Multiple parameters are seperated by a “&”.
  3. Request Headers
    Operation input headers.
  4. Response Headers
    Headers returned by the interface.
  5. Response Data.
    Data returned by the interface in ASCII format.
  6. Status Code
    HTTP status code. This code is in the range 1 – 600 and can be broken down as follows:

    • 1xx Informational.
    • 2xx Success.
    • 3xx Redirection.
    • 4xx Client Error.
    • 5xx Server Error.

Next time I’ll present a SWIFT REST Interface API example. In the meantime, what would you add to this explanation?