diff --git a/samples/compute/v2/servers/resume_server.php b/samples/compute/v2/servers/resume_server.php new file mode 100644 index 00000000..e155f86a --- /dev/null +++ b/samples/compute/v2/servers/resume_server.php @@ -0,0 +1,21 @@ + '{authUrl}', + 'region' => '{region}', + 'user' => [ + 'id' => '{userId}', + 'password' => '{password}' + ], + 'scope' => ['project' => ['id' => '{projectId}']] +]); + +$compute = $openstack->computeV2(['region' => '{region}']); + +$server = $compute->getServer([ + 'id' => '{serverId}', +]); + +$server->resume(); diff --git a/samples/compute/v2/servers/suspend_server.php b/samples/compute/v2/servers/suspend_server.php new file mode 100644 index 00000000..42b80016 --- /dev/null +++ b/samples/compute/v2/servers/suspend_server.php @@ -0,0 +1,21 @@ + '{authUrl}', + 'region' => '{region}', + 'user' => [ + 'id' => '{userId}', + 'password' => '{password}' + ], + 'scope' => ['project' => ['id' => '{projectId}']] +]); + +$compute = $openstack->computeV2(['region' => '{region}']); + +$server = $compute->getServer([ + 'id' => '{serverId}', +]); + +$server->suspend(); diff --git a/src/BlockStorage/Enum.php b/src/BlockStorage/Enum.php new file mode 100644 index 00000000..31e4c709 --- /dev/null +++ b/src/BlockStorage/Enum.php @@ -0,0 +1,33 @@ + 'POST', + 'path' => 'volumes/{id}/action', + 'jsonKey' => 'os-extend', + 'params' => [ + 'id' => $this->params->idPath(), + 'new_size' => $this->params->size(), + ], + ]; + } + + public function resetVolumeStatus(): array + { + return [ + 'method' => 'POST', + 'path' => 'volumes/{id}/action', + 'jsonKey' => 'os-reset_status', + 'params' => [ + 'id' => $this->params->idPath(), + 'status' => $this->params->status() + ], + ]; + } } diff --git a/src/BlockStorage/v2/Models/Volume.php b/src/BlockStorage/v2/Models/Volume.php index 2b170827..e180db8a 100644 --- a/src/BlockStorage/v2/Models/Volume.php +++ b/src/BlockStorage/v2/Models/Volume.php @@ -110,6 +110,15 @@ public function delete() $this->executeWithState($this->api->deleteVolume()); } + public function resetStatus(string $status) + { + $response = $this->execute($this->api->resetVolumeStatus(), [ + 'id' => $this->id, + 'status' => $status + ]); + $this->populateFromResponse($response); + } + public function getMetadata(): array { $response = $this->executeWithState($this->api->getVolumeMetadata()); @@ -135,4 +144,13 @@ public function parseMetadata(ResponseInterface $response): array $json = Utils::jsonDecode($response); return isset($json['metadata']) ? $json['metadata'] : []; } + + public function extend(int $size_in_gb) + { + $response = $this->execute($this->api->extendVolume(), [ + 'id' => $this->id, + 'new_size' => $size_in_gb + ]); + $this->populateFromResponse($response); + } } diff --git a/src/BlockStorage/v2/Params.php b/src/BlockStorage/v2/Params.php index 000fa4e0..3baa9c8a 100644 --- a/src/BlockStorage/v2/Params.php +++ b/src/BlockStorage/v2/Params.php @@ -159,7 +159,7 @@ public function snapshotName(): array 'location' => self::JSON, ]; } - + protected function quotaSetLimit($sentAs, $description): array { return [ @@ -224,4 +224,23 @@ public function quotaSetVolumesIscsi(): array { return $this->quotaSetLimit('volumes_iscsi', 'The number of allowed volumes iscsi'); } + + public function nullAction(): array + { + return [ + 'type' => self::NULL_TYPE, + 'location' => self::JSON, + 'required' => true + ]; + } + + public function status(): array + { + return [ + 'type' => self::STRING_TYPE, + 'location' => self::JSON, + 'required' => true, + 'description' => 'The new status of the volume', + ]; + } } diff --git a/src/Compute/v2/Api.php b/src/Compute/v2/Api.php index 6ef4d24f..3b21e48e 100644 --- a/src/Compute/v2/Api.php +++ b/src/Compute/v2/Api.php @@ -316,6 +316,90 @@ public function stopServer() : array ]; } + public function resumeServer() : array + { + return [ + 'method' => 'POST', + 'path' => 'servers/{id}/action', + 'params' => [ + 'id' => $this->params->urlId('server'), + 'resume' => $this->params->nullAction() + ], + ]; + } + + public function suspendServer() : array + { + return [ + 'method' => 'POST', + 'path' => 'servers/{id}/action', + 'params' => [ + 'id' => $this->params->urlId('server'), + 'suspend' => $this->params->nullAction() + ], + ]; + } + + public function shelveServer() : array + { + return [ + 'method' => 'POST', + 'path' => 'servers/{id}/action', + 'params' => [ + 'id' => $this->params->urlId('server'), + 'shelve' => $this->params->nullAction(), + ], + ]; + } + + public function shelveOffloadServer() : array + { + return [ + 'method' => 'POST', + 'path' => 'servers/{id}/action', + 'params' => [ + 'id' => $this->params->urlId('server'), + 'shelveOffload' => $this->params->nullAction() + ], + ]; + } + + public function unshelveServer() : array + { + return [ + 'method' => 'POST', + 'path' => 'servers/{id}/action', + 'params' => [ + 'id' => $this->params->urlId('server'), + 'unshelve' => $this->params->nullAction() + ], + ]; + } + + public function lockServer() : array + { + return [ + 'method' => 'POST', + 'path' => 'servers/{id}/action', + 'params' => [ + 'id' => $this->params->urlId('server'), + 'lock' => $this->params->nullAction() + ], + ]; + } + + public function unlockServer() : array + { + return [ + 'method' => 'POST', + 'path' => 'servers/{id}/action', + 'params' => [ + 'id' => $this->params->urlId('server'), + 'unlock' => $this->params->nullAction() + ], + ]; + } + public function rebuildServer(): array { return [ @@ -438,6 +522,19 @@ public function getRDPConsole(): array ]; } + public function getConsoleLog(): array + { + return [ + 'method' => 'POST', + 'path' => 'servers/{id}/action', + 'jsonKey' => 'os-getConsoleOutput', + 'params' => [ + 'id' => $this->params->urlId('server'), + 'length' => $this->params->consoleLogLength() + ] + ]; + } + public function getAddresses(): array { return [ @@ -777,4 +874,28 @@ public function putQuotaSet(): array ] ]; } + + public function getInstanceActions(): array + { + return [ + 'method' => 'GET', + 'path' => 'servers/{id}/os-instance-actions', + 'params' => [ + 'id' => $this->params->urlId('server') + ] + ]; + } + + public function getInstanceAction(): array + { + return [ + 'method' => 'GET', + 'path' => 'servers/{id}/os-instance-actions/{requestId}', + 'params' => [ + 'id' => $this->params->urlId('server'), + 'requestId' => $this->params->urlId('request') + ] + ]; + } + } diff --git a/src/Compute/v2/Models/InstanceAction.php b/src/Compute/v2/Models/InstanceAction.php new file mode 100644 index 00000000..52e889ca --- /dev/null +++ b/src/Compute/v2/Models/InstanceAction.php @@ -0,0 +1,44 @@ + 'instanceUuid', + 'project_id' => 'projectId', + 'request_id' => 'requestId', + 'start_time' => 'startTime', + 'user_id' => 'userId' + ]; + +} diff --git a/src/Compute/v2/Models/Server.php b/src/Compute/v2/Models/Server.php index f5c531d1..14c8b7df 100644 --- a/src/Compute/v2/Models/Server.php +++ b/src/Compute/v2/Models/Server.php @@ -201,6 +201,83 @@ public function stop() ]); } + /** + * Shelves server + */ + public function shelve() + { + $this->execute($this->api->shelveServer(), [ + 'id' => $this->id, + 'shelve' => null + ]); + } + + /* + * Suspend server + */ + public function suspend() + { + $this->execute($this->api->suspendServer(), [ + 'id' => $this->id, + 'suspend' => null + ]); + } + + /** + * Shelf-offloads server + */ + public function shelveOffload() + { + $this->execute($this->api->shelveOffloadServer(), [ + 'id' => $this->id, + 'shelveOffload' => null + ]); + } + + /** + * Unshelves server + */ + public function unshelve() + { + $this->execute($this->api->unshelveServer(), [ + 'id' => $this->id, + 'unshelve' => null + ]); + } + + /* + * Resume server + */ + public function resume() + { + $this->execute($this->api->resumeServer(), [ + 'id' => $this->id, + 'resume' => null + ]); + } + + /** + * Locks server + */ + public function lock() + { + $this->execute($this->api->lockServer(), [ + 'id' => $this->id, + 'lock' => null + ]); + } + + /** + * Unlocks server + */ + public function unlock() + { + $this->execute($this->api->unlockServer(), [ + 'id' => $this->id, + 'unlock' => null + ]); + } + /** * Rebuilds the server. * @@ -299,6 +376,19 @@ public function getSerialConsole($type = Enum::CONSOLE_SERIAL): array return Utils::jsonDecode($response)['console']; } + /** + * Get the console log. + * + * @param int $length Number of lines of console log to grab. + * + * @return string - the console log output + */ + public function getConsoleLog(int $length = 50): string + { + $response = $this->execute($this->api->getConsoleLog(), ['id' => $this->id, 'length' => $length]); + return Utils::jsonDecode($response)['output']; + } + /** * Creates an image for the current server. * @@ -482,4 +572,27 @@ public function detachVolume(string $attachmentId) { $this->execute($this->api->deleteVolumeAttachments(), ['id' => $this->id, 'attachmentId' => $attachmentId]); } + + /** + * Get a Generator for the instance actions + * + * @return \Generator + */ + public function listInstanceActions(): \Generator + { + return $this->model(InstanceAction::class)->enumerate($this->api->getInstanceActions(), ['id' => $this->id]); + } + + /** + * Get a specific instance action + * + * @string The request ID of the instance action + * @return InstanceAction + */ + public function getInstanceAction(string $requestId): InstanceAction + { + $response = $this->execute($this->api->getInstanceAction(), ['id' => $this->id, 'requestId' => $requestId]); + + return $this->model(InstanceAction::class)->populateFromResponse($response); + } } diff --git a/src/Compute/v2/Params.php b/src/Compute/v2/Params.php index b65df14f..29f0982e 100644 --- a/src/Compute/v2/Params.php +++ b/src/Compute/v2/Params.php @@ -548,4 +548,13 @@ public function quotaSetLimitServerGroupMembers(): array { return $this->quotaSetLimit('server_group_members', 'The number of allowed members for each server group.'); } + + public function consoleLogLength(): array + { + return [ + 'type' => self::INT_TYPE, + 'location' => self::JSON, + 'description' => 'Specifies the length of console log to grab', + ]; + } } diff --git a/src/Networking/v2/Api.php b/src/Networking/v2/Api.php index 6329ab2a..0ac5c23d 100644 --- a/src/Networking/v2/Api.php +++ b/src/Networking/v2/Api.php @@ -700,4 +700,24 @@ public function deleteLoadBalancerHealthMonitor() : array ] ]; } + + public function getNetworkIpAvailability() : array + { + return [ + 'method' => 'GET', + 'path' => $this->pathPrefix . '/network-ip-availabilities/{id}', + 'params' => ['id' => $this->params->urlId('network')], + ]; + } + + public function getNetworkIpAvailabilities(): array + { + return [ + 'method' => 'GET', + 'path' => $this->pathPrefix . '/network-ip-availabilities', + 'params' => [ + 'tenantId' => $this->params->queryTenantId() + ] + ]; + } } diff --git a/src/Networking/v2/Models/NetworkIpAvailability.php b/src/Networking/v2/Models/NetworkIpAvailability.php new file mode 100644 index 00000000..3fd20be2 --- /dev/null +++ b/src/Networking/v2/Models/NetworkIpAvailability.php @@ -0,0 +1,61 @@ + 'id', + 'network_name' => 'networkName', + 'tenant_id' => 'tenantId', + 'project_id' => 'projectId', + 'total_ips' => 'totalIps', + 'used_ips' => 'usedIps', + 'subnet_ip_availability' => 'subnetIpAvailability' + ]; + + protected $resourceKey = 'network_ip_availability'; + protected $resourcesKey = 'network_ip_availabilities'; + + /** + * {@inheritDoc} + */ + public function retrieve() + { + $response = $this->execute($this->api->getNetworkIpAvailability(), ['id' => (string)$this->id]); + $this->populateFromResponse($response); + } +} diff --git a/src/Networking/v2/Service.php b/src/Networking/v2/Service.php index 68afdc46..526e928e 100644 --- a/src/Networking/v2/Service.php +++ b/src/Networking/v2/Service.php @@ -9,6 +9,7 @@ use OpenStack\Networking\v2\Models\LoadBalancerMember; use OpenStack\Networking\v2\Models\LoadBalancerPool; use OpenStack\Networking\v2\Models\Network; +use OpenStack\Networking\v2\Models\NetworkIpAvailability; use OpenStack\Networking\v2\Models\Pool; use OpenStack\Networking\v2\Models\Port; use OpenStack\Networking\v2\Models\Quota; @@ -383,4 +384,30 @@ public function createLoadBalancerHealthMonitor(array $options): LoadBalancerHea { return $this->model(LoadBalancerHealthMonitor::class)->create($options); } + + /** + * Retrieve a network IP availability object without calling the remote API. Any values provided in the array will populate the + * empty object, allowing you greater control without the expense of network transactions. To call the remote API + * and have the response populate the object, call {@see NetworkIpAvailabilities::retrieve}. + * + * @param string $id + * + * @return NetworkIpAvailability + */ + public function getNetworkIpAvailability(string $id): NetworkIpAvailability + { + return $this->model(NetworkIpAvailability::class, ['id' => $id]); + } + + /** + * List network IP availability(es) + * + * @param array $options {@see \OpenStack\Networking\v2\Api::getNetworkIpAvailabilities() + * + * @return \Generator + */ + public function listNetworkIpAvailabilities(array $options = []): \Generator + { + return $this->model(NetworkIpAvailability::class)->enumerate($this->api->getNetworkIpAvailabilities(), $options); + } } diff --git a/tests/integration/Compute/v2/CoreTest.php b/tests/integration/Compute/v2/CoreTest.php index 2bf2e4b0..0d3368e6 100644 --- a/tests/integration/Compute/v2/CoreTest.php +++ b/tests/integration/Compute/v2/CoreTest.php @@ -145,6 +145,8 @@ public function runTests() //$this->changeServerPassword(); $this->stopServer(); $this->startServer(); + $this->suspendServer(); + $this->resumeServer(); $this->resizeServer(); $this->confirmServerResize(); $this->rebuildServer(); @@ -399,6 +401,30 @@ private function startServer() $this->logStep('Started server {serverId}', $replacements); } + private function suspendServer() + { + $replacements = ['{serverId}' => $this->serverId]; + + /** @var $server \OpenStack\Compute\v2\Models\Server */ + require_once $this->sampleFile($replacements, 'servers/suspend_server.php'); + + $server->waitUntilActive(false); + + $this->logStep('Suspended server {serverId}', $replacements); + } + + private function resumeServer() + { + $replacements = ['{serverId}' => $this->serverId]; + + /** @var $server \OpenStack\Compute\v2\Models\Server */ + require_once $this->sampleFile($replacements, 'servers/resume_server.php'); + + $server->waitUntilActive(false); + + $this->logStep('Resumed server {serverId}', $replacements); + } + private function createFlavor() { $replacements = [ diff --git a/tests/unit/Compute/v2/Models/ServerTest.php b/tests/unit/Compute/v2/Models/ServerTest.php index 605becbe..3185e9af 100644 --- a/tests/unit/Compute/v2/Models/ServerTest.php +++ b/tests/unit/Compute/v2/Models/ServerTest.php @@ -185,6 +185,24 @@ public function test_it_stops() $this->assertNull($this->server->stop()); } + public function test_it_suspends() + { + $expectedJson = ['suspend' => null]; + + $this->setupMock('POST', 'servers/serverId/action', $expectedJson, [], new Response(202)); + + $this->assertNull($this->server->suspend()); + } + + public function test_it_resumes() + { + $expectedJson = ['resume' => null]; + + $this->setupMock('POST', 'servers/serverId/action', $expectedJson, [], new Response(202)); + + $this->assertNull($this->server->resume()); + } + public function test_it_resizes() { $expectedJson = ['resize' => ['flavorRef' => 'flavorId']];