Merge ~newell-jensen/maas:virsh-storage-pools into maas:master

Proposed by Newell Jensen
Status: Merged
Approved by: Newell Jensen
Approved revision: aaa4698a5d5bac7bdc8bcd4a4cecdff1d631f3b3
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~newell-jensen/maas:virsh-storage-pools
Merge into: maas:master
Diff against target: 377 lines (+83/-12)
7 files modified
src/maasserver/api/tests/test_enlistment.py (+7/-0)
src/maasserver/api/tests/test_machine.py (+4/-0)
src/maasserver/forms/tests/test_pods.py (+9/-0)
src/maasserver/rpc/tests/test_nodes.py (+1/-0)
src/maasserver/websockets/handlers/tests/test_machine.py (+3/-0)
src/provisioningserver/drivers/pod/tests/test_virsh.py (+21/-1)
src/provisioningserver/drivers/pod/virsh.py (+38/-11)
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
MAAS Lander Needs Fixing
Review via email: mp+342365@code.launchpad.net

Commit message

LP: #1742708 -- Add ability to define default storage pool for Virsh pods.

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b virsh-storage-pools lp:~newell-jensen/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/2180/console
COMMIT: 03cc1edfb2ad5619660df60b3fecc16ce1cc958c

review: Needs Fixing
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good.

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
aaa4698... by Newell Jensen

Fix failing tests.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/api/tests/test_enlistment.py b/src/maasserver/api/tests/test_enlistment.py
2index 2f03848..5fc5563 100644
3--- a/src/maasserver/api/tests/test_enlistment.py
4+++ b/src/maasserver/api/tests/test_enlistment.py
5@@ -480,6 +480,7 @@ class SimpleUserLoggedInEnlistmentAPITest(APITestCase.ForUser):
6 def test_POST_simple_user_can_set_power_type_and_parameters(self):
7 new_power_address = factory.make_ip_address() # XXX: URLs don't work.
8 new_power_id = factory.make_name('power_id')
9+ new_default_storage_pool = factory.make_name('default_pool')
10 response = self.client.post(
11 reverse('machines_handler'), {
12 'architecture': make_usable_architecture(self),
13@@ -488,6 +489,7 @@ class SimpleUserLoggedInEnlistmentAPITest(APITestCase.ForUser):
14 {
15 "power_address": new_power_address,
16 "power_id": new_power_id,
17+ "default_storage_pool": new_default_storage_pool,
18 }),
19 'mac_addresses': ['AA:BB:CC:DD:EE:FF'],
20 })
21@@ -500,6 +502,7 @@ class SimpleUserLoggedInEnlistmentAPITest(APITestCase.ForUser):
22 'power_pass': '',
23 'power_id': new_power_id,
24 'power_address': new_power_address,
25+ 'default_storage_pool': new_default_storage_pool,
26 }, machine.power_parameters)
27
28 def test_POST_returns_limited_fields(self):
29@@ -614,6 +617,7 @@ class AdminLoggedInEnlistmentAPITest(APITestCase.ForAdmin):
30 new_power_id = factory.make_name('power_id')
31 new_power_address = factory.make_ipv4_address()
32 new_power_pass = factory.make_name('power_pass')
33+ new_default_storage_pool = factory.make_name('default_pool')
34 response = self.client.post(
35 reverse('machines_handler'), {
36 'architecture': make_usable_architecture(self),
37@@ -621,6 +625,8 @@ class AdminLoggedInEnlistmentAPITest(APITestCase.ForAdmin):
38 'power_parameters_power_id': new_power_id,
39 'power_parameters_power_pass': new_power_pass,
40 'power_parameters_power_address': new_power_address,
41+ 'power_parameters_default_storage_pool': (
42+ new_default_storage_pool),
43 'mac_addresses': ['AA:BB:CC:DD:EE:FF'],
44 })
45
46@@ -633,6 +639,7 @@ class AdminLoggedInEnlistmentAPITest(APITestCase.ForAdmin):
47 'power_id': new_power_id,
48 'power_pass': new_power_pass,
49 'power_address': new_power_address,
50+ 'default_storage_pool': new_default_storage_pool,
51 },
52 reload_object(machine).power_parameters)
53
54diff --git a/src/maasserver/api/tests/test_machine.py b/src/maasserver/api/tests/test_machine.py
55index 65fd929..b143f22 100644
56--- a/src/maasserver/api/tests/test_machine.py
57+++ b/src/maasserver/api/tests/test_machine.py
58@@ -1280,12 +1280,15 @@ class TestMachineAPI(APITestCase.ForUser):
59 new_power_id = factory.make_name('power_id')
60 new_power_pass = factory.make_name('power_pass')
61 new_power_address = factory.make_ipv4_address()
62+ new_default_storage_pool = factory.make_name('default_pool')
63 response = self.client.put(
64 self.get_machine_uri(machine),
65 {
66 'power_parameters_power_id': new_power_id,
67 'power_parameters_power_pass': new_power_pass,
68 'power_parameters_power_address': new_power_address,
69+ 'power_parameters_default_storage_pool': (
70+ new_default_storage_pool),
71 }
72 )
73
74@@ -1295,6 +1298,7 @@ class TestMachineAPI(APITestCase.ForUser):
75 'power_id': new_power_id,
76 'power_pass': new_power_pass,
77 'power_address': new_power_address,
78+ 'default_storage_pool': new_default_storage_pool,
79 },
80 reload_object(machine).power_parameters)
81
82diff --git a/src/maasserver/forms/tests/test_pods.py b/src/maasserver/forms/tests/test_pods.py
83index 3808839..6eb2805 100644
84--- a/src/maasserver/forms/tests/test_pods.py
85+++ b/src/maasserver/forms/tests/test_pods.py
86@@ -94,6 +94,7 @@ class TestPodForm(MAASTransactionServerTestCase):
87 pod_ip_adddress = factory.make_ipv4_address()
88 pod_power_address = 'qemu+ssh://user@%s/system' % pod_ip_adddress
89 pod_password = factory.make_name('password')
90+ pod_default_storage_pool = factory.make_name('default_pool')
91 pod_tags = [
92 factory.make_name("tag")
93 for _ in range(3)
94@@ -104,6 +105,7 @@ class TestPodForm(MAASTransactionServerTestCase):
95 'type': pod_type,
96 'power_address': pod_power_address,
97 'power_pass': pod_password,
98+ 'default_storage_pool': pod_default_storage_pool,
99 'ip_address': pod_ip_adddress,
100 'tags': ",".join(pod_tags),
101 'cpu_over_commit_ratio': pod_cpu_over_commit_ratio,
102@@ -168,6 +170,7 @@ class TestPodForm(MAASTransactionServerTestCase):
103 power_parameters=Equals({
104 'power_address': pod_info['power_address'],
105 'power_pass': pod_info['power_pass'],
106+ 'default_storage_pool': pod_info['default_storage_pool'],
107 }),
108 ip_address=MatchesStructure(ip=Equals(pod_info['ip_address'])),
109 ))
110@@ -208,6 +211,7 @@ class TestPodForm(MAASTransactionServerTestCase):
111 power_parameters=Equals({
112 'power_address': pod_info['power_address'],
113 'power_pass': pod_info['power_pass'],
114+ 'default_storage_pool': pod_info['default_storage_pool'],
115 }),
116 ip_address=MatchesStructure(ip=Equals(pod_info['ip_address'])),
117 ))
118@@ -239,6 +243,7 @@ class TestPodForm(MAASTransactionServerTestCase):
119 power_parameters=Equals({
120 'power_address': pod_info['power_address'],
121 'power_pass': pod_info['power_pass'],
122+ 'default_storage_pool': pod_info['default_storage_pool'],
123 }),
124 ip_address=MatchesStructure(ip=Equals(pod_info['ip_address'])),
125 ))
126@@ -288,6 +293,7 @@ class TestPodForm(MAASTransactionServerTestCase):
127 power_parameters=Equals({
128 'power_address': pod_info['power_address'],
129 'power_pass': pod_info['power_pass'],
130+ 'default_storage_pool': pod_info['default_storage_pool'],
131 }),
132 ip_address=MatchesStructure(ip=Equals(pod_info['ip_address'])),
133 ))
134@@ -339,6 +345,7 @@ class TestPodForm(MAASTransactionServerTestCase):
135 power_parameters={
136 'power_address': pod_info['power_address'],
137 'power_pass': pod_info['power_pass'],
138+ 'default_storage_pool': pod_info['default_storage_pool'],
139 })
140 request = MagicMock()
141 request.user = factory.make_User()
142@@ -375,6 +382,7 @@ class TestPodForm(MAASTransactionServerTestCase):
143 power_parameters=Equals({
144 'power_address': pod_info['power_address'],
145 'power_pass': pod_info['power_pass'],
146+ 'default_storage_pool': pod_info['default_storage_pool'],
147 }),
148 ip_address=MatchesStructure(ip=Equals(pod_info['ip_address'])),
149 ))
150@@ -425,6 +433,7 @@ class TestPodForm(MAASTransactionServerTestCase):
151 power_parameters=Equals({
152 'power_address': pod_info['power_address'],
153 'power_pass': pod_info['power_pass'],
154+ 'default_storage_pool': pod_info['default_storage_pool'],
155 }),
156 ip_address=MatchesStructure(ip=Equals(pod_info['ip_address'])),
157 ))
158diff --git a/src/maasserver/rpc/tests/test_nodes.py b/src/maasserver/rpc/tests/test_nodes.py
159index c8034b1..f8f99ff 100644
160--- a/src/maasserver/rpc/tests/test_nodes.py
161+++ b/src/maasserver/rpc/tests/test_nodes.py
162@@ -240,6 +240,7 @@ class TestCreateNode(MAASTransactionServerTestCase):
163 'power_address': factory.make_ip_address(), # XXX: URLs break.
164 'power_pass': factory.make_name('power_pass'),
165 'power_id': factory.make_name('power_id'),
166+ 'default_storage_pool': factory.make_name('default_pool'),
167 }
168
169 node = create_node(
170diff --git a/src/maasserver/websockets/handlers/tests/test_machine.py b/src/maasserver/websockets/handlers/tests/test_machine.py
171index 27bc195..b027541 100644
172--- a/src/maasserver/websockets/handlers/tests/test_machine.py
173+++ b/src/maasserver/websockets/handlers/tests/test_machine.py
174@@ -1853,6 +1853,7 @@ class TestMachineHandler(MAASServerTestCase):
175 power_id = factory.make_name('power_id')
176 power_pass = factory.make_name('power_pass')
177 power_address = factory.make_ipv4_address()
178+ default_storage_pool = factory.make_name('default_pool')
179 node_data["hostname"] = new_hostname
180 node_data["architecture"] = new_architecture
181 node_data["zone"] = {
182@@ -1863,6 +1864,7 @@ class TestMachineHandler(MAASServerTestCase):
183 'power_id': power_id,
184 'power_pass': power_pass,
185 'power_address': power_address,
186+ 'default_storage_pool': default_storage_pool,
187 }
188 updated_node = handler.update(node_data)
189 self.expectThat(updated_node["hostname"], Equals(new_hostname))
190@@ -1873,6 +1875,7 @@ class TestMachineHandler(MAASServerTestCase):
191 'power_id': power_id,
192 'power_pass': power_pass,
193 'power_address': power_address,
194+ 'default_storage_pool': default_storage_pool,
195 }))
196
197 def test_update_adds_tags_to_node(self):
198diff --git a/src/provisioningserver/drivers/pod/tests/test_virsh.py b/src/provisioningserver/drivers/pod/tests/test_virsh.py
199index d73ebc3..44cd8d7 100644
200--- a/src/provisioningserver/drivers/pod/tests/test_virsh.py
201+++ b/src/provisioningserver/drivers/pod/tests/test_virsh.py
202@@ -1416,12 +1416,29 @@ class TestVirshPodDriver(MAASTestCase):
203 yield driver.discover(system_id, context)
204
205 @inlineCallbacks
206+ def test_discover_errors_on_incorrect_default_storage_pool(self):
207+ driver = VirshPodDriver()
208+ system_id = factory.make_name('system_id')
209+ context = {
210+ 'power_address': factory.make_name('power_address'),
211+ 'power_pass': factory.make_name('power_pass'),
212+ 'default_storage_pool': factory.make_name('non-existent-pool')
213+ }
214+ mock_login = self.patch(virsh.VirshSSH, 'login')
215+ mock_login.return_value = True
216+ mock_run = self.patch(virsh.VirshSSH, 'run')
217+ mock_run.return_value = SAMPLE_POOLLIST
218+ with ExpectedException(virsh.VirshError):
219+ yield driver.discover(system_id, context)
220+
221+ @inlineCallbacks
222 def test_discover(self):
223 driver = VirshPodDriver()
224 system_id = factory.make_name('system_id')
225 context = {
226 'power_address': factory.make_name('power_address'),
227- 'power_pass': factory.make_name('power_pass')
228+ 'power_pass': factory.make_name('power_pass'),
229+ 'default_storage_pool': 'default'
230 }
231 machines = [
232 factory.make_name('machine')
233@@ -1429,6 +1446,8 @@ class TestVirshPodDriver(MAASTestCase):
234 ]
235 mock_login = self.patch(virsh.VirshSSH, 'login')
236 mock_login.return_value = True
237+ mock_run = self.patch(virsh.VirshSSH, 'run')
238+ mock_run.return_value = SAMPLE_POOLLIST
239 mock_get_pod_resources = self.patch(
240 virsh.VirshSSH, 'get_pod_resources')
241 mock_get_pod_hints = self.patch(
242@@ -1451,6 +1470,7 @@ class TestVirshPodDriver(MAASTestCase):
243 call(machines[1]),
244 call(machines[2])))
245 self.expectThat(['virtual'], Equals(discovered_pod.tags))
246+ self.expectThat(driver.default_storage_pool, Equals('default'))
247
248 @inlineCallbacks
249 def test_compose(self):
250diff --git a/src/provisioningserver/drivers/pod/virsh.py b/src/provisioningserver/drivers/pod/virsh.py
251index a32d935..afdca25 100644
252--- a/src/provisioningserver/drivers/pod/virsh.py
253+++ b/src/provisioningserver/drivers/pod/virsh.py
254@@ -295,11 +295,16 @@ class VirshSSH(pexpect.spawn):
255 machines = machines.strip().splitlines()
256 return [m for m in machines if m.startswith(self.dom_prefix)]
257
258- def list_pools(self):
259- """Lists all pools in the pod."""
260+ def list_pools(self, default_pool=None):
261+ """Lists all pools in the pod.
262+
263+ If default_pool is set, only return this pool.
264+ """
265 keys = ['Name']
266 output = self.run(['pool-list'])
267 pools = self.get_column_values(output, keys)
268+ if default_pool:
269+ return [p[0] for p in pools if p[0] == default_pool]
270 return [p[0] for p in pools]
271
272 def list_machine_block_devices(self, machine):
273@@ -372,10 +377,10 @@ class VirshSSH(pexpect.spawn):
274 # Memory in MiB.
275 return int(KiB / 1024)
276
277- def get_pod_pool_size_map(self, key):
278+ def get_pod_pool_size_map(self, key, default_pool=None):
279 """Return the mapping for a size calculation based on key."""
280 pools = {}
281- for pool in self.list_pools():
282+ for pool in self.list_pools(default_pool):
283 output = self.run(['pool-info', pool]).strip()
284 if output is None:
285 # Skip if cannot get more information.
286@@ -445,6 +450,11 @@ class VirshSSH(pexpect.spawn):
287 # name, that MAAS understands.
288 return ARCH_FIX.get(arch, arch)
289
290+ def check_default_storage_pool(self, default_pool):
291+ """Check that default_pool exists."""
292+ if not len(self.list_pools(default_pool)):
293+ raise VirshError("Storage pool '%s' doesn't exist." % default_pool)
294+
295 def get_pod_resources(self):
296 """Get the pod resources."""
297 discovered_pod = DiscoveredPod(
298@@ -596,17 +606,17 @@ class VirshSSH(pexpect.spawn):
299 return False
300 return True
301
302- def get_usable_pool(self, size):
303+ def get_usable_pool(self, size, default_pool=None):
304 """Return the pool that as enough space for `size`."""
305- pools = self.get_pod_pool_size_map("Available")
306+ pools = self.get_pod_pool_size_map("Available", default_pool)
307 for pool, available in pools.items():
308 if size <= available:
309 return pool
310 return None
311
312- def create_local_volume(self, size):
313+ def create_local_volume(self, size, default_pool=None):
314 """Create a local volume with `size`."""
315- usable_pool = self.get_usable_pool(size)
316+ usable_pool = self.get_usable_pool(size, default_pool)
317 if usable_pool is None:
318 return None
319 volume = str(uuid.uuid4())
320@@ -718,7 +728,7 @@ class VirshSSH(pexpect.spawn):
321 idx = (idx // 26) - 1
322 return "vd" + name
323
324- def create_domain(self, request):
325+ def create_domain(self, request, default_pool=None):
326 """Create a domain based on the `request` with hostname.
327
328 For now this just uses `get_best_network` to connect the interfaces
329@@ -730,7 +740,7 @@ class VirshSSH(pexpect.spawn):
330 created_disks = []
331 for idx, disk in enumerate(request.block_devices):
332 try:
333- disk_info = self.create_local_volume(disk.size)
334+ disk_info = self.create_local_volume(disk.size, default_pool)
335 except Exception:
336 self.cleanup_disks(created_disks)
337 raise
338@@ -802,7 +812,11 @@ class VirshPodDriver(PodDriver):
339 make_setting_field(
340 'power_id', "Virsh VM ID", scope=SETTING_SCOPE.NODE,
341 required=True),
342+ make_setting_field(
343+ 'default_storage_pool', "Default storage pool (optional)",
344+ required=False),
345 ]
346+ default_storage_pool = None
347 ip_extractor = make_ip_extractor(
348 'power_address', IP_EXTRACTOR_PATTERNS.URL)
349
350@@ -903,6 +917,18 @@ class VirshPodDriver(PodDriver):
351 """
352 conn = yield self.get_virsh_connection(context)
353
354+ # Check and set default storage pool.
355+ self.default_storage_pool = context.get('default_storage_pool')
356+ if self.default_storage_pool:
357+ try:
358+ yield deferToThread(
359+ conn.check_default_storage_pool, self.default_storage_pool)
360+ except VirshError:
361+ # Set the default_storage_pool to None since
362+ # one wasn't found and raise the error.
363+ self.default_storage_pool = None
364+ raise
365+
366 # Discover pod resources.
367 discovered_pod = yield deferToThread(conn.get_pod_resources)
368
369@@ -930,7 +956,8 @@ class VirshPodDriver(PodDriver):
370 def compose(self, system_id, context, request):
371 """Compose machine."""
372 conn = yield self.get_virsh_connection(context)
373- created_machine = yield deferToThread(conn.create_domain, request)
374+ created_machine = yield deferToThread(
375+ conn.create_domain, request, self.default_storage_pool)
376 hints = yield deferToThread(conn.get_pod_hints)
377 return created_machine, hints
378

Subscribers

People subscribed via source and target branches