diff --git a/doc/services/compute/v2/index.rst b/doc/services/compute/v2/index.rst index 26190cdaf..512593705 100644 --- a/doc/services/compute/v2/index.rst +++ b/doc/services/compute/v2/index.rst @@ -11,6 +11,7 @@ OpenStack Compute (Nova) API Version 2. Nova is the OpenStack project that provi create servers + volume-attachments flavors images states \ No newline at end of file diff --git a/doc/services/compute/v2/volume-attachments.rst b/doc/services/compute/v2/volume-attachments.rst new file mode 100644 index 000000000..7cc61687f --- /dev/null +++ b/doc/services/compute/v2/volume-attachments.rst @@ -0,0 +1,38 @@ +Volume Attachments +================== + +Nova allows you to attach a volume to a server on the fly. This model represents a point of attachment between a server and a volume. + +.. osdoc:: https://docs.openstack.org/api-ref/compute/#servers-with-volume-attachments-servers-os-volume-attachments + +.. |models| replace:: volume attachments + +.. include:: /common/service.rst + +.. warning:: + + The server must be fully started before you can attach a volume to it. Just because the server is in the ``ACTIVE`` + state does not mean that it is ready to accept a volume attachment. See https://bugs.launchpad.net/nova/+bug/1960346 + and https://bugs.launchpad.net/nova/+bug/1998148 for more information. + +Create +------ + +To attach a volume to a server, you need to know the server ID and the volume ID. + +.. sample:: Compute/v2/volume_attachments/create.php + + +Delete +------ + +To detach a volume from a server, you need to know the server ID and the volume ID. + +.. sample:: Compute/v2/volume_attachments/delete.php + +List +---- + +.. sample:: Compute/v2/volume_attachments/list.php + +.. include:: /common/generators.rst diff --git a/samples/Compute/v2/servers/attach_volume_attachment.php b/samples/Compute/v2/volume_attachments/create.php similarity index 82% rename from samples/Compute/v2/servers/attach_volume_attachment.php rename to samples/Compute/v2/volume_attachments/create.php index e072d3322..769c2cfa6 100644 --- a/samples/Compute/v2/servers/attach_volume_attachment.php +++ b/samples/Compute/v2/volume_attachments/create.php @@ -13,11 +13,9 @@ 'id' => '{userId}', 'password' => '{password}' ], - 'scope' => ['project' => ['id' => '{projectId}']] ]); -$compute = $openstack->computeV2(['region' => '{region}']); - +$compute = $openstack->computeV2(); $server = $compute->getServer(['id' => '{serverId}']); $volumeAttachment = $server->attachVolume('{volumeId}'); diff --git a/samples/Compute/v2/servers/detach_volume_attachment.php b/samples/Compute/v2/volume_attachments/delete.php similarity index 66% rename from samples/Compute/v2/servers/detach_volume_attachment.php rename to samples/Compute/v2/volume_attachments/delete.php index f08c21767..482152a1b 100644 --- a/samples/Compute/v2/servers/detach_volume_attachment.php +++ b/samples/Compute/v2/volume_attachments/delete.php @@ -13,13 +13,9 @@ 'id' => '{userId}', 'password' => '{password}' ], - 'scope' => ['project' => ['id' => '{projectId}']] ]); -$compute = $openstack->computeV2(['region' => '{region}']); - -/**@var OpenStack\Compute\v2\Models\Server $server */ +$compute = $openstack->computeV2(); $server = $compute->getServer(['id' => '{serverId}']); -//Must detach by volumeAttachment id -$server->detachVolume('{volumeAttachmentId}'); +$server->detachVolume('{volumeId}'); diff --git a/samples/Compute/v2/servers/list_volume_attachments.php b/samples/Compute/v2/volume_attachments/list.php similarity index 79% rename from samples/Compute/v2/servers/list_volume_attachments.php rename to samples/Compute/v2/volume_attachments/list.php index 683e47402..58cc6639a 100644 --- a/samples/Compute/v2/servers/list_volume_attachments.php +++ b/samples/Compute/v2/volume_attachments/list.php @@ -9,11 +9,9 @@ 'id' => '{userId}', 'password' => '{password}', ], - 'scope' => ['project' => ['id' => '{projectId}']], ]); -$compute = $openstack->computeV2(['region' => '{region}']); - +$compute = $openstack->computeV2(); $server = $compute->getServer(['id' => '{serverId}']); foreach ($server->listVolumeAttachments() as $volumeAttachment) { diff --git a/src/Common/Error/Builder.php b/src/Common/Error/Builder.php index 7de837109..f2717aa1a 100644 --- a/src/Common/Error/Builder.php +++ b/src/Common/Error/Builder.php @@ -19,6 +19,8 @@ */ class Builder { + public const MAX_BODY_LENGTH = 5000; + /** * The default domain to use for further link documentation. * @@ -96,8 +98,13 @@ public function str(MessageInterface $message, int $verbosity = 0): string return $msg; } - if (ini_get('memory_limit') < 0 || $message->getBody()->getSize() < ini_get('memory_limit')) { - $msg .= "\r\n\r\n".$message->getBody(); + $contentType = strtolower($message->getHeaderLine('content-type')); + if (false !== strpos($contentType, 'application/json')) { + $body = $message->getBody()->read(self::MAX_BODY_LENGTH); + $msg .= "\r\n\r\n".$body; + if ('' !== $message->getBody()->read(1)) { + $msg .= '...'; + } } return trim($msg); diff --git a/tests/sample/Compute/v2/ServerTest.php b/tests/sample/Compute/v2/ServerTest.php index 18c8f5a3c..6d78a6cda 100644 --- a/tests/sample/Compute/v2/ServerTest.php +++ b/tests/sample/Compute/v2/ServerTest.php @@ -374,6 +374,9 @@ public function testSuspend() $server->waitUntil('SUSPENDED'); $this->assertEquals('SUSPENDED', $server->status); + // wait for the server to be fully suspended + sleep(5); + return $server; } diff --git a/tests/sample/Compute/v2/VolumeAttachmentTest.php b/tests/sample/Compute/v2/VolumeAttachmentTest.php index 4399a4498..17361b3c6 100644 --- a/tests/sample/Compute/v2/VolumeAttachmentTest.php +++ b/tests/sample/Compute/v2/VolumeAttachmentTest.php @@ -27,7 +27,7 @@ public function testAttach(): VolumeAttachment $this->assertEquals('available', $volume->status); /** @var \OpenStack\BlockStorage\v2\Models\VolumeAttachment $volumeAttachment */ - require_once $this->sampleFile('servers/attach_volume_attachment.php', [ + require_once $this->sampleFile('volume_attachments/create.php', [ '{serverId}' => $server->id, '{volumeId}' => $volume->id, ]); @@ -47,12 +47,12 @@ public function testList(VolumeAttachment $createdVolumeAttachment) { $found = false; require_once $this->sampleFile( - 'servers/list_volume_attachments.php', + 'volume_attachments/list.php', [ '{serverId}' => $createdVolumeAttachment->serverId, '/** @var \OpenStack\BlockStorage\v2\Models\VolumeAttachment $volumeAttachment */' => <<<'PHP' /** @var \OpenStack\BlockStorage\v2\Models\VolumeAttachment $volumeAttachment */ -if ($volumeAttachment->id === $createdVolumeAttachment->id) { +if ($volumeAttachment->volumeId === $createdVolumeAttachment->volumeId) { $found = true; } PHP @@ -73,10 +73,10 @@ public function testDetach(VolumeAttachment $createdVolumeAttachment) sleep(15); require_once $this->sampleFile( - 'servers/detach_volume_attachment.php', + 'volume_attachments/delete.php', [ - '{serverId}' => $createdVolumeAttachment->serverId, - '{volumeAttachmentId}' => $createdVolumeAttachment->id, + '{serverId}' => $createdVolumeAttachment->serverId, + '{volumeId}' => $createdVolumeAttachment->volumeId, ] ); diff --git a/tests/unit/Common/Error/BuilderTest.php b/tests/unit/Common/Error/BuilderTest.php index 51ce95fb6..6f0abc7bf 100644 --- a/tests/unit/Common/Error/BuilderTest.php +++ b/tests/unit/Common/Error/BuilderTest.php @@ -4,6 +4,8 @@ use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Psr7\NoSeekStream; +use GuzzleHttp\Psr7\PumpStream; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; use GuzzleHttp\Psr7\Utils; @@ -75,6 +77,85 @@ public function verbosityProvider(): array ]; } + public function test_it_outputs_body_for_json() + { + $value = 'foobar'; + + $request = new Request( + 'POST', + '/servers', + ['Content-Type' => 'application/json'], + json_encode(['foo' => $value]) + ); + + $str = $this->builder->str($request, 2); + $this->assertStringContainsString($value, $str); + } + + public function test_it_skips_body_for_low_verbosity() + { + $value = 'foobar'; + + $request = new Request( + 'POST', + '/servers', + ['Content-Type' => 'application/json'], + json_encode(['foo' => $value]) + ); + + $str = $this->builder->str($request, 1); + $this->assertStringNotContainsString($value, $str); + } + + public function test_it_cuts_big_body_for_json() + { + $value = str_repeat('A', Builder::MAX_BODY_LENGTH); + + $request = new Request( + 'POST', + '/servers', + ['Content-Type' => 'application/json'], + json_encode(['foo' => $value]) + ); + + $str = $this->builder->str($request, 2); + $this->assertStringNotContainsString($value, $str); + $this->assertStringContainsString('AAAAAA...', $str); + } + + public function test_it_did_not_read_full_body_for_json() + { + $value = str_repeat('A', Builder::MAX_BODY_LENGTH + 1); + + $request = new Request( + 'POST', + '/servers', + ['Content-Type' => 'application/json'], + new PumpStream(function ($size) { + return str_repeat('A', $size); + }) + ); + + $str = $this->builder->str($request, 2); + $this->assertStringNotContainsString($value, $str); + $this->assertStringContainsString('AAAAAA...', $str); + } + + public function test_it_skips_body_for_binary() + { + $value = 'foobar'; + + $request = new Request( + 'POST', + '/servers', + ['Content-Type' => 'binary/octet-stream'], + $value + ); + + $str = $this->builder->str($request, 2); + $this->assertStringNotContainsString($value, $str); + } + public function test_it_builds_user_input_errors() { $expected = 'A well-formed string';