Welcome to mirror list, hosted at ThFree Co, Russian Federation.

Service.php « Common « OpenCloud « lib « php-opencloud « 3rdparty « files_external « apps - github.com/nextcloud/server.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 5b3aa729a97798ddcd8a80cfca812570fef9a629 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
<?php
/**
 * PHP OpenCloud library.
 * 
 * @copyright Copyright 2013 Rackspace US, Inc. See COPYING for licensing information.
 * @license   https://www.apache.org/licenses/LICENSE-2.0 Apache 2.0
 * @version   1.6.0
 * @author    Jamie Hannaford <jamie.hannaford@rackspace.com>
 */

namespace OpenCloud\Common;

use OpenCloud\Common\Base;
use OpenCloud\Common\Lang;
use OpenCloud\OpenStack;
use OpenCloud\Common\Exceptions;

/**
 * This class defines a cloud service; a relationship between a specific OpenStack
 * and a provided service, represented by a URL in the service catalog.
 *
 * Because Service is an abstract class, it cannot be called directly. Provider
 * services such as Rackspace Cloud Servers or OpenStack Swift are each
 * subclassed from Service.
 *
 * @author Glen Campbell <glen.campbell@rackspace.com>
 */

abstract class Service extends Base
{

    protected $conn;
    private $service_type;
    private $service_name;
    private $service_region;
    private $service_url;

    protected $_namespaces = array();

    /**
     * Creates a service on the specified connection
     *
     * Usage: `$x = new Service($conn, $type, $name, $region, $urltype);`
     * The service's URL is defined in the OpenStack's serviceCatalog; it
     * uses the $type, $name, $region, and $urltype to find the proper URL
     * and set it. If it cannot find a URL in the service catalog that matches
     * the criteria, then an exception is thrown.
     *
     * @param OpenStack $conn - a Connection object
     * @param string $type - the service type (e.g., "compute")
     * @param string $name - the service name (e.g., "cloudServersOpenStack")
     * @param string $region - the region (e.g., "ORD")
     * @param string $urltype - the specified URL from the catalog
     *      (e.g., "publicURL")
     */
    public function __construct(
        OpenStack $conn,
        $type,
        $name,
        $region,
        $urltype = RAXSDK_URL_PUBLIC,
        $customServiceUrl = null
    ) {
        $this->setConnection($conn);
        $this->service_type = $type;
        $this->service_name = $name;
        $this->service_region = $region;
        $this->service_url = $customServiceUrl ?: $this->getEndpoint($type, $name, $region, $urltype);
    }
    
    /**
     * Set this service's connection.
     * 
     * @param type $connection
     */
    public function setConnection($connection)
    {
        $this->conn = $connection;
    }
    
    /**
     * Get this service's connection.
     * 
     * @return type
     */
    public function getConnection()
    {
        return $this->conn;
    }
    
    /**
     * Returns the URL for the Service
     *
     * @param string $resource optional sub-resource
     * @param array $query optional k/v pairs for query strings
     * @return string
     */
    public function url($resource = '', array $param = array())
    {
        $baseurl = $this->service_url;

		// use strlen instead of boolean test because '0' is a valid name
        if (strlen($resource) > 0) {
            $baseurl = Lang::noslash($baseurl).'/'.$resource;
        }

        if (!empty($param)) {
            $baseurl .= '?'.$this->MakeQueryString($param);
        }

        return $baseurl;
    }

    /**
     * Returns the /extensions for the service
     *
     * @api
     * @return array of objects
     */
    public function extensions()
    {
        $ext = $this->getMetaUrl('extensions');
        return (is_object($ext) && isset($ext->extensions)) ? $ext->extensions : array();
    }

    /**
     * Returns the /limits for the service
     *
     * @api
     * @return array of limits
     */
    public function limits()
    {
        $limits = $this->getMetaUrl('limits');
        return (is_object($limits)) ? $limits->limits : array();
    }

    /**
     * Performs an authenticated request
     *
     * This method handles the addition of authentication headers to each
     * request. It always adds the X-Auth-Token: header and will add the
     * X-Auth-Project-Id: header if there is a tenant defined on the
     * connection.
     *
     * @param string $url The URL of the request
     * @param string $method The HTTP method (defaults to "GET")
     * @param array $headers An associative array of headers
     * @param string $body An optional body for POST/PUT requests
     * @return \OpenCloud\HttpResult
     */
    public function request(
    	$url,
    	$method = 'GET',
    	array $headers = array(),
    	$body = null
    ) {

        $headers['X-Auth-Token'] = $this->conn->Token();

        if ($tenant = $this->conn->Tenant()) {
            $headers['X-Auth-Project-Id'] = $tenant;
        }
        
        return $this->conn->request($url, $method, $headers, $body);
    }

    /**
     * returns a collection of objects
     *
     * @param string $class the class of objects to fetch
     * @param string $url (optional) the URL to retrieve
     * @param mixed $parent (optional) the parent service/object
     * @return OpenCloud\Common\Collection
     */
    public function collection($class, $url = null, $parent = null)
    {
        // Set the element names
        $collectionName = $class::JsonCollectionName();
        $elementName    = $class::JsonCollectionElement();

        // Set the parent if empty
        if (!$parent) {
            $parent = $this;
        }

        // Set the URL if empty
        if (!$url) {
            $url = $parent->url($class::ResourceName());
        }

        // Save debug info
        $this->getLogger()->info(
            '{class}:Collection({url}, {collectionClass}, {collectionName})',
            array(
                'class' => get_class($this),
                'url'   => $url,
                'collectionClass' => $class,
                'collectionName'  => $collectionName
            )
        );

        // Fetch the list
        $response = $this->request($url);
        
        $this->getLogger()->info('Response {status} [{body}]', array(
            'status' => $response->httpStatus(),
            'body'   => $response->httpBody()
        ));
        
        // Check return code
        if ($response->httpStatus() > 204) {
            throw new Exceptions\CollectionError(sprintf(
                Lang::translate('Unable to retrieve [%s] list from [%s], status [%d] response [%s]'),
                $class,
                $url,
                $response->httpStatus(),
                $response->httpBody()
            ));
        }
        
        // Handle empty response
        if (strlen($response->httpBody()) == 0) {
            return new Collection($parent, $class, array());
        }

        // Parse the return
        $object = json_decode($response->httpBody());
        $this->checkJsonError();
        
        // See if there's a "next" link
        // Note: not sure if the current API offers links as top-level structures;
        //       might have to refactor to allow $nextPageUrl as method argument
        // @codeCoverageIgnoreStart
        if (isset($object->links) && is_array($object->links)) {
            foreach($object->links as $link) {
                if (isset($link->rel) && $link->rel == 'next') {
                    if (isset($link->href)) {
                        $nextPageUrl = $link->href;
                    } else {
                        $this->getLogger()->warning(
                            'Unexpected [links] found with no [href]'
                        );
                    }
                }
            }
        }
        // @codeCoverageIgnoreEnd
        
        // How should we populate the collection?
        $data = array();

        if (!$collectionName) {
            // No element name, just a plain object
            // @codeCoverageIgnoreStart
            $data = $object;
            // @codeCoverageIgnoreEnd
        } elseif (isset($object->$collectionName)) {
            if (!$elementName) {
                // The object has a top-level collection name only
                $data = $object->$collectionName;
            } else {
                // The object has element levels which need to be iterated over
                $data = array();
                foreach($object->$collectionName as $item) {
                    $subValues = $item->$elementName;
                    unset($item->$elementName);
                    $data[] = array_merge((array)$item, (array)$subValues);
                }
            }
        }
        
        $collectionObject = new Collection($parent, $class, $data);
        
        // if there's a $nextPageUrl, then we need to establish a callback
        // @codeCoverageIgnoreStart
        if (!empty($nextPageUrl)) {
            $collectionObject->setNextPageCallback(array($this, 'Collection'), $nextPageUrl);
        }
        // @codeCoverageIgnoreEnd

        return $collectionObject;
    }

    /**
     * returns the Region associated with the service
     *
     * @api
     * @return string
     */
    public function region()
    {
        return $this->service_region;
    }

    /**
     * returns the serviceName associated with the service
     *
     * This is used by DNS for PTR record lookups
     *
     * @api
     * @return string
     */
    public function name()
    {
        return $this->service_name;
    }

    /**
     * Returns a list of supported namespaces
     *
     * @return array
     */
    public function namespaces()
    {
        return (isset($this->_namespaces) && is_array($this->_namespaces)) ? $this->_namespaces : array();
    }

    /**
     * Given a service type, name, and region, return the url
     *
     * This function ensures that services are represented by an entry in the
     * service catalog, and NOT by an arbitrarily-constructed URL.
     *
     * Note that it will always return the first match found in the
     * service catalog (there *should* be only one, but you never know...)
     *
     * @param string $type The OpenStack service type ("compute" or
     *      "object-store", for example
     * @param string $name The name of the service in the service catlog
     * @param string $region The region of the service
     * @param string $urltype The URL type; defaults to "publicURL"
     * @return string The URL of the service
     */
    private function getEndpoint($type, $name, $region, $urltype = 'publicURL')
    {
        $catalog = $this->getConnection()->serviceCatalog();

        // Search each service to find The One
        foreach ($catalog as $service) {
            // Find the service by comparing the type ("compute") and name ("openstack")
            if (!strcasecmp($service->type, $type) && !strcasecmp($service->name, $name)) {
                foreach($service->endpoints as $endpoint) {
                    // Only set the URL if:
                    // a. It is a regionless service (i.e. no region key set)
                    // b. The region matches the one we want
                    if (isset($endpoint->$urltype) && 
                        (!isset($endpoint->region) || !strcasecmp($endpoint->region, $region))
                    ) {
                        $url = $endpoint->$urltype;
                    }
                }
            }
        }

        // error if not found
        if (empty($url)) {
            throw new Exceptions\EndpointError(sprintf(
                'No endpoints for service type [%s], name [%s], region [%s] and urlType [%s]',
                $type,
                $name,
                $region,
                $urltype
            ));
        }
        
        return $url;
    }

    /**
     * Constructs a specified URL from the subresource
     *
     * Given a subresource (e.g., "extensions"), this constructs the proper
     * URL and retrieves the resource.
     *
     * @param string $resource The resource requested; should NOT have slashes
     *      at the beginning or end
     * @return \stdClass object
     */
    private function getMetaUrl($resource)
    {
        $urlBase = $this->getEndpoint(
            $this->service_type,
            $this->service_name,
            $this->service_region,
            RAXSDK_URL_PUBLIC
        );

        $url = Lang::noslash($urlBase) . '/' . $resource;

        $response = $this->request($url);

        // check for NOT FOUND response
        if ($response->httpStatus() == 404) {
            return array();
        }

        // @codeCoverageIgnoreStart
        if ($response->httpStatus() >= 300) {
            throw new Exceptions\HttpError(sprintf(
                Lang::translate('Error accessing [%s] - status [%d], response [%s]'),
                $urlBase,
                $response->httpStatus(),
                $response->httpBody()
            ));
        }
        // @codeCoverageIgnoreEnd

        // we're good; proceed
        $object = json_decode($response->httpBody());

        $this->checkJsonError();

        return $object;
    }
    
    /**
     * Get all associated resources for this service.
     * 
     * @access public
     * @return void
     */
    public function getResources()
    {
        return $this->resources;
    }

    /**
     * Internal method for accessing child namespace from parent scope.
     * 
     * @return type
     */
    protected function getCurrentNamespace()
    {
        $namespace = get_class($this);
        return substr($namespace, 0, strrpos($namespace, '\\'));
    }
    
    /**
     * Resolves fully-qualified classname for associated local resource.
     * 
     * @param  string $resourceName
     * @return string
     */
    protected function resolveResourceClass($resourceName)
    {
        $className = substr_count($resourceName, '\\') 
            ? $resourceName 
            : $this->getCurrentNamespace() . '\\Resource\\' . ucfirst($resourceName);
        
        if (!class_exists($className)) {
            throw new Exceptions\UnrecognizedServiceError(sprintf(
                '%s resource does not exist, please try one of the following: %s', 
                $resourceName, 
                implode(', ', $this->getResources())
            ));
        }
        
        return $className;
    }
    
    /**
     * Factory method for instantiating resource objects.
     * 
     * @access public
     * @param  string $resourceName
     * @param  mixed $info (default: null)
     * @return object
     */
    public function resource($resourceName, $info = null)
    {
        $className = $this->resolveResourceClass($resourceName);
        return new $className($this, $info);
    }
    
    /**
     * Factory method for instantiate a resource collection.
     * 
     * @param  string $resourceName
     * @param  string|null $url
     * @return Collection
     */
    public function resourceList($resourceName, $url = null, $service = null)
    {
        $className = $this->resolveResourceClass($resourceName);
        return $this->collection($className, $url, $service);
    }

}