Merge lp:~blake-rouse/maas/rsd-compose-storage into lp:~maas-committers/maas/trunk
- rsd-compose-storage
- Merge into trunk
Proposed by
Blake Rouse
Status: | Rejected |
---|---|
Rejected by: | MAAS Lander |
Proposed branch: | lp:~blake-rouse/maas/rsd-compose-storage |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
951 lines (+442/-140) 2 files modified
src/provisioningserver/drivers/pod/rsd.py (+226/-74) src/provisioningserver/drivers/pod/tests/test_rsd.py (+216/-66) |
To merge this branch: | bzr merge lp:~blake-rouse/maas/rsd-compose-storage |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
MAAS Maintainers | Pending | ||
Review via email: mp+322203@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
- 5936. By Blake Rouse
-
Merge pod-storage discovery.
Revision history for this message
MAAS Lander (maas-lander) wrote : | # |
Unmerged revisions
- 5936. By Blake Rouse
-
Merge pod-storage discovery.
- 5935. By Blake Rouse
-
Improve the logic of generating the JSON payload to allocation.
- 5934. By Blake Rouse
-
Merge trunk.
- 5933. By Blake Rouse
-
Use newells branch to set the master to clone from.
- 5932. By Blake Rouse
-
Merge trunk.
- 5931. By Blake Rouse
-
Merge rsd scrape storage.
- 5930. By Blake Rouse
-
Add remote storage to compose.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/provisioningserver/drivers/pod/rsd.py' | |||
2 | --- src/provisioningserver/drivers/pod/rsd.py 2017-04-06 19:59:20 +0000 | |||
3 | +++ src/provisioningserver/drivers/pod/rsd.py 2017-04-10 13:49:22 +0000 | |||
4 | @@ -19,6 +19,7 @@ | |||
5 | 19 | SETTING_SCOPE, | 19 | SETTING_SCOPE, |
6 | 20 | ) | 20 | ) |
7 | 21 | from provisioningserver.drivers.pod import ( | 21 | from provisioningserver.drivers.pod import ( |
8 | 22 | BlockDeviceType, | ||
9 | 22 | Capabilities, | 23 | Capabilities, |
10 | 23 | DiscoveredMachine, | 24 | DiscoveredMachine, |
11 | 24 | DiscoveredMachineBlockDevice, | 25 | DiscoveredMachineBlockDevice, |
12 | @@ -31,7 +32,10 @@ | |||
13 | 31 | ) | 32 | ) |
14 | 32 | from provisioningserver.logger import get_maas_logger | 33 | from provisioningserver.logger import get_maas_logger |
15 | 33 | from provisioningserver.rpc.exceptions import PodInvalidResources | 34 | from provisioningserver.rpc.exceptions import PodInvalidResources |
17 | 34 | from provisioningserver.utils.twisted import asynchronous | 35 | from provisioningserver.utils.twisted import ( |
18 | 36 | asynchronous, | ||
19 | 37 | pause, | ||
20 | 38 | ) | ||
21 | 35 | from twisted.internet import reactor | 39 | from twisted.internet import reactor |
22 | 36 | from twisted.internet._sslverify import ( | 40 | from twisted.internet._sslverify import ( |
23 | 37 | ClientTLSOptions, | 41 | ClientTLSOptions, |
24 | @@ -229,12 +233,7 @@ | |||
25 | 229 | targets.append(remote_drive['@odata.id']) | 233 | targets.append(remote_drive['@odata.id']) |
26 | 230 | return set(targets) | 234 | return set(targets) |
27 | 231 | 235 | ||
34 | 232 | @inlineCallbacks | 236 | def calculate_remote_storage(self, remote_drives, logical_drives, targets): |
29 | 233 | def calculate_remote_storage(self, url, headers): | ||
30 | 234 | logical_drives, target_links = ( | ||
31 | 235 | yield self.scrape_logical_drives_and_targets(url, headers)) | ||
32 | 236 | remote_drives = yield self.scrape_remote_drives(url, headers) | ||
33 | 237 | |||
35 | 238 | # Find LVGs and LVs out of all logical drives. | 237 | # Find LVGs and LVs out of all logical drives. |
36 | 239 | lvgs = {} | 238 | lvgs = {} |
37 | 240 | lvs = {} | 239 | lvs = {} |
38 | @@ -251,7 +250,8 @@ | |||
39 | 251 | total = 0 | 250 | total = 0 |
40 | 252 | available = 0 | 251 | available = 0 |
41 | 253 | master_id = 0 | 252 | master_id = 0 |
43 | 254 | master = None | 253 | master_size = 0 |
44 | 254 | master_path = None | ||
45 | 255 | 255 | ||
46 | 256 | # Find size of LVG and get LVs for this LVG. | 256 | # Find size of LVG and get LVs for this LVG. |
47 | 257 | lvg_capacity = lvg_data['CapacityGiB'] | 257 | lvg_capacity = lvg_data['CapacityGiB'] |
48 | @@ -282,8 +282,9 @@ | |||
49 | 282 | if not (lv_target_links & remote_drives): | 282 | if not (lv_target_links & remote_drives): |
50 | 283 | lvs_capacity_unused += lv_capacity | 283 | lvs_capacity_unused += lv_capacity |
51 | 284 | new_master_id = int(lv_info['Id']) | 284 | new_master_id = int(lv_info['Id']) |
54 | 285 | if (master is None or master_id > new_master_id): | 285 | if (master_path is None or master_id > new_master_id): |
55 | 286 | master = lv_link | 286 | master_path = b"/" + lv_link |
56 | 287 | master_size = lv_capacity | ||
57 | 287 | master_id = new_master_id | 288 | master_id = new_master_id |
58 | 288 | 289 | ||
59 | 289 | total = ( | 290 | total = ( |
60 | @@ -293,10 +294,34 @@ | |||
61 | 293 | remote_storage[lvg] = { | 294 | remote_storage[lvg] = { |
62 | 294 | 'total': total, | 295 | 'total': total, |
63 | 295 | 'available': available, | 296 | 'available': available, |
65 | 296 | 'master': master | 297 | 'master': { |
66 | 298 | 'path': master_path, | ||
67 | 299 | 'size': master_size | ||
68 | 300 | } | ||
69 | 297 | } | 301 | } |
70 | 298 | return remote_storage | 302 | return remote_storage |
71 | 299 | 303 | ||
72 | 304 | def calculate_pod_remote_storage( | ||
73 | 305 | self, remote_drives, logical_drives, targets): | ||
74 | 306 | """Calculate the total sum of LVG capacities in the pod and retrieve the | ||
75 | 307 | largest LV for the hints. | ||
76 | 308 | """ | ||
77 | 309 | remote_storage = self.calculate_remote_storage( | ||
78 | 310 | remote_drives, logical_drives, targets) | ||
79 | 311 | |||
80 | 312 | total_capacity = 0 | ||
81 | 313 | for lvg, rs_data in remote_storage.items(): | ||
82 | 314 | total_capacity += rs_data['total'] | ||
83 | 315 | |||
84 | 316 | hint_capacity = 0 | ||
85 | 317 | for lv, lv_data in logical_drives.items(): | ||
86 | 318 | if lv_data['Mode'] == "LV": | ||
87 | 319 | if lv_data['CapacityGiB'] > hint_capacity: | ||
88 | 320 | hint_capacity = lv_data['CapacityGiB'] | ||
89 | 321 | hint_capacity *= 1024 ** 3 | ||
90 | 322 | |||
91 | 323 | return total_capacity, hint_capacity | ||
92 | 324 | |||
93 | 300 | @inlineCallbacks | 325 | @inlineCallbacks |
94 | 301 | def get_pod_memory_resources(self, url, headers, system): | 326 | def get_pod_memory_resources(self, url, headers, system): |
95 | 302 | """Get all the memory resources for the given system.""" | 327 | """Get all the memory resources for the given system.""" |
96 | @@ -408,7 +433,7 @@ | |||
97 | 408 | return discovered_pod | 433 | return discovered_pod |
98 | 409 | 434 | ||
99 | 410 | @inlineCallbacks | 435 | @inlineCallbacks |
101 | 411 | def get_pod_machine(self, node, url, headers): | 436 | def get_pod_machine(self, node, url, headers, logical_drives, targets): |
102 | 412 | """Get pod composed machine. | 437 | """Get pod composed machine. |
103 | 413 | 438 | ||
104 | 414 | If required resources cannot be found, this | 439 | If required resources cannot be found, this |
105 | @@ -425,44 +450,37 @@ | |||
106 | 425 | node_data, _ = yield self.redfish_request( | 450 | node_data, _ = yield self.redfish_request( |
107 | 426 | b"GET", join(url, node), headers) | 451 | b"GET", join(url, node), headers) |
108 | 427 | # Get hostname. | 452 | # Get hostname. |
112 | 428 | hostname = node_data.get('Name') | 453 | discovered_machine.hostname = node_data['Name'] |
110 | 429 | if hostname is not None: | ||
111 | 430 | discovered_machine.hostname = hostname | ||
113 | 431 | # Get power state. | 454 | # Get power state. |
118 | 432 | power_state = node_data.get('PowerState') | 455 | power_state = node_data['PowerState'] |
119 | 433 | if power_state is not None: | 456 | discovered_machine.power_state = RSD_SYSTEM_POWER_STATE.get( |
120 | 434 | discovered_machine.power_state = RSD_SYSTEM_POWER_STATE.get( | 457 | power_state) |
117 | 435 | power_state) | ||
121 | 436 | # Get memories. | 458 | # Get memories. |
123 | 437 | memories = node_data.get('Links', {}).get('Memory') | 459 | memories = node_data.get('Links', {}).get('Memory', []) |
124 | 438 | for memory in memories: | 460 | for memory in memories: |
125 | 439 | memory_data, _ = yield self.redfish_request( | 461 | memory_data, _ = yield self.redfish_request( |
126 | 440 | b"GET", join(url, memory[ | 462 | b"GET", join(url, memory[ |
127 | 441 | '@odata.id'].lstrip('/').encode('utf-8')), headers) | 463 | '@odata.id'].lstrip('/').encode('utf-8')), headers) |
131 | 442 | mem = memory_data.get('CapacityMiB') | 464 | discovered_machine.memory += memory_data['CapacityMiB'] |
129 | 443 | if mem is not None: | ||
130 | 444 | discovered_machine.memory += mem | ||
132 | 445 | # Get processors. | 465 | # Get processors. |
134 | 446 | processors = node_data.get('Links', {}).get('Processors') | 466 | processors = node_data.get('Links', {}).get('Processors', []) |
135 | 447 | for processor in processors: | 467 | for processor in processors: |
136 | 448 | processor_data, _ = yield self.redfish_request( | 468 | processor_data, _ = yield self.redfish_request( |
137 | 449 | b"GET", join(url, processor[ | 469 | b"GET", join(url, processor[ |
138 | 450 | '@odata.id'].lstrip('/').encode('utf-8')), headers) | 470 | '@odata.id'].lstrip('/').encode('utf-8')), headers) |
139 | 451 | # Using 'TotalThreads' instead of 'TotalCores' | 471 | # Using 'TotalThreads' instead of 'TotalCores' |
140 | 452 | # as this is what MAAS finds when commissioning. | 472 | # as this is what MAAS finds when commissioning. |
144 | 453 | total_threads = processor_data.get('TotalThreads') | 473 | discovered_machine.cores += processor_data['TotalThreads'] |
142 | 454 | if total_threads is not None: | ||
143 | 455 | discovered_machine.cores += total_threads | ||
145 | 456 | discovered_machine.cpu_speeds.append( | 474 | discovered_machine.cpu_speeds.append( |
147 | 457 | processor_data.get('MaxSpeedMHz')) | 475 | processor_data['MaxSpeedMHz']) |
148 | 458 | # Set architecture to first processor | 476 | # Set architecture to first processor |
149 | 459 | # architecture type found. | 477 | # architecture type found. |
150 | 460 | if not discovered_machine.architecture: | 478 | if not discovered_machine.architecture: |
152 | 461 | arch = processor_data.get('InstructionSet') | 479 | arch = processor_data['InstructionSet'] |
153 | 462 | discovered_machine.architecture = ( | 480 | discovered_machine.architecture = ( |
154 | 463 | RSD_ARCH.get(arch, arch)) | 481 | RSD_ARCH.get(arch, arch)) |
155 | 464 | # Get local storages. | 482 | # Get local storages. |
157 | 465 | local_drives = node_data.get('Links', {}).get('LocalDrives') | 483 | local_drives = node_data.get('Links', {}).get('LocalDrives', []) |
158 | 466 | for local_drive in local_drives: | 484 | for local_drive in local_drives: |
159 | 467 | discovered_machine_block_device = ( | 485 | discovered_machine_block_device = ( |
160 | 468 | DiscoveredMachineBlockDevice( | 486 | DiscoveredMachineBlockDevice( |
161 | @@ -470,38 +488,61 @@ | |||
162 | 470 | drive_data, _ = yield self.redfish_request( | 488 | drive_data, _ = yield self.redfish_request( |
163 | 471 | b"GET", join(url, local_drive[ | 489 | b"GET", join(url, local_drive[ |
164 | 472 | '@odata.id'].lstrip('/').encode('utf-8')), headers) | 490 | '@odata.id'].lstrip('/').encode('utf-8')), headers) |
178 | 473 | model = drive_data.get('Model') | 491 | discovered_machine_block_device.model = drive_data['Model'] |
179 | 474 | if model is not None: | 492 | discovered_machine_block_device.serial = drive_data['SerialNumber'] |
180 | 475 | discovered_machine_block_device.model = model | 493 | discovered_machine_block_device.size = float( |
181 | 476 | serial_number = drive_data.get('SerialNumber') | 494 | drive_data['CapacityGiB']) * (1024 ** 3) |
182 | 477 | if serial_number is not None: | 495 | if drive_data['Type'] == 'SSD': |
170 | 478 | discovered_machine_block_device.serial = ( | ||
171 | 479 | serial_number) | ||
172 | 480 | capacity = drive_data.get('CapacityGiB') | ||
173 | 481 | if capacity is not None: | ||
174 | 482 | # GiB to Bytes. | ||
175 | 483 | discovered_machine_block_device.size = float( | ||
176 | 484 | capacity) * 1073741824 | ||
177 | 485 | if drive_data.get('Type') == 'SSD': | ||
183 | 486 | discovered_machine_block_device.tags = ['ssd'] | 496 | discovered_machine_block_device.tags = ['ssd'] |
184 | 487 | discovered_machine.block_devices.append( | 497 | discovered_machine.block_devices.append( |
185 | 488 | discovered_machine_block_device) | 498 | discovered_machine_block_device) |
186 | 499 | # Get remote storages. | ||
187 | 500 | remote_drives = node_data.get('Links', {}).get('RemoteDrives', []) | ||
188 | 501 | for remote_drive in remote_drives: | ||
189 | 502 | discovered_machine_block_device = ( | ||
190 | 503 | DiscoveredMachineBlockDevice( | ||
191 | 504 | model=None, serial=None, size=0, | ||
192 | 505 | type=BlockDeviceType.ISCSI)) | ||
193 | 506 | target_data = targets[ | ||
194 | 507 | remote_drive['@odata.id'].lstrip('/').encode('utf-8')] | ||
195 | 508 | addresses = target_data.get('Addresses')[0] | ||
196 | 509 | host = addresses.get('iSCSI', {}).get('TargetPortalIP') | ||
197 | 510 | proto = '6' # curtin currently only supports TCP. | ||
198 | 511 | port = str(addresses.get('iSCSI', {}).get('TargetPortalPort')) | ||
199 | 512 | luns = addresses.get('iSCSI', {}).get('TargetLUN', []) | ||
200 | 513 | if luns: | ||
201 | 514 | lun = str(luns[0]['LUN']) | ||
202 | 515 | else: | ||
203 | 516 | # Set LUN to 0 if not available. | ||
204 | 517 | lun = '0' | ||
205 | 518 | target_name = addresses.get('iSCSI', {}).get('TargetIQN') | ||
206 | 519 | discovered_machine_block_device.iscsi_target = ':'.join( | ||
207 | 520 | [host, proto, port, lun, target_name]) | ||
208 | 521 | discovered_machine_block_device.tags = ['iscsi'] | ||
209 | 522 | # Loop through all the logical drives till we | ||
210 | 523 | # find which one contains this remote drive. | ||
211 | 524 | for lv, lv_data in logical_drives.items(): | ||
212 | 525 | lv_targets = lv_data.get('Links', {}).get('Targets', []) | ||
213 | 526 | if remote_drive in lv_targets: | ||
214 | 527 | discovered_machine_block_device.size = float( | ||
215 | 528 | lv_data['CapacityGiB']) * (1024 ** 3) | ||
216 | 529 | discovered_machine.block_devices.append( | ||
217 | 530 | discovered_machine_block_device) | ||
218 | 489 | # Get interfaces. | 531 | # Get interfaces. |
220 | 490 | interfaces = node_data.get('Links', {}).get('EthernetInterfaces') | 532 | interfaces = node_data.get('Links', {}).get('EthernetInterfaces', []) |
221 | 491 | for interface in interfaces: | 533 | for interface in interfaces: |
222 | 492 | discovered_machine_interface = DiscoveredMachineInterface( | 534 | discovered_machine_interface = DiscoveredMachineInterface( |
223 | 493 | mac_address='') | 535 | mac_address='') |
224 | 494 | interface_data, _ = yield self.redfish_request( | 536 | interface_data, _ = yield self.redfish_request( |
225 | 495 | b"GET", join(url, interface[ | 537 | b"GET", join(url, interface[ |
226 | 496 | '@odata.id'].lstrip('/').encode('utf-8')), headers) | 538 | '@odata.id'].lstrip('/').encode('utf-8')), headers) |
231 | 497 | mac_address = interface_data.get('MACAddress') | 539 | discovered_machine_interface.mac_address = ( |
232 | 498 | if mac_address is not None: | 540 | interface_data['MACAddress']) |
233 | 499 | discovered_machine_interface.mac_address = mac_address | 541 | nic_speed = interface_data['SpeedMbps'] |
230 | 500 | nic_speed = interface_data.get('SpeedMbps') | ||
234 | 501 | if nic_speed is not None: | 542 | if nic_speed is not None: |
235 | 502 | if nic_speed < 1000: | 543 | if nic_speed < 1000: |
236 | 503 | discovered_machine_interface.tags = ["e%s" % nic_speed] | 544 | discovered_machine_interface.tags = ["e%s" % nic_speed] |
238 | 504 | elif nic_speed == "1000": | 545 | elif nic_speed == 1000: |
239 | 505 | discovered_machine_interface.tags = ["1g", "e1000"] | 546 | discovered_machine_interface.tags = ["1g", "e1000"] |
240 | 506 | else: | 547 | else: |
241 | 507 | # We know that the Mbps > 1000 | 548 | # We know that the Mbps > 1000 |
242 | @@ -522,9 +563,8 @@ | |||
243 | 522 | vlan = vlan.lstrip('/').encode('utf-8') | 563 | vlan = vlan.lstrip('/').encode('utf-8') |
244 | 523 | vlan_data, _ = yield self.redfish_request( | 564 | vlan_data, _ = yield self.redfish_request( |
245 | 524 | b"GET", join(url, vlan), headers) | 565 | b"GET", join(url, vlan), headers) |
249 | 525 | vlan_id = vlan_data.get('VLANId') | 566 | discovered_machine_interface.vid = ( |
250 | 526 | if vlan_id is not None: | 567 | vlan_data['VLANId']) |
248 | 527 | discovered_machine_interface.vid = vlan_id | ||
251 | 528 | else: | 568 | else: |
252 | 529 | # If no NeighborPort, this interface is on | 569 | # If no NeighborPort, this interface is on |
253 | 530 | # the management network. | 570 | # the management network. |
254 | @@ -547,7 +587,7 @@ | |||
255 | 547 | return discovered_machine | 587 | return discovered_machine |
256 | 548 | 588 | ||
257 | 549 | @inlineCallbacks | 589 | @inlineCallbacks |
259 | 550 | def get_pod_machines(self, url, headers): | 590 | def get_pod_machines(self, url, headers, logical_drives, targets): |
260 | 551 | """Get pod composed machines. | 591 | """Get pod composed machines. |
261 | 552 | 592 | ||
262 | 553 | If required resources cannot be found, these | 593 | If required resources cannot be found, these |
263 | @@ -560,7 +600,8 @@ | |||
264 | 560 | nodes = yield self.list_resources(nodes_uri, headers) | 600 | nodes = yield self.list_resources(nodes_uri, headers) |
265 | 561 | # Iterate over all composed nodes in the pod. | 601 | # Iterate over all composed nodes in the pod. |
266 | 562 | for node in nodes: | 602 | for node in nodes: |
268 | 563 | discovered_machine = yield self.get_pod_machine(node, url, headers) | 603 | discovered_machine = yield self.get_pod_machine( |
269 | 604 | node, url, headers, logical_drives, targets) | ||
270 | 564 | discovered_machines.append(discovered_machine) | 605 | discovered_machines.append(discovered_machine) |
271 | 565 | return discovered_machines | 606 | return discovered_machines |
272 | 566 | 607 | ||
273 | @@ -573,8 +614,6 @@ | |||
274 | 573 | for cpu_speed in machine.cpu_speeds: | 614 | for cpu_speed in machine.cpu_speeds: |
275 | 574 | if cpu_speed in discovered_pod.cpu_speeds: | 615 | if cpu_speed in discovered_pod.cpu_speeds: |
276 | 575 | discovered_pod.cpu_speeds.remove(cpu_speed) | 616 | discovered_pod.cpu_speeds.remove(cpu_speed) |
277 | 576 | # Delete cpu_speeds place holder. | ||
278 | 577 | del machine.cpu_speeds | ||
279 | 578 | used_cores += machine.cores | 617 | used_cores += machine.cores |
280 | 579 | used_memory += machine.memory | 618 | used_memory += machine.memory |
281 | 580 | for blk_dev in machine.block_devices: | 619 | for blk_dev in machine.block_devices: |
282 | @@ -600,22 +639,50 @@ | |||
283 | 600 | """ | 639 | """ |
284 | 601 | url = self.get_url(context) | 640 | url = self.get_url(context) |
285 | 602 | headers = self.make_auth_headers(**context) | 641 | headers = self.make_auth_headers(**context) |
286 | 642 | logical_drives, targets = yield self.scrape_logical_drives_and_targets( | ||
287 | 643 | url, headers) | ||
288 | 644 | remote_drives = yield self.scrape_remote_drives(url, headers) | ||
289 | 645 | pod_remote_storage, pod_hints_remote_storage = ( | ||
290 | 646 | self.calculate_pod_remote_storage( | ||
291 | 647 | remote_drives, logical_drives, targets)) | ||
292 | 603 | 648 | ||
293 | 604 | # Discover pod resources. | 649 | # Discover pod resources. |
294 | 605 | discovered_pod = yield self.get_pod_resources(url, headers) | 650 | discovered_pod = yield self.get_pod_resources(url, headers) |
295 | 606 | 651 | ||
296 | 652 | # Discover pod remote storage resources. | ||
297 | 653 | discovered_pod.capabilities.append(Capabilities.ISCSI_STORAGE) | ||
298 | 654 | discovered_pod.iscsi_storage = pod_remote_storage | ||
299 | 655 | |||
300 | 607 | # Discover composed machines. | 656 | # Discover composed machines. |
301 | 608 | discovered_pod.machines = yield self.get_pod_machines( | 657 | discovered_pod.machines = yield self.get_pod_machines( |
302 | 609 | url, headers) | 658 | url, headers) |
303 | 610 | 659 | ||
304 | 611 | # Discover pod hints. | 660 | # Discover pod hints. |
305 | 612 | discovered_pod.hints = self.get_pod_hints(discovered_pod) | 661 | discovered_pod.hints = self.get_pod_hints(discovered_pod) |
306 | 662 | discovered_pod.hints.iscsi_storage = pod_hints_remote_storage | ||
307 | 613 | 663 | ||
308 | 614 | # Delete cpu_speeds place holder. | ||
309 | 615 | del discovered_pod.cpu_speeds | ||
310 | 616 | return discovered_pod | 664 | return discovered_pod |
311 | 617 | 665 | ||
313 | 618 | def convert_request_to_json_payload(self, processors, cores, request): | 666 | def select_remote_master(self, remote_storage, size): |
314 | 667 | """Select the remote master drive that has enough space.""" | ||
315 | 668 | for lvg, data in remote_storage.items(): | ||
316 | 669 | if data['master'] and data['available'] >= size: | ||
317 | 670 | data['available'] -= size | ||
318 | 671 | return data['master'] | ||
319 | 672 | |||
320 | 673 | def set_drive_type(self, drive, block_device): | ||
321 | 674 | """Set type of drive requested on `drive` based on the tags on | ||
322 | 675 | `block_device`.""" | ||
323 | 676 | if 'ssd' in block_device.tags: | ||
324 | 677 | drive['Type'] = 'SSD' | ||
325 | 678 | elif 'nvme' in block_device.tags: | ||
326 | 679 | drive['Type'] = 'NVMe' | ||
327 | 680 | elif 'hdd' in block_device.tags: | ||
328 | 681 | drive['Type'] = 'HDD' | ||
329 | 682 | |||
330 | 683 | def convert_request_to_json_payload( | ||
331 | 684 | self, processors, cores, request, | ||
332 | 685 | remote_drives, logical_drives, targets): | ||
333 | 619 | """Convert the RequestedMachine object to JSON.""" | 686 | """Convert the RequestedMachine object to JSON.""" |
334 | 620 | # The below fields are for RSD allocation. | 687 | # The below fields are for RSD allocation. |
335 | 621 | # Most of these fields are nullable and could be used at | 688 | # Most of these fields are nullable and could be used at |
336 | @@ -643,6 +710,14 @@ | |||
337 | 643 | "SerialNumber": None, | 710 | "SerialNumber": None, |
338 | 644 | "Interface": None, | 711 | "Interface": None, |
339 | 645 | } | 712 | } |
340 | 713 | remote_drive = { | ||
341 | 714 | "CapacityGiB": None, | ||
342 | 715 | "iSCSIAddress": None, | ||
343 | 716 | "Master": { | ||
344 | 717 | "Type": "Clone", | ||
345 | 718 | "Resource": None, | ||
346 | 719 | }, | ||
347 | 720 | } | ||
348 | 646 | interface = { | 721 | interface = { |
349 | 647 | "SpeedMbps": None, | 722 | "SpeedMbps": None, |
350 | 648 | "PrimaryVLAN": None, | 723 | "PrimaryVLAN": None, |
351 | @@ -652,41 +727,108 @@ | |||
352 | 652 | "Processors": [], | 727 | "Processors": [], |
353 | 653 | "Memory": [], | 728 | "Memory": [], |
354 | 654 | "LocalDrives": [], | 729 | "LocalDrives": [], |
355 | 730 | "RemoteDrives": [], | ||
356 | 655 | "EthernetInterfaces": [], | 731 | "EthernetInterfaces": [], |
357 | 656 | } | 732 | } |
358 | 657 | request = request.asdict() | ||
359 | 658 | 733 | ||
360 | 659 | # Processors. | 734 | # Processors. |
361 | 660 | for _ in range(processors): | 735 | for _ in range(processors): |
362 | 661 | proc = processor.copy() | 736 | proc = processor.copy() |
363 | 662 | proc['TotalCores'] = cores | 737 | proc['TotalCores'] = cores |
365 | 663 | arch = request.get('architecture') | 738 | arch = request.architecture |
366 | 664 | for key, val in RSD_ARCH.items(): | 739 | for key, val in RSD_ARCH.items(): |
367 | 665 | if val == arch: | 740 | if val == arch: |
368 | 666 | proc['InstructionSet'] = key | 741 | proc['InstructionSet'] = key |
369 | 667 | # cpu_speed is only optional field in request. | 742 | # cpu_speed is only optional field in request. |
371 | 668 | cpu_speed = request.get('cpu_speed') | 743 | cpu_speed = request.cpu_speed |
372 | 669 | if cpu_speed is not None: | 744 | if cpu_speed is not None: |
373 | 670 | proc['AchievableSpeedMHz'] = cpu_speed | 745 | proc['AchievableSpeedMHz'] = cpu_speed |
374 | 671 | data['Processors'].append(proc) | 746 | data['Processors'].append(proc) |
375 | 672 | 747 | ||
376 | 748 | # Determine remote storage information if more than one driver is | ||
377 | 749 | # requested. | ||
378 | 750 | remote_storage = None | ||
379 | 751 | if len(request.block_devices) > 1: | ||
380 | 752 | remote_storage = self.calculate_remote_storage( | ||
381 | 753 | remote_drives, logical_drives, targets) | ||
382 | 754 | |||
383 | 673 | # Block Devices. | 755 | # Block Devices. |
390 | 674 | block_devices = request.get('block_devices') | 756 | # |
391 | 675 | for block_device in block_devices: | 757 | # Tags are matched on the block devices to create different types |
392 | 676 | drive = local_drive.copy() | 758 | # of requested storage for a block device. |
393 | 677 | # Convert from bytes to GiB. | 759 | # local: Locally attached disk (aka. LocalDrive). |
394 | 678 | drive['CapacityGiB'] = block_device['size'] / 1073741824 | 760 | # ssd: Locally attached SSD (aka. LocalDrive). |
395 | 679 | data['LocalDrives'].append(drive) | 761 | # hdd: Locally attached HDD (aka. LocalDrive). |
396 | 762 | # nvme: Locally attached NVMe (aka. LocalDrive). | ||
397 | 763 | # iscsi: Remotely attached disk over ISCSI (aka. RemoteTarget) | ||
398 | 764 | # (none): Remotely attached disk will be picked unless its the | ||
399 | 765 | # first disk. First disk is locally attached. | ||
400 | 766 | block_devices = request.block_devices | ||
401 | 767 | boot_disk = True | ||
402 | 768 | for idx, block_device in enumerate(block_devices): | ||
403 | 769 | if boot_disk: | ||
404 | 770 | if 'iscsi' in block_device.tags: | ||
405 | 771 | raise PodActionError( | ||
406 | 772 | 'iSCSI is not supported as being a boot disk.') | ||
407 | 773 | else: | ||
408 | 774 | # Force 'local' into the tags if not present. | ||
409 | 775 | if 'local' not in block_device.tags: | ||
410 | 776 | block_device.tags.append('local') | ||
411 | 777 | drive = local_drive.copy() | ||
412 | 778 | # Convert from bytes to GiB. | ||
413 | 779 | drive['CapacityGiB'] = block_device.size / (1024 ** 3) | ||
414 | 780 | self.set_drive_type(drive, block_device.tags) | ||
415 | 781 | data['LocalDrives'].append(drive) | ||
416 | 782 | boot_disk = False | ||
417 | 783 | else: | ||
418 | 784 | is_local = max( | ||
419 | 785 | tag in block_device.tags | ||
420 | 786 | for tag in ['local', 'ssd', 'nvme', 'hdd'] | ||
421 | 787 | ) | ||
422 | 788 | if is_local: | ||
423 | 789 | # Force the local tag if it wasn't provided. | ||
424 | 790 | if 'local' not in block_device.tags: | ||
425 | 791 | block_device.tags.append('local') | ||
426 | 792 | drive = local_drive.copy() | ||
427 | 793 | # Convert from bytes to GiB. | ||
428 | 794 | drive['CapacityGiB'] = block_device.size / (1024 ** 3) | ||
429 | 795 | self.set_drive_type(drive, block_device.tags) | ||
430 | 796 | data['LocalDrives'].append(drive) | ||
431 | 797 | else: | ||
432 | 798 | # Force 'iscsi' into the tags if not present. | ||
433 | 799 | if 'iscsi' not in block_device.tags: | ||
434 | 800 | block_device.tags.append('iscsi') | ||
435 | 801 | size = block_device.size / (1024 ** 3) | ||
436 | 802 | # Determine the remote master that can be used. | ||
437 | 803 | remote_master = self.select_remote_master( | ||
438 | 804 | remote_storage, size) | ||
439 | 805 | if remote_master is None: | ||
440 | 806 | raise PodActionError( | ||
441 | 807 | 'iSCSI remote drive cannot be created because ' | ||
442 | 808 | 'not enough space is available.') | ||
443 | 809 | drive = remote_drive.copy() | ||
444 | 810 | # Convert from bytes to GiB. | ||
445 | 811 | drive['CapacityGiB'] = size | ||
446 | 812 | drive['iSCSIAddress'] = 'iqn.2010-08.io.maas:%s-%s' % ( | ||
447 | 813 | request.hostname, idx) | ||
448 | 814 | drive['Master']['Resource'] = { | ||
449 | 815 | '@odata.id': remote_master.decode('utf-8'), | ||
450 | 816 | } | ||
451 | 817 | data['RemoteDrives'].append(drive) | ||
452 | 818 | # Save the iSCSIAddress on the RequestBlockDevice. This is | ||
453 | 819 | # used to map the DiscoveredMachineBlockDevice tags to | ||
454 | 820 | # the same tags used during the request. | ||
455 | 821 | block_device.iscsi_target = drive['iSCSIAddress'] | ||
456 | 680 | 822 | ||
457 | 681 | # Interfaces. | 823 | # Interfaces. |
459 | 682 | interfaces = request.get('interfaces') | 824 | interfaces = request.interfaces |
460 | 683 | for iface in interfaces: | 825 | for iface in interfaces: |
461 | 684 | nic = interface.copy() | 826 | nic = interface.copy() |
462 | 685 | data['EthernetInterfaces'].append(nic) | 827 | data['EthernetInterfaces'].append(nic) |
463 | 686 | 828 | ||
464 | 687 | # Memory. | 829 | # Memory. |
465 | 688 | mem = memory.copy() | 830 | mem = memory.copy() |
467 | 689 | mem['CapacityMiB'] = request.get('memory') | 831 | mem['CapacityMiB'] = request.memory |
468 | 690 | data['Memory'].append(mem) | 832 | data['Memory'].append(mem) |
469 | 691 | 833 | ||
470 | 692 | return json.dumps(data).encode('utf-8') | 834 | return json.dumps(data).encode('utf-8') |
471 | @@ -697,6 +839,9 @@ | |||
472 | 697 | url = self.get_url(context) | 839 | url = self.get_url(context) |
473 | 698 | headers = self.make_auth_headers(**context) | 840 | headers = self.make_auth_headers(**context) |
474 | 699 | endpoint = b"redfish/v1/Nodes/Actions/Allocate" | 841 | endpoint = b"redfish/v1/Nodes/Actions/Allocate" |
475 | 842 | logical_drives, targets = ( | ||
476 | 843 | yield self.scrape_logical_drives_and_targets(url, headers)) | ||
477 | 844 | remote_drives = yield self.scrape_remote_drives(url, headers) | ||
478 | 700 | # Create allocate payload. | 845 | # Create allocate payload. |
479 | 701 | requested_cores = request.cores | 846 | requested_cores = request.cores |
480 | 702 | if requested_cores % 2 != 0: | 847 | if requested_cores % 2 != 0: |
481 | @@ -709,9 +854,11 @@ | |||
482 | 709 | # Find the correct procesors and cores combination from RSD POD. | 854 | # Find the correct procesors and cores combination from RSD POD. |
483 | 710 | processors = 1 | 855 | processors = 1 |
484 | 711 | cores = requested_cores | 856 | cores = requested_cores |
485 | 857 | response_headers = None | ||
486 | 712 | while True: | 858 | while True: |
487 | 713 | payload = self.convert_request_to_json_payload( | 859 | payload = self.convert_request_to_json_payload( |
489 | 714 | processors, cores, request) | 860 | processors, cores, request, remote_drives, |
490 | 861 | logical_drives, targets) | ||
491 | 715 | try: | 862 | try: |
492 | 716 | _, response_headers = yield self.redfish_request( | 863 | _, response_headers = yield self.redfish_request( |
493 | 717 | b"POST", join(url, endpoint), headers, | 864 | b"POST", join(url, endpoint), headers, |
494 | @@ -732,14 +879,16 @@ | |||
495 | 732 | node_id = location[0].rsplit('/', 1)[-1] | 879 | node_id = location[0].rsplit('/', 1)[-1] |
496 | 733 | node_path = location[0].split('/', 3)[-1] | 880 | node_path = location[0].split('/', 3)[-1] |
497 | 734 | 881 | ||
498 | 735 | # Retrieve new node. | ||
499 | 736 | discovered_machine = yield self.get_pod_machine( | ||
500 | 737 | node_path.encode('utf-8'), url, headers) | ||
501 | 738 | # Assemble the node. | 882 | # Assemble the node. |
502 | 739 | yield self.assemble_node(url, node_id.encode('utf-8'), headers) | 883 | yield self.assemble_node(url, node_id.encode('utf-8'), headers) |
503 | 740 | # Set to PXE boot. | 884 | # Set to PXE boot. |
504 | 741 | yield self.set_pxe_boot(url, node_id.encode('utf-8'), headers) | 885 | yield self.set_pxe_boot(url, node_id.encode('utf-8'), headers) |
505 | 742 | 886 | ||
506 | 887 | # Retrieve new node. | ||
507 | 888 | discovered_machine = yield self.get_pod_machine( | ||
508 | 889 | node_path.encode('utf-8'), url, | ||
509 | 890 | headers, logical_drives, targets) | ||
510 | 891 | |||
511 | 743 | # Retrieve pod resources. | 892 | # Retrieve pod resources. |
512 | 744 | discovered_pod = yield self.get_pod_resources(url, headers) | 893 | discovered_pod = yield self.get_pod_resources(url, headers) |
513 | 745 | # Retrive pod hints. | 894 | # Retrive pod hints. |
514 | @@ -830,8 +979,11 @@ | |||
515 | 830 | node_state = yield self.get_composed_node_state( | 979 | node_state = yield self.get_composed_node_state( |
516 | 831 | url, node_id, headers) | 980 | url, node_id, headers) |
517 | 832 | while node_state == 'Assembling': | 981 | while node_state == 'Assembling': |
518 | 982 | # Wait 2 seconds before getting updated state. | ||
519 | 983 | yield pause(2) | ||
520 | 833 | node_state = yield self.get_composed_node_state( | 984 | node_state = yield self.get_composed_node_state( |
521 | 834 | url, node_id, headers) | 985 | url, node_id, headers) |
522 | 986 | |||
523 | 835 | # Check one last time if the state has became `Failed`. | 987 | # Check one last time if the state has became `Failed`. |
524 | 836 | if node_state == 'Failed': | 988 | if node_state == 'Failed': |
525 | 837 | # Broken system. | 989 | # Broken system. |
526 | 838 | 990 | ||
527 | === modified file 'src/provisioningserver/drivers/pod/tests/test_rsd.py' | |||
528 | --- src/provisioningserver/drivers/pod/tests/test_rsd.py 2017-04-06 19:26:13 +0000 | |||
529 | +++ src/provisioningserver/drivers/pod/tests/test_rsd.py 2017-04-10 13:49:22 +0000 | |||
530 | @@ -28,6 +28,7 @@ | |||
531 | 28 | MAASTwistedRunTest, | 28 | MAASTwistedRunTest, |
532 | 29 | ) | 29 | ) |
533 | 30 | from provisioningserver.drivers.pod import ( | 30 | from provisioningserver.drivers.pod import ( |
534 | 31 | BlockDeviceType, | ||
535 | 31 | Capabilities, | 32 | Capabilities, |
536 | 32 | DiscoveredMachine, | 33 | DiscoveredMachine, |
537 | 33 | DiscoveredMachineBlockDevice, | 34 | DiscoveredMachineBlockDevice, |
538 | @@ -50,6 +51,7 @@ | |||
539 | 50 | from testtools import ExpectedException | 51 | from testtools import ExpectedException |
540 | 51 | from testtools.matchers import ( | 52 | from testtools.matchers import ( |
541 | 52 | Equals, | 53 | Equals, |
542 | 54 | Is, | ||
543 | 53 | MatchesDict, | 55 | MatchesDict, |
544 | 54 | MatchesListwise, | 56 | MatchesListwise, |
545 | 55 | MatchesStructure, | 57 | MatchesStructure, |
546 | @@ -279,6 +281,8 @@ | |||
547 | 279 | }], | 281 | }], |
548 | 280 | "RemoteDrives": [{ | 282 | "RemoteDrives": [{ |
549 | 281 | "@odata.id": "/redfish/v1/Services/1/Targets/1" | 283 | "@odata.id": "/redfish/v1/Services/1/Targets/1" |
550 | 284 | }, { | ||
551 | 285 | "@odata.id": "/redfish/v1/Services/1/Targets/2" | ||
552 | 282 | }], | 286 | }], |
553 | 283 | "ManagedBy": [{ | 287 | "ManagedBy": [{ |
554 | 284 | "@odata.id": "/redfish/v1/Managers/1" | 288 | "@odata.id": "/redfish/v1/Managers/1" |
555 | @@ -600,6 +604,8 @@ | |||
556 | 600 | }], | 604 | }], |
557 | 601 | "Targets": [{ | 605 | "Targets": [{ |
558 | 602 | "@odata.id": "/redfish/v1/Services/1/Targets/1" | 606 | "@odata.id": "/redfish/v1/Services/1/Targets/1" |
559 | 607 | }, { | ||
560 | 608 | "@odata.id": "/redfish/v1/Services/1/Targets/2" | ||
561 | 603 | }] | 609 | }] |
562 | 604 | } | 610 | } |
563 | 605 | } | 611 | } |
564 | @@ -958,14 +964,12 @@ | |||
565 | 958 | mock_redfish_request.return_value = (SAMPLE_JSON_NODE, None) | 964 | mock_redfish_request.return_value = (SAMPLE_JSON_NODE, None) |
566 | 959 | 965 | ||
567 | 960 | remote_drives = yield driver.scrape_remote_drives(url, headers) | 966 | remote_drives = yield driver.scrape_remote_drives(url, headers) |
569 | 961 | self.assertEquals({'/redfish/v1/Services/1/Targets/1'}, remote_drives) | 967 | self.assertEquals({ |
570 | 968 | '/redfish/v1/Services/1/Targets/1', | ||
571 | 969 | '/redfish/v1/Services/1/Targets/2'}, remote_drives) | ||
572 | 962 | 970 | ||
573 | 963 | @inlineCallbacks | ||
574 | 964 | def test__calculate_remote_storage(self): | 971 | def test__calculate_remote_storage(self): |
575 | 965 | driver = RSDPodDriver() | 972 | driver = RSDPodDriver() |
576 | 966 | context = make_context() | ||
577 | 967 | url = driver.get_url(context) | ||
578 | 968 | headers = driver.make_auth_headers(**context) | ||
579 | 969 | LV_NO_TARGETS = deepcopy(SAMPLE_JSON_LV) | 973 | LV_NO_TARGETS = deepcopy(SAMPLE_JSON_LV) |
580 | 970 | LV_NO_TARGETS['Links']['Targets'] = [] | 974 | LV_NO_TARGETS['Links']['Targets'] = [] |
581 | 971 | logical_drives = { | 975 | logical_drives = { |
582 | @@ -975,31 +979,64 @@ | |||
583 | 975 | b"redfish/v1/Services/1/LogicalDrives/4": SAMPLE_JSON_PV, | 979 | b"redfish/v1/Services/1/LogicalDrives/4": SAMPLE_JSON_PV, |
584 | 976 | b"redfish/v1/Services/1/LogicalDrives/5": SAMPLE_JSON_PV, | 980 | b"redfish/v1/Services/1/LogicalDrives/5": SAMPLE_JSON_PV, |
585 | 977 | } | 981 | } |
587 | 978 | target_links = { | 982 | targets = { |
588 | 979 | b"redfish/v1/Services/1/Targets/1": SAMPLE_JSON_TARGET, | 983 | b"redfish/v1/Services/1/Targets/1": SAMPLE_JSON_TARGET, |
589 | 980 | b"redfish/v1/Services/1/Targets/2": SAMPLE_JSON_TARGET, | 984 | b"redfish/v1/Services/1/Targets/2": SAMPLE_JSON_TARGET, |
590 | 981 | b"redfish/v1/Services/1/Targets/3": SAMPLE_JSON_TARGET, | 985 | b"redfish/v1/Services/1/Targets/3": SAMPLE_JSON_TARGET, |
591 | 982 | } | 986 | } |
592 | 983 | remote_drives = set( | 987 | remote_drives = set( |
593 | 984 | "redfish/v1/Services/1/Targets/1") | 988 | "redfish/v1/Services/1/Targets/1") |
594 | 985 | mock_scrape_logical_drives_and_targets = self.patch( | ||
595 | 986 | driver, 'scrape_logical_drives_and_targets') | ||
596 | 987 | mock_scrape_logical_drives_and_targets.return_value = ( | ||
597 | 988 | logical_drives, target_links) | ||
598 | 989 | mock_scrape_remote_drives = self.patch(driver, 'scrape_remote_drives') | ||
599 | 990 | mock_scrape_remote_drives.return_value = remote_drives | ||
600 | 991 | 989 | ||
602 | 992 | remote_storage = yield driver.calculate_remote_storage(url, headers) | 990 | remote_storage = driver.calculate_remote_storage( |
603 | 991 | remote_drives, logical_drives, targets) | ||
604 | 993 | self.assertDictEqual( | 992 | self.assertDictEqual( |
605 | 994 | remote_storage, | 993 | remote_storage, |
606 | 995 | { | 994 | { |
607 | 996 | b'redfish/v1/Services/1/LogicalDrives/2': { | 995 | b'redfish/v1/Services/1/LogicalDrives/2': { |
608 | 997 | 'total': 11830638411776.0, | 996 | 'total': 11830638411776.0, |
609 | 998 | 'available': 11830638411776.0, | 997 | 'available': 11830638411776.0, |
611 | 999 | 'master': b'redfish/v1/Services/1/LogicalDrives/1' | 998 | 'master': { |
612 | 999 | 'path': b'/redfish/v1/Services/1/LogicalDrives/1', | ||
613 | 1000 | 'size': 80 | ||
614 | 1001 | } | ||
615 | 1000 | } | 1002 | } |
616 | 1001 | }) | 1003 | }) |
617 | 1002 | 1004 | ||
618 | 1005 | def test__calculate_pod_remote_storage(self): | ||
619 | 1006 | driver = RSDPodDriver() | ||
620 | 1007 | logical_drives = { | ||
621 | 1008 | b"redfish/v1/Services/1/LogicalDrives/1": SAMPLE_JSON_LV, | ||
622 | 1009 | b"redfish/v1/Services/1/LogicalDrives/2": SAMPLE_JSON_LVG, | ||
623 | 1010 | b"redfish/v1/Services/1/LogicalDrives/3": SAMPLE_JSON_LV | ||
624 | 1011 | } | ||
625 | 1012 | remote_storage = { | ||
626 | 1013 | b"redfish/v1/Services/1/LogicalDrives/1": { | ||
627 | 1014 | 'total': 80 * (1024 ** 3), | ||
628 | 1015 | 'available': None, | ||
629 | 1016 | 'master': { | ||
630 | 1017 | 'path': None, | ||
631 | 1018 | 'size': None | ||
632 | 1019 | } | ||
633 | 1020 | }, | ||
634 | 1021 | b"redfish/v1/Services/1/LogicalDrives/2": { | ||
635 | 1022 | 'total': 12 * (1024 ** 3), | ||
636 | 1023 | 'available': None, | ||
637 | 1024 | 'master': { | ||
638 | 1025 | 'path': None, | ||
639 | 1026 | 'size': None | ||
640 | 1027 | } | ||
641 | 1028 | } | ||
642 | 1029 | } | ||
643 | 1030 | mock_calculate_remote_storage = self.patch( | ||
644 | 1031 | driver, 'calculate_remote_storage') | ||
645 | 1032 | mock_calculate_remote_storage.return_value = remote_storage | ||
646 | 1033 | |||
647 | 1034 | pod_capacity, pod_hints_capacity = driver.calculate_pod_remote_storage( | ||
648 | 1035 | factory.make_name('remote_drives'), | ||
649 | 1036 | logical_drives, factory.make_name('targets')) | ||
650 | 1037 | self.assertEquals(92 * (1024 ** 3), pod_capacity) | ||
651 | 1038 | self.assertEquals(80 * (1024 ** 3), pod_hints_capacity) | ||
652 | 1039 | |||
653 | 1003 | @inlineCallbacks | 1040 | @inlineCallbacks |
654 | 1004 | def test__get_pod_memory_resources(self): | 1041 | def test__get_pod_memory_resources(self): |
655 | 1005 | driver = RSDPodDriver() | 1042 | driver = RSDPodDriver() |
656 | @@ -1147,10 +1184,6 @@ | |||
657 | 1147 | url = driver.get_url(context) | 1184 | url = driver.get_url(context) |
658 | 1148 | headers = driver.make_auth_headers(**context) | 1185 | headers = driver.make_auth_headers(**context) |
659 | 1149 | mock_redfish_request = self.patch(driver, 'redfish_request') | 1186 | mock_redfish_request = self.patch(driver, 'redfish_request') |
660 | 1150 | NO_MEMORY = deepcopy(SAMPLE_JSON_MEMORY) | ||
661 | 1151 | NO_MEMORY['CapacityMiB'] = None | ||
662 | 1152 | NO_THREADS = deepcopy(SAMPLE_JSON_PROCESSOR) | ||
663 | 1153 | NO_THREADS['TotalThreads'] = None | ||
664 | 1154 | NIC1_DATA = deepcopy(SAMPLE_JSON_INTERFACE) | 1187 | NIC1_DATA = deepcopy(SAMPLE_JSON_INTERFACE) |
665 | 1155 | NIC1_DATA['SpeedMbps'] = 900 | 1188 | NIC1_DATA['SpeedMbps'] = 900 |
666 | 1156 | NIC2_DATA = deepcopy(SAMPLE_JSON_INTERFACE) | 1189 | NIC2_DATA = deepcopy(SAMPLE_JSON_INTERFACE) |
667 | @@ -1162,10 +1195,10 @@ | |||
668 | 1162 | mock_redfish_request.side_effect = [ | 1195 | mock_redfish_request.side_effect = [ |
669 | 1163 | (SAMPLE_JSON_NODE, None), | 1196 | (SAMPLE_JSON_NODE, None), |
670 | 1164 | (SAMPLE_JSON_MEMORY, None), | 1197 | (SAMPLE_JSON_MEMORY, None), |
675 | 1165 | (NO_MEMORY, None), | 1198 | (SAMPLE_JSON_MEMORY, None), |
676 | 1166 | (SAMPLE_JSON_MEMORY, None), | 1199 | (SAMPLE_JSON_MEMORY, None), |
677 | 1167 | (SAMPLE_JSON_MEMORY, None), | 1200 | (SAMPLE_JSON_MEMORY, None), |
678 | 1168 | (NO_THREADS, None), | 1201 | (SAMPLE_JSON_PROCESSOR, None), |
679 | 1169 | (SAMPLE_JSON_PROCESSOR, None), | 1202 | (SAMPLE_JSON_PROCESSOR, None), |
680 | 1170 | (SAMPLE_JSON_DEVICE, None), | 1203 | (SAMPLE_JSON_DEVICE, None), |
681 | 1171 | (SAMPLE_JSON_DEVICE, None), | 1204 | (SAMPLE_JSON_DEVICE, None), |
682 | @@ -1182,46 +1215,98 @@ | |||
683 | 1182 | (SAMPLE_JSON_PORT, None), | 1215 | (SAMPLE_JSON_PORT, None), |
684 | 1183 | (SAMPLE_JSON_VLAN, None), | 1216 | (SAMPLE_JSON_VLAN, None), |
685 | 1184 | ] | 1217 | ] |
686 | 1218 | LV_NO_TARGETS = deepcopy(SAMPLE_JSON_LV) | ||
687 | 1219 | LV_NO_TARGETS['Links']['Targets'] = [] | ||
688 | 1220 | TARGET_LUN = deepcopy(SAMPLE_JSON_TARGET) | ||
689 | 1221 | TARGET_LUN['Addresses'][0]['iSCSI']['TargetLUN'].append({'LUN': 3}) | ||
690 | 1222 | logical_drives = { | ||
691 | 1223 | b"redfish/v1/Services/1/LogicalDrives/1": SAMPLE_JSON_LV, | ||
692 | 1224 | b"redfish/v1/Services/1/LogicalDrives/2": SAMPLE_JSON_LVG, | ||
693 | 1225 | b"redfish/v1/Services/1/LogicalDrives/3": LV_NO_TARGETS, | ||
694 | 1226 | b"redfish/v1/Services/1/LogicalDrives/4": SAMPLE_JSON_PV, | ||
695 | 1227 | b"redfish/v1/Services/1/LogicalDrives/5": SAMPLE_JSON_PV, | ||
696 | 1228 | } | ||
697 | 1229 | targets = { | ||
698 | 1230 | b"redfish/v1/Services/1/Targets/1": SAMPLE_JSON_TARGET, | ||
699 | 1231 | b"redfish/v1/Services/1/Targets/2": TARGET_LUN, | ||
700 | 1232 | b"redfish/v1/Services/1/Targets/3": SAMPLE_JSON_TARGET, | ||
701 | 1233 | b"redfish/v1/Services/1/Targets/4": SAMPLE_JSON_TARGET, | ||
702 | 1234 | } | ||
703 | 1185 | 1235 | ||
704 | 1186 | machine = yield driver.get_pod_machine( | 1236 | machine = yield driver.get_pod_machine( |
743 | 1187 | b"redfish/v1/Nodes/1", url, headers) | 1237 | b"redfish/v1/Nodes/1", url, headers, logical_drives, targets) |
744 | 1188 | self.assertEquals("amd64/generic", machine.architecture) | 1238 | self.assertThat(machine, MatchesStructure( |
745 | 1189 | self.assertEquals(28, machine.cores) | 1239 | architecture=Equals("amd64/generic"), |
746 | 1190 | self.assertEquals(2300, machine.cpu_speed) | 1240 | cores=Equals(56), |
747 | 1191 | self.assertEquals(23436, machine.memory) | 1241 | cpu_speed=Equals(2300), |
748 | 1192 | self.assertEquals( | 1242 | memory=Equals(31248), |
749 | 1193 | "INTEL_SSDMCEAC120B3", machine.block_devices[0].model) | 1243 | power_state=Equals("off"), |
750 | 1194 | self.assertEquals("CVLI310601PY120E", machine.block_devices[0].serial) | 1244 | power_parameters=MatchesDict({'node_id': Equals('1')}), |
751 | 1195 | self.assertEquals(119999999999.99997, machine.block_devices[0].size) | 1245 | interfaces=MatchesListwise([ |
752 | 1196 | self.assertEquals(['ssd'], machine.block_devices[0].tags) | 1246 | MatchesStructure( |
753 | 1197 | self.assertEquals("off", machine.power_state) | 1247 | mac_address=Equals('54:ab:3a:36:af:45'), |
754 | 1198 | self.assertEquals({'node_id': '1'}, machine.power_parameters) | 1248 | vid=Equals(4088), |
755 | 1199 | self.assertThat(machine.interfaces, MatchesListwise([ | 1249 | tags=Equals(['e900']), |
756 | 1200 | MatchesStructure( | 1250 | boot=Equals(False), |
757 | 1201 | mac_address=Equals('54:ab:3a:36:af:45'), | 1251 | ), |
758 | 1202 | vid=Equals(4088), | 1252 | MatchesStructure( |
759 | 1203 | tags=Equals(['e900']), | 1253 | mac_address=Equals('54:ab:3a:36:af:45'), |
760 | 1204 | boot=Equals(False), | 1254 | vid=Equals(4088), |
761 | 1205 | ), | 1255 | tags=Equals(['1g', 'e1000']), |
762 | 1206 | MatchesStructure( | 1256 | boot=Equals(False), |
763 | 1207 | mac_address=Equals('54:ab:3a:36:af:45'), | 1257 | ), |
764 | 1208 | vid=Equals(4088), | 1258 | MatchesStructure( |
765 | 1209 | tags=Equals(['1.0']), | 1259 | mac_address=Equals('54:ab:3a:36:af:45'), |
766 | 1210 | boot=Equals(False), | 1260 | vid=Equals(4088), |
767 | 1211 | ), | 1261 | tags=Equals(['2.0']), |
768 | 1212 | MatchesStructure( | 1262 | boot=Equals(False), |
769 | 1213 | mac_address=Equals('54:ab:3a:36:af:45'), | 1263 | ), |
770 | 1214 | vid=Equals(4088), | 1264 | MatchesStructure( |
771 | 1215 | tags=Equals(['2.0']), | 1265 | mac_address=Equals('54:ab:3a:36:af:45'), |
772 | 1216 | boot=Equals(False), | 1266 | vid=Equals(-1), |
773 | 1217 | ), | 1267 | tags=Equals([]), |
774 | 1218 | MatchesStructure( | 1268 | boot=Equals(True), |
775 | 1219 | mac_address=Equals('54:ab:3a:36:af:45'), | 1269 | ), |
776 | 1220 | vid=Equals(-1), | 1270 | ]), |
777 | 1221 | tags=Equals([]), | 1271 | block_devices=MatchesListwise([ |
778 | 1222 | boot=Equals(True), | 1272 | MatchesStructure( |
779 | 1223 | ), | 1273 | model=Equals('INTEL_SSDMCEAC120B3'), |
780 | 1224 | ])) | 1274 | serial=Equals('CVLI310601PY120E'), |
781 | 1275 | size=Equals(119999999999.99997), | ||
782 | 1276 | block_size=Equals(512), | ||
783 | 1277 | tags=Equals(['ssd']), | ||
784 | 1278 | type=Equals(BlockDeviceType.PHYSICAL), | ||
785 | 1279 | ), | ||
786 | 1280 | MatchesStructure( | ||
787 | 1281 | model=Equals('INTEL_SSDMCEAC120B3'), | ||
788 | 1282 | serial=Equals('CVLI310601PY120E'), | ||
789 | 1283 | size=Equals(119999999999.99997), | ||
790 | 1284 | block_size=Equals(512), | ||
791 | 1285 | tags=Equals(['ssd']), | ||
792 | 1286 | type=Equals(BlockDeviceType.PHYSICAL), | ||
793 | 1287 | ), | ||
794 | 1288 | MatchesStructure( | ||
795 | 1289 | model=Is(None), | ||
796 | 1290 | serial=Is(None), | ||
797 | 1291 | size=Equals(85899345920.0), | ||
798 | 1292 | block_size=Equals(512), | ||
799 | 1293 | tags=Equals(['iscsi']), | ||
800 | 1294 | type=Equals(BlockDeviceType.ISCSI), | ||
801 | 1295 | iscsi_target=Equals( | ||
802 | 1296 | '10.1.0.100:6:3260:0:iqn.maas.io:test'), | ||
803 | 1297 | ), | ||
804 | 1298 | MatchesStructure( | ||
805 | 1299 | model=Is(None), | ||
806 | 1300 | serial=Is(None), | ||
807 | 1301 | size=Equals(85899345920.0), | ||
808 | 1302 | block_size=Equals(512), | ||
809 | 1303 | tags=Equals(['iscsi']), | ||
810 | 1304 | type=Equals(BlockDeviceType.ISCSI), | ||
811 | 1305 | iscsi_target=Equals( | ||
812 | 1306 | '10.1.0.100:6:3260:3:iqn.maas.io:test'), | ||
813 | 1307 | ) | ||
814 | 1308 | ]) | ||
815 | 1309 | )) | ||
816 | 1225 | 1310 | ||
817 | 1226 | @inlineCallbacks | 1311 | @inlineCallbacks |
818 | 1227 | def test__get_pod_machines(self): | 1312 | def test__get_pod_machines(self): |
819 | @@ -1229,6 +1314,14 @@ | |||
820 | 1229 | context = make_context() | 1314 | context = make_context() |
821 | 1230 | url = driver.get_url(context) | 1315 | url = driver.get_url(context) |
822 | 1231 | headers = driver.make_auth_headers(**context) | 1316 | headers = driver.make_auth_headers(**context) |
823 | 1317 | logical_drives = { | ||
824 | 1318 | factory.make_name('lv_path'): factory.make_name('lv_data') | ||
825 | 1319 | for _ in range(3) | ||
826 | 1320 | } | ||
827 | 1321 | targets = { | ||
828 | 1322 | factory.make_name('target_path'): factory.make_name('target_data') | ||
829 | 1323 | for _ in range(3) | ||
830 | 1324 | } | ||
831 | 1232 | mock_list_resources = self.patch(driver, 'list_resources') | 1325 | mock_list_resources = self.patch(driver, 'list_resources') |
832 | 1233 | mock_list_resources.side_effect = [ | 1326 | mock_list_resources.side_effect = [ |
833 | 1234 | [b"redfish/v1/Nodes/1"], | 1327 | [b"redfish/v1/Nodes/1"], |
834 | @@ -1242,10 +1335,11 @@ | |||
835 | 1242 | mock_get_pod_machine = self.patch(driver, 'get_pod_machine') | 1335 | mock_get_pod_machine = self.patch(driver, 'get_pod_machine') |
836 | 1243 | mock_get_pod_machine.return_value = expected_machines | 1336 | mock_get_pod_machine.return_value = expected_machines |
837 | 1244 | 1337 | ||
839 | 1245 | discovered_machines = yield driver.get_pod_machines(url, headers) | 1338 | discovered_machines = yield driver.get_pod_machines( |
840 | 1339 | url, headers, logical_drives, targets) | ||
841 | 1246 | self.assertEquals(1, len(discovered_machines)) | 1340 | self.assertEquals(1, len(discovered_machines)) |
842 | 1247 | self.assertThat(mock_get_pod_machine, MockCalledOnceWith( | 1341 | self.assertThat(mock_get_pod_machine, MockCalledOnceWith( |
844 | 1248 | b"redfish/v1/Nodes/1", url, headers)) | 1342 | b"redfish/v1/Nodes/1", url, headers, logical_drives, targets)) |
845 | 1249 | 1343 | ||
846 | 1250 | def test__get_pod_hints(self): | 1344 | def test__get_pod_hints(self): |
847 | 1251 | driver = RSDPodDriver() | 1345 | driver = RSDPodDriver() |
848 | @@ -1283,12 +1377,41 @@ | |||
849 | 1283 | context = make_context() | 1377 | context = make_context() |
850 | 1284 | headers = driver.make_auth_headers(**context) | 1378 | headers = driver.make_auth_headers(**context) |
851 | 1285 | url = driver.get_url(context) | 1379 | url = driver.get_url(context) |
852 | 1380 | remote_drives = factory.make_name('remote_drive') | ||
853 | 1381 | logical_drives = factory.make_name('logical_drives') | ||
854 | 1382 | targets = factory.make_name('targets') | ||
855 | 1383 | pod_iscsi_capacity = random.randint( | ||
856 | 1384 | 10 * 1024 ** 3, 20 * 1024 ** 3) | ||
857 | 1385 | pod_hints_iscsi_capacity = random.randint( | ||
858 | 1386 | 10 * 1024 ** 3, 20 * 1024 ** 3) | ||
859 | 1387 | mock_scrape_logical_drives_and_targets = self.patch( | ||
860 | 1388 | driver, 'scrape_logical_drives_and_targets') | ||
861 | 1389 | mock_scrape_logical_drives_and_targets.return_value = ( | ||
862 | 1390 | logical_drives, targets) | ||
863 | 1391 | mock_scrape_remote_drives = self.patch(driver, 'scrape_remote_drives') | ||
864 | 1392 | mock_scrape_remote_drives.return_value = remote_drives | ||
865 | 1393 | mock_calculate_pod_remote_storage = self.patch( | ||
866 | 1394 | driver, 'calculate_pod_remote_storage') | ||
867 | 1395 | mock_calculate_pod_remote_storage.return_value = ( | ||
868 | 1396 | pod_iscsi_capacity, pod_hints_iscsi_capacity) | ||
869 | 1286 | mock_get_pod_resources = self.patch( | 1397 | mock_get_pod_resources = self.patch( |
870 | 1287 | driver, 'get_pod_resources') | 1398 | driver, 'get_pod_resources') |
871 | 1288 | mock_get_pod_machines = self.patch(driver, 'get_pod_machines') | 1399 | mock_get_pod_machines = self.patch(driver, 'get_pod_machines') |
872 | 1289 | mock_get_pod_hints = self.patch(driver, 'get_pod_hints') | 1400 | mock_get_pod_hints = self.patch(driver, 'get_pod_hints') |
873 | 1290 | 1401 | ||
875 | 1291 | yield driver.discover(factory.make_name('system_id'), context) | 1402 | discovered_pod = yield driver.discover( |
876 | 1403 | factory.make_name('system_id'), context) | ||
877 | 1404 | self.assertEquals(pod_iscsi_capacity, discovered_pod.iscsi_storage) | ||
878 | 1405 | self.assertEquals( | ||
879 | 1406 | pod_hints_iscsi_capacity, discovered_pod.hints.iscsi_storage) | ||
880 | 1407 | self.assertThat( | ||
881 | 1408 | mock_scrape_logical_drives_and_targets, MockCalledOnceWith( | ||
882 | 1409 | url, headers)) | ||
883 | 1410 | self.assertThat( | ||
884 | 1411 | mock_scrape_remote_drives, MockCalledOnceWith(url, headers)) | ||
885 | 1412 | self.assertThat( | ||
886 | 1413 | mock_calculate_pod_remote_storage, MockCalledOnceWith( | ||
887 | 1414 | remote_drives, logical_drives, targets)) | ||
888 | 1292 | self.assertThat( | 1415 | self.assertThat( |
889 | 1293 | mock_get_pod_resources, MockCalledOnceWith(url, headers)) | 1416 | mock_get_pod_resources, MockCalledOnceWith(url, headers)) |
890 | 1294 | self.assertThat( | 1417 | self.assertThat( |
891 | @@ -1302,7 +1425,7 @@ | |||
892 | 1302 | processors = 2 | 1425 | processors = 2 |
893 | 1303 | cores = request.cores / 2 | 1426 | cores = request.cores / 2 |
894 | 1304 | payload = driver.convert_request_to_json_payload( | 1427 | payload = driver.convert_request_to_json_payload( |
896 | 1305 | processors, cores, request) | 1428 | processors, cores, request, None, None, None) |
897 | 1306 | self.assertThat( | 1429 | self.assertThat( |
898 | 1307 | json.loads(payload.decode('utf-8')), | 1430 | json.loads(payload.decode('utf-8')), |
899 | 1308 | MatchesDict({ | 1431 | MatchesDict({ |
900 | @@ -1383,6 +1506,25 @@ | |||
901 | 1383 | request = make_requested_machine(cores=64) | 1506 | request = make_requested_machine(cores=64) |
902 | 1384 | discovered_pod = make_discovered_pod() | 1507 | discovered_pod = make_discovered_pod() |
903 | 1385 | new_machine = make_discovered_machine() | 1508 | new_machine = make_discovered_machine() |
904 | 1509 | logical_drives = { | ||
905 | 1510 | factory.make_name('lv_path'): factory.make_name('lv_data') | ||
906 | 1511 | for _ in range(3) | ||
907 | 1512 | } | ||
908 | 1513 | targets = { | ||
909 | 1514 | factory.make_name('target_path'): factory.make_name('target_data') | ||
910 | 1515 | for _ in range(3) | ||
911 | 1516 | } | ||
912 | 1517 | remote_drives = set([ | ||
913 | 1518 | factory.make_name('target_path') | ||
914 | 1519 | for _ in range(3) | ||
915 | 1520 | ]) | ||
916 | 1521 | mock_scrape_logical_drives_and_targets = self.patch( | ||
917 | 1522 | driver, 'scrape_logical_drives_and_targets') | ||
918 | 1523 | mock_scrape_logical_drives_and_targets.return_value = ( | ||
919 | 1524 | logical_drives, targets) | ||
920 | 1525 | mock_scrape_remote_drives = self.patch( | ||
921 | 1526 | driver, 'scrape_remote_drives') | ||
922 | 1527 | mock_scrape_remote_drives.return_value = remote_drives | ||
923 | 1386 | mock_get_pod_machine = self.patch(driver, 'get_pod_machine') | 1528 | mock_get_pod_machine = self.patch(driver, 'get_pod_machine') |
924 | 1387 | mock_get_pod_machine.return_value = new_machine | 1529 | mock_get_pod_machine.return_value = new_machine |
925 | 1388 | mock_convert_request_to_json_payload = self.patch( | 1530 | mock_convert_request_to_json_payload = self.patch( |
926 | @@ -1408,8 +1550,10 @@ | |||
927 | 1408 | factory.make_name('system_id'), context, request) | 1550 | factory.make_name('system_id'), context, request) |
928 | 1409 | self.assertThat( | 1551 | self.assertThat( |
929 | 1410 | mock_convert_request_to_json_payload, MockCallsMatch( | 1552 | mock_convert_request_to_json_payload, MockCallsMatch( |
932 | 1411 | call(1, 32, request), call(2, 16, request), | 1553 | call(1, 32, request, remote_drives, logical_drives, targets), |
933 | 1412 | call(4, 8, request), call(8, 4, request))) | 1554 | call(2, 16, request, remote_drives, logical_drives, targets), |
934 | 1555 | call(4, 8, request, remote_drives, logical_drives, targets), | ||
935 | 1556 | call(8, 4, request, remote_drives, logical_drives, targets))) | ||
936 | 1413 | self.assertThat(mock_assemble_node, MockCalledOnceWith( | 1557 | self.assertThat(mock_assemble_node, MockCalledOnceWith( |
937 | 1414 | url, new_machine.power_parameters.get( | 1558 | url, new_machine.power_parameters.get( |
938 | 1415 | 'node_id').encode('utf-8'), headers)) | 1559 | 'node_id').encode('utf-8'), headers)) |
939 | @@ -1427,6 +1571,12 @@ | |||
940 | 1427 | discovered_pod = make_discovered_pod() | 1571 | discovered_pod = make_discovered_pod() |
941 | 1428 | new_machines = deepcopy(discovered_pod.machines) | 1572 | new_machines = deepcopy(discovered_pod.machines) |
942 | 1429 | machines = deepcopy(new_machines) | 1573 | machines = deepcopy(new_machines) |
943 | 1574 | mock_scrape_logical_drives_and_targets = self.patch( | ||
944 | 1575 | driver, 'scrape_logical_drives_and_targets') | ||
945 | 1576 | mock_scrape_logical_drives_and_targets.return_value = (None, None) | ||
946 | 1577 | mock_scrape_remote_drives = self.patch( | ||
947 | 1578 | driver, 'scrape_remote_drives') | ||
948 | 1579 | mock_scrape_remote_drives.return_value = None | ||
949 | 1430 | mock_get_pod_machines = self.patch(driver, 'get_pod_machines') | 1580 | mock_get_pod_machines = self.patch(driver, 'get_pod_machines') |
950 | 1431 | mock_get_pod_machines.side_effect = [ | 1581 | mock_get_pod_machines.side_effect = [ |
951 | 1432 | machines, new_machines] | 1582 | machines, new_machines] |
Transitioned to Git.
lp:maas has now moved from Bzr to Git.
Please propose your branches with Launchpad using Git.
git clone https:/ /git.launchpad. net/maas