Skip to content

Commit 22c3c9a

Browse files
authored
Server groups (#435)
Add Nova server-groups API support
1 parent 7e12e56 commit 22c3c9a

File tree

18 files changed

+889
-13
lines changed

18 files changed

+889
-13
lines changed

AGENTS.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# AGENTS.md
2+
3+
## Project Scope
4+
5+
This repository is the `php-opencloud/openstack` SDK: a PHP client for multiple OpenStack services. The public entry point is `src/OpenStack.php`, which creates versioned service objects through shared builder logic in `src/Common/`.
6+
7+
The project follows semantic versioning and still supports PHP `^7.2.5 || ^8.0`. Keep changes narrowly scoped and avoid API breaks unless the task explicitly requires them.
8+
9+
## Repository Layout
10+
11+
- `src/`
12+
- `Common/`: shared API, resource, service, auth, error, JSON-schema, and transport infrastructure.
13+
- `<Service>/<version>/`: versioned SDK surface for each OpenStack service. Most service folders contain:
14+
- `Api.php`: declarative operation definitions (`method`, `path`, `params`, optional `jsonKey`)
15+
- `Params.php`: parameter schemas and wire-format metadata
16+
- `Service.php`: top-level user-facing service methods
17+
- `Models/`: resource classes with behavior and hydration rules
18+
- `tests/unit/`: mocked unit tests, usually mirroring the `src/` namespace layout.
19+
- `tests/sample/`: integration-style tests that execute files from `samples/`.
20+
- `samples/`: runnable examples; these double as integration-test inputs.
21+
- `doc/`: Sphinx/reStructuredText documentation.
22+
- `.github/workflows/`: CI definitions for formatting, unit tests, and integration tests.
23+
24+
## Architecture Conventions
25+
26+
When adding or changing SDK functionality, preserve the existing layering:
27+
28+
1. Define or update the REST operation in `Api.php`.
29+
2. Add or reuse parameter definitions in `Params.php`.
30+
3. Expose the behavior from `Service.php` if it is part of the top-level service API.
31+
4. Implement resource behavior in `Models/*` when the operation belongs on a model instance.
32+
5. Add unit tests and, for user-facing flows, a sample plus sample test when practical.
33+
34+
Specific patterns used throughout the codebase:
35+
36+
- `Service.php` methods are intentionally thin. They usually create a model, populate it, or delegate to `enumerate(...)`.
37+
- Resource classes commonly extend `OpenStack\Common\Resource\OperatorResource`, set `$resourceKey`, `$resourcesKey`, and `$markerKey`, and use `execute(...)` plus `populateFromResponse(...)`.
38+
- API field renames are handled with `$aliases` and `Alias` objects instead of ad hoc mapping code.
39+
- Operation option arrays are documented with `{@see \Namespace\Api::methodName}` docblocks. Keep those references accurate when signatures change.
40+
- Reuse shared abstractions in `src/Common/` before introducing service-specific helpers.
41+
42+
## PHP Compatibility Rules
43+
44+
The SDK is tested against PHP `7.2` through `8.4` in CI. Do not introduce syntax or standard-library dependencies that require newer PHP versions than `7.2.5`.
45+
46+
In practice, avoid:
47+
48+
- union types
49+
- attributes
50+
- constructor property promotion
51+
- enums
52+
- `match`
53+
- `readonly`
54+
- typed properties
55+
- named arguments in code examples or tests
56+
57+
Follow the surrounding file for `declare(strict_types=1);`. Many `src/` files use it, but not every file in the repository does.
58+
59+
## Testing Expectations
60+
61+
There are no Composer scripts in this repository; run tools directly from `vendor/bin`.
62+
63+
Primary local checks:
64+
65+
```bash
66+
composer install
67+
vendor/bin/parallel-lint --exclude vendor .
68+
vendor/bin/phpunit --configuration phpunit.xml.dist
69+
vendor/bin/php-cs-fixer fix --dry-run --diff
70+
```
71+
72+
Additional checks when relevant:
73+
74+
```bash
75+
composer normalize
76+
vendor/bin/phpunit --configuration phpunit.sample.xml.dist
77+
```
78+
79+
Notes:
80+
81+
- CI runs unit tests against both lowest and highest dependency sets, so avoid relying on the latest transitive behavior only.
82+
- Integration tests require a live OpenStack environment, the environment variables from `env_test.sh.dist`, and an image named `cirros`.
83+
- `php-cs-fixer` is configured for `src/`, `samples/`, and `.php-cs-fixer.dist.php`; tests are not auto-formatted by that config, so keep test edits manually consistent with surrounding code.
84+
85+
## Unit Test Patterns
86+
87+
Unit tests usually extend `tests/unit/TestCase.php`.
88+
89+
Follow the existing test style:
90+
91+
- set `rootFixturesDir` in `setUp()` when the test uses fixture responses
92+
- use `mockRequest(...)` to assert HTTP method, path, body, headers, and returned response
93+
- store larger or realistic HTTP responses as `.resp` files under a nearby `Fixtures/` directory
94+
- mirror the production namespace and folder layout where possible
95+
96+
Prefer adding focused tests around the exact operation being changed instead of broad cross-service rewrites.
97+
98+
## Samples And Integration Coverage
99+
100+
`samples/` are executable examples and are also exercised by `tests/sample/`. When you add a new user-facing capability, consider whether it should have:
101+
102+
- a sample under the matching service/version folder in `samples/`
103+
- a corresponding sample test under `tests/sample/`
104+
- documentation in `doc/services/` if the feature is part of the supported public workflow
105+
106+
All code snippets used in the docs must live in `samples/` rather than being maintained only inline in `.rst` files, and they must be covered by the sample test suite.
107+
108+
Sample tests typically create a temporary PHP file from a template and `require_once` it, so keep samples self-contained and readable.
109+
110+
## Documentation
111+
112+
User docs live in `doc/` and use Sphinx plus reStructuredText. If a change affects public behavior, examples, or supported options, update docs as needed.
113+
114+
Typical doc build:
115+
116+
```bash
117+
pip install -r doc/requirements.txt
118+
make -C doc html
119+
```
120+
121+
## Change Heuristics
122+
123+
- Prefer small, service-scoped changes over broad refactors.
124+
- Preserve public method names and option shapes unless the task explicitly calls for a breaking change.
125+
- Keep docblocks accurate for public APIs and option arrays.
126+
- Reuse existing fixtures, sample patterns, and helper methods before inventing new ones.
127+
- If `composer.json` changes, run `composer normalize` because CI auto-normalizes that file.
128+

doc/services/compute/v2/index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ OpenStack Compute (Nova) API Version 2. Nova is the OpenStack project that provi
1111

1212
create
1313
servers
14+
server-groups
1415
volume-attachments
1516
flavors
1617
images
17-
states
18+
states
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
Server Groups
2+
=============
3+
4+
Server groups let you express affinity or anti-affinity placement rules for Nova servers.
5+
6+
.. osdoc:: https://docs.openstack.org/api-ref/compute/#server-groups-os-server-groups
7+
8+
.. |models| replace:: server groups
9+
10+
.. include:: /common/service.rst
11+
12+
List
13+
----
14+
15+
.. sample:: Compute/v2/server_groups/list.php
16+
17+
Each iteration will return a :php:class:`ServerGroup` instance <OpenStack/Compute/v2/Models/ServerGroup.html>.
18+
19+
.. include:: /common/generators.rst
20+
21+
Admin-only listing across all projects is also available:
22+
23+
.. code-block:: php
24+
25+
$serverGroups = $service->listServerGroups([
26+
'allProjects' => true,
27+
]);
28+
29+
Create
30+
------
31+
32+
Use ``name`` and ``policies`` for the baseline Compute API:
33+
34+
.. sample:: Compute/v2/server_groups/create.php
35+
36+
Microversion 2.64+
37+
~~~~~~~~~~~~~~~~~~
38+
39+
If the Compute service is created with microversion ``2.64`` or later, you can use the singular ``policy`` field and
40+
optional ``rules`` object instead:
41+
42+
.. sample:: Compute/v2/server_groups/create_2_64.php
43+
44+
When Nova responds with the newer singular ``policy`` field, the SDK also exposes that value as the first item in
45+
``policies`` for compatibility with the older response shape.
46+
47+
Read
48+
----
49+
50+
.. sample:: Compute/v2/server_groups/read.php
51+
52+
Delete
53+
------
54+
55+
.. sample:: Compute/v2/server_groups/delete.php
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
require 'vendor/autoload.php';
4+
5+
$openstack = new OpenStack\OpenStack([
6+
'authUrl' => '{authUrl}',
7+
'region' => '{region}',
8+
'user' => [
9+
'id' => '{userId}',
10+
'password' => '{password}',
11+
],
12+
'scope' => ['project' => ['id' => '{projectId}']],
13+
]);
14+
15+
$compute = $openstack->computeV2(['region' => '{region}']);
16+
17+
$serverGroup = $compute->createServerGroup([
18+
'name' => '{serverGroupName}',
19+
'policies' => ['affinity'],
20+
]);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
require 'vendor/autoload.php';
4+
5+
$openstack = new OpenStack\OpenStack([
6+
'authUrl' => '{authUrl}',
7+
'region' => '{region}',
8+
'user' => [
9+
'id' => '{userId}',
10+
'password' => '{password}',
11+
],
12+
'scope' => ['project' => ['id' => '{projectId}']],
13+
]);
14+
15+
$compute = $openstack->computeV2([
16+
'region' => '{region}',
17+
'microVersion' => '2.64',
18+
]);
19+
20+
$serverGroup = $compute->createServerGroup([
21+
'name' => '{serverGroupName}',
22+
'policy' => 'anti-affinity',
23+
'rules' => [
24+
'max_server_per_host' => 1,
25+
],
26+
]);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
require 'vendor/autoload.php';
4+
5+
$openstack = new OpenStack\OpenStack([
6+
'authUrl' => '{authUrl}',
7+
'region' => '{region}',
8+
'user' => [
9+
'id' => '{userId}',
10+
'password' => '{password}'
11+
],
12+
'scope' => ['project' => ['id' => '{projectId}']]
13+
]);
14+
15+
$compute = $openstack->computeV2(['region' => '{region}']);
16+
17+
$serverGroup = $compute->getServerGroup(['id' => '{serverGroupId}']);
18+
$serverGroup->delete();
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
require 'vendor/autoload.php';
4+
5+
$openstack = new OpenStack\OpenStack([
6+
'authUrl' => '{authUrl}',
7+
'region' => '{region}',
8+
'user' => [
9+
'id' => '{userId}',
10+
'password' => '{password}'
11+
],
12+
'scope' => ['project' => ['id' => '{projectId}']]
13+
]);
14+
15+
$compute = $openstack->computeV2(['region' => '{region}']);
16+
17+
$serverGroups = $compute->listServerGroups();
18+
19+
foreach ($serverGroups as $serverGroup) {
20+
/** @var \OpenStack\Compute\v2\Models\ServerGroup $serverGroup */
21+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
require 'vendor/autoload.php';
4+
5+
$openstack = new OpenStack\OpenStack([
6+
'authUrl' => '{authUrl}',
7+
'region' => '{region}',
8+
'user' => [
9+
'id' => '{userId}',
10+
'password' => '{password}'
11+
],
12+
'scope' => ['project' => ['id' => '{projectId}']]
13+
]);
14+
15+
$compute = $openstack->computeV2(['region' => '{region}']);
16+
17+
$serverGroup = $compute->getServerGroup(['id' => '{serverGroupId}']);
18+
$serverGroup->retrieve();

src/Compute/v2/Api.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,68 @@ public function deleteKeypair(): array
708708
];
709709
}
710710

711+
public function getServerGroups(): array
712+
{
713+
return [
714+
'method' => 'GET',
715+
'path' => 'os-server-groups',
716+
'params' => [
717+
'allProjects' => $this->params->allProjects(),
718+
'limit' => $this->params->limit(),
719+
'offset' => $this->params->offset(),
720+
],
721+
];
722+
}
723+
724+
public function getServerGroup(): array
725+
{
726+
return [
727+
'method' => 'GET',
728+
'path' => 'os-server-groups/{id}',
729+
'params' => [
730+
'id' => $this->params->urlId('server group'),
731+
],
732+
];
733+
}
734+
735+
public function postServerGroup(): array
736+
{
737+
return [
738+
'method' => 'POST',
739+
'path' => 'os-server-groups',
740+
'jsonKey' => 'server_group',
741+
'params' => [
742+
'name' => $this->isRequired($this->params->name('server group')),
743+
'policies' => $this->isRequired($this->params->serverGroupPolicies()),
744+
],
745+
];
746+
}
747+
748+
public function postServerGroupWithPolicy(): array
749+
{
750+
return [
751+
'method' => 'POST',
752+
'path' => 'os-server-groups',
753+
'jsonKey' => 'server_group',
754+
'params' => [
755+
'name' => $this->isRequired($this->params->name('server group')),
756+
'policy' => $this->isRequired($this->params->serverGroupPolicy()),
757+
'rules' => $this->notRequired($this->params->serverGroupRules()),
758+
],
759+
];
760+
}
761+
762+
public function deleteServerGroup(): array
763+
{
764+
return [
765+
'method' => 'DELETE',
766+
'path' => 'os-server-groups/{id}',
767+
'params' => [
768+
'id' => $this->params->urlId('server group'),
769+
],
770+
];
771+
}
772+
711773
public function postSecurityGroup(): array
712774
{
713775
return [

0 commit comments

Comments
 (0)