Project

General

Profile

Get Started with the Calamari REST API and PHP » History » Version 1

Jessica Mack, 07/03/2015 10:35 PM

1 1 Jessica Mack
h1. Get Started with the Calamari REST API and PHP
2
3
{{toc}}
4
5
h3. Introduction
6
7
So you've spent some time getting your Ceph cluster configured just the way you like it, everything is balances, your data is being read and written safely and securely and things are humming along nicely. But then, when you least expect it, an SSD goes down, you've lost a journal and an OSD. Ceph is self-healing, so you're not too worried about data loss...but you still need to know which nodes are affected and, once you swap in a new SSD, you need to be able to monitor things for a few hours or days to make sure it was an isolated incident and not the first segment of a larger problem.
8
It's precisely to address these sorts of monitoring and control needs that Calamari was created. Calamari is an API for Ceph, allowing Ceph administrators to get a birds-eye view of Ceph cluster status and providing an all-in-one management and monitoring service for cluster elements. Like Ceph itself, it's open source and freely downloadable from Github.
9
In this article, I'll introduce you to Calamari, show you how to install it and connect it to your Ceph cluster, and then run you through a few quick examples of how to use it. Once you understand how it works, you can easily integrate Ceph with your own user interface or with third-party applications using the Calamari API. Keep reading, you're going to enjoy this!
10
11
h3. Understanding Calamari
12
13
Calamari is a management and monitoring service for Ceph exposing a high-level REST API. One of the primary goals for the REST API is to make it easy to integrate Ceph with your own user interface. A reference implementation for this user interface already exists as Romana.
14
The Calamari REST API is different from the REST API included with Ceph itself. The Ceph REST API functions more as a wrapper around equivalent Ceph CLI commands, while the Calamari REST API operates at a higher level and allows API consumers to manipulate Ceph objects using GET/POST/PUT/DELETE commands.
15
This tutorial will focus specifically on using the Calamari REST API to perform common operations on your Ceph cluster. My weapon of choice for interacting with the REST API will be PHP, although you should be able to easily migrate the code listings to other programming languages as well.
16
17
h3. Assumptions and Requirements
18
19
To follow the steps in this tutorial, you'll need an Ubuntu or CentOS server on which to install Calamari and your PHP scripts. This server should be network-accessible by your Ceph cluster. Although I'll be using a VirtualBox server running CentOS 7 for this tutorial, the steps below have also been tested to work on Amazon Web Services (AWS) cloud servers running both Ubuntu and CentOS.
20
Before we get started, here are a few key assumptions that I'll be making:
21
You have a working knowledge of CentOS, VirtualBox and VirtualBox networking.
22
You have downloaded and installed the latest version of VirtualBox.
23
You're familiar with installing software using yum, the CentOS package manager.
24
You're familiar with common git operations.
25
You have a working understanding of REST APIs and common REST operations.
26
You have some familiarity with PHP scripting.
27
You have administrative access to a working Ceph cluster with at least 3 nodes.
28
In case you’re not familiar with the above topics, look in the “Read More” section at the end of this tutorial, which has links to relevant guides.
29
Did you check all the boxes above? Let's get started!
30
31
h3. Step 1: Install Calamari
32
33
To begin, initialize a new VirtualBox server with CentOS 7, configure networking to make sure it's connect to the Internet and is also accessible by your Ceph cluster, and then run the following commands as root to install some basic necessities:
34
shell> yum update
35
shell> yum install git
36
shell> yum install sudo
37
shell> yum install wget
38
Next, create a calamari user account and add it to the sudoers list:
39
shell> useradd calamari
40
shell> passwd calamari
41
shell> gpasswd -a calamari wheel
42
You should now log in as the calamari user, clone the Calamari repository and install Calamari, as below:
43
shell> git clone https://github.com/ceph/calamari.git calamari_source
44
shell> cd calamari_source
45
shell> ./vps_bootstrap.sh
46
The vps_bootstrap.sh script downloads all the necessary packages for Calamari and sets up the development environment, including the PostgreSQL database. Here's an example of what you will see during the process:
47
image1.png
48
The installation process should have created a ~/calamari directory. Change to this directory and activate the created Python virtual environment in ~/calamari/env:
49
shell> cd ~/calamari
50
shell> source env/bin/activate
51
You should now be able to start up the various server processes by running the command below:
52
shell> cd ~/calamari
53
shell> supervisord -n -c dev/supervisord.conf
54
Here's an example of what you should see:
55
image2.png
56
 
57
NOTE: In case you see a number of errors related to salt-master respawning and exiting very quickly, run the following commands, which will correct some Salt directory permissions, then try running the previous command again.
58
shell> cd ~/calamari
59
shell> sudo salt-master -d
60
shell> killall salt-master
61
You should now be able to browse to your server on port 8000 and see the Calamari REST API interface. For example, if your server IP address is 192.168.56.106, browse to http://192.168.56.106:8000/api/v2/ and you should get an HTTP 503 Forbidden response that looks like this:
62
image3.png
63
If you're not able to access the Calamari API interface on port 8000, check that your firewall is set up to allow connections on that port. For example, you could run the following command to allow connections on port 8000:
64
shell> sudo iptables -A INPUT -p tcp  -m tcp --dport 8000 -j ACCEPT
65
By default, the vps_bootstrap.sh script creates an administrative user for Calamari named admin with password admin. Click the "Log In" menu item and enter these credentials, and you should now see an HTTP 200 OK response and the output of the /api/v2/ API call, as shown below:
66
image4.png
67
At this point, the Calamari server is running and you can proceed to connect it with your Ceph cluster.
68
69
h3. Step 2: Connect Calamari with Your Ceph Cluster
70
71
Ceph cluster nodes communicate with Calamari using Salt minions. To connect a Ceph node to Calamari, follow these steps:
72
Log in to the Ceph node as the ceph user.
73
Run the commands below to install and start the Salt minion on the node. Remember to replace the master's IP address in the fourth command with the IP address or host name for your Calamari server.
74
shell> wget https://raw.github.com/saltstack/sal...tstrap-salt.sh
75
shell> chmod +x bootstrap-salt.sh
76
shell> sudo bootstrap-salt.sh
77
shell> sudo sh -c "echo 'master: 192.168.56.106' >> /etc/salt/minion"
78
shell> sudo service salt-minion restart
79
Here's a sample of what you will see as the Salt minion starts and connects to the Salt master.
80
image5.png
81
Repeat the steps above for each Ceph node in the cluster.
82
Once all the Salt minions are installed and running, you must accept their keys using the Salt master running on the Calamari server. To do this:
83
Log in to the Calamari node as the calamari user.
84
Run the following commands to list and accept all pending keys:
85
shell> cd ~/calamari
86
shell> source env/bin/activate
87
shell> salt-key -c dev/etc/salt -L
88
shell> salt-key -c dev/etc/salt -A
89
image6.png
90
Now, browse to your Calamari server at port 8000 and access the URL endpoint at /api/v2/cluster after logging in - for example, at http://192.168.56.106:8000/api/v2/cluster. You should now see your cluster listed, as shown below:
91
image7.png
92
To get a quick overview of the various nodes and their roles in the cluster, try browsing to the /api/v2/server endpoint, and you'll see a listing like the one below:
93
image8.png
94
Once you're satisfied that you've got the API working and providing "real" information, flip over to the next page and I'll show you how to perform some common tasks with it from a custom application!
95
96
h3. Step 3: Install PHP and Guzzle
97
98
I'll be using PHP to build a few simple scripts that interact with the Calamari API. Obviously, to do this, you need a server with PHP installed. This can be either the Calamari server itself, or another server or development environment.
99
Begin by installing PHP and Apache (if you don't already have them). On CentOS-based systems, use the following command:
100
shell> sudo yum install php curl
101
If you're using a Debian or Ubuntu-based system, use the following command instead:
102
shell> sudo apt-get install php5 php5-curl curl
103
Next, create a working directory for your PHP files under your Web server's document root. Download Composer, the PHP dependency manager, into this directory.
104
shell> cd /var/www/html
105
shell> mkdir calamari-php
106
shell> cd calamari-php
107
shell> curl -sS https://getcomposer.org/installer | php
108
Create a composer.json file in the working directory and fill it with the following content:
109
{
110
    "require": {
111
            "guzzlehttp/guzzle": "~5.0"
112
    }
113
}
114
You should now be able to run php composer.phar install and have Composer download the Guzzle HTTP client for PHP and any related dependencies.
115
shell> cd /var/www/html/calamari-php
116
shell> php composer.phar install
117
118
h3. Step 4: Handle API Authentication
119
120
Before you can begin using the API, you need to understand how to authenticate against it. Here's a quick overview of the steps involved:
121
Begin by sending a GET request to /api/v2/auth/login.
122
You should receive an HTTP 200 OK response containing a cookie named XSRF-TOKEN. Retrieve the value of this cookie.
123
Follow up with a POST request to /api/v2/auth/login containing:
124
A header named X-XSRF-TOKEN with the token value.
125
A JSON-encoded body containing the username and password - for example {username: "admin", password: "admin"}.
126
You should receive an HTTP 200 OK response containing a session cookie.
127
You can now proceed to use the API as usual, always remembering to include the session cookie in your requests. Once done, destroy your session by sending an empty POST request /api/v2/auth/logout.
128
In all the code examples that follow, remember to replace the host name, user name and password shown with correct values for your environment.
129
Translating the steps above into PHP code, here's what you'll end up with:
130
<?php
131
// load classes
132
include 'vendor/autoload.php';
133
use GuzzleHttp\Client as Client;
134
use GuzzleHttp\Cookie\CookieJar as CookieJar;
135
 
136
// define host and credentials
137
$config = new stdClass;
138
$config->user = 'admin';
139
$config->password = 'admin';
140
$config->host = '192.168.56.106';
141
 
142
// initialize client and cookie jar
143
$client = new Client(['base_url' => 'http://' . $config->host . ':8000']);
144
$jar = new GuzzleHttp\Cookie\CookieJar();
145
 
146
// initial request
147
// get the token
148
$response = $client->get('/api/v2/auth/login',  ['cookies' => $jar]);
149
$cookies = $jar->toArray();
150
foreach ($cookies as $c) {
151
  if ($c['Name'] == 'XSRF-TOKEN') {
152
    $token = $c['Value'];
153
  }
154
}
155
 
156
// authentication request
157
// include the token from the previous step
158
$response = $client->post('/api/v2/auth/login', [
159
  'cookies' => $jar,
160
  'headers' =>  ['X-XSRF-TOKEN' => $token, 'Content-type' => 'application/json; charset=UTF-8'],
161
  'body' => json_encode(array('username' => $config->user, 'password' => $config->password))
162
]);
163
 
164
// authenticated request for /api/v2/cluster
165
$response = $client->get('/api/v2/cluster', [
166
  'cookies' => $jar,
167
  'headers' =>  ['X-XSRF-TOKEN' => $token, 'Content-type' => 'application/json; charset=UTF-8'],
168
]);
169
echo $response->getBody();
170
 
171
// logout request
172
$client->post('/api/v2/auth/logout');
173
 
174
As discussed previously, the code above demonstrates an initial GET request, followed by a POST request containing authentication credentials. A common cookie jar is used throughout to store the session cookie and once authenticated, this session cookie accompanies all subsequent requests.
175
Here's an example of the output of the previous code:
176
image9.png
177
Since these steps will need to be performed every time you use the Calamari API, it makes sense to encapsulate them into a class. Here's an example of a custom CalamariClient class, which extends the base Guzzle HTTP Client class with some additional methods:
178
<?php
179
// load classes
180
include 'vendor/autoload.php';
181
use GuzzleHttp\Client as Client;
182
use GuzzleHttp\Cookie\CookieJar as CookieJar;
183
 
184
class CalamariClient extends Client {
185
  // cookie jar
186
  private $jar;
187
 
188
  // initialize client with cookie jar and base URL
189
  public function __construct(array $config = [], $host) {
190
    $this->jar = new CookieJar();
191
    $config['base_url'] = 'http://' . $host . ':8000';
192
    $config['defaults']['cookies'] = $this->jar;
193
    return parent::__construct($config);
194
  }
195
  // add token to all requests
196
  public function createRequest($method, $url = null, array $options = []) {
197
    if ($token = $this->getToken($this->jar)) {
198
      $options['headers'] = array('X-XSRF-TOKEN' => $token, 'Content-type' => 'application/json; charset=UTF-8');
199
    }
200
    return parent::createRequest($method, $url, $options);
201
  }
202
 
203
  // perform login
204
  public function login($username, $password) {
205
    $request = $this->get('/api/v2/auth/login');
206
    $request = $this->post('/api/v2/auth/login', [
207
          'headers' =>  ['X-XSRF-TOKEN' => $this->getToken($this->jar), 'Content-type' => 'application/json; charset=UTF-8'],
208
          'body' => json_encode(array('username' => $username, 'password' => $password))
209
    ]);
210
    return $this;
211
  }
212
 
213
  // perform logout
214
  public function logout() {
215
    $request = $this->post('/api/v2/auth/logout');
216
    return $this;
217
  }
218
 
219
  public function getToken($jar) {
220
    $cookies = $jar->toArray();
221
    foreach ($cookies as $c) {
222
      if ($c['Name'] == 'XSRF-TOKEN') {
223
        return $c['Value'];
224
      }
225
    }
226
    return false;
227
  }
228
 
229
}
230
And here's how you'd use it:
231
<?php
232
use CalamariClient;
233
try {
234
  $client = new CalamariClient(array(), '192.168.56.106');
235
  if ($client->login('admin', 'admin')) {
236
    $response = $client->get('/api/v2/cluster');
237
    echo $response->getBody();
238
    $client->logout();
239
  }
240
} catch (Exception $e) {
241
  die('ERROR: ' . $e->getMessage());
242
}
243
244
h3. Step 5: Start Using the API
245
246
Now that you've got a working, authenticated client, let's look at performing a few common tasks using the Calamari API. First up, checking cluster health and status.
247
Although v2 of the Calamari API doesn't include a specific method to report cluster health, this is available in v1, as the /api/v1/cluster/{fsid}/health method. To see how it works, consider the following script, which requests this endpoint and processes the response:
248
<?php
249
use CalamariClient;
250
try {
251
  $client = new CalamariClient(array(), '192.168.56.106');
252
  if ($client->login('admin', 'admin')) {
253
    $response = $client->get('/api/v2/cluster');
254
    $data = json_decode($response->getBody());
255
    $fsid = $data[0]->id;
256
    $response = $client->get('/api/v1/cluster/' . $fsid . '/health');
257
    $data = json_decode($response->getBody());
258
    echo 'Current cluster status: ' . $data->report->overall_status;
259
    $client->logout();
260
  }
261
} catch (Exception $e) {
262
  die('ERROR: ' . $e->getMessage());
263
}
264
Here, the authenticated client makes two requests to the API. The first retrieves the list of available cluster and selects the ID of the first one. The second sends a GET request to the /api/v1/cluster/{fsid}/health endpoint. The JSON-encoded response includes an overall_status field which includes the current cluster health. Here's what it looks like:
265
image10.png
266
You can also use the Calamari API to map services to hosts, by requesting the /api/v2/cluster/{fsid}/server endpoint. This will return a list of all the nodes that are part of the cluster, together with information on the services running on each. Here's an example of the code:
267
<?php
268
use CalamariClient;
269
try {
270
  $client = new CalamariClient(array(), '192.168.56.106');
271
  if ($client->login('admin', 'admin')) {
272
    $response = $client->get('/api/v2/cluster');
273
    $data = json_decode($response->getBody());
274
    $fsid = $data[0]->id;
275
    $response = $client->get('/api/v2/cluster/' . $fsid . '/server');
276
    $nodes = json_decode($response->getBody());
277
    if (count($nodes)) {
278
      echo '<ul>';
279
      foreach ($nodes as $n) {
280
        echo '<li>' . $n->fqdn . '</li>';
281
        if (count($n->services)) {
282
          echo '<ul>';
283
          foreach ($n->services as $s) {
284
            echo '<li>' . $s->type . ' (id: ' . $s->fsid . ')</li>';
285
          }
286
          echo '</ul>';
287
        }
288
      }
289
      echo '</ul>';    
290
    }
291
    $client->logout();
292
  }
293
} catch (Exception $e) {
294
  die('ERROR: ' . $e->getMessage());
295
}
296
The output of the /api/v2/cluster/{fsid}/server endpoint is a JSON-encoded collection of nodes and services (see an example). It's quite easy to convert this collection into a PHP array with the json_decode() function and then iterate over the array, printing each node and its services as a nested HTML list. That's what the code above does, and here's an example of what the output looks like:
297
image11.png
298
 
299
There are a couple of important points of difference between the /api/v2/server and the /api/v2/cluster/{fsid}/server endpoints. The former lists all nodes and services that Calamari knows about, while the latter only lists nodes and services that are part of the specified cluster. If you have an object gateway configured on one of your nodes, it will appear in the output of the former, but not in the output of the latter, since at the time of writing, Calamari does not yet include management integration with the Ceph Object Gateway.
300
Another common use case involves creating a new pool using the API. This is accomplished by sending a POSt request to the /api/v2/cluster/{fsid}/pool endpoint and including, in the body of the request, a JSON-encoded packet containing the name of the pool and the number of placement groups (see an example). Here's an example of the code:
301
<?php
302
use CalamariClient;
303
try {
304
  $client = new CalamariClient(array(), '192.168.56.106');
305
  if ($client->login('admin', 'admin')) {
306
    $response = $client->get('/api/v2/cluster');
307
    $data = json_decode($response->getBody());
308
    $fsid = $data[0]->id;
309
    $response = $client->post('/api/v2/cluster/' . $fsid . '/pool', [
310
      'body' => json_encode(array('name' => 'work', 'pg_num' => 100))
311
    ]);
312
    $client->logout();
313
  }
314
} catch (Exception $e) {
315
  die('ERROR: ' . $e->getMessage());
316
}
317
After you run this code, switch over to your cluster and you should be able to see the new pool, as shown below:
318
 
319
image12.png
320
 
321
Although the API includes 50+ methods, there's always the possibility that it might be missing one you need. For those situations, you have the /api/v2/cluster/{fsid}/cli method. This method works as an interface to the Ceph CLI, executing the provided command and returning the results as a JSON-encoded response.
322
To see how it works, consider the following script, which executes the ceph status command using the REST API:
323
<?php
324
use CalamariClient;
325
try {
326
  $client = new CalamariClient(array(), '192.168.56.106');
327
  if ($client->login('admin', 'admin')) {
328
    $response = $client->get('/api/v2/cluster');
329
    $data = json_decode($response->getBody());
330
    $fsid = $data[0]->id;
331
    $response = $client->post('/api/v2/cluster/' . $fsid . '/cli', [
332
      'body' => json_encode(array('command' => 'status'))
333
    ]);
334
    $data = json_decode($response->getBody());
335
    echo 'Output of ceph status: <pre>' . $data->out . '</pre>';
336
    $client->logout();
337
  }
338
} catch (Exception $e) {
339
  die('ERROR: ' . $e->getMessage());
340
}
341
Here, the authenticated client makes two requests to the API. The first retrieves the list of available cluster and selects the ID of the first one. The second sends a POST request with the status command in the body to the /api/v2/cluster/{fsid}/cli endpoint. The JSON-encoded response includes an out field which includes the output of the command. Here's what it looks like:
342
image13.png
343
If you wanted to, you could easily revise the previous listing to build an interactive tool that accepts commands through a Web browser and returns the result. Here's how:
344
  <form method="post">
345
    Ceph CLI command: ceph <input name="command" type="text" />
346
    <input  type="submit" name="submit" value="Run" />
347
    <br/>
348
    Example: To run 'ceph osd dump', enter 'osd dump' in the field above.    
349
  </form>
350
 
351
<?php
352
  use CalamariClient;
353
  if (isset($_POST['submit'])) {
354
    try {
355
      $client = new CalamariClient(array(), '192.168.56.106');
356
      if ($client->login('admin', 'admin')) {
357
        $response = $client->get('/api/v2/cluster');
358
        $data = json_decode($response->getBody());
359
        $fsid = $data[0]->id;
360
        $response = $client->post('/api/v2/cluster/' . $fsid . '/cli', [
361
          'body' => json_encode(array('command' => $_POST['command']))
362
        ]);
363
        $data = json_decode($response->getBody());
364
        echo 'Output of \'ceph ' . $_POST['command'] . '\': <pre>' . $data->out . '</pre>';
365
        $client->logout();
366
      }
367
    } catch (Exception $e) {
368
      die('ERROR: ' . $e->getMessage());
369
    }
370
 }
371
In this version, the user's form input is transferred into the POST request as a command and the output is displayed as a Web page. Here's an example of it in action:
372
image14.png
373
374
h3. Conclusion
375
376
As you might imagine, there's a lot more you can do with Calamari. This tutorial has illustrated a few common use cases, but the Calamari API also lets you work with CRUSH maps, monitor nodes, OSDs, pools, user accounts and more. With Calamari, you have everything you need to integrate Ceph cluster operations with your enterprise workflows or build your own management and monitoring solution for Ceph.
377
378
h3. Read More
379
380
* "Introduction to Ceph":http://ceph.com/docs/master/start/intro/
381
* "Introduction to Calamari":http://calamari.readthedocs.org/
382
* "Calamari API Documentation":http://calamari.readthedocs.org/en/latest/calamari_rest/resources/resources.html
383
* "Guzzle Quickstart":http://guzzle.readthedocs.org/en/latest/quickstart.html
384
* "VirtualBox Documentation":https://www.virtualbox.org/wiki/Documentation
385
* "PHP Manual":http://php.net/manual/