inmanta-module-openstack 5.1.0__tar.gz → 5.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/PKG-INFO +1 -1
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_module_openstack.egg-info/PKG-INFO +1 -1
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_module_openstack.egg-info/SOURCES.txt +2 -2
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/common/deps.py +19 -6
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/compute/virtual_machine.py +164 -30
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/model/_init.cf +78 -60
- inmanta_module_openstack-5.1.2/inmanta_plugins/openstack/network/network_port.py +504 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/network/trunk_network.py +2 -2
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/setup.cfg +1 -1
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/setup.cfg +1 -1
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/tests/test_dependency_handling.py +6 -6
- inmanta_module_openstack-5.1.2/tests/test_network_port_fixed_ips.py +347 -0
- inmanta_module_openstack-5.1.0/tests/test_host_port_security_groups.py → inmanta_module_openstack-5.1.2/tests/test_network_port_security_groups.py +8 -8
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/tests/test_neutron.py +28 -26
- inmanta_module_openstack-5.1.2/tests/test_nova.py +570 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/tests/test_trunk.py +72 -0
- inmanta_module_openstack-5.1.0/inmanta_plugins/openstack/compute/host_port.py +0 -330
- inmanta_module_openstack-5.1.0/inmanta_plugins/openstack/network/network_port.py +0 -163
- inmanta_module_openstack-5.1.0/tests/test_nova.py +0 -287
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/MANIFEST.in +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/README.md +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_module_openstack.egg-info/dependency_links.txt +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_module_openstack.egg-info/not-zip-safe +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_module_openstack.egg-info/requires.txt +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_module_openstack.egg-info/top_level.txt +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/__init__.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/common/__init__.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/common/base.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/compute/__init__.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/compute/flavor.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/compute/host_aggregate.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/identity/__init__.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/identity/group.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/identity/group_role.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/identity/keystone_base.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/identity/project.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/identity/role.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/identity/user.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/image/__init__.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/image/image.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/network/__init__.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/network/floating_ip.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/network/network.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/network/qos_policy.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/network/router.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/network/router_port.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/network/security_group.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/network/subnet.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/network/subnet_v6.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/network/trunk_port.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/quota/__init__.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/inmanta_plugins/openstack/quota/quota.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/pyproject.toml +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/tests/test_block_devices.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/tests/test_examples.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/tests/test_flavor.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/tests/test_host_aggregate.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/tests/test_image.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/tests/test_keystone.py +0 -0
- {inmanta_module_openstack-5.1.0 → inmanta_module_openstack-5.1.2}/tests/test_quota.py +0 -0
|
@@ -16,7 +16,6 @@ inmanta_plugins/openstack/common/deps.py
|
|
|
16
16
|
inmanta_plugins/openstack/compute/__init__.py
|
|
17
17
|
inmanta_plugins/openstack/compute/flavor.py
|
|
18
18
|
inmanta_plugins/openstack/compute/host_aggregate.py
|
|
19
|
-
inmanta_plugins/openstack/compute/host_port.py
|
|
20
19
|
inmanta_plugins/openstack/compute/virtual_machine.py
|
|
21
20
|
inmanta_plugins/openstack/identity/__init__.py
|
|
22
21
|
inmanta_plugins/openstack/identity/group.py
|
|
@@ -47,9 +46,10 @@ tests/test_dependency_handling.py
|
|
|
47
46
|
tests/test_examples.py
|
|
48
47
|
tests/test_flavor.py
|
|
49
48
|
tests/test_host_aggregate.py
|
|
50
|
-
tests/test_host_port_security_groups.py
|
|
51
49
|
tests/test_image.py
|
|
52
50
|
tests/test_keystone.py
|
|
51
|
+
tests/test_network_port_fixed_ips.py
|
|
52
|
+
tests/test_network_port_security_groups.py
|
|
53
53
|
tests/test_neutron.py
|
|
54
54
|
tests/test_nova.py
|
|
55
55
|
tests/test_quota.py
|
|
@@ -32,7 +32,7 @@ MANAGED_DEPENDENCIES = {
|
|
|
32
32
|
"openstack::Router",
|
|
33
33
|
"openstack::Subnet",
|
|
34
34
|
"openstack::VirtualMachine",
|
|
35
|
-
"openstack::
|
|
35
|
+
"openstack::NetworkPort",
|
|
36
36
|
"openstack::FloatingIP",
|
|
37
37
|
"openstack::SecurityGroup",
|
|
38
38
|
}
|
|
@@ -66,15 +66,21 @@ def set_dependencies_on_purged_resources(collector: ResourceCollector) -> None:
|
|
|
66
66
|
Set the dependencies between purged VMs/ports and the security groups they reference.
|
|
67
67
|
"""
|
|
68
68
|
vms = collector.get("openstack::VirtualMachine", {})
|
|
69
|
-
ports = collector.get("openstack::
|
|
69
|
+
ports = collector.get("openstack::NetworkPort", {})
|
|
70
70
|
sgs = collector.get("openstack::SecurityGroup", {})
|
|
71
71
|
|
|
72
72
|
# When the VM is purged, all its purged ports should be deployed first
|
|
73
73
|
for vm in vms.values():
|
|
74
74
|
for vm_port in vm.ports:
|
|
75
75
|
if vm_port["name"] in ports:
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
port = ports[vm_port["name"]]
|
|
77
|
+
if port.is_trunk_parent:
|
|
78
|
+
# The vm boots on (and binds) its trunk parent port, so the vm must be
|
|
79
|
+
# torn down before the port. This mirrors the create-time inversion and
|
|
80
|
+
# avoids a cycle with the trunk (which is deleted before its parent).
|
|
81
|
+
port.requires.add(vm.id)
|
|
82
|
+
else:
|
|
83
|
+
vm.requires.add(port.id)
|
|
78
84
|
|
|
79
85
|
# When a security group is purged, every port referencing it must be removed (or have the
|
|
80
86
|
# reference dropped) before the group itself can be deleted.
|
|
@@ -90,7 +96,7 @@ def set_dependencies_on_non_purged_resources(collector: ResourceCollector) -> No
|
|
|
90
96
|
routers = collector.get("openstack::Router", {})
|
|
91
97
|
subnets = collector.get("openstack::Subnet", {})
|
|
92
98
|
vms = collector.get("openstack::VirtualMachine", {})
|
|
93
|
-
ports = collector.get("openstack::
|
|
99
|
+
ports = collector.get("openstack::NetworkPort", {})
|
|
94
100
|
fips = collector.get("openstack::FloatingIP", {})
|
|
95
101
|
sgs = collector.get("openstack::SecurityGroup", {})
|
|
96
102
|
router_map = {}
|
|
@@ -143,7 +149,14 @@ def set_dependencies_on_non_purged_resources(collector: ResourceCollector) -> No
|
|
|
143
149
|
port.requires.add(subnets[port.network].id)
|
|
144
150
|
|
|
145
151
|
if port.host in vms:
|
|
146
|
-
port.
|
|
152
|
+
if port.is_trunk_parent:
|
|
153
|
+
# A trunk parent must exist (unbound) before its vm: the trunk is built
|
|
154
|
+
# on it and the vm then boots on the pre-created port. Ordering the port
|
|
155
|
+
# after the vm would both bind it too early (neutron rejects a trunk on a
|
|
156
|
+
# bound port) and cycle with the trunk (vm -> trunk -> parent port).
|
|
157
|
+
vms[port.host].requires.add(port.id)
|
|
158
|
+
else:
|
|
159
|
+
port.requires.add(vms[port.host].id)
|
|
147
160
|
|
|
148
161
|
for sg_name in port.security_groups:
|
|
149
162
|
if sg_name in sgs:
|
|
@@ -20,6 +20,7 @@ import time
|
|
|
20
20
|
import traceback
|
|
21
21
|
from typing import Any
|
|
22
22
|
|
|
23
|
+
from inmanta import ast
|
|
23
24
|
from inmanta.agent.handler import (
|
|
24
25
|
HandlerContext,
|
|
25
26
|
ResourcePurged,
|
|
@@ -29,7 +30,7 @@ from inmanta.agent.handler import (
|
|
|
29
30
|
)
|
|
30
31
|
from inmanta.execute import proxy
|
|
31
32
|
from inmanta.resources import resource
|
|
32
|
-
from openstack.exceptions import ResourceFailure, ResourceTimeout
|
|
33
|
+
from openstack.exceptions import NotFoundException, ResourceFailure, ResourceTimeout
|
|
33
34
|
|
|
34
35
|
from inmanta_plugins.openstack.common.base import (
|
|
35
36
|
NotUniqueException,
|
|
@@ -47,6 +48,7 @@ class VirtualMachine(OpenstackResource):
|
|
|
47
48
|
fields = (
|
|
48
49
|
"name",
|
|
49
50
|
"flavor",
|
|
51
|
+
"flavor_spec",
|
|
50
52
|
"image",
|
|
51
53
|
"key_name",
|
|
52
54
|
"user_data",
|
|
@@ -61,6 +63,7 @@ class VirtualMachine(OpenstackResource):
|
|
|
61
63
|
)
|
|
62
64
|
name: str
|
|
63
65
|
flavor: str
|
|
66
|
+
flavor_spec: dict[str, int] | None
|
|
64
67
|
image: str
|
|
65
68
|
key_name: str
|
|
66
69
|
user_data: str
|
|
@@ -73,6 +76,25 @@ class VirtualMachine(OpenstackResource):
|
|
|
73
76
|
availability_zone: str | None
|
|
74
77
|
block_devices: list[dict]
|
|
75
78
|
|
|
79
|
+
@staticmethod
|
|
80
|
+
def get_flavor_spec(_, vm):
|
|
81
|
+
"""Resources of the linked flavor, used to detect a redefined flavor.
|
|
82
|
+
|
|
83
|
+
Returns None when no flavor relation is set; in that case the handler
|
|
84
|
+
only compares flavor names and does not resize on a content change.
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
flavor = vm.flavor_rel
|
|
88
|
+
except ast.OptionalValueException:
|
|
89
|
+
return None
|
|
90
|
+
return {
|
|
91
|
+
"ram": flavor.ram,
|
|
92
|
+
"vcpus": flavor.vcpus,
|
|
93
|
+
"disk": flavor.disk,
|
|
94
|
+
"ephemeral": flavor.ephemeral,
|
|
95
|
+
"swap": flavor.swap,
|
|
96
|
+
}
|
|
97
|
+
|
|
76
98
|
@staticmethod
|
|
77
99
|
def get_key_name(exporter, vm):
|
|
78
100
|
return vm.key_pair.name
|
|
@@ -100,13 +122,18 @@ class VirtualMachine(OpenstackResource):
|
|
|
100
122
|
port = {
|
|
101
123
|
"name": p.name,
|
|
102
124
|
"address": None,
|
|
125
|
+
"address_v6": None,
|
|
103
126
|
"network": p.subnet.name,
|
|
104
127
|
"dhcp": p.dhcp,
|
|
105
128
|
"index": p.port_index,
|
|
106
129
|
}
|
|
107
130
|
try:
|
|
108
131
|
port["address"] = p.address
|
|
109
|
-
except proxy.UnknownException:
|
|
132
|
+
except (proxy.UnknownException, ast.OptionalValueException):
|
|
133
|
+
pass
|
|
134
|
+
try:
|
|
135
|
+
port["address_v6"] = p.address_v6
|
|
136
|
+
except (proxy.UnknownException, ast.OptionalValueException):
|
|
110
137
|
pass
|
|
111
138
|
ports.append(port)
|
|
112
139
|
|
|
@@ -156,7 +183,9 @@ class VirtualMachineHandler(OpenStackHandler):
|
|
|
156
183
|
@cache(timeout=10, cache_none=False)
|
|
157
184
|
def _port_id(self, port_name):
|
|
158
185
|
ports = self._connection.network.ports(name=port_name)
|
|
159
|
-
|
|
186
|
+
port = self.require_single_item(ports)
|
|
187
|
+
# Nova expects the port id (a string) in the boot NIC, not the port object.
|
|
188
|
+
return port.id if port is not None else None
|
|
160
189
|
|
|
161
190
|
@cache(timeout=10, cache_none=False)
|
|
162
191
|
def _get_subnet_id(self, subnet_name):
|
|
@@ -176,8 +205,13 @@ class VirtualMachineHandler(OpenStackHandler):
|
|
|
176
205
|
if network is None:
|
|
177
206
|
raise SkipResource(f"Network {port['network']} not found")
|
|
178
207
|
nic = {"uuid": network}
|
|
208
|
+
# A boot NIC carries a single fixed_ip. A dual-stack port boots with its
|
|
209
|
+
# IPv4 here (preserving its port_index slot); the NetworkPort handler adds the
|
|
210
|
+
# IPv6 to the same port afterwards via update_port, which keeps the slot.
|
|
179
211
|
if port["address"] is not None:
|
|
180
212
|
nic["fixed_ip"] = port["address"]
|
|
213
|
+
elif port["address_v6"] is not None:
|
|
214
|
+
nic["fixed_ip"] = port["address_v6"]
|
|
181
215
|
else:
|
|
182
216
|
nic["port"] = port_id
|
|
183
217
|
|
|
@@ -245,29 +279,111 @@ class VirtualMachineHandler(OpenStackHandler):
|
|
|
245
279
|
f"Server {self.server.id} ({self.server.name}) is in ERROR state: {msg}"
|
|
246
280
|
) from None
|
|
247
281
|
|
|
248
|
-
def
|
|
249
|
-
|
|
282
|
+
def _wait_for_status(
|
|
283
|
+
self,
|
|
284
|
+
ctx: HandlerContext,
|
|
285
|
+
status: str,
|
|
286
|
+
operation: str,
|
|
287
|
+
timeout: int = 600,
|
|
288
|
+
) -> None:
|
|
289
|
+
"""Wait for self.server to reach `status`, raise on ERROR or timeout.
|
|
290
|
+
|
|
291
|
+
`operation` describes the action in progress (e.g. "build" or "resize")
|
|
292
|
+
and is only used to build the error messages.
|
|
293
|
+
|
|
294
|
+
Nova may briefly return a 404 for the instance while a resize is being
|
|
295
|
+
applied; such transient NotFoundException errors are tolerated and polling
|
|
296
|
+
resumes until the status is reached or the timeout elapses.
|
|
297
|
+
"""
|
|
298
|
+
deadline = time.monotonic() + timeout
|
|
299
|
+
while True:
|
|
300
|
+
remaining = deadline - time.monotonic()
|
|
301
|
+
if remaining <= 0:
|
|
302
|
+
self._raise_status_timeout(ctx, status, timeout)
|
|
303
|
+
try:
|
|
304
|
+
self.server = self._connection.compute.wait_for_server(
|
|
305
|
+
self.server, status=status, wait=max(1, int(remaining))
|
|
306
|
+
)
|
|
307
|
+
return
|
|
308
|
+
except ResourceFailure:
|
|
309
|
+
self._raise_server_error(
|
|
310
|
+
ctx, f"VM entered ERROR state during {operation}"
|
|
311
|
+
)
|
|
312
|
+
except ResourceTimeout:
|
|
313
|
+
self._raise_status_timeout(ctx, status, timeout)
|
|
314
|
+
except NotFoundException:
|
|
315
|
+
# The instance is temporarily not retrievable (e.g. mid-resize);
|
|
316
|
+
# give it a moment and keep polling until the deadline.
|
|
317
|
+
time.sleep(2)
|
|
318
|
+
|
|
319
|
+
def _raise_status_timeout(
|
|
320
|
+
self, ctx: HandlerContext, status: str, timeout: int
|
|
321
|
+
) -> None:
|
|
250
322
|
try:
|
|
251
|
-
self.server = self._connection.compute.wait_for_server(
|
|
252
|
-
self.server, wait=timeout
|
|
253
|
-
)
|
|
254
|
-
except ResourceFailure:
|
|
255
|
-
self._raise_server_error(ctx, "VM entered ERROR state during build")
|
|
256
|
-
except ResourceTimeout:
|
|
257
323
|
server = self._connection.compute.get_server(self.server.id)
|
|
258
|
-
|
|
324
|
+
current_status = getattr(server, "status", "unknown")
|
|
259
325
|
task_state = getattr(server, "task_state", "unknown")
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
326
|
+
except NotFoundException:
|
|
327
|
+
current_status = "not_found"
|
|
328
|
+
task_state = "unknown"
|
|
329
|
+
ctx.error(
|
|
330
|
+
f"Timed out waiting for VM to reach {status}",
|
|
331
|
+
timeout_seconds=timeout,
|
|
332
|
+
current_status=current_status,
|
|
333
|
+
current_task_state=task_state,
|
|
334
|
+
server_id=self.server.id,
|
|
335
|
+
)
|
|
336
|
+
raise Exception(
|
|
337
|
+
f"Server {self.server.id} did not reach {status} within {timeout}s "
|
|
338
|
+
f"(status={current_status}, task_state={task_state})"
|
|
339
|
+
) from None
|
|
340
|
+
|
|
341
|
+
def _get_server_flavor_name(self, server) -> str:
|
|
342
|
+
"""Return the flavor name of `server`.
|
|
343
|
+
|
|
344
|
+
Depending on the negotiated Nova microversion the flavor embedded in a
|
|
345
|
+
server may only carry its id, so fall back to resolving the flavor by id.
|
|
346
|
+
"""
|
|
347
|
+
flavor = server.flavor
|
|
348
|
+
if flavor.original_name:
|
|
349
|
+
return flavor.original_name
|
|
350
|
+
return self._connection.compute.get_flavor(flavor.id).name
|
|
351
|
+
|
|
352
|
+
def _get_server_flavor_spec(self, server) -> dict[str, int]:
|
|
353
|
+
"""Return the resources `server` is actually running with.
|
|
354
|
+
|
|
355
|
+
Mirrors _get_server_flavor_name: recent Nova microversions embed the full
|
|
356
|
+
flavor spec in the server, older ones only its id, so resolve by id then.
|
|
357
|
+
The keys match those exported by VirtualMachine.get_flavor_spec.
|
|
358
|
+
"""
|
|
359
|
+
flavor = server.flavor
|
|
360
|
+
if not flavor.original_name:
|
|
361
|
+
flavor = self._connection.compute.get_flavor(flavor.id)
|
|
362
|
+
return {
|
|
363
|
+
"ram": flavor.ram,
|
|
364
|
+
"vcpus": flavor.vcpus,
|
|
365
|
+
"disk": flavor.disk,
|
|
366
|
+
"ephemeral": getattr(flavor, "ephemeral", 0) or 0,
|
|
367
|
+
"swap": getattr(flavor, "swap", 0) or 0,
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
def _resize_server(
|
|
371
|
+
self, ctx: HandlerContext, resource: VirtualMachine, flavor_name: str
|
|
372
|
+
) -> None:
|
|
373
|
+
"""Resize self.server to the flavor named `flavor_name` and confirm it."""
|
|
374
|
+
flavor = self._connection.compute.find_flavor(flavor_name, ignore_missing=False)
|
|
375
|
+
# Confirming a resize returns the server to the status it had before,
|
|
376
|
+
# so remember it to know what to wait for afterwards.
|
|
377
|
+
original_status = self.server.status
|
|
378
|
+
ctx.info(
|
|
379
|
+
f"Resizing server {resource.name} to flavor {flavor_name}",
|
|
380
|
+
flavor_id=flavor.id,
|
|
381
|
+
)
|
|
382
|
+
self._connection.compute.resize_server(self.server, flavor.id)
|
|
383
|
+
# A resize must be explicitly confirmed once the server reports VERIFY_RESIZE.
|
|
384
|
+
self._wait_for_status(ctx, "VERIFY_RESIZE", "resize")
|
|
385
|
+
self._connection.compute.confirm_server_resize(self.server)
|
|
386
|
+
self._wait_for_status(ctx, original_status, "resize")
|
|
271
387
|
|
|
272
388
|
def read_resource(self, ctx: HandlerContext, resource: VirtualMachine) -> None:
|
|
273
389
|
"""
|
|
@@ -278,12 +394,16 @@ class VirtualMachineHandler(OpenStackHandler):
|
|
|
278
394
|
if self.server is None:
|
|
279
395
|
raise ResourcePurged()
|
|
280
396
|
|
|
281
|
-
if self.server.status == "ERROR":
|
|
282
|
-
self._raise_server_error(
|
|
283
|
-
ctx, "Server is in ERROR state; delete it to allow redeployment"
|
|
284
|
-
)
|
|
285
|
-
|
|
286
397
|
resource.purged = False
|
|
398
|
+
# Reflect the live flavor so a flavor change in the model is detected as
|
|
399
|
+
# drift and handled by update_resource through a resize.
|
|
400
|
+
resource.flavor = self._get_server_flavor_name(self.server)
|
|
401
|
+
# When the VM is linked to a managed flavor, also reflect the resources it
|
|
402
|
+
# is currently running with. This makes a flavor that was redefined under
|
|
403
|
+
# the same name (e.g. deleted and recreated with more RAM) show up as drift
|
|
404
|
+
# even though its name is unchanged.
|
|
405
|
+
if resource.flavor_spec is not None:
|
|
406
|
+
resource.flavor_spec = self._get_server_flavor_spec(self.server)
|
|
287
407
|
if resource.security_groups:
|
|
288
408
|
# An empty relation leaves the server-level security groups unmanaged, so port
|
|
289
409
|
# security groups (managed through neutron) can be used for fine-grained control
|
|
@@ -335,7 +455,7 @@ class VirtualMachineHandler(OpenStackHandler):
|
|
|
335
455
|
)
|
|
336
456
|
|
|
337
457
|
self.server = self._connection.compute.create_server(**args)
|
|
338
|
-
self.
|
|
458
|
+
self._wait_for_status(ctx, "ACTIVE", "build")
|
|
339
459
|
|
|
340
460
|
self._set_facts(ctx)
|
|
341
461
|
# Note: attach/detach of interfaces handled by Neutron or by passing ports at creation time
|
|
@@ -365,9 +485,23 @@ class VirtualMachineHandler(OpenStackHandler):
|
|
|
365
485
|
def update_resource(
|
|
366
486
|
self, ctx: HandlerContext, changes: dict, resource: VirtualMachine
|
|
367
487
|
) -> None:
|
|
368
|
-
|
|
488
|
+
|
|
489
|
+
if self.server.status == "ERROR":
|
|
490
|
+
self._raise_server_error(
|
|
491
|
+
ctx, "Server is in ERROR state; delete it to allow redeployment"
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
self.validate_allowed_fields(
|
|
495
|
+
changes, {"security_groups", "key_name", "flavor", "flavor_spec"}
|
|
496
|
+
)
|
|
369
497
|
|
|
370
498
|
self._ensure_key(ctx, resource)
|
|
499
|
+
|
|
500
|
+
# A change to either the flavor name or, for a flavor-linked VM, the flavor
|
|
501
|
+
# resources both translate to an in-place resize to the desired flavor.
|
|
502
|
+
if "flavor" in changes or "flavor_spec" in changes:
|
|
503
|
+
self._resize_server(ctx, resource, resource.flavor)
|
|
504
|
+
|
|
371
505
|
if "security_groups" in changes:
|
|
372
506
|
current = set(changes["security_groups"]["current"])
|
|
373
507
|
desired = set(changes["security_groups"]["desired"])
|
|
@@ -345,42 +345,6 @@ implement RouterPort using providerRequire
|
|
|
345
345
|
|
|
346
346
|
RouterPort.subnet [1] -- Subnet.routers [0:]
|
|
347
347
|
|
|
348
|
-
entity HostPort extends Port:
|
|
349
|
-
"""
|
|
350
|
-
A port attached to a VM
|
|
351
|
-
|
|
352
|
-
:param name: The name of the host port.
|
|
353
|
-
:param portsecurity: Enable or disable port security (security groups and spoofing filters)
|
|
354
|
-
:param dhcp: Enable dhcp for this port or not for this port
|
|
355
|
-
:param port_index: The index of the port. This determines the order of the interfaces on the virtual machine. 0 means no specific order.
|
|
356
|
-
:param retries: A hostport can only be attached to a VM when it is in an active state. The handler will skip this port when the VM is not ready. To
|
|
357
|
-
speed up deployments, the handler can retry this number of times before skipping the resource.
|
|
358
|
-
:param wait: The number of seconds to wait between retries.
|
|
359
|
-
"""
|
|
360
|
-
std::ipv4_address address
|
|
361
|
-
bool portsecurity=true
|
|
362
|
-
bool dhcp=true
|
|
363
|
-
int port_index=0
|
|
364
|
-
int retries=20
|
|
365
|
-
int wait=5
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
implementation noSecurityGroupsWhenPortSecurityDisabled for HostPort:
|
|
369
|
-
# Security groups can only be enforced on a port when port security is enabled.
|
|
370
|
-
std::assert(
|
|
371
|
-
std::count(self.security_groups) == 0,
|
|
372
|
-
"HostPort '{{name}}' cannot have security_groups assigned while portsecurity is disabled."
|
|
373
|
-
)
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
implement HostPort using providerRequire
|
|
377
|
-
implement HostPort using noSecurityGroupsWhenPortSecurityDisabled when not self.portsecurity
|
|
378
|
-
|
|
379
|
-
HostPort.subnet [1] -- Subnet.host_ports [0:]
|
|
380
|
-
HostPort.vm [1] -- VirtualMachine.ports [0:]
|
|
381
|
-
|
|
382
|
-
index HostPort(provider, vm, name)
|
|
383
|
-
|
|
384
348
|
entity SubnetBase extends OpenStackResource:
|
|
385
349
|
"""
|
|
386
350
|
A neutron subnet
|
|
@@ -502,25 +466,66 @@ implement QoSPolicy using providerRequire
|
|
|
502
466
|
|
|
503
467
|
entity NetworkPort extends Port:
|
|
504
468
|
"""
|
|
505
|
-
A
|
|
506
|
-
|
|
507
|
-
``openstack port create --network <network> <name>`` command and is
|
|
508
|
-
|
|
509
|
-
|
|
469
|
+
A neutron port. By default it is a standalone port, managed directly on a
|
|
470
|
+
network and not bound to a virtual machine -- this mirrors the
|
|
471
|
+
``openstack port create --network <network> <name>`` command and is suitable
|
|
472
|
+
as the parent port of a :py:class:`TrunkNetwork`. Setting the ``vm`` relation
|
|
473
|
+
instead attaches the port to that virtual machine.
|
|
474
|
+
|
|
475
|
+
The port lives on an explicit ``network`` or, when only a ``subnet`` is given,
|
|
476
|
+
on that subnet's network.
|
|
477
|
+
|
|
478
|
+
:attr address: An optional fixed IPv4 to request. When omitted neutron assigns one; leave
|
|
479
|
+
unset for an IPv6-only port. Ignored on a vm-attached port when dhcp is true
|
|
480
|
+
(a standalone port always takes the address it is given).
|
|
481
|
+
:attr address_v6: An optional fixed IPv6 to request from an IPv6 subnet on the port's
|
|
482
|
+
network. When omitted no IPv6 is requested. Ignored when dhcp is true.
|
|
483
|
+
:attr mac_address: An optional MAC address for the port. When omitted, neutron assigns one.
|
|
484
|
+
It is typed as a plain string (not the ``mac_addr`` typedef) so it can hold a
|
|
485
|
+
fact reference resolved at deploy time, e.g.
|
|
486
|
+
``future::std::create_fact_reference(parent_port, "mac_address")`` to make a
|
|
487
|
+
trunk sub-port share its parent port's MAC. Set only at creation; neutron does
|
|
488
|
+
not allow changing it afterwards.
|
|
510
489
|
:attr portsecurity: Enable or disable port security (security groups and spoofing filters).
|
|
511
|
-
:attr
|
|
512
|
-
|
|
513
|
-
|
|
490
|
+
:attr dhcp: Enable dhcp for this port or not. Only relevant when the port is attached to a vm.
|
|
491
|
+
:attr port_index: The index of the port. This determines the order of the interfaces on the
|
|
492
|
+
virtual machine. 0 means no specific order. Only relevant when the port is
|
|
493
|
+
attached to a vm.
|
|
494
|
+
:attr retries: A port attached to a vm can only be attached when the vm is in an active state.
|
|
495
|
+
The handler will skip this port when the vm is not ready. To speed up
|
|
496
|
+
deployments, the handler can retry this number of times before skipping.
|
|
497
|
+
:attr wait: The number of seconds to wait between retries.
|
|
498
|
+
"""
|
|
499
|
+
std::ipv4_address? address = null
|
|
500
|
+
std::ipv6_address? address_v6 = null
|
|
501
|
+
string? mac_address = null
|
|
514
502
|
bool portsecurity=true
|
|
515
|
-
|
|
503
|
+
bool dhcp=true
|
|
504
|
+
int port_index=0
|
|
505
|
+
int retries=20
|
|
506
|
+
int wait=5
|
|
516
507
|
end
|
|
517
508
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
509
|
+
implementation noSecurityGroupsWhenPortSecurityDisabled for NetworkPort:
|
|
510
|
+
# Security groups can only be enforced on a port when port security is enabled.
|
|
511
|
+
std::assert(
|
|
512
|
+
std::count(self.security_groups) == 0,
|
|
513
|
+
"NetworkPort '{{name}}' cannot have security_groups assigned while portsecurity is disabled."
|
|
514
|
+
)
|
|
515
|
+
end
|
|
522
516
|
|
|
523
517
|
implement NetworkPort using providerRequire
|
|
518
|
+
implement NetworkPort using noSecurityGroupsWhenPortSecurityDisabled when not self.portsecurity
|
|
519
|
+
|
|
520
|
+
index NetworkPort(project, name)
|
|
521
|
+
|
|
522
|
+
# The port lives on an explicit network, or derives it from its subnet when only the
|
|
523
|
+
# subnet is given.
|
|
524
|
+
NetworkPort.network [0:1] -- Network.network_ports [0:]
|
|
525
|
+
NetworkPort.subnet [0:1] -- SubnetBase.network_ports [0:]
|
|
526
|
+
# When set, the port is attached to this virtual machine. Left unset, the port is a
|
|
527
|
+
# standalone port (e.g. the parent port of a trunk).
|
|
528
|
+
NetworkPort.vm [0:1] -- VirtualMachine.ports [0:]
|
|
524
529
|
|
|
525
530
|
|
|
526
531
|
entity TrunkNetwork extends OpenStackResource:
|
|
@@ -529,9 +534,11 @@ entity TrunkNetwork extends OpenStackResource:
|
|
|
529
534
|
subports (see :py:class:`TrunkPort`) so that a single VM interface can
|
|
530
535
|
carry traffic for multiple networks using VLAN tagging.
|
|
531
536
|
|
|
532
|
-
The trunk is built on top of an existing parent port
|
|
533
|
-
|
|
534
|
-
a virtual machine
|
|
537
|
+
The trunk is built on top of an existing parent port (a
|
|
538
|
+
:py:class:`NetworkPort`). Neutron only accepts a parent port that is not yet
|
|
539
|
+
bound to a virtual machine, so when the trunk parent is attached to a vm the
|
|
540
|
+
port must be created (and the trunk built on it) before the vm boots on it.
|
|
541
|
+
The parent port is managed separately.
|
|
535
542
|
|
|
536
543
|
:attr name: The name of the trunk. Must be unique per project.
|
|
537
544
|
:attr description: An optional human-readable description.
|
|
@@ -553,8 +560,8 @@ implement TrunkNetwork using providerRequire
|
|
|
553
560
|
|
|
554
561
|
entity TrunkPort extends OpenStackResource:
|
|
555
562
|
"""
|
|
556
|
-
A subport of a :py:class:`TrunkNetwork`. It attaches an existing
|
|
557
|
-
|
|
563
|
+
A subport of a :py:class:`TrunkNetwork`. It attaches an existing
|
|
564
|
+
:py:class:`NetworkPort` to the trunk with the
|
|
558
565
|
configured segmentation type and id (typically a VLAN). The referenced
|
|
559
566
|
port is managed separately; this resource only manages the trunk
|
|
560
567
|
subport attachment, not the port itself.
|
|
@@ -627,7 +634,7 @@ end
|
|
|
627
634
|
SecurityGroup.provider [1] -- Provider.security_groups [0:]
|
|
628
635
|
SecurityGroup.project [1] -- Project.security_groups [0:]
|
|
629
636
|
SecurityGroup.virtual_machines [0:] -- VirtualMachine.security_groups [0:]
|
|
630
|
-
|
|
637
|
+
NetworkPort.security_groups [0:] -- SecurityGroup
|
|
631
638
|
"""
|
|
632
639
|
:rel security_groups: The security groups to enforce on this port. When left empty (the default), the
|
|
633
640
|
port's security groups are left unmanaged for backward compatibility: existing groups (including the
|
|
@@ -686,7 +693,7 @@ implement FloatingIP using fipAddr when not force_ip
|
|
|
686
693
|
FloatingIP.project [1] -- Project.floating_ips [0:]
|
|
687
694
|
FloatingIP.provider [1] -- Provider.floating_ips [0:]
|
|
688
695
|
FloatingIP.external_network [1] -- Network.floating_ips [0:]
|
|
689
|
-
FloatingIP.port [1] --
|
|
696
|
+
FloatingIP.port [1] -- NetworkPort.floating_ips [0:]
|
|
690
697
|
|
|
691
698
|
entity VMAttributes:
|
|
692
699
|
"""
|
|
@@ -713,14 +720,25 @@ entity VirtualMachine extends OpenStackResource, VMAttributes:
|
|
|
713
720
|
string name
|
|
714
721
|
end
|
|
715
722
|
|
|
716
|
-
implement VirtualMachine using providerRequire
|
|
717
|
-
|
|
718
723
|
index VirtualMachine(provider, name)
|
|
719
724
|
|
|
720
|
-
VirtualMachine.eth0_port [1] --
|
|
725
|
+
VirtualMachine.eth0_port [1] -- NetworkPort
|
|
721
726
|
VirtualMachine.key_pair [1] -- ssh::Key
|
|
722
727
|
VirtualMachine.project [1] -- Project
|
|
723
728
|
VirtualMachine.provider [1] -- Provider.virtual_machines [0:]
|
|
729
|
+
# Optional link to the managed Flavor backing this VM. When set, the flavor name
|
|
730
|
+
# is derived from it and the handler compares the flavor's resources against what
|
|
731
|
+
# the VM is actually running, so a flavor recreated with different resources under
|
|
732
|
+
# the same name is detected as drift and applied as an in-place resize.
|
|
733
|
+
VirtualMachine.flavor_rel [0:1] -- Flavor
|
|
734
|
+
|
|
735
|
+
implementation flavorFromRelation for VirtualMachine:
|
|
736
|
+
self.flavor = self.flavor_rel.name
|
|
737
|
+
self.requires += self.flavor_rel
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
implement VirtualMachine using providerRequire
|
|
741
|
+
implement VirtualMachine using flavorFromRelation when flavor_rel is defined
|
|
724
742
|
|
|
725
743
|
entity Host extends std::Host, VMAttributes:
|
|
726
744
|
"""
|
|
@@ -739,7 +757,7 @@ Host.provider [1] -- Provider
|
|
|
739
757
|
Host.security_groups [0:] -- SecurityGroup
|
|
740
758
|
|
|
741
759
|
implementation eth0Port for Host:
|
|
742
|
-
port =
|
|
760
|
+
port = NetworkPort(provider=provider, vm=self.vm, subnet=subnet, name="{{name}}_eth0", address=std::getfact(self.vm, "ip_address"),
|
|
743
761
|
project=project, port_index=1, purged=purged, purge_on_delete=self.purge_on_delete)
|
|
744
762
|
self.vm.eth0_port = port
|
|
745
763
|
end
|