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\Compute;
13:
14: use OpenCloud\Common\Lang;
15: use OpenCloud\Common\Metadata;
16: use OpenCloud\Common\Exceptions;
17:
18: /**
19: * This class handles specialized metadata for OpenStack Server objects (metadata
20: * items can be managed individually or in aggregate).
21: *
22: * Server metadata is a weird beast in that it has resource representations
23: * and HTTP calls to set the entire server metadata as well as individual
24: * items.
25: *
26: * @author Glen Campbell <glen.campbell@rackspace.com>
27: */
28: class ServerMetadata extends Metadata
29: {
30:
31: private $_parent; // the parent object
32: private $_key; // the metadata item (if supplied)
33: private $_url; // the URL of this particular metadata item or block
34:
35: /**
36: * Contructs a Metadata object associated with a Server or Image object
37: *
38: * @param object $parent either a Server or an Image object
39: * @param string $key the (optional) key for the metadata item
40: * @throws MetadataError
41: */
42: public function __construct(Server $parent, $key = null)
43: {
44: // construct defaults
45: $this->_parent = $parent;
46:
47: // set the URL according to whether or not we have a key
48: if ($this->Parent()->id) {
49: $this->_url = $this->Parent()->Url() . '/metadata';
50: $this->_key = $key;
51:
52: // in either case, retrieve the data
53: $response = $this->Parent()->Service()->Request($this->Url());
54:
55: // @codeCoverageIgnoreStart
56: if ($response->httpStatus() >= 300) {
57: throw new Exceptions\MetadataError(sprintf(
58: Lang::translate('Unable to retrieve metadata [%s], response [%s]'),
59: $this->Url(),
60: $response->HttpBody()
61: ));
62: }
63: // @codeCoverageIgnoreEnd
64:
65: $this->getLogger()->info(
66: Lang::translate('Metadata for [{url}] is [{body}]'),
67: array(
68: 'url' => $this->url(),
69: 'body' => $response->httpBody()
70: )
71: );
72:
73: // parse and assign the server metadata
74: $obj = json_decode($response->HttpBody());
75:
76: if ((!$this->CheckJsonError()) && isset($obj->metadata)) {
77: foreach($obj->metadata as $k => $v) {
78: $this->$k = $v;
79: }
80: }
81: }
82: }
83:
84: /**
85: * Returns the URL of the metadata (key or block)
86: *
87: * @return string
88: * @param string $subresource not used; required for strict compatibility
89: * @throws ServerUrlerror
90: */
91: public function url($subresource = '')
92: {
93: if (!isset($this->_url)) {
94: throw new Exceptions\ServerUrlError(
95: 'Metadata has no URL (new object)'
96: );
97: }
98:
99: if ($this->_key) {
100: return $this->_url . '/' . $this->_key;
101: } else {
102: return $this->_url;
103: }
104: }
105:
106: /**
107: * Sets a new metadata value or block
108: *
109: * Note that, if you're setting a block, the block specified will
110: * *entirely replace* the existing block.
111: *
112: * @api
113: * @return void
114: * @throws MetadataCreateError
115: */
116: public function create()
117: {
118: // perform the request
119: $response = $this->parent()->Service()->Request(
120: $this->Url(),
121: 'PUT',
122: array(),
123: $this->GetMetadataJson()
124: );
125:
126: // @codeCoverageIgnoreStart
127: if ($response->HttpStatus() >= 300) {
128: throw new \OpenCloud\Common\Exceptions\MetadataCreateError(sprintf(
129: Lang::translate('Error setting metadata on [%s], response [%s]'),
130: $this->Url(),
131: $response->HttpBody()
132: ));
133: }
134: // @codeCoverageIgnoreEnd
135: }
136:
137: /**
138: * Updates a metadata key or block
139: *
140: * @api
141: * @return void
142: * @throws MetadataUpdateError
143: */
144: public function update()
145: {
146: // perform the request
147: $response = $this->parent()->getService()->Request(
148: $this->url(),
149: 'POST',
150: array(),
151: $this->getMetadataJson()
152: );
153:
154: // @codeCoverageIgnoreStart
155: if ($response->HttpStatus() >= 300) {
156: throw new Exceptions\MetadataUpdateError(sprintf(
157: Lang::translate('Error updating metadata on [%s], response [%s]'),
158: $this->Url(),
159: $response->HttpBody()
160: ));
161: }
162: // @codeCoverageIgnoreEnd
163: }
164:
165: /**
166: * Deletes a metadata key or block
167: *
168: * @api
169: * @return void
170: * @throws MetadataDeleteError
171: */
172: public function delete()
173: {
174: // perform the request
175: $response = $this->parent()->getService()->Request(
176: $this->url(),
177: 'DELETE',
178: array()
179: );
180:
181: // @codeCoverageIgnoreStart
182: if ($response->httpStatus() >= 300) {
183: throw new Exceptions\MetadataDeleteError(sprintf(
184: Lang::translate('Error deleting metadata on [%s], response [%s]'),
185: $this->url(),
186: $response->httpBody()
187: ));
188: }
189: // @codeCoverageIgnoreEnd
190: }
191:
192: /**
193: * Returns the parent Server object
194: *
195: * @return Server
196: */
197: public function parent()
198: {
199: return $this->_parent;
200: }
201:
202: /**
203: * Overrides the base setter method, since the metadata key can be
204: * anything (no name-checking is required)
205: *
206: * @param string $key
207: * @param string $value
208: * @return void
209: * @throws MetadataKeyError
210: */
211: public function __set($key, $value)
212: {
213: // if a key was supplied when creating the object, then we can't set
214: // any other values
215: if ($this->_key && $key != $this->_key) {
216: throw new Exceptions\MetadataKeyError(sprintf(
217: Lang::translate('You cannot set extra values on [%s]'),
218: $this->Url()
219: ));
220: }
221:
222: // otherwise, just set it;
223: parent::__set($key, $value);
224: }
225:
226: /********** PRIVATE METHODS **********/
227:
228: /**
229: * Builds a metadata JSON string
230: *
231: * @return string
232: * @throws MetadataJsonError
233: */
234: private function getMetadataJson()
235: {
236: $object = (object) array(
237: 'meta' => new \stdClass,
238: 'metadata' => new \stdClass
239: );
240:
241: // different element if only a key is set
242: if ($name = $this->_key) {
243: $object->meta->$name = $this->$name;
244: } else {
245: $object->metadata = new \stdClass();
246: foreach ($this->keylist() as $key) {
247: $object->metadata->$key = (string) $this->$key;
248: }
249: }
250:
251: $json = json_encode($object);
252:
253: $this->checkJsonError();
254:
255: return $json;
256: }
257:
258: }
259: