Merge ~newell-jensen/maas:lxd-pod-driver-discovery into maas:master
- Git
- lp:~newell-jensen/maas
- lxd-pod-driver-discovery
- Merge into master
Status: | Merged |
---|---|
Approved by: | Newell Jensen |
Approved revision: | 93c1d03c6041a1119ae1b3d8045f760696d9309b |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~newell-jensen/maas:lxd-pod-driver-discovery |
Merge into: | maas:master |
Diff against target: |
702 lines (+406/-81) 2 files modified
src/provisioningserver/drivers/pod/lxd.py (+170/-23) src/provisioningserver/drivers/pod/tests/test_lxd.py (+236/-58) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Lee Trager (community) | Approve | ||
MAAS Lander | Approve | ||
Review via email: mp+381791@code.launchpad.net |
Commit message
Discover Pod storage pools and machines during the Pod adding/refresh process so that machines not known to MAAS are commissioned.
Description of the change
Lee Trager (ltrager) wrote : | # |
I think this branch also needs to configure an LXD profile for MAAS use if one isn't already created. Otherwise compose won't know which storage pool or parent interface to use when composing a machine.
Also a couple of questions below.
Newell Jensen (newell-jensen) wrote : | # |
The LXD profile is going to be included with the branch that does compose and decompose, which this branch does not do. This branch is for adding discovered resources.
See inline for my responses on items that I don't agree or an explanation is needed (will change the others).
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b lxd-pod-
STATUS: SUCCESS
COMMIT: 208674ac978485f
Lee Trager (ltrager) wrote : | # |
Looks good, just a couple of things inline. I think we should ask stgraber about the vid but we can get that in a later branch.
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b lxd-pod-
STATUS: SUCCESS
COMMIT: 93c1d03c6041a11
Lee Trager (ltrager) wrote : | # |
Thanks for the fixes! LGTM!
Preview Diff
1 | diff --git a/src/provisioningserver/drivers/pod/lxd.py b/src/provisioningserver/drivers/pod/lxd.py |
2 | index 28c441f..70a1cbb 100644 |
3 | --- a/src/provisioningserver/drivers/pod/lxd.py |
4 | +++ b/src/provisioningserver/drivers/pod/lxd.py |
5 | @@ -20,20 +20,32 @@ from provisioningserver.drivers import ( |
6 | ) |
7 | from provisioningserver.drivers.pod import ( |
8 | Capabilities, |
9 | + DiscoveredMachine, |
10 | + DiscoveredMachineBlockDevice, |
11 | + DiscoveredMachineInterface, |
12 | DiscoveredPod, |
13 | + DiscoveredPodStoragePool, |
14 | PodDriver, |
15 | ) |
16 | +from provisioningserver.logger import get_maas_logger |
17 | from provisioningserver.maas_certificates import ( |
18 | MAAS_CERTIFICATE, |
19 | MAAS_PRIVATE_KEY, |
20 | ) |
21 | from provisioningserver.utils import kernel_to_debian_architecture, typed |
22 | +from provisioningserver.utils.ipaddr import get_vid_from_ifname |
23 | +from provisioningserver.utils.twisted import asynchronous |
24 | |
25 | -# LXD Status Codes |
26 | +maaslog = get_maas_logger("drivers.pod.lxd") |
27 | + |
28 | +# LXD status codes |
29 | LXD_VM_POWER_STATE = {101: "on", 102: "off", 103: "on", 110: "off"} |
30 | |
31 | +# LXD vm disk path |
32 | +LXD_VM_ID_PATH = "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_lxd_" |
33 | + |
34 | |
35 | -class LXDError(Exception): |
36 | +class LXDPodError(Exception): |
37 | """Failure communicating to LXD. """ |
38 | |
39 | |
40 | @@ -86,8 +98,8 @@ class LXDPodDriver(PodDriver): |
41 | |
42 | @typed |
43 | @inlineCallbacks |
44 | - def get_client(self, system_id: str, context: dict): |
45 | - """Connect and return PyLXD client.""" |
46 | + def get_client(self, pod_id: str, context: dict): |
47 | + """Connect pylxd client.""" |
48 | endpoint = self.get_url(context) |
49 | password = context.get("password") |
50 | try: |
51 | @@ -101,62 +113,165 @@ class LXDPodDriver(PodDriver): |
52 | if password: |
53 | yield deferToThread(client.authenticate, password) |
54 | else: |
55 | - raise LXDError( |
56 | - f"{system_id}: Certificate is not trusted and no password was given." |
57 | + raise LXDPodError( |
58 | + f"Pod {pod_id}: Certificate is not trusted and no password was given." |
59 | ) |
60 | - return client |
61 | except ClientConnectionFailed: |
62 | - raise LXDError( |
63 | - f"{system_id}: Failed to connect to the LXD REST API." |
64 | + raise LXDPodError( |
65 | + f"Pod {pod_id}: Failed to connect to the LXD REST API." |
66 | ) |
67 | + return client |
68 | |
69 | @typed |
70 | @inlineCallbacks |
71 | - def get_machine(self, system_id: str, context: dict): |
72 | + def get_machine(self, pod_id: str, context: dict): |
73 | """Retrieve LXD VM.""" |
74 | - client = yield self.get_client(system_id, context) |
75 | + client = yield self.get_client(pod_id, context) |
76 | instance_name = context.get("instance_name") |
77 | try: |
78 | machine = yield deferToThread( |
79 | client.virtual_machines.get, instance_name |
80 | ) |
81 | except NotFound: |
82 | - raise LXDError(f"{system_id}: LXD VM {instance_name} not found.") |
83 | + raise LXDPodError( |
84 | + f"Pod {pod_id}: LXD VM {instance_name} not found." |
85 | + ) |
86 | return machine |
87 | |
88 | + def get_discovered_machine( |
89 | + self, machine, storage_pools, request=None, cpu_speed=0 |
90 | + ): |
91 | + """Get the discovered machine.""" |
92 | + # Check the power state first. |
93 | + state = machine.status_code |
94 | + try: |
95 | + power_state = LXD_VM_POWER_STATE[state] |
96 | + except KeyError: |
97 | + maaslog.error( |
98 | + f"{machine.name}: Unknown power status code: {state}" |
99 | + ) |
100 | + power_state = "unknown" |
101 | + |
102 | + expanded_config = machine.expanded_config |
103 | + expanded_devices = machine.expanded_devices |
104 | + |
105 | + # Discover block devices. |
106 | + block_devices = [] |
107 | + for idx, device in enumerate(expanded_devices): |
108 | + # Block device. |
109 | + # When request is provided map the tags from the request block |
110 | + # devices to the discovered block devices. This ensures that |
111 | + # composed machine has the requested tags on the block device. |
112 | + tags = [] |
113 | + if request is not None: |
114 | + tags = request.block_devices[idx].tags |
115 | + |
116 | + device_info = expanded_devices[device] |
117 | + if device_info["type"] == "disk": |
118 | + # Default disk size is 10GB. |
119 | + size = device_info.get("size", 10 * 1000 ** 3) |
120 | + pool = device_info.get("pool") |
121 | + block_devices.append( |
122 | + DiscoveredMachineBlockDevice( |
123 | + model=None, |
124 | + serial=None, |
125 | + id_path=LXD_VM_ID_PATH + device, |
126 | + size=size, |
127 | + tags=tags, |
128 | + storage_pool=pool, |
129 | + ) |
130 | + ) |
131 | + |
132 | + # Discover interfaces. |
133 | + interfaces = [] |
134 | + boot = True |
135 | + for configuration in expanded_config: |
136 | + if configuration.endswith("hwaddr"): |
137 | + mac = expanded_config[configuration] |
138 | + name = configuration.split(".")[1] |
139 | + nictype = expanded_devices[name]["nictype"] |
140 | + interfaces.append( |
141 | + DiscoveredMachineInterface( |
142 | + mac_address=mac, |
143 | + vid=get_vid_from_ifname(name), |
144 | + boot=boot, |
145 | + attach_type=nictype, |
146 | + attach_name=name, |
147 | + ) |
148 | + ) |
149 | + boot = False |
150 | + |
151 | + return DiscoveredMachine( |
152 | + hostname=machine.name, |
153 | + architecture=kernel_to_debian_architecture(machine.architecture), |
154 | + # 1 core and 1GiB of memory (we need it in MiB) is default for |
155 | + # LXD if not specified. |
156 | + cores=int(expanded_config.get("limits.cpu", 1)), |
157 | + memory=int(expanded_config.get("limits.memory", 1024)), |
158 | + cpu_speed=cpu_speed, |
159 | + interfaces=interfaces, |
160 | + block_devices=block_devices, |
161 | + power_state=power_state, |
162 | + power_parameters={"instance_name": machine.name}, |
163 | + tags=[], |
164 | + ) |
165 | + |
166 | + def get_discovered_pod_storage_pool(self, storage_pool): |
167 | + """Get the Pod storage pool.""" |
168 | + storage_pool_config = storage_pool.config |
169 | + # Sometimes the config is empty, use get() method on the dictionary in case. |
170 | + storage_pool_path = storage_pool_config.get("source") |
171 | + storage_pool_resources = storage_pool.resources.get() |
172 | + total_storage = storage_pool_resources.space["total"] |
173 | + |
174 | + return DiscoveredPodStoragePool( |
175 | + # No ID's with LXD so we are just using the name as the ID. |
176 | + id=storage_pool.name, |
177 | + name=storage_pool.name, |
178 | + path=storage_pool_path, |
179 | + type=storage_pool.driver, |
180 | + storage=total_storage, |
181 | + ) |
182 | + |
183 | @typed |
184 | + @asynchronous |
185 | @inlineCallbacks |
186 | - def power_on(self, system_id: str, context: dict): |
187 | + def power_on(self, pod_id: str, context: dict): |
188 | """Power on LXD VM.""" |
189 | - machine = yield deferToThread(self.get_machine, system_id, context) |
190 | + machine = yield self.get_machine(pod_id, context) |
191 | if LXD_VM_POWER_STATE[machine.status_code] == "off": |
192 | yield deferToThread(machine.start) |
193 | |
194 | @typed |
195 | + @asynchronous |
196 | @inlineCallbacks |
197 | - def power_off(self, system_id: str, context: dict): |
198 | + def power_off(self, pod_id: str, context: dict): |
199 | """Power off LXD VM.""" |
200 | - machine = yield deferToThread(self.get_machine, system_id, context) |
201 | + machine = yield self.get_machine(pod_id, context) |
202 | if LXD_VM_POWER_STATE[machine.status_code] == "on": |
203 | yield deferToThread(machine.stop) |
204 | |
205 | @typed |
206 | + @asynchronous |
207 | @inlineCallbacks |
208 | - def power_query(self, system_id: str, context: dict): |
209 | + def power_query(self, pod_id: str, context: dict): |
210 | """Power query LXD VM.""" |
211 | - machine = yield deferToThread(self.get_machine, system_id, context) |
212 | + machine = yield self.get_machine(pod_id, context) |
213 | state = machine.status_code |
214 | try: |
215 | return LXD_VM_POWER_STATE[state] |
216 | except KeyError: |
217 | - raise LXDError(f"{system_id}: Unknown power status code: {state}") |
218 | + raise LXDPodError( |
219 | + f"Pod {pod_id}: Unknown power status code: {state}" |
220 | + ) |
221 | |
222 | @inlineCallbacks |
223 | def discover(self, pod_id, context): |
224 | """Discover all Pod host resources.""" |
225 | + # Connect to the Pod and make sure it is valid. |
226 | client = yield self.get_client(pod_id, context) |
227 | if not client.has_api_extension("virtual-machines"): |
228 | - raise LXDError( |
229 | + raise LXDPodError( |
230 | "Please upgrade your LXD host to 3.19+ for virtual machine support." |
231 | ) |
232 | resources = yield deferToThread(lambda: client.resources) |
233 | @@ -168,7 +283,7 @@ class LXDPodDriver(PodDriver): |
234 | |
235 | # After the region creates the Pod object it will sync LXD commissioning |
236 | # data for all hardware information. |
237 | - return DiscoveredPod( |
238 | + discovered_pod = DiscoveredPod( |
239 | architectures=[ |
240 | kernel_to_debian_architecture(arch) |
241 | for arch in client.host_info["environment"]["architectures"] |
242 | @@ -183,14 +298,46 @@ class LXDPodDriver(PodDriver): |
243 | ], |
244 | ) |
245 | |
246 | + # Check that we have at least one storage pool. If not, create it. |
247 | + pools = yield deferToThread(client.storage_pools.all) |
248 | + if not len(pools): |
249 | + yield deferToThread( |
250 | + client.storage_pools.create, {"name": "maas", "driver": "dir"} |
251 | + ) |
252 | + |
253 | + # Discover Storage Pools. |
254 | + pools = [] |
255 | + storage_pools = yield deferToThread(client.storage_pools.all) |
256 | + for storage_pool in storage_pools: |
257 | + discovered_storage_pool = self.get_discovered_pod_storage_pool( |
258 | + storage_pool |
259 | + ) |
260 | + pools.append(discovered_storage_pool) |
261 | + discovered_pod.storage_pools = pools |
262 | + |
263 | + # Discover VMs. |
264 | + machines = [] |
265 | + virtual_machines = yield deferToThread(client.virtual_machines.all) |
266 | + for virtual_machine in virtual_machines: |
267 | + discovered_machine = self.get_discovered_machine( |
268 | + virtual_machine, |
269 | + storage_pools=discovered_pod.storage_pools, |
270 | + cpu_speed=discovered_pod.cpu_speed, |
271 | + ) |
272 | + machines.append(discovered_machine) |
273 | + discovered_pod.machines = machines |
274 | + |
275 | + # Return the DiscoveredPod. |
276 | + return discovered_pod |
277 | + |
278 | @inlineCallbacks |
279 | - def compose(self, system_id, context, request): |
280 | + def compose(self, pod_id, context, request): |
281 | """Compose a virtual machine.""" |
282 | # abstract method, will update in subsequent branch. |
283 | pass |
284 | |
285 | @inlineCallbacks |
286 | - def decompose(self, system_id, context): |
287 | + def decompose(self, pod_id, context): |
288 | """Decompose a virtual machine machine.""" |
289 | # abstract method, will update in subsequent branch. |
290 | pass |
291 | diff --git a/src/provisioningserver/drivers/pod/tests/test_lxd.py b/src/provisioningserver/drivers/pod/tests/test_lxd.py |
292 | index 904c7af..7c81873 100644 |
293 | --- a/src/provisioningserver/drivers/pod/tests/test_lxd.py |
294 | +++ b/src/provisioningserver/drivers/pod/tests/test_lxd.py |
295 | @@ -8,23 +8,20 @@ __all__ = [] |
296 | from os.path import join |
297 | from unittest.mock import Mock |
298 | |
299 | -from testtools.matchers import Equals |
300 | +from testtools.matchers import Equals, MatchesStructure |
301 | from testtools.testcase import ExpectedException |
302 | from twisted.internet.defer import inlineCallbacks |
303 | |
304 | -# XXX - Remove provisioningserver.drivers.power from testing in this file |
305 | -# and rename lxd_pod_module. |
306 | from maastesting.factory import factory |
307 | from maastesting.matchers import MockCalledOnceWith |
308 | from maastesting.testcase import MAASTestCase, MAASTwistedRunTest |
309 | from provisioningserver.drivers.pod import Capabilities |
310 | from provisioningserver.drivers.pod import lxd as lxd_pod_module |
311 | -from provisioningserver.drivers.pod.lxd import LXDPodDriver |
312 | -from provisioningserver.drivers.power import lxd as lxd_module |
313 | from provisioningserver.maas_certificates import ( |
314 | MAAS_CERTIFICATE, |
315 | MAAS_PRIVATE_KEY, |
316 | ) |
317 | +from provisioningserver.utils import kernel_to_debian_architecture |
318 | |
319 | |
320 | class TestLXDPodDriver(MAASTestCase): |
321 | @@ -32,7 +29,7 @@ class TestLXDPodDriver(MAASTestCase): |
322 | run_tests_with = MAASTwistedRunTest.make_factory(timeout=5) |
323 | |
324 | def test_missing_packages(self): |
325 | - driver = LXDPodDriver() |
326 | + driver = lxd_pod_module.LXDPodDriver() |
327 | missing = driver.detect_missing_packages() |
328 | self.assertItemsEqual([], missing) |
329 | |
330 | @@ -56,7 +53,7 @@ class TestLXDPodDriver(MAASTestCase): |
331 | ) |
332 | |
333 | def test_get_url(self): |
334 | - driver = lxd_module.LXDPowerDriver() |
335 | + driver = lxd_pod_module.LXDPodDriver() |
336 | context = {"power_address": factory.make_hostname()} |
337 | |
338 | # Test ip adds protocol and port |
339 | @@ -86,15 +83,13 @@ class TestLXDPodDriver(MAASTestCase): |
340 | @inlineCallbacks |
341 | def test__get_client(self): |
342 | context = self.make_parameters_context() |
343 | - Client = self.patch(lxd_module, "Client") |
344 | + Client = self.patch(lxd_pod_module, "Client") |
345 | client = Client.return_value |
346 | client.has_api_extension.return_value = True |
347 | client.trusted = False |
348 | - driver = lxd_module.LXDPowerDriver() |
349 | + driver = lxd_pod_module.LXDPodDriver() |
350 | endpoint = driver.get_url(context) |
351 | - returned_client = yield driver.get_client( |
352 | - factory.make_name("system_id"), context |
353 | - ) |
354 | + returned_client = yield driver.get_client(None, context) |
355 | self.assertThat( |
356 | Client, |
357 | MockCalledOnceWith( |
358 | @@ -112,111 +107,109 @@ class TestLXDPodDriver(MAASTestCase): |
359 | def test_get_client_raises_error_when_not_trusted_and_no_password(self): |
360 | context = self.make_parameters_context() |
361 | context["password"] = None |
362 | - system_id = factory.make_name("system_id") |
363 | - Client = self.patch(lxd_module, "Client") |
364 | + pod_id = factory.make_name("pod_id") |
365 | + Client = self.patch(lxd_pod_module, "Client") |
366 | client = Client.return_value |
367 | - client.has_api_extension.return_value = True |
368 | client.trusted = False |
369 | - driver = lxd_module.LXDPowerDriver() |
370 | - error_msg = f"{system_id}: Certificate is not trusted and no password was given." |
371 | - with ExpectedException(lxd_module.LXDError, error_msg): |
372 | - yield driver.get_machine(system_id, context) |
373 | - self.assertThat( |
374 | - client.has_api_extension, MockCalledOnceWith("virtual-machines") |
375 | - ) |
376 | + driver = lxd_pod_module.LXDPodDriver() |
377 | + error_msg = f"Pod {pod_id}: Certificate is not trusted and no password was given." |
378 | + with ExpectedException(lxd_pod_module.LXDPodError, error_msg): |
379 | + yield driver.get_client(pod_id, context) |
380 | |
381 | @inlineCallbacks |
382 | def test_get_client_raises_error_when_cannot_connect(self): |
383 | context = self.make_parameters_context() |
384 | - system_id = factory.make_name("system_id") |
385 | - Client = self.patch(lxd_module, "Client") |
386 | - Client.side_effect = lxd_module.ClientConnectionFailed() |
387 | - driver = lxd_module.LXDPowerDriver() |
388 | - error_msg = f"{system_id}: Failed to connect to the LXD REST API." |
389 | - with ExpectedException(lxd_module.LXDError, error_msg): |
390 | - yield driver.get_client(system_id, context) |
391 | + pod_id = factory.make_name("pod_id") |
392 | + Client = self.patch(lxd_pod_module, "Client") |
393 | + Client.side_effect = lxd_pod_module.ClientConnectionFailed() |
394 | + driver = lxd_pod_module.LXDPodDriver() |
395 | + error_msg = f"Pod {pod_id}: Failed to connect to the LXD REST API." |
396 | + with ExpectedException(lxd_pod_module.LXDPodError, error_msg): |
397 | + yield driver.get_client(pod_id, context) |
398 | |
399 | @inlineCallbacks |
400 | def test__get_machine(self): |
401 | context = self.make_parameters_context() |
402 | - system_id = factory.make_name("system_id") |
403 | - driver = lxd_module.LXDPowerDriver() |
404 | + driver = lxd_pod_module.LXDPodDriver() |
405 | Client = self.patch(driver, "get_client") |
406 | client = Client.return_value |
407 | mock_machine = Mock() |
408 | client.virtual_machines.get.return_value = mock_machine |
409 | - returned_machine = yield driver.get_machine(system_id, context) |
410 | - self.assertThat(Client, MockCalledOnceWith(system_id, context)) |
411 | + returned_machine = yield driver.get_machine(None, context) |
412 | + self.assertThat(Client, MockCalledOnceWith(None, context)) |
413 | self.assertEquals(mock_machine, returned_machine) |
414 | |
415 | @inlineCallbacks |
416 | def test_get_machine_raises_error_when_machine_not_found(self): |
417 | context = self.make_parameters_context() |
418 | - system_id = factory.make_name("system_id") |
419 | + pod_id = factory.make_name("pod_id") |
420 | instance_name = context.get("instance_name") |
421 | - driver = lxd_module.LXDPowerDriver() |
422 | + driver = lxd_pod_module.LXDPodDriver() |
423 | Client = self.patch(driver, "get_client") |
424 | client = Client.return_value |
425 | - client.virtual_machines.get.side_effect = lxd_module.NotFound("Error") |
426 | - error_msg = f"{system_id}: LXD VM {instance_name} not found." |
427 | - with ExpectedException(lxd_module.LXDError, error_msg): |
428 | - yield driver.get_machine(system_id, context) |
429 | + client.virtual_machines.get.side_effect = lxd_pod_module.NotFound( |
430 | + "Error" |
431 | + ) |
432 | + error_msg = f"Pod {pod_id}: LXD VM {instance_name} not found." |
433 | + with ExpectedException(lxd_pod_module.LXDPodError, error_msg): |
434 | + yield driver.get_machine(pod_id, context) |
435 | |
436 | @inlineCallbacks |
437 | def test__power_on(self): |
438 | context = self.make_parameters_context() |
439 | - system_id = factory.make_name("system_id") |
440 | - driver = lxd_module.LXDPowerDriver() |
441 | + driver = lxd_pod_module.LXDPodDriver() |
442 | mock_machine = self.patch(driver, "get_machine").return_value |
443 | mock_machine.status_code = 110 |
444 | - yield driver.power_on(system_id, context) |
445 | + yield driver.power_on(None, context) |
446 | self.assertThat(mock_machine.start, MockCalledOnceWith()) |
447 | |
448 | @inlineCallbacks |
449 | def test__power_off(self): |
450 | context = self.make_parameters_context() |
451 | - system_id = factory.make_name("system_id") |
452 | - driver = lxd_module.LXDPowerDriver() |
453 | + driver = lxd_pod_module.LXDPodDriver() |
454 | mock_machine = self.patch(driver, "get_machine").return_value |
455 | mock_machine.status_code = 103 |
456 | - yield driver.power_off(system_id, context) |
457 | + yield driver.power_off(None, context) |
458 | self.assertThat(mock_machine.stop, MockCalledOnceWith()) |
459 | |
460 | @inlineCallbacks |
461 | def test__power_query(self): |
462 | context = self.make_parameters_context() |
463 | - system_id = factory.make_name("system_id") |
464 | - driver = lxd_module.LXDPowerDriver() |
465 | + driver = lxd_pod_module.LXDPodDriver() |
466 | mock_machine = self.patch(driver, "get_machine").return_value |
467 | mock_machine.status_code = 103 |
468 | - state = yield driver.power_query(system_id, context) |
469 | + state = yield driver.power_query(None, context) |
470 | self.assertThat(state, Equals("on")) |
471 | |
472 | @inlineCallbacks |
473 | - def test__power_query_raises_error_on_unknown_state(self): |
474 | + def test_power_query_raises_error_on_unknown_state(self): |
475 | context = self.make_parameters_context() |
476 | - system_id = factory.make_name("system_id") |
477 | - driver = lxd_module.LXDPowerDriver() |
478 | + pod_id = factory.make_name("pod_id") |
479 | + driver = lxd_pod_module.LXDPodDriver() |
480 | mock_machine = self.patch(driver, "get_machine").return_value |
481 | mock_machine.status_code = 106 |
482 | - error_msg = f"{system_id}: Unknown power status code: {mock_machine.status_code}" |
483 | - with ExpectedException(lxd_module.LXDError, error_msg): |
484 | - yield driver.power_query(system_id, context) |
485 | + error_msg = f"Pod {pod_id}: Unknown power status code: {mock_machine.status_code}" |
486 | + with ExpectedException(lxd_pod_module.LXDPodError, error_msg): |
487 | + yield driver.power_query(pod_id, context) |
488 | |
489 | @inlineCallbacks |
490 | - def test__discover_requires_client_to_have_vm_support(self): |
491 | + def test_discover_requires_client_to_have_vm_support(self): |
492 | context = self.make_parameters_context() |
493 | - driver = LXDPodDriver() |
494 | + driver = lxd_pod_module.LXDPodDriver() |
495 | Client = self.patch(lxd_pod_module, "Client") |
496 | client = Client.return_value |
497 | client.has_api_extension.return_value = False |
498 | - with ExpectedException(lxd_pod_module.LXDError): |
499 | + error_msg = "Please upgrade your LXD host to *." |
500 | + with ExpectedException(lxd_pod_module.LXDPodError, error_msg): |
501 | yield driver.discover(None, context) |
502 | + self.assertThat( |
503 | + client.has_api_extension, MockCalledOnceWith("virtual-machines") |
504 | + ) |
505 | |
506 | @inlineCallbacks |
507 | def test__discover(self): |
508 | context = self.make_parameters_context() |
509 | - driver = LXDPodDriver() |
510 | + driver = lxd_pod_module.LXDPodDriver() |
511 | Client = self.patch(lxd_pod_module, "Client") |
512 | client = Client.return_value |
513 | client.has_api_extension.return_value = True |
514 | @@ -260,3 +253,188 @@ class TestLXDPodDriver(MAASTestCase): |
515 | self.assertItemsEqual([], discovered_pod.machines) |
516 | self.assertItemsEqual([], discovered_pod.tags) |
517 | self.assertItemsEqual([], discovered_pod.storage_pools) |
518 | + |
519 | + @inlineCallbacks |
520 | + def test__get_discovered_pod_storage_pool(self): |
521 | + driver = lxd_pod_module.LXDPodDriver() |
522 | + mock_storage_pool = Mock() |
523 | + mock_storage_pool.name = factory.make_name("pool") |
524 | + mock_storage_pool.driver = "dir" |
525 | + mock_storage_pool.config = { |
526 | + "size": "61203283968", |
527 | + "source": "/home/chb/mnt/l2/disks/default.img", |
528 | + "volume.size": "0", |
529 | + "zfs.pool_name": "default", |
530 | + } |
531 | + mock_resources = Mock() |
532 | + mock_resources.space = {"used": 207111192576, "total": 306027577344} |
533 | + mock_storage_pool.resources.get.return_value = mock_resources |
534 | + discovered_pod_storage_pool = yield driver.get_discovered_pod_storage_pool( |
535 | + mock_storage_pool |
536 | + ) |
537 | + |
538 | + self.assertEquals( |
539 | + mock_storage_pool.name, discovered_pod_storage_pool.id |
540 | + ) |
541 | + self.assertEquals( |
542 | + mock_storage_pool.name, discovered_pod_storage_pool.name |
543 | + ) |
544 | + self.assertEquals( |
545 | + mock_storage_pool.config["source"], |
546 | + discovered_pod_storage_pool.path, |
547 | + ) |
548 | + self.assertEquals( |
549 | + mock_storage_pool.driver, discovered_pod_storage_pool.type |
550 | + ) |
551 | + self.assertEquals( |
552 | + mock_resources.space["total"], discovered_pod_storage_pool.storage |
553 | + ) |
554 | + |
555 | + @inlineCallbacks |
556 | + def test__get_discovered_machine(self): |
557 | + driver = lxd_pod_module.LXDPodDriver() |
558 | + mock_machine = Mock() |
559 | + mock_machine.name = factory.make_name("machine") |
560 | + mock_machine.architecture = "x86_64" |
561 | + expanded_config = { |
562 | + "limits.cpu": "2", |
563 | + "limits.memory": "1024", |
564 | + "volatile.eth0.hwaddr": "00:16:3e:78:be:04", |
565 | + "volatile.eth1.hwaddr": "00:16:3e:f9:fc:cb", |
566 | + } |
567 | + expanded_devices = { |
568 | + "eth0": { |
569 | + "name": "eth0", |
570 | + "nictype": "bridged", |
571 | + "parent": "lxdbr0", |
572 | + "type": "nic", |
573 | + }, |
574 | + "eth1": { |
575 | + "name": "eth1", |
576 | + "nictype": "bridged", |
577 | + "parent": "virbr1", |
578 | + "type": "nic", |
579 | + }, |
580 | + "root": {"path": "/", "pool": "default", "type": "disk"}, |
581 | + } |
582 | + mock_machine.expanded_config = expanded_config |
583 | + mock_machine.expanded_devices = expanded_devices |
584 | + mock_machine.status_code = 102 |
585 | + mock_storage_pool = Mock() |
586 | + mock_storage_pool.name = "default" |
587 | + mock_storage_pool_resources = Mock() |
588 | + mock_storage_pool_resources.space = { |
589 | + "used": 207111192576, |
590 | + "total": 306027577344, |
591 | + } |
592 | + mock_storage_pool.resources.get.return_value = ( |
593 | + mock_storage_pool_resources |
594 | + ) |
595 | + mock_machine.storage_pools.get.return_value = mock_storage_pool |
596 | + discovered_machine = yield driver.get_discovered_machine( |
597 | + mock_machine, [mock_storage_pool], cpu_speed=2500 |
598 | + ) |
599 | + |
600 | + self.assertEquals(mock_machine.name, discovered_machine.hostname) |
601 | + |
602 | + self.assertEquals( |
603 | + kernel_to_debian_architecture(mock_machine.architecture), |
604 | + discovered_machine.architecture, |
605 | + ) |
606 | + self.assertEquals( |
607 | + lxd_pod_module.LXD_VM_POWER_STATE[mock_machine.status_code], |
608 | + discovered_machine.power_state, |
609 | + ) |
610 | + self.assertEquals(2, discovered_machine.cores) |
611 | + self.assertEquals(2500, discovered_machine.cpu_speed) |
612 | + self.assertEquals(1024, discovered_machine.memory) |
613 | + self.assertEquals( |
614 | + mock_machine.name, |
615 | + discovered_machine.power_parameters["instance_name"], |
616 | + ) |
617 | + self.assertThat( |
618 | + discovered_machine.block_devices[0], |
619 | + MatchesStructure.byEquality( |
620 | + model=None, |
621 | + serial=None, |
622 | + id_path=lxd_pod_module.LXD_VM_ID_PATH + "root", |
623 | + size=10 * 1000 ** 3, |
624 | + block_size=512, |
625 | + tags=[], |
626 | + type="physical", |
627 | + storage_pool=expanded_devices["root"]["pool"], |
628 | + iscsi_target=None, |
629 | + ), |
630 | + ) |
631 | + self.assertThat( |
632 | + discovered_machine.interfaces[0], |
633 | + MatchesStructure.byEquality( |
634 | + mac_address=expanded_config["volatile.eth0.hwaddr"], |
635 | + vid=0, |
636 | + tags=[], |
637 | + boot=True, |
638 | + attach_type=expanded_devices["eth0"]["nictype"], |
639 | + attach_name="eth0", |
640 | + ), |
641 | + ) |
642 | + self.assertThat( |
643 | + discovered_machine.interfaces[1], |
644 | + MatchesStructure.byEquality( |
645 | + mac_address=expanded_config["volatile.eth1.hwaddr"], |
646 | + vid=0, |
647 | + tags=[], |
648 | + boot=False, |
649 | + attach_type=expanded_devices["eth1"]["nictype"], |
650 | + attach_name="eth1", |
651 | + ), |
652 | + ) |
653 | + self.assertItemsEqual([], discovered_machine.tags) |
654 | + |
655 | + @inlineCallbacks |
656 | + def test_get_discovered_machine_sets_power_state_to_unknown_for_unknown( |
657 | + self |
658 | + ): |
659 | + driver = lxd_pod_module.LXDPodDriver() |
660 | + mock_machine = Mock() |
661 | + mock_machine.name = factory.make_name("machine") |
662 | + mock_machine.architecture = "x86_64" |
663 | + expanded_config = { |
664 | + "limits.cpu": "2", |
665 | + "limits.memory": "1024", |
666 | + "volatile.eth0.hwaddr": "00:16:3e:78:be:04", |
667 | + "volatile.eth1.hwaddr": "00:16:3e:f9:fc:cb", |
668 | + } |
669 | + expanded_devices = { |
670 | + "eth0": { |
671 | + "name": "eth0", |
672 | + "nictype": "bridged", |
673 | + "parent": "lxdbr0", |
674 | + "type": "nic", |
675 | + }, |
676 | + "eth1": { |
677 | + "name": "eth1", |
678 | + "nictype": "bridged", |
679 | + "parent": "virbr1", |
680 | + "type": "nic", |
681 | + }, |
682 | + "root": {"path": "/", "pool": "default", "type": "disk"}, |
683 | + } |
684 | + mock_machine.expanded_config = expanded_config |
685 | + mock_machine.expanded_devices = expanded_devices |
686 | + mock_machine.status_code = 100 |
687 | + mock_storage_pool = Mock() |
688 | + mock_storage_pool.name = "default" |
689 | + mock_storage_pool_resources = Mock() |
690 | + mock_storage_pool_resources.space = { |
691 | + "used": 207111192576, |
692 | + "total": 306027577344, |
693 | + } |
694 | + mock_storage_pool.resources.get.return_value = ( |
695 | + mock_storage_pool_resources |
696 | + ) |
697 | + mock_machine.storage_pools.get.return_value = mock_storage_pool |
698 | + discovered_machine = yield driver.get_discovered_machine( |
699 | + mock_machine, [mock_storage_pool], cpu_speed=2500 |
700 | + ) |
701 | + |
702 | + self.assertEquals("unknown", discovered_machine.power_state) |
UNIT TESTS driver- discovery lp:~newell-jensen/maas/+git/maas into -b master lp:~maas-committers/maas
-b lxd-pod-
STATUS: FAILED maas-ci- jenkins. internal: 8080/job/ maas/job/ branch- tester/ 7356/console 39f60eed59be92e 81b35f570d
LOG: http://
COMMIT: 4d2cd8ddd424eaa