
multiple API verstions for each service. This included updating the directory structure for the tests and moving the Common files to a Common namespace. Implements blueprint multiple-api-versions Change-Id: I9f9dfc4ef8f4172243519772a9af86dd92690fcf
495 lines
19 KiB
Markdown
495 lines
19 KiB
Markdown
Tutorial: Using OpenStack PHP-Client
|
|
=================
|
|
|
|
PHP-Client provides PHP language bindings for the OpenStack APIs.
|
|
|
|
In this tutorial, we will walk through the process of creating a simple
|
|
tool that interacts with OpenStack's Object Storage. The emphasis in this
|
|
article is on getting started and learning the concepts, not building a
|
|
polished product.
|
|
|
|
**This tutorial focuses on the object-oriented API.** The other way to
|
|
work with this library is through the stream wrapper. That topic is
|
|
covered in [another tutorial](@ref streams-tutorial).
|
|
|
|
## Pre-flight Check
|
|
|
|
PHP-Client has been developed to require PHP 5.3 or later. You are
|
|
strongly encouraged to also install the CURL PHP extension. Many
|
|
distributions of PHP come with this enabled. Sometimes, though, you may
|
|
need to do something like `apt-get php5-curl` or similar. (Don't take
|
|
our word for it -- check your system's documentation.)
|
|
|
|
You can check for both of these conditions by checking the output of
|
|
`php --info` (on the commandline) or `<?php phpinfo(); ?>`.
|
|
|
|
### Check the pilot, too!
|
|
|
|
In our pre-flight check, we would be remiss if we didn't point out that
|
|
there are some requirements for the pilot (that's you), too.
|
|
|
|
The PHP-Client library is composed of two parts:
|
|
|
|
1. The Object-Oriented part, which is the subject of this tutorial.
|
|
2. The Stream Wrapper, which is the subject of another tutorial.
|
|
|
|
The object-oriented library makes ample use of PHP namespaces. If you've
|
|
never seen these before, they look like this:
|
|
|
|
<?php
|
|
\OpenStack\ObjectStore\v1\Resource\RemoteObject
|
|
?>
|
|
|
|
The namespace above is read like this: "The RemoteObject class is part
|
|
of the ObjectStorage package in the Storage package in the base OpenStack
|
|
package." Those familiar with Java, Python, and other languages will
|
|
recognize this way of talking (though the backslash is an unfortunate
|
|
symbol choice).
|
|
|
|
For our library, we followed the recommendation of SPR-0, which means
|
|
that the class above can be found in the file at:
|
|
|
|
src/OpenStack/ObjectStore/v1/Resource/RemoteObject.php
|
|
|
|
The pattern of matching namespace to file name should (we hope) make it
|
|
easier for you to navigate our code.
|
|
|
|
If this namespace stuff continues to confuse you, you may want to take a
|
|
look at [the PHP documentation](http://us3.php.net/manual/en/language.namespaces.php),
|
|
or you may just prefer to keep on reading and learn by example. We don't
|
|
do anything really fancy with namespaces.
|
|
|
|
**In this document, we sometimes replace the backslash (\\) with double
|
|
colons (`::`) so that links are automatically generated.** So
|
|
`\OpenStack\Bootstrap` may appear as OpenStack::Bootstrap. The reason for
|
|
this is [explained elsewhere](@ref styleguide).
|
|
|
|
## Step 1: Getting the Library
|
|
|
|
You can get the OpenStack PHP-CLient library at the [OpenStack PHP-Client
|
|
Repository](https://FIXME). The latest code is always
|
|
available there.
|
|
|
|
The project also uses [Composer](http://packagist.org/), and this is the
|
|
best method for adding PHP-Client to your PHP project.
|
|
|
|
For our example, we will assume that the library is accessible in the
|
|
default include path, so the following line will include the
|
|
`Bootstrap.php` file:
|
|
|
|
include 'OpenStack/Bootstrap.php';
|
|
|
|
## Step 2: Bootstrap the Library
|
|
|
|
The first thing to do in your application is make sure the OpenStack
|
|
library is bootstrapped. When we say "bootstrap", what we really mean is
|
|
letting the library initialize itself.
|
|
|
|
Bootstrapping does not always involve any manual interaction on your
|
|
part. If you are using an PSR-4 autoloader that knows of the OpenStack
|
|
directory, that is enough for the system to initialize itself.
|
|
|
|
Sometimes, though, you will need to bootstrap OpenStack in your own code,
|
|
and this is done as follows:
|
|
|
|
<?php
|
|
require_once 'OpenStack/Autoloader.php';
|
|
|
|
use \OpenStack\Autoloader;
|
|
use \OpenStack\Bootstrap;
|
|
use \OpenStack\Identity\v2\IdentityService;
|
|
use \OpenStack\ObjectStore\v1\ObjectStorage;
|
|
use \OpenStack\ObjectStore\v1\Resource\Object;
|
|
|
|
\OpenStack\Autoloader::useAutoloader();
|
|
?>
|
|
|
|
The first line should be self-explanatory: We require the main
|
|
`Bootstrap.php` file (which contains the `Bootstrap` class).
|
|
|
|
After that, we declare a list of namespaced objects that we will use.
|
|
This way we can refer to them by their short name, rather than by their
|
|
fully qualified name.
|
|
|
|
The last line initializes the built-in OpenStack autoloader. What does
|
|
this mean? It means that this is the only `require` or `include`
|
|
statement you need in your code. The library does the rest of the
|
|
including for you, on demand, in a performance-sensitive way.
|
|
|
|
There are some other fancy things that OpenStack::Bootstrap can do for
|
|
you. Most notably, you can pass configuration parameters into it. But
|
|
for the time being, we are good to go.
|
|
|
|
Our library is boostrapped. Next up: Let's connect to our account.
|
|
|
|
## Step 3: Connecting
|
|
|
|
Our programming goal, in this tutorial, is to interact with the Object
|
|
Storage service on OpenStack. (Object Storage is, for all intents and
|
|
purposes, basically a service for storing files in the cloud.)
|
|
|
|
But before we can interact directly with Object Storage, we need to
|
|
authenticate to the system. And to do this, we need the following four
|
|
pieces of information:
|
|
|
|
- Username: The username for an account.
|
|
- Password: The password associated with the username.
|
|
- Tenant ID: An identifier that maps an account to a set of services.
|
|
(In theory at least, one account can have multiple tenant IDs, and one
|
|
tenant ID can be linked to multiple accounts.)
|
|
- Endpoint URL: The URL to the Identity Services endpoint for OpenStack.
|
|
|
|
Before you issue a forlorn sigh, envisioning some laborious task, let us
|
|
point out that all of this information is available in one place, Log
|
|
into the console and go to the `API Keys`
|
|
page. It's all there.
|
|
|
|
### Identity Services
|
|
|
|
OpenStack is composed of numerous services. There's the Compute
|
|
service, the Object Storage service... and so on.
|
|
|
|
Authenticating separately to each of these would be a collosal waste of
|
|
network resources. And behind the scenes, account management would be
|
|
difficult on the server side.
|
|
|
|
That's where Identity Services comes in. It is a central service that
|
|
handles all things authorization and authentication related. Roughly,
|
|
it works as follows:
|
|
|
|
- The client sends an authentication request
|
|
- If it fails, the service returns an error
|
|
- If authentication succeeds, the service returns a time-sensitive token
|
|
(basically a shared secret) and a "service catalog"
|
|
|
|
The *token* is valid for some fixed period of time (say, 30 minutes),
|
|
during which time it can be used for every other service. Each request
|
|
to an OpenStack service should send (along with other info) the token. The
|
|
remote service then validates the token with identity services, saving
|
|
our app the trouble of making another round trip.
|
|
|
|
The *service catalog* lists all of the OpenStack services that the present
|
|
account can access.
|
|
|
|
### Authenticating
|
|
|
|
With that little bit of theory behind us, we can now go about
|
|
authenticating.
|
|
|
|
<?php
|
|
$username = 'ADD USERNAME HERE';
|
|
$password = 'ADD PASSWORD HERE';
|
|
$tenantId = 'ADD TENANT ID HERE';
|
|
$endpoint = 'ADD ENDPOINT URL HERE';
|
|
|
|
$idService = new \OpenStack\Identity\v2\IdentityService($endpoint);
|
|
$token = $idService->authenticateAsUser($username, $password, $tenantId);
|
|
?>
|
|
|
|
Assuming the variables above have been set to include valid data, this
|
|
script can connect to OpenStack and authenticate.
|
|
|
|
When we construct a new OpenStack::Services::IdentityService object, we must pass it the
|
|
endpoint URL for OpenStack Identity Service. Typically, that URL will
|
|
look something like this:
|
|
|
|
~~~
|
|
https://region-a.geo-1.identity.hpcloudsvc.com:35357
|
|
~~~
|
|
|
|
The `authenticateAsUser()` method will authenticate to the
|
|
Identity Services endpoint. For convenience, it returns the
|
|
authorization token (`$token`), though we can also get the token from
|
|
`$idService->token()`.
|
|
|
|
Note that the `IdentityService` object may throw various exceptions
|
|
(all subclasses of OpenStack\Common\Exception) during authentication. Failed
|
|
authentication results in an \OpenStack\Common\Transport\AuthorizationException, while
|
|
a network failure may result in an \OpenStack\Common\Transport\ServerException.
|
|
|
|
Earlier, we talked about the service catalog. Once we've authenticated,
|
|
we can get the service catalog from `$idService->serviceCatalog()`. It
|
|
is an associative array, and you can get an idea of what it contains by
|
|
dumping it with `var_dump()`, should you so desire.
|
|
|
|
At this point, we have what we need from Identity Service. It's time to
|
|
look at Object Storage.
|
|
|
|
### IdentityService in a Nutshell
|
|
|
|
Instances of `OpenStack\Identity\v2\IdentityService` are responsible for:
|
|
|
|
- Authentication
|
|
- Accessing the service catalog
|
|
- Accessing account info
|
|
- Associating tenant IDs with accounts (advanced)
|
|
|
|
## Step 4: Connecting to Object Storage
|
|
|
|
The Object Storage system is concerned with two classes of things:
|
|
|
|
- An Object: A self-contained bundle of data (back in my day, we called
|
|
them "files").
|
|
- A Container: A storage container (bucket) for objects.
|
|
|
|
Your object storage can have any number of containers, and each
|
|
container can have any number of objects.
|
|
|
|
In the object model for the OpenStack PHP-Client library, a top-level object
|
|
called OpenStack::Storage::ObjectStorage provides access to the Object
|
|
Storage service. In this step, we will be working with that object.
|
|
|
|
### Getting an ObjectStorage Instance
|
|
|
|
Earlier, we created an `IdentityService` instance called `$idService`.
|
|
We will use that here to get the service catalog. Once we have the
|
|
catalog, we can have a new `ObjectStorage` instance created for us,
|
|
configured to talk to our account's Object Storage instance in
|
|
OpenStack. Along with the service catalog, we also need our token that
|
|
shows the Object Storage endpoint that we have already authenticated to
|
|
Identity Services. Earlier, we captured that value in the `$token`
|
|
variable.
|
|
|
|
Now we can get a new `\OpenStack\ObjectStore\v1\ObjectStorage` instance:
|
|
|
|
<?php
|
|
$catalog = $idService->serviceCatalog();
|
|
|
|
$store = ObjectStorage::newFromServiceCatalog($catalog, $token);
|
|
|
|
// UPDATE: As of Beta 6, you can use newFromIdentity():
|
|
// $store = ObjectStorage::newFromIdentity($idService);
|
|
?>
|
|
|
|
First we get the service catalog (`$catalog`), and then we use the
|
|
`ObjectStorage::newFromServiceCatalog()` static method to create the new
|
|
Object Storage instance.
|
|
|
|
The pattern of using a constructor-like static function is used
|
|
throughout the OpenStack PHP-Client library. Inspired by Objective-C constructors
|
|
and the Factory design pattern, it makes it possible for a single class
|
|
to have multiple constructors.
|
|
|
|
In particular, many top-level classes provide a
|
|
`newFromServiceCatalog()` constructor function, since these classes know
|
|
how to construct instances from a service catalog, thus freeing the
|
|
developer up from knowing the details of a service catalog entry.
|
|
|
|
Now we have an `ObjectStorage` instance that is already configured to
|
|
talk to our OpenStack object storage service. Next, we can create a
|
|
container.
|
|
|
|
### ObjectStorage in a Nutshell
|
|
|
|
Instances of OpenStack::Storage::ObjectStorage are responsbile for:
|
|
|
|
- Providing high-level information about the Object Storage service
|
|
- Creating, deleting, loading, and listing Containers
|
|
- Modifying Container ACLs
|
|
|
|
## Step 5: Adding a Container
|
|
|
|
Before we can start putting objects (files) into our Object Storage
|
|
service, we need a place to put them. An Object Storage service can hold
|
|
numerous containers (and each container can have different access
|
|
controls -- a topic we won't get into here).
|
|
|
|
Containers are represented in the library by the
|
|
OpenStack::Storage::ObjectStorage::Container class. And creating a
|
|
container is done by a method on the `ObjectStorage` object that we
|
|
created above:
|
|
|
|
<?php
|
|
$store->createContainer('Example');
|
|
$container = $store->container('Example');
|
|
?>
|
|
|
|
Recall that `$store` is the name of our `ObjectStorage` instance. In the
|
|
first of the two lines above, we create a new container named `Example`.
|
|
Then in the second line, we get that container.
|
|
|
|
Why is this two steps? The answer is that the OpenStack PHP-Client library mimics
|
|
the architecture of the underlying API. This is two operations (which
|
|
means it requires two network requests to the remote host), and so we
|
|
must perform two operations.
|
|
|
|
The `createContainer()` call actually creates the new container on the
|
|
cloud's Object Storage. The second call connects to the remote object
|
|
storage, and gets the new container. The container that is returned will
|
|
have some additional information, such as the amount of space it takes
|
|
up on the remote storage, and the access control rules for that
|
|
container. All of this information will be available on the `$container`
|
|
instance.
|
|
|
|
Our `$container` instance is an instance of
|
|
OpenStack::Storage::ObjectStorage::Container. This object can be used not
|
|
only to find out about a container, but also to get information about
|
|
the objects in that container.
|
|
|
|
Now that we have a `Container`, we can add an object.
|
|
|
|
### The Container in a Nutshell
|
|
|
|
(Yes, we realize the irony of that title.)
|
|
|
|
A `\OpenStack\ObjectStore\v1\Resource\Container` instance is responsible for the following:
|
|
|
|
- Accessing information about the container
|
|
- Creating, saving, deleting, and listing objects in the container
|
|
- Providing path-like traversal of objects
|
|
- Copying objects across containers
|
|
- Loading a lightweight representation of an object without fetching the
|
|
entire object (more on this later).
|
|
|
|
Among the features of a Container, it can act like an `Iterator` and is
|
|
`Countable`. That means you can loop through a Container in a `foreach`
|
|
loop and also use `count($container)` to find out the number of objects
|
|
in a Container.
|
|
|
|
## Step 6: Storing an Object
|
|
|
|
Now we are ready to create an object, and then store it in our
|
|
container.
|
|
|
|
Before diving too deeply, it is important to point out a detail: When
|
|
working with a remote data storage service, we are typically working
|
|
with a local copy and a remote copy. If our code isn't constructed
|
|
correctly, it is possible for these two to get out of sync.
|
|
|
|
Earlier, we created a container directly on the remote side, and then
|
|
fetched the container. As we create an object, we are going to do the
|
|
opposite: We will create a local object, and then save it to the remote
|
|
storage. Later, we will fetch the remote object.
|
|
|
|
<?php
|
|
$name = 'hello.txt';
|
|
$content = 'Hello World';
|
|
$mime = 'text/plain';
|
|
|
|
$localObject = new Object($name, $content, $mime);
|
|
$container->save($localObject);
|
|
?>
|
|
|
|
In the code above, we create `$localObject` with a `$name`, some
|
|
`$content`, and a `$mime` type. Strictly speaking, only `$name` is
|
|
required.
|
|
|
|
The OpenStack::Storage::ObjectStorage::Object class is used primarily to
|
|
describe a locally created object. Once we have our new `Object`, we can
|
|
save it remotely using the `save()` method on our `$container` object.
|
|
This will push the object to the remote object storage service.
|
|
|
|
While we can continue manipulating `$localObject`, we are working with
|
|
the local version, not the latest version of what's on the server. This
|
|
is fine if what we are doing is writing more data. However, when
|
|
examining the content of the object, remember that we are working with
|
|
the local copy, and its properties may differ from the remote copy's.
|
|
|
|
### What if I call save() twice with the same Object?
|
|
|
|
Objects, like files on a file system, are referenced by name. Any time
|
|
you `save()` an object, it will be pushed to the remote object storage
|
|
server, which will happily replace the old content with your newly
|
|
submitted content.
|
|
|
|
Next let's turn to loading objects from the remote object storage.
|
|
|
|
### The Object in a Nutshell
|
|
|
|
The `\OpenStack\ObjectStore\v1\Resource\Object` instances are used for:
|
|
|
|
- Creating a local object to be stored remotely
|
|
|
|
This class is also the base class for the `RemoteObject` class that we
|
|
will look at later.
|
|
|
|
The API is generally constructed so that a developer needn't worry about
|
|
the differences between an `Object` and a `RemoteObject`. But in all but
|
|
the edgiest of edge cases, you would only create an instance of
|
|
`Object`, never of `RemoteObject`.
|
|
|
|
## Step 7: Loading an Object
|
|
|
|
Containers not only provide the methods for saving objects, but also for
|
|
loading objects. Thus, we can fetch the object that we just created:
|
|
|
|
<?php
|
|
$object = $container->object('hello.txt');
|
|
|
|
printf("Name: %s \n", $object->name());
|
|
printf("Size: %d \n", $object->contentLength());
|
|
printf("Type: %s \n", $object->contentType());
|
|
print $object->content() . PHP_EOL;
|
|
?>
|
|
|
|
The `$object` variable now references an instance of a
|
|
`\OpenStack\ObjectStore\v1\Resource\RemoteObject` that contains the entire
|
|
object. `RemoteObject` represents an object that was loaded from the
|
|
remote server. Along with providing the features of the `Object` class
|
|
we saw earlier, it also provides numerous optimizations for working over
|
|
the network.
|
|
|
|
Now that we have the object, we print out several pieces of information
|
|
-- `name()`, `size()`, amd `type()`. Then, using `content()`, we fetch
|
|
the content of the object.
|
|
|
|
### Lazily Loading an Object
|
|
|
|
The method we used above to fetch the object is perfect for our needs.
|
|
It pulls the entire object down in a single request. But imagine this
|
|
scenario: Our object storage has large media files, and we don't know
|
|
at loading time whether or not we need to access the body content, or
|
|
just the other data about the object.
|
|
|
|
It would be a time-consuming task to download the entire body of a large
|
|
media file if we don't actually use the body. On the other hand, from an
|
|
API standpoint it is great to be able to pass around a single object,
|
|
and not require the application to know whether or not the body has been
|
|
retrieved.
|
|
|
|
The `RemoteObject` solves this problem using a technique known as "lazy
|
|
loading". That is, it can pull some of the data right away, but defer
|
|
fetching the rest of the data until that data is actually needed.
|
|
|
|
To fetch an object this way, we can just swap out one line in the
|
|
example above:
|
|
|
|
<?php
|
|
$object = $container->proxyObject('hello.txt');
|
|
|
|
printf("Name: %s \n", $object->name());
|
|
printf("Size: %d \n", $object->contentLength());
|
|
printf("Type: %s \n", $object->contentType());
|
|
print $object->content() . PHP_EOL;
|
|
?>
|
|
|
|
Instead of using `object()`, we now use `proxyObject()`. This method
|
|
immediately loads the core data about the remote object, but defers
|
|
fetching the content until the content is requested.
|
|
|
|
In the example above, then, one network request is issued by
|
|
`proxyObject()`, but another is initiated when `$object->content()` is
|
|
called.
|
|
|
|
### The RemoteObject in a Nutshell
|
|
|
|
Instances of a `\OpenStack\ObjectStore\v1\Resource\RemoteObject` offer the following features:
|
|
|
|
- Access to an object stored on the remote object storage
|
|
- A proxying mechanism for lazily loading objects
|
|
- A stream-based API for using stream and file-based PHP functions
|
|
- Automatic tempfile-based caching for large objects (using
|
|
`php://temp`).
|
|
|
|
`RemoteObject` instances can be updated and then passed to
|
|
`Container::save()` to update the copy on the server, too.
|
|
|
|
## Summary
|
|
|
|
At this point we have created a very basic script that connects to
|
|
OpenStack and works with object storage. Clearly, this only scratches the
|
|
surface of what the OpenStack PHP-Client library does. But hopefully this is
|
|
enough to get you started with the library.
|
|
|
|
\see oo-tutorial-code.php
|