Overview

Namespaces

  • None
  • OpenCloud
    • Autoscale
      • Resource
    • CloudMonitoring
      • Exception
      • Resource
    • Common
      • Exceptions
      • Log
      • Request
        • Response
    • Compute
    • Database
    • DNS
    • LoadBalancer
      • Resources
    • ObjectStore
      • Resource
    • Orchestration
    • Volume
  • PHP

Classes

  • OpenStack
  • Rackspace

Functions

  • __raxsdk_timezone_set
  • define_gettext
  • noslash
  • setDebug
  • Overview
  • Namespace
  • Class
  • Tree
  • Download
   1: <?php
   2: /**
   3:  * PHP OpenCloud library.
   4:  * 
   5:  * @copyright Copyright 2013 Rackspace US, Inc. See COPYING for licensing information.
   6:  * @license   https://www.apache.org/licenses/LICENSE-2.0 Apache 2.0
   7:  * @version   1.6.0
   8:  * @author    Glen Campbell <glen.campbell@rackspace.com>
   9:  * @author    Jamie Hannaford <jamie.hannaford@rackspace.com>
  10:  */
  11: 
  12: namespace OpenCloud;
  13: 
  14: require_once __DIR__ . '/Globals.php';
  15: 
  16: use OpenCloud\Common\Base;
  17: use OpenCloud\Common\Lang;
  18: use OpenCloud\Common\Exceptions;
  19: use OpenCloud\Common\ServiceCatalogItem;
  20: 
  21: /**
  22:  * The OpenStack class represents a relationship (or "connection")
  23:  * between a user and a service.
  24:  *
  25:  * This is the primary entry point into an OpenStack system, and the only one
  26:  * where the developer is required to know and provide the endpoint URL (in
  27:  * all other cases, the endpoint is derived from the Service Catalog provided
  28:  * by the authentication system).
  29:  *
  30:  * Since various providers have different mechanisms for authentication, users
  31:  * will often use a subclass of OpenStack. For example, the Rackspace
  32:  * class is provided for users of Rackspace's cloud services, and other cloud
  33:  * providers are welcome to add their own subclasses as well.
  34:  *
  35:  * General usage example:
  36:  * <code>
  37:  *  $username = 'My Username';
  38:  *  $secret = 'My Secret';
  39:  *  $connection = new OpenCloud\OpenStack($username, $secret);
  40:  *  // having established the connection, we can set some defaults
  41:  *  // this sets the default name and region of the Compute service
  42:  *  $connection->SetDefaults('Compute', 'cloudServersOpenStack', 'ORD');
  43:  *  // access a Compute service
  44:  *  $chicago = $connection->Compute();
  45:  *  // if we want to access a different service, we can:
  46:  *  $dallas = $connection->Compute('cloudServersOpenStack', 'DFW');
  47:  * </code>
  48:  */
  49: class OpenStack extends Base
  50: {
  51: 
  52:     /**
  53:      * This holds the HTTP User-Agent: used for all requests to the services. It 
  54:      * is public so that, if necessary, it can be entirely overridden by the 
  55:      * developer. However, it's strongly recomended that you use the 
  56:      * appendUserAgent() method to APPEND your own User Agent identifier to the 
  57:      * end of this string; the user agent information can be very valuable to 
  58:      * service providers to track who is using their service.
  59:      * 
  60:      * @var string 
  61:      */
  62:     public $useragent = RAXSDK_USER_AGENT;
  63: 
  64:     protected $url;
  65:     protected $secret = array();
  66:     protected $token;
  67:     protected $expiration = 0;
  68:     protected $tenant;
  69:     protected $catalog;
  70:     protected $connectTimeout = RAXSDK_CONNECTTIMEOUT;
  71:     protected $httpTimeout = RAXSDK_TIMEOUT;
  72:     protected $overlimitTimeout = RAXSDK_OVERLIMIT_TIMEOUT;
  73: 
  74:     /**
  75:      * This associative array holds default values used to identify each
  76:      * service (and to select it from the Service Catalog). Use the
  77:      * Compute::SetDefaults() method to change the default values, or
  78:      * define the global constants (for example, RAXSDK_COMPUTE_NAME)
  79:      * BEFORE loading the OpenCloud library:
  80:      *
  81:      * <code>
  82:      * define('RAXSDK_COMPUTE_NAME', 'cloudServersOpenStack');
  83:      * include('openstack.php');
  84:      * </code>
  85:      */
  86:     protected $defaults = array(
  87:         'Compute' => array(
  88:             'name'      => RAXSDK_COMPUTE_NAME,
  89:             'region'    => RAXSDK_COMPUTE_REGION,
  90:             'urltype'   => RAXSDK_COMPUTE_URLTYPE
  91:         ),
  92:         'ObjectStore' => array(
  93:             'name'      => RAXSDK_OBJSTORE_NAME,
  94:             'region'    => RAXSDK_OBJSTORE_REGION,
  95:             'urltype'   => RAXSDK_OBJSTORE_URLTYPE
  96:         ),
  97:         'Database' => array(
  98:             'name'      => RAXSDK_DATABASE_NAME,
  99:             'region'    => RAXSDK_DATABASE_REGION,
 100:             'urltype'   => RAXSDK_DATABASE_URLTYPE
 101:         ),
 102:         'Volume' => array(
 103:             'name'      => RAXSDK_VOLUME_NAME,
 104:             'region'    => RAXSDK_VOLUME_REGION,
 105:             'urltype'   => RAXSDK_VOLUME_URLTYPE
 106:         ),
 107:         'LoadBalancer' => array(
 108:             'name'      => RAXSDK_LBSERVICE_NAME,
 109:             'region'    => RAXSDK_LBSERVICE_REGION,
 110:             'urltype'   => RAXSDK_LBSERVICE_URLTYPE
 111:         ),
 112:         'DNS' => array(
 113:             'name'      => RAXSDK_DNS_NAME,
 114:             'region'    => RAXSDK_DNS_REGION,
 115:             'urltype'   => RAXSDK_DNS_URLTYPE
 116:         ),
 117:         'Orchestration' => array(
 118:             'name'      => RAXSDK_ORCHESTRATION_NAME,
 119:             'region'    => RAXSDK_ORCHESTRATION_REGION,
 120:             'urltype'   => RAXSDK_ORCHESTRATION_URLTYPE
 121:         ),
 122:         'CloudMonitoring' => array(
 123:             'name'      => RAXSDK_MONITORING_NAME,
 124:             'region'    => RAXSDK_MONITORING_REGION,
 125:             'urltype'   => RAXSDK_MONITORING_URLTYPE
 126:         ),
 127:         'Autoscale' => array(
 128:             'name'      => RAXSDK_AUTOSCALE_NAME,
 129:             'region'    => RAXSDK_AUTOSCALE_REGION,
 130:             'urltype'   => RAXSDK_AUTOSCALE_URLTYPE
 131:         )
 132:     );
 133: 
 134:     private $_user_write_progress_callback_func;
 135:     private $_user_read_progress_callback_func;
 136: 
 137:     /**
 138:      * Tracks file descriptors used by streaming downloads
 139:      *
 140:      * This will permit multiple simultaneous streaming downloads; the
 141:      * key is the URL of the object, and the value is its file descriptor.
 142:      *
 143:      * To prevent memory overflows, each array element is deleted when
 144:      * the end of the file is reached.
 145:      */
 146:     private $fileDescriptors = array();
 147: 
 148:     /**
 149:      * array of options to pass to the CURL request object
 150:      */
 151:     private $curlOptions = array();
 152: 
 153:     /**
 154:      * list of attributes to export/import
 155:      */
 156:     private $exportItems = array(
 157:         'token',
 158:         'expiration',
 159:         'tenant',
 160:         'catalog'
 161:     );
 162: 
 163:     /**
 164:      * Creates a new OpenStack object
 165:      *
 166:      * The OpenStack object needs two bits of information: the URL to
 167:      * authenticate against, and a "secret", which is an associative array
 168:      * of name/value pairs. Usually, the secret will be a username and a
 169:      * password, but other values may be required by different authentication
 170:      * systems. For example, OpenStack Keystone requires a username and
 171:      * password, but Rackspace uses a username, tenant ID, and API key.
 172:      * (See OpenCloud\Rackspace for that.)
 173:      *
 174:      * @param string $url - the authentication endpoint URL
 175:      * @param array $secret - an associative array of auth information:
 176:      * * username
 177:      * * password
 178:      * @param array $options - CURL options to pass to the HttpRequest object
 179:      */
 180:     public function __construct($url, array $secret, array $options = array())
 181:     {
 182:         // check for supported version
 183:         // @codeCoverageIgnoreStart
 184:         $version = phpversion();
 185:         if ($version < '5.3.1') {
 186:             throw new Exceptions\UnsupportedVersionError(sprintf(
 187:                 Lang::translate('PHP version [%s] is not supported'),
 188:                 $version
 189:             ));
 190:         }
 191:         // @codeCoverageIgnoreEnd
 192:         
 193:         // Start processing
 194:         $this->getLogger()->info(Lang::translate('Initializing OpenStack client'));
 195:         
 196:         // Set properties
 197:         $this->setUrl($url);
 198:         $this->setSecret($secret);
 199:         $this->setCurlOptions($options);
 200:     }
 201:     
 202:     /**
 203:      * Set user agent.
 204:      * 
 205:      * @param  string $useragent
 206:      * @return OpenCloud\OpenStack
 207:      */
 208:     public function setUserAgent($useragent)
 209:     {
 210:         $this->useragent = $useragent;
 211:         
 212:         return $this;
 213:     }
 214:     
 215:     /**
 216:      * Allows the user to append a user agent string
 217:      *
 218:      * Programs that are using these bindings are encouraged to add their
 219:      * user agent to the one supplied by this SDK. This will permit cloud
 220:      * providers to track users so that they can provide better service.
 221:      *
 222:      * @param string $agent an arbitrary user-agent string; e.g. "My Cloud App"
 223:      * @return OpenCloud\OpenStack
 224:      */
 225:     public function appendUserAgent($useragent)
 226:     {
 227:         $this->useragent .= ';' . $useragent;
 228:         
 229:         return $this;
 230:     }
 231:     
 232:     /**
 233:      * Get user agent.
 234:      * 
 235:      * @return string
 236:      */
 237:     public function getUserAgent()
 238:     {
 239:         return $this->useragent;
 240:     }
 241:     
 242:     /**
 243:      * Sets the URL which the client will access.
 244:      * 
 245:      * @param string $url
 246:      * @return OpenCloud\OpenStack
 247:      */
 248:     public function setUrl($url)
 249:     {
 250:         $this->url = $url;
 251:         
 252:         return $this;
 253:     }
 254:     
 255:     /**
 256:      * Get the URL.
 257:      * 
 258:      * @return string
 259:      */
 260:     public function getUrl()
 261:     {
 262:         return $this->url;
 263:     }
 264:     
 265:     /**
 266:      * Set the secret for the client.
 267:      * 
 268:      * @param  array $secret
 269:      * @return OpenCloud\OpenStack
 270:      */
 271:     public function setSecret(array $secret = array())
 272:     {
 273:         $this->secret = $secret;
 274:         
 275:         return $this;
 276:     }
 277:     
 278:     /**
 279:      * Get the secret.
 280:      * 
 281:      * @return array
 282:      */
 283:     public function getSecret()
 284:     {
 285:         return $this->secret;
 286:     }
 287:     
 288:     /**
 289:      * Set the token for this client.
 290:      * 
 291:      * @param  string $token
 292:      * @return OpenCloud\OpenStack
 293:      */
 294:     public function setToken($token)
 295:     {
 296:         $this->token = $token;
 297:         
 298:         return $this;
 299:     }
 300:     
 301:     /**
 302:      * Get the token for this client.
 303:      * 
 304:      * @return string
 305:      */
 306:     public function getToken()
 307:     {
 308:         return $this->token;
 309:     }
 310:     
 311:     /**
 312:      * Set the expiration for this token.
 313:      * 
 314:      * @param  int $expiration
 315:      * @return OpenCloud\OpenStack
 316:      */
 317:     public function setExpiration($expiration)
 318:     {
 319:         $this->expiration = $expiration;
 320:         
 321:         return $this;
 322:     }
 323:     
 324:     /**
 325:      * Get the expiration time.
 326:      * 
 327:      * @return int
 328:      */
 329:     public function getExpiration()
 330:     {
 331:         return $this->expiration;
 332:     }
 333:     
 334:     /**
 335:      * Set the tenant for this client.
 336:      * 
 337:      * @param  string $tenant
 338:      * @return OpenCloud\OpenStack
 339:      */
 340:     public function setTenant($tenant)
 341:     {
 342:         $this->tenant = $tenant;
 343:         
 344:         return $this;
 345:     }
 346:     
 347:     /**
 348:      * Get the tenant for this client.
 349:      * 
 350:      * @return string
 351:      */
 352:     public function getTenant()
 353:     {
 354:         return $this->tenant;
 355:     }
 356:     
 357:     /**
 358:      * Set the service catalog.
 359:      * 
 360:      * @param  mixed $catalog
 361:      * @return OpenCloud\OpenStack
 362:      */
 363:     public function setCatalog($catalog)
 364:     {
 365:         $this->catalog = $catalog;
 366:         
 367:         return $this;
 368:     }
 369:     
 370:     /**
 371:      * Get the service catalog.
 372:      * 
 373:      * @return array
 374:      */
 375:     public function getCatalog()
 376:     {
 377:         return $this->catalog;
 378:     }
 379:     
 380:     /**
 381:      * Set (all) the cURL options.
 382:      * 
 383:      * @param  array $options
 384:      * @return OpenCloud\OpenStack
 385:      */
 386:     public function setCurlOptions(array $options)
 387:     {
 388:         $this->curlOptions = $options;
 389:         
 390:         return $this;
 391:     }
 392:     
 393:     /**
 394:      * Get the cURL options.
 395:      * 
 396:      * @return array
 397:      */
 398:     public function getCurlOptions()
 399:     {
 400:         return $this->curlOptions;
 401:     }
 402:     
 403:     /**
 404:      * Set a specific file descriptor (associated with a URL)
 405:      * 
 406:      * @param  string $key
 407:      * @param  resource $value
 408:      * @return OpenCloud\OpenStack
 409:      */
 410:     public function setFileDescriptor($key, $value)
 411:     {
 412:         $this->descriptors[$key] = $value;
 413:         
 414:         return $this;
 415:     }
 416:     
 417:     /**
 418:      * Get a specific file descriptor (associated with a URL)
 419:      * 
 420:      * @param  string $key
 421:      * @return resource|false
 422:      */
 423:     public function getFileDescriptor($key)
 424:     {
 425:         return (!isset($this->descriptors[$key])) ? false : $this->descriptors[$key];
 426:     }
 427:     
 428:     /**
 429:      * Get the items to be exported.
 430:      * 
 431:      * @return array
 432:      */
 433:     public function getExportItems()
 434:     {
 435:         return $this->exportItems;
 436:     }
 437:     
 438:     /**
 439:      * Sets the connect timeout.
 440:      * 
 441:      * @param  int $timeout
 442:      * @return OpenCloud\OpenStack
 443:      */
 444:     public function setConnectTimeout($timeout)
 445:     {
 446:         $this->connectTimeout = $timeout;
 447:         
 448:         return $this;
 449:     }
 450:     
 451:     /**
 452:      * Get the connect timeout.
 453:      * 
 454:      * @return int
 455:      */
 456:     public function getConnectTimeout()
 457:     {
 458:         return $this->connectTimeout;
 459:     }
 460:     
 461:     /**
 462:      * Set the HTTP timeout.
 463:      * 
 464:      * @param  int $timeout
 465:      * @return OpenCloud\OpenStack
 466:      */
 467:     public function setHttpTimeout($timeout)
 468:     {
 469:         $this->httpTimeout = $timeout;
 470:         
 471:         return $this;
 472:     }
 473:     
 474:     /**
 475:      * Get the HTTP timeout.
 476:      * 
 477:      * @return int
 478:      */
 479:     public function getHttpTimeout()
 480:     {
 481:         return $this->httpTimeout;
 482:     }
 483:     
 484:     /**
 485:      * Set the overlimit timeout.
 486:      * 
 487:      * @param  int $timeout
 488:      * @return OpenCloud\OpenStack
 489:      */
 490:     public function setOverlimitTimeout($timeout)
 491:     {
 492:         $this->overlimitTimeout = $timeout;
 493:         
 494:         return $this;
 495:     }
 496:     
 497:     /**
 498:      * Get the overlimit timeout.
 499:      * 
 500:      * @return int
 501:      */
 502:     public function getOverlimitTimeout()
 503:     {
 504:         return $this->overlimitTimeout;
 505:     }
 506:     
 507:     /**
 508:      * Sets default values (an array) for a service. Each array must contain a
 509:      * "name", "region" and "urltype" key.
 510:      * 
 511:      * @param string $service
 512:      * @param array $value
 513:      * @return OpenCloud\OpenStack
 514:      */
 515:     public function setDefault($service, array $value = array())
 516:     {
 517:         if (isset($value['name']) && isset($value['region']) && isset($value['urltype'])) {
 518:             $this->defaults[$service] = $value;
 519:         }
 520:         
 521:         return $this;
 522:     }
 523:     
 524:     /**
 525:      * Get a specific default value for a service. If none exist, return FALSE.
 526:      * 
 527:      * @param  string $service
 528:      * @return array|false
 529:      */
 530:     public function getDefault($service)
 531:     {
 532:         return (!isset($this->defaults[$service])) ? false : $this->defaults[$service];
 533:     }
 534:     
 535: /**
 536:      * Sets the timeouts for the current connection
 537:      *
 538:      * @api
 539:      * @param integer $t_http the HTTP timeout value (the max period that
 540:      *      the OpenStack object will wait for any HTTP request to complete).
 541:      *      Value is in seconds.
 542:      * @param integer $t_conn the Connect timeout value (the max period
 543:      *      that the OpenStack object will wait to establish an HTTP
 544:      *      connection). Value is in seconds.
 545:      * @param integer $t_overlimit the overlimit timeout value (the max period
 546:      *      that the OpenStack object will wait to retry on an overlimit
 547:      *      condition). Value is in seconds.
 548:      * @return void
 549:      */
 550:     public function setTimeouts($httpTimeout, $connectTimeout = null, $overlimitTimeout = null)
 551:     {
 552:         $this->setHttpTimeout($httpTimeout);
 553: 
 554:         if (isset($connectTimeout)) {
 555:             $this->setConnectTimeout($connectTimeout);
 556:         }
 557: 
 558:         if (isset($overlimitTimeout)) {
 559:             $this->setOverlimitTimeout($overlimitTimeout);
 560:         }
 561:     }
 562:     
 563:     /**
 564:      * Returns the URL of this object
 565:      *
 566:      * @api
 567:      * @param string $subresource specified subresource
 568:      * @return string
 569:      */
 570:     public function url($subresource='tokens')
 571:     {
 572:         return Lang::noslash($this->url) . '/' . $subresource;
 573:     }
 574: 
 575:     /**
 576:      * Returns the stored secret
 577:      *
 578:      * @return array
 579:      */
 580:     public function secret()
 581:     {
 582:         return $this->getSecret();
 583:     }
 584:    
 585:     /**
 586:      * Re-authenticates session if expired.
 587:      */
 588:     public function checkExpiration()
 589:     {
 590:         if ($this->hasExpired()) {
 591:             $this->authenticate();
 592:         }
 593:     }
 594:     
 595:     /**
 596:      * Checks whether token has expired.
 597:      * 
 598:      * @return bool
 599:      */
 600:     public function hasExpired()
 601:     {
 602:         return time() > ($this->getExpiration() - RAXSDK_FUDGE);
 603:     }
 604:     
 605:     /**
 606:      * Returns the cached token; if it has expired, then it re-authenticates
 607:      *
 608:      * @api
 609:      * @return string
 610:      */
 611:     public function token()
 612:     {
 613:         $this->checkExpiration();
 614:         
 615:         return $this->getToken();
 616:     }
 617: 
 618:     /**
 619:      * Returns the cached expiration time;
 620:      * if it has expired, then it re-authenticates
 621:      *
 622:      * @api
 623:      * @return string
 624:      */
 625:     public function expiration()
 626:     {
 627:         $this->checkExpiration();
 628:         
 629:         return $this->getExpiration();
 630:     }
 631: 
 632:     /**
 633:      * Returns the tenant ID, re-authenticating if necessary
 634:      *
 635:      * @api
 636:      * @return string
 637:      */
 638:     public function tenant()
 639:     {
 640:         $this->checkExpiration();
 641:         
 642:         return $this->getTenant();
 643:     }
 644: 
 645:     /**
 646:      * Returns the service catalog object from the auth service
 647:      *
 648:      * @return \stdClass
 649:      */
 650:     public function serviceCatalog()
 651:     {
 652:         $this->checkExpiration();
 653:         
 654:         return $this->getCatalog();
 655:     }
 656: 
 657:     /**
 658:      * Returns a Collection of objects with information on services
 659:      *
 660:      * Note that these are informational (read-only) and are not actually
 661:      * 'Service'-class objects.
 662:      */
 663:     public function serviceList()
 664:     {
 665:         return new Common\Collection($this, 'ServiceCatalogItem', $this->serviceCatalog());
 666:     }
 667: 
 668:     /**
 669:      * Creates and returns the formatted credentials to POST to the auth
 670:      * service.
 671:      *
 672:      * @return string
 673:      */
 674:     public function credentials()
 675:     {
 676:         if (isset($this->secret['username']) && isset($this->secret['password'])) {
 677:             
 678:             $credentials = array(
 679:                 'auth' => array(
 680:                     'passwordCredentials' => array(
 681:                         'username' => $this->secret['username'],
 682:                         'password' => $this->secret['password']
 683:                     )
 684:                 )
 685:             );
 686: 
 687:             if (isset($this->secret['tenantName'])) {
 688:                 $credentials['auth']['tenantName'] = $this->secret['tenantName'];
 689:             }
 690: 
 691:             return json_encode($credentials);
 692:             
 693:         } else {
 694:             throw new Exceptions\CredentialError(
 695:                Lang::translate('Unrecognized credential secret')
 696:             );
 697:         }
 698:     }
 699: 
 700:     /**
 701:      * Authenticates using the supplied credentials
 702:      *
 703:      * @api
 704:      * @return void
 705:      * @throws AuthenticationError
 706:      */
 707:     public function authenticate()
 708:     {
 709:         // try to auth
 710:         $response = $this->request(
 711:             $this->url(),
 712:             'POST',
 713:             array('Content-Type'=>'application/json'),
 714:             $this->credentials()
 715:         );
 716: 
 717:         $json = $response->httpBody();
 718: 
 719:         // check for errors
 720:         if ($response->HttpStatus() >= 400) {
 721:             throw new Exceptions\AuthenticationError(sprintf(
 722:                 Lang::translate('Authentication failure, status [%d], response [%s]'),
 723:                 $response->httpStatus(),
 724:                 $json
 725:             ));
 726:         }
 727: 
 728:         // Decode and check
 729:         $object = json_decode($json);
 730:         $this->checkJsonError();
 731:         
 732:         // Save the token information as well as the ServiceCatalog
 733:         $this->setToken($object->access->token->id);
 734:         $this->setExpiration(strtotime($object->access->token->expires));
 735:         $this->setCatalog($object->access->serviceCatalog);
 736: 
 737:         /**
 738:          * In some cases, the tenant name/id is not returned
 739:          * as part of the auth token, so we check for it before
 740:          * we set it. This occurs with pure Keystone, but not
 741:          * with the Rackspace auth.
 742:          */
 743:         if (isset($object->access->token->tenant)) {
 744:             $this->setTenant($object->access->token->tenant->id);
 745:         }
 746:     }
 747: 
 748:     /**
 749:      * Performs a single HTTP request
 750:      *
 751:      * The request() method is one of the most frequently-used in the entire
 752:      * library. It performs an HTTP request using the specified URL, method,
 753:      * and with the supplied headers and body. It handles error and
 754:      * exceptions for the request.
 755:      *
 756:      * @api
 757:      * @param string url - the URL of the request
 758:      * @param string method - the HTTP method (defaults to GET)
 759:      * @param array headers - an associative array of headers
 760:      * @param string data - either a string or a resource (file pointer) to
 761:      *      use as the request body (for PUT or POST)
 762:      * @return HttpResponse object
 763:      * @throws HttpOverLimitError, HttpUnauthorizedError, HttpForbiddenError
 764:      */
 765:     public function request($url, $method = 'GET', $headers = array(), $data = null)
 766:     {
 767:         $this->getLogger()->info('Resource [{url}] method [{method}] body [{body}]', array(
 768:             'url'    => $url, 
 769:             'method' => $method, 
 770:             'data'   => $data
 771:         ));
 772: 
 773:         // get the request object
 774:         $http = $this->getHttpRequestObject($url, $method, $this->getCurlOptions());
 775: 
 776:         // set various options
 777:         $this->getLogger()->info('Headers: [{headers}]', array(
 778:             'headers' => print_r($headers, true)
 779:         ));
 780:         
 781:         $http->setheaders($headers);
 782:         $http->setHttpTimeout($this->getHttpTimeout());
 783:         $http->setConnectTimeout($this->getConnectTimeout());
 784:         $http->setOption(CURLOPT_USERAGENT, $this->getUserAgent());
 785: 
 786:         // data can be either a resource or a string
 787:         if (is_resource($data)) {
 788:             // loading from or writing to a file
 789:             // set the appropriate callback functions
 790:             switch($method) {
 791:                 // @codeCoverageIgnoreStart
 792:                 case 'GET':
 793:                     // need to save the file descriptor
 794:                     $this->setFileDescriptor($url, $data);
 795:                     // set the CURL options
 796:                     $http->setOption(CURLOPT_FILE, $data);
 797:                     $http->setOption(CURLOPT_WRITEFUNCTION, array($this, '_write_cb'));
 798:                     break;
 799:                 // @codeCoverageIgnoreEnd
 800:                 case 'PUT':
 801:                 case 'POST':
 802:                     // need to save the file descriptor
 803:                     $this->setFileDescriptor($url, $data);
 804:                     if (!isset($headers['Content-Length'])) {
 805:                         throw new Exceptions\HttpError(
 806:                             Lang::translate('The Content-Length: header must be specified for file uploads')
 807:                         );
 808:                     }
 809:                     $http->setOption(CURLOPT_UPLOAD, TRUE);
 810:                     $http->setOption(CURLOPT_INFILE, $data);
 811:                     $http->setOption(CURLOPT_INFILESIZE, $headers['Content-Length']);
 812:                     $http->setOption(CURLOPT_READFUNCTION, array($this, '_read_cb'));
 813:                     break;
 814:                 default:
 815:                     // do nothing
 816:                     break;
 817:             }
 818:         } elseif (is_string($data)) {
 819:             $http->setOption(CURLOPT_POSTFIELDS, $data);
 820:         } elseif (isset($data)) {
 821:             throw new Exceptions\HttpError(
 822:                 Lang::translate('Unrecognized data type for PUT/POST body, must be string or resource')
 823:             );
 824:         }
 825:         
 826:         // perform the HTTP request; returns an HttpResult object
 827:         $response = $http->execute();
 828: 
 829:         // handle and retry on overlimit errors
 830:         if ($response->httpStatus() == 413) {
 831:             
 832:             $object = json_decode($response->httpBody());
 833:             $this->checkJsonError();
 834:             
 835:             // @codeCoverageIgnoreStart
 836:             if (isset($object->overLimit)) {
 837:                 /**
 838:                  * @TODO(glen) - The documentation says "retryAt", but
 839:                  * the field returned is "retryAfter". If the doc changes,
 840:                  * then there's no problem, but we'll need to fix this if
 841:                  * they change the code to match the docs.
 842:                  */
 843:                 $retryAfter    = $object->overLimit->retryAfter;
 844:                 $sleepInterval = strtotime($retryAfter) - time();
 845: 
 846:                 if ($sleepInterval && $sleepInterval <= $this->getOverlimitTimeout()) {
 847:                     sleep($sleepInterval);
 848:                     $response = $http->Execute();
 849:                 } else {
 850:                     throw new Exceptions\HttpOverLimitError(sprintf(
 851:                         Lang::translate('Over limit; next available request [%s][%s] is not for [%d] seconds at [%s]'),
 852:                         $method,
 853:                         $url,
 854:                         $sleepInterval,
 855:                         $retryAfter
 856:                     ));
 857:                 }
 858:             }
 859:             // @codeCoverageIgnoreEnd
 860:         }
 861: 
 862:         // do some common error checking
 863:         switch ($response->httpStatus()) {
 864:             case 401:
 865:                 throw new Exceptions\HttpUnauthorizedError(sprintf(
 866:                     Lang::translate('401 Unauthorized for [%s] [%s]'),
 867:                     $url,
 868:                     $response->HttpBody()
 869:                 ));
 870:                 break;
 871:             case 403:
 872:                 throw new Exceptions\HttpForbiddenError(sprintf(
 873:                     Lang::translate('403 Forbidden for [%s] [%s]'),
 874:                     $url,
 875:                     $response->HttpBody()
 876:                 ));
 877:                 break;
 878:             case 413:   // limit
 879:                 throw new Exceptions\HttpOverLimitError(sprintf(
 880:                     Lang::translate('413 Over limit for [%s] [%s]'),
 881:                     $url,
 882:                     $response->HttpBody()
 883:                 ));
 884:                 break;
 885:             default:
 886:                 // everything is fine here, we're fine, how are you?
 887:                 break;
 888:         }
 889: 
 890:         // free the handle
 891:         $http->close();
 892: 
 893:         // return the HttpResponse object
 894:         $this->getLogger()->info('HTTP STATUS [{code}]', array(
 895:             'code' => $response->httpStatus()
 896:         ));
 897: 
 898:         return $response;
 899:     }
 900: 
 901:     /**
 902:      * Sets default values for name, region, URL type for a service
 903:      *
 904:      * Once these are set (and they can also be set by defining global
 905:      * constants), then you do not need to specify these values when
 906:      * creating new service objects.
 907:      *
 908:      * @api
 909:      * @param string $service the name of a supported service; e.g. 'Compute'
 910:      * @param string $name the service name; e.g., 'cloudServersOpenStack'
 911:      * @param string $region the region name; e.g., 'LON'
 912:      * @param string $urltype the type of URL to use; e.g., 'internalURL'
 913:      * @return void
 914:      * @throws UnrecognizedServiceError
 915:      */
 916:     public function setDefaults(
 917:         $service,
 918:         $name = null,
 919:         $region = null,
 920:         $urltype = null
 921:     ) {
 922: 
 923:         if (!isset($this->defaults[$service])) {
 924:             throw new Exceptions\UnrecognizedServiceError(sprintf(
 925:                 Lang::translate('Service [%s] is not recognized'), $service
 926:             ));
 927:         }
 928: 
 929:         if (isset($name)) {
 930:             $this->defaults[$service]['name'] = $name;
 931:         }
 932: 
 933:         if (isset($region)) {
 934:             $this->defaults[$service]['region'] = $region;
 935:         }
 936: 
 937:         if (isset($urltype)) {
 938:             $this->defaults[$service]['urltype'] = $urltype;
 939:         }
 940:     }
 941: 
 942:     /**
 943:      * Allows the user to define a function for tracking uploads
 944:      *
 945:      * This can be used to implement a progress bar or similar function. The
 946:      * callback function is called with a single parameter, the length of the
 947:      * data that is being uploaded on this call.
 948:      *
 949:      * @param callable $callback the name of a global callback function, or an
 950:      *      array($object, $functionname)
 951:      * @return void
 952:      */
 953:     public function setUploadProgressCallback($callback)
 954:     {
 955:         $this->_user_write_progress_callback_func = $callback;
 956:     }
 957: 
 958:     /**
 959:      * Allows the user to define a function for tracking downloads
 960:      *
 961:      * This can be used to implement a progress bar or similar function. The
 962:      * callback function is called with a single parameter, the length of the
 963:      * data that is being downloaded on this call.
 964:      *
 965:      * @param callable $callback the name of a global callback function, or an
 966:      *      array($object, $functionname)
 967:      * @return void
 968:      */
 969:     public function setDownloadProgressCallback($callback)
 970:     {
 971:         $this->_user_read_progress_callback_func = $callback;
 972:     }
 973: 
 974:     /**
 975:      * Callback function to handle reads for file uploads
 976:      *
 977:      * Internal function for handling file uploads. Note that, although this
 978:      * function's visibility is public, this is only because it must be called
 979:      * from the HttpRequest interface. This should NOT be called by users
 980:      * directly.
 981:      *
 982:      * @param resource $ch a CURL handle
 983:      * @param resource $fd a file descriptor
 984:      * @param integer $length the amount of data to read
 985:      * @return string the data read
 986:      * @codeCoverageIgnore
 987:      */
 988:     public function _read_cb($ch, $fd, $length)
 989:     {
 990:         $data = fread($fd, $length);
 991:         $len = strlen($data);
 992:         if (isset($this->_user_write_progress_callback_func)) {
 993:             call_user_func($this->_user_write_progress_callback_func, $len);
 994:         }
 995:         return $data;
 996:     }
 997: 
 998:     /**
 999:      * Callback function to handle writes for file downloads
1000:      *
1001:      * Internal function for handling file downloads. Note that, although this
1002:      * function's visibility is public, this is only because it must be called
1003:      * via the HttpRequest interface. This should NOT be called by users
1004:      * directly.
1005:      *
1006:      * @param resource $ch a CURL handle
1007:      * @param string $data the data to be written to a file
1008:      * @return integer the number of bytes written
1009:      * @codeCoverageIgnore
1010:      */
1011:     public function _write_cb($ch, $data)
1012:     {
1013:         $url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
1014: 
1015:         if (false === ($fp = $this->getFileDescriptor($url))) {
1016:             throw new Exceptions\HttpUrlError(sprintf(
1017:                 Lang::translate('Cannot find file descriptor for URL [%s]'), $url)
1018:             );
1019:         }
1020: 
1021:         $dlen = strlen($data);
1022:         fwrite($fp, $data, $dlen);
1023: 
1024:         // call used callback function
1025:         if (isset($this->_user_read_progress_callback_func)) {
1026:             call_user_func($this->_user_read_progress_callback_func, $dlen);
1027:         }
1028: 
1029:         // MUST return the length to CURL
1030:         return $dlen;
1031:     }
1032: 
1033:     /**
1034:      * exports saved token, expiration, tenant, and service catalog as an array
1035:      *
1036:      * This could be stored in a cache (APC or disk file) and reloaded using
1037:      * ImportCredentials()
1038:      *
1039:      * @return array
1040:      */
1041:     public function exportCredentials()
1042:     {
1043:         $this->authenticate();
1044:         
1045:         $array = array();
1046:         
1047:         foreach ($this->getExportItems() as $key) {
1048:             $array[$key] = $this->$key;
1049:         }
1050:         
1051:         return $array;
1052:     }
1053: 
1054:     /**
1055:      * imports credentials from an array
1056:      *
1057:      * Takes the same values as ExportCredentials() and reuses them.
1058:      *
1059:      * @return void
1060:      */
1061:     public function importCredentials(array $values)
1062:     {
1063:         foreach ($this->getExportItems() as $item) {
1064:             $this->$item = $values[$item];
1065:         }
1066:     }
1067: 
1068:     /********** FACTORY METHODS **********
1069:      * 
1070:      * These methods are provided to permit easy creation of services
1071:      * (for example, Nova or Swift) from a connection object. As new
1072:      * services are supported, factory methods should be provided here.
1073:      */
1074: 
1075:     /**
1076:      * Creates a new ObjectStore object (Swift/Cloud Files)
1077:      *
1078:      * @api
1079:      * @param string $name the name of the Object Storage service to attach to
1080:      * @param string $region the name of the region to use
1081:      * @param string $urltype the URL type (normally "publicURL")
1082:      * @return ObjectStore
1083:      */
1084:     public function objectStore($name = null, $region = null, $urltype = null)
1085:     {
1086:         return $this->service('ObjectStore', $name, $region, $urltype);
1087:     }
1088: 
1089:     /**
1090:      * Creates a new Compute object (Nova/Cloud Servers)
1091:      *
1092:      * @api
1093:      * @param string $name the name of the Compute service to attach to
1094:      * @param string $region the name of the region to use
1095:      * @param string $urltype the URL type (normally "publicURL")
1096:      * @return Compute
1097:      */
1098:     public function compute($name = null, $region = null, $urltype = null)
1099:     {
1100:         return $this->service('Compute', $name, $region, $urltype);
1101:     }
1102: 
1103:     /**
1104:      * Creates a new Orchestration (heat) service object
1105:      *
1106:      * @api
1107:      * @param string $name the name of the Compute service to attach to
1108:      * @param string $region the name of the region to use
1109:      * @param string $urltype the URL type (normally "publicURL")
1110:      * @return Orchestration\Service
1111:      * @codeCoverageIgnore
1112:      */
1113:     public function orchestration($name = null, $region = null, $urltype = null)
1114:     {
1115:         return $this->service('Orchestration', $name, $region, $urltype);
1116:     }
1117: 
1118:     /**
1119:      * Creates a new VolumeService (cinder) service object
1120:      *
1121:      * This is a factory method that is Rackspace-only (NOT part of OpenStack).
1122:      *
1123:      * @param string $name the name of the service (e.g., 'cloudBlockStorage')
1124:      * @param string $region the region (e.g., 'DFW')
1125:      * @param string $urltype the type of URL (e.g., 'publicURL');
1126:      */
1127:     public function volumeService($name = null, $region = null, $urltype = null)
1128:     {
1129:         return $this->service('Volume', $name, $region, $urltype);
1130:     }
1131: 
1132:     /**
1133:      * Generic Service factory method
1134:      *
1135:      * Contains code reused by the other service factory methods.
1136:      *
1137:      * @param string $class the name of the Service class to produce
1138:      * @param string $name the name of the Compute service to attach to
1139:      * @param string $region the name of the region to use
1140:      * @param string $urltype the URL type (normally "publicURL")
1141:      * @return Service (or subclass such as Compute, ObjectStore)
1142:      * @throws ServiceValueError
1143:      */
1144:     public function service($class, $name = null, $region = null, $urltype = null)
1145:     {
1146:         // debug message
1147:         $this->getLogger()->info('Factory for class [{class}] [{name}/{region}/{urlType}]', array(
1148:             'class'   => $class, 
1149:             'name'    => $name, 
1150:             'region'  => $region, 
1151:             'urlType' => $urltype
1152:         ));
1153: 
1154:         // Strips off base namespace 
1155:         $class = preg_replace('#\\\?OpenCloud\\\#', '', $class);
1156: 
1157:         // check for defaults
1158:         $default = $this->getDefault($class);
1159: 
1160:         // report errors
1161:         if (!$name = $name ?: $default['name']) {
1162:             throw new Exceptions\ServiceValueError(sprintf(
1163:                 Lang::translate('No value for %s name'),
1164:                 $class
1165:             ));
1166:         }
1167: 
1168:         if (!$region = $region ?: $default['region']) {
1169:             throw new Exceptions\ServiceValueError(sprintf(
1170:                 Lang::translate('No value for %s region'),
1171:                 $class
1172:             ));
1173:         }
1174: 
1175:         if (!$urltype = $urltype ?: $default['urltype']) {
1176:             throw new Exceptions\ServiceValueError(sprintf(
1177:                 Lang::translate('No value for %s URL type'),
1178:                 $class
1179:             ));
1180:         }
1181: 
1182:         // return the object
1183:         $fullclass = 'OpenCloud\\' . $class . '\\Service';
1184: 
1185:         return new $fullclass($this, $name, $region, $urltype);
1186:     }
1187: 
1188:     /**
1189:      * returns a service catalog item
1190:      *
1191:      * This is a helper function used to list service catalog items easily
1192:      */
1193:     public function serviceCatalogItem($info = array())
1194:     {
1195:         return new ServiceCatalogItem($info);
1196:     }
1197:     
1198: }
1199: 
PHP OpenCloud API API documentation generated by ApiGen 2.8.0