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 * Constructs 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 $this->url = $this->getParent()->url('metadata');
55 $this->key = $key;
56
57 // in either case, retrieve the data
58 $response = $this->getParent()
59 ->getClient()
60 ->get($this->getUrl())
61 ->send();
62
63 // parse and assign the server metadata
64 $body = Formatter::decode($response);
65
66 if (isset($body->metadata)) {
67 foreach ($body->metadata as $key => $value) {
68 $this->$key = $value;
69 }
70 }
71 }
72 }
73
74 public function getParent()
75 {
76 return $this->parent;
77 }
78
79 public function setParent($parent)
80 {
81 $this->parent = $parent;
82
83 return $this;
84 }
85
86 /**
87 * Returns the URL of the metadata (key or block)
88 *
89 * @return string
90 * @param string $subresource not used; required for strict compatibility
91 * @throws ServerUrlerror
92 */
93 public function getUrl($path = null, array $query = array())
94 {
95 if (!isset($this->url)) {
96 throw new Exceptions\ServerUrlError(
97 'Metadata has no URL (new object)'
98 );
99 }
100
101 return Url::factory($this->url)->addPath($this->key);
102 }
103
104 /**
105 * Sets a new metadata value or block
106 *
107 * Note that, if you're setting a block, the block specified will
108 * *entirely replace* the existing block.
109 *
110 * @api
111 * @return void
112 * @throws MetadataCreateError
113 */
114 public function create()
115 {
116 return $this->getParent()
117 ->getClient()
118 ->put($this->getUrl(), self::getJsonHeader(), $this->getMetadataJson())
119 ->send();
120 }
121
122 /**
123 * Updates a metadata key or block
124 *
125 * @api
126 * @return void
127 * @throws MetadataUpdateError
128 */
129 public function update()
130 {
131 return $this->getParent()
132 ->getClient()
133 ->post($this->getUrl(), self::getJsonHeader(), $this->getMetadataJson())
134 ->send();
135 }
136
137 /**
138 * Deletes a metadata key or block
139 *
140 * @api
141 * @return void
142 * @throws MetadataDeleteError
143 */
144 public function delete()
145 {
146 return $this->getParent()->getClient()->delete($this->getUrl(), array());
147 }
148
149 public function __set($key, $value)
150 {
151 // if a key was supplied when creating the object, then we can't set
152 // any other values
153 if ($this->key && $key != $this->key) {
154 throw new Exceptions\MetadataKeyError(sprintf(
155 Lang::translate('You cannot set extra values on [%s]'),
156 $this->getUrl()
157 ));
158 }
159
160 // otherwise, just set it;
161 parent::__set($key, $value);
162 }
163
164 /**
165 * Builds a metadata JSON string
166 *
167 * @return string
168 * @throws MetadataJsonError
169 * @codeCoverageIgnore
170 */
171 private function getMetadataJson()
172 {
173 $object = (object) array(
174 'meta' => (object) array(),
175 'metadata' => (object) array()
176 );
177
178 // different element if only a key is set
179 if ($name = $this->key) {
180 $object->meta->$name = $this->$name;
181 } else {
182 $object->metadata = new \stdClass();
183 foreach ($this->keylist() as $key) {
184 $object->metadata->$key = (string) $this->$key;
185 }
186 }
187
188 $json = json_encode($object);
189 $this->checkJsonError();
190
191 return $json;
192 }
193 }
194