      1: <?php
      2: /**
      3:  * Copyright 2012-2014 Rackspace US, Inc.
      4:  *
      5:  * Licensed under the Apache License, Version 2.0 (the "License");
      6:  * you may not use this file except in compliance with the License.
      7:  * You may obtain a copy of the License at
      8:  *
      9:  * http://www.apache.org/licenses/LICENSE-2.0
     10:  *
     11:  * Unless required by applicable law or agreed to in writing, software
     12:  * distributed under the License is distributed on an "AS IS" BASIS,
     13:  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14:  * See the License for the specific language governing permissions and
     15:  * limitations under the License.
     16:  */
     17:
     18: namespace OpenCloud\Compute\Resource;
     19:
     20: use Guzzle\Http\Url;
     21: use OpenCloud\Common\Exceptions;
     22: use OpenCloud\Common\Http\Message\Formatter;
     23: use OpenCloud\Common\Lang;
     24: use OpenCloud\Common\Metadata;
     25:
     26: /**
     27:  * This class handles specialized metadata for OpenStack Server objects (metadata
     28:  * items can be managed individually or in aggregate).
     29:  *
     30:  * Server metadata is a weird beast in that it has resource representations
     31:  * and HTTP calls to set the entire server metadata as well as individual
     32:  * items.
     33:  */
     34: class ServerMetadata extends Metadata
     35: {
     36:     private $parent;
     37:     protected $key; // the metadata item (if supplied)
     38:     private $url; // the URL of this particular metadata item or block
     39:
     40:     /**
     41:      * Contructs a Metadata object associated with a Server or Image object
     42:      *
     43:      * @param object $parent either a Server or an Image object
     44:      * @param string $key    the (optional) key for the metadata item
     45:      * @throws MetadataError
     46:      */
     47:     public function __construct(Server $parent, $key = null)
     48:     {
     49:         // construct defaults
     50:         $this->setParent($parent);
     51:
     52:         // set the URL according to whether or not we have a key
     53:         if ($this->getParent()->getId()) {
     54:
     55:             $this->url = $this->getParent()->url('metadata');
     56:             $this->key = $key;
     57:
     58:             // in either case, retrieve the data
     59:             $response = $this->getParent()
     60:                 ->getClient()
     61:                 ->get($this->getUrl())
     62:                 ->send();
     63:
     64:             // parse and assign the server metadata
     65:             $body = Formatter::decode($response);
     66:
     67:             if (isset($body->metadata)) {
     68:                 foreach ($body->metadata as $key => $value) {
     69:                     $this->$key = $value;
     70:                 }
     71:             }
     72:         }
     73:     }
     74:
     75:     public function getParent()
     76:     {
     77:         return $this->parent;
     78:     }
     79:
     80:     public function setParent($parent)
     81:     {
     82:         $this->parent = $parent;
     83:
     84:         return $this;
     85:     }
     86:
     87:     /**
     88:      * Returns the URL of the metadata (key or block)
     89:      *
     90:      * @return string
     91:      * @param string $subresource not used; required for strict compatibility
     92:      * @throws ServerUrlerror
     93:      */
     94:     public function getUrl($path = null, array $query = array())
     95:     {
     96:         if (!isset($this->url)) {
     97:             throw new Exceptions\ServerUrlError(
     98:                 'Metadata has no URL (new object)'
     99:             );
    100:         }
    101:
    102:         return Url::factory($this->url)->addPath($this->key);
    103:     }
    104:
    105:     /**
    106:      * Sets a new metadata value or block
    107:      *
    108:      * Note that, if you're setting a block, the block specified will
    109:      * *entirely replace* the existing block.
    110:      *
    111:      * @api
    112:      * @return void
    113:      * @throws MetadataCreateError
    114:      */
    115:     public function create()
    116:     {
    117:         return $this->getParent()
    118:             ->getClient()
    119:             ->put($this->getUrl(), self::getJsonHeader(), $this->getMetadataJson())
    120:             ->send();
    121:     }
    122:
    123:     /**
    124:      * Updates a metadata key or block
    125:      *
    126:      * @api
    127:      * @return void
    128:      * @throws MetadataUpdateError
    129:      */
    130:     public function update()
    131:     {
    132:         return $this->getParent()
    133:             ->getClient()
    134:             ->post($this->getUrl(), self::getJsonHeader(), $this->getMetadataJson())
    135:             ->send();
    136:     }
    137:
    138:     /**
    139:      * Deletes a metadata key or block
    140:      *
    141:      * @api
    142:      * @return void
    143:      * @throws MetadataDeleteError
    144:      */
    145:     public function delete()
    146:     {
    147:         return $this->getParent()->getClient()->delete($this->getUrl(), array());
    148:     }
    149:
    150:     public function __set($key, $value)
    151:     {
    152:         // if a key was supplied when creating the object, then we can't set
    153:         // any other values
    154:         if ($this->key && $key != $this->key) {
    155:             throw new Exceptions\MetadataKeyError(sprintf(
    156:                 Lang::translate('You cannot set extra values on [%s]'),
    157:                 $this->getUrl()
    158:             ));
    159:         }
    160:
    161:         // otherwise, just set it;
    162:         parent::__set($key, $value);
    163:     }
    164:
    165:     /**
    166:      * Builds a metadata JSON string
    167:      *
    168:      * @return string
    169:      * @throws MetadataJsonError
    170:      * @codeCoverageIgnore
    171:      */
    172:     private function getMetadataJson()
    173:     {
    174:         $object = (object) array(
    175:             'meta'     => (object) array(),
    176:             'metadata' => (object) array()
    177:         );
    178:
    179:         // different element if only a key is set
    180:         if ($name = $this->key) {
    181:             $object->meta->$name = $this->$name;
    182:         } else {
    183:             $object->metadata = new \stdClass();
    184:             foreach ($this->keylist() as $key) {
    185:                 $object->metadata->$key = (string) $this->$key;
    186:             }
    187:         }
    188:
    189:         $json = json_encode($object);
    190:         $this->checkJsonError();
    191:
    192:         return $json;
    193:     }
    194: }
    195: