4242import com .cloud .hypervisor .kvm .resource .LibvirtConnection ;
4343import com .cloud .hypervisor .kvm .resource .LibvirtStoragePoolDef ;
4444import com .cloud .storage .Storage ;
45+ import com .cloud .storage .StorageLayer ;
4546import com .cloud .utils .Pair ;
4647import com .cloud .utils .exception .CloudRuntimeException ;
4748import com .cloud .utils .script .Script ;
49+ import org .libvirt .LibvirtException ;
50+
51+ import org .springframework .test .util .ReflectionTestUtils ;
4852
4953@ RunWith (MockitoJUnitRunner .class )
5054public class LibvirtStorageAdaptorTest {
@@ -56,6 +60,9 @@ public class LibvirtStorageAdaptorTest {
5660 @ Mock
5761 LibvirtStoragePool mockPool ;
5862
63+ @ Mock
64+ StorageLayer storageLayer ;
65+
5966 MockedStatic <Script > mockScript ;
6067
6168 @ Spy
@@ -67,6 +74,7 @@ public void initMocks() {
6774 libvirtConnectionMockedStatic = Mockito .mockStatic (LibvirtConnection .class );
6875 Mockito .reset (mockPool );
6976 mockScript = Mockito .mockStatic (Script .class );
77+ ReflectionTestUtils .setField (libvirtStorageAdaptor , "_storageLayer" , storageLayer );
7078 }
7179
7280 @ After
@@ -176,4 +184,171 @@ public void testUpdateLocalPoolIops_NullResultFromScript() {
176184
177185 Mockito .verify (mockPool , never ()).setUsedIops (anyLong ());
178186 }
187+
188+ /**
189+ * Creates a {@link LibvirtException} via reflection since its only constructor is package-private.
190+ */
191+ private static LibvirtException createLibvirtException (String message ) throws Exception {
192+ org .libvirt .Error virError = Mockito .mock (org .libvirt .Error .class );
193+ Mockito .when (virError .getMessage ()).thenReturn (message );
194+ java .lang .reflect .Constructor <LibvirtException > ctor =
195+ LibvirtException .class .getDeclaredConstructor (org .libvirt .Error .class );
196+ ctor .setAccessible (true );
197+ return ctor .newInstance (virError );
198+ }
199+
200+ private String buildNfsPoolXml (String uuid , String host , String dir , String targetPath ) {
201+ return "<pool type='netfs'>\n " +
202+ "<name>" + uuid + "</name>\n <uuid>" + uuid + "</uuid>\n " +
203+ "<source>\n <host name='" + host + "'/>\n <dir path='" + dir + "'/>\n </source>\n " +
204+ "<target>\n <path>" + targetPath + "</path>\n </target>\n </pool>\n " ;
205+ }
206+
207+ /**
208+ * NFS pool exists with same host and path: should reuse it without destroying.
209+ */
210+ @ Test
211+ public void testCreateStoragePool_NFS_SameHostAndPath_ReusesPool () throws Exception {
212+ String uuid = UUID .randomUUID ().toString ();
213+ String host = "10.0.0.1" ;
214+ String path = "/export/primary" ;
215+ String targetPath = "/mnt/" + uuid ;
216+ String poolXml = buildNfsPoolXml (uuid , host , path , targetPath );
217+
218+ Connect conn = Mockito .mock (Connect .class );
219+ StoragePool sp = Mockito .mock (StoragePool .class );
220+ Mockito .when (LibvirtConnection .getConnection ()).thenReturn (conn );
221+ Mockito .when (conn .storagePoolLookupByUUIDString (uuid )).thenReturn (sp );
222+ Mockito .when (sp .isActive ()).thenReturn (1 );
223+ Mockito .when (sp .getXMLDesc (0 )).thenReturn (poolXml );
224+ Mockito .when (Script .runSimpleBashScriptForExitValue (anyString ())).thenReturn (0 );
225+ Mockito .doReturn (Mockito .mock (KVMStoragePool .class )).when (libvirtStorageAdaptor ).getStoragePool (uuid );
226+
227+ libvirtStorageAdaptor .createStoragePool (uuid , host , 0 , path , null ,
228+ Storage .StoragePoolType .NetworkFilesystem , null , true );
229+
230+ // pool was not destroyed since source didn't change
231+ Mockito .verify (sp , Mockito .never ()).destroy ();
232+ Mockito .verify (sp , Mockito .never ()).undefine ();
233+ }
234+
235+ /**
236+ * NFS pool exists but path has changed: should destroy and recreate.
237+ */
238+ @ Test
239+ public void testCreateStoragePool_NFS_PathChanged_DestroysAndRecreates () throws Exception {
240+ String uuid = UUID .randomUUID ().toString ();
241+ String host = "10.0.0.1" ;
242+ String oldPath = "/export/old" ;
243+ String newPath = "/export/new" ;
244+ String targetPath = "/mnt/" + uuid ;
245+ String poolXml = buildNfsPoolXml (uuid , host , oldPath , targetPath );
246+
247+ Connect conn = Mockito .mock (Connect .class );
248+ StoragePool sp = Mockito .mock (StoragePool .class );
249+ Mockito .when (LibvirtConnection .getConnection ()).thenReturn (conn );
250+ Mockito .when (conn .storagePoolLookupByUUIDString (uuid )).thenReturn (sp );
251+ Mockito .when (sp .isActive ()).thenReturn (1 );
252+ Mockito .when (sp .getXMLDesc (0 )).thenReturn (poolXml );
253+ Mockito .when (sp .isPersistent ()).thenReturn (1 );
254+ Mockito .when (conn .listStoragePools ()).thenReturn (new String []{});
255+ StoragePool newSp = Mockito .mock (StoragePool .class );
256+ Mockito .when (conn .storagePoolCreateXML (anyString (), Mockito .eq (0 ))).thenReturn (newSp );
257+ Mockito .when (Script .runSimpleBashScriptForExitValue (anyString ())).thenReturn (0 );
258+ Mockito .doReturn (Mockito .mock (KVMStoragePool .class )).when (libvirtStorageAdaptor ).getStoragePool (uuid );
259+
260+ libvirtStorageAdaptor .createStoragePool (uuid , host , 0 , newPath , null ,
261+ Storage .StoragePoolType .NetworkFilesystem , null , true );
262+
263+ Mockito .verify (sp ).destroy ();
264+ Mockito .verify (sp ).undefine ();
265+ Mockito .verify (sp ).free ();
266+ }
267+
268+ /**
269+ * NFS pool exists but host has changed: should destroy and recreate.
270+ */
271+ @ Test
272+ public void testCreateStoragePool_NFS_HostChanged_DestroysAndRecreates () throws Exception {
273+ String uuid = UUID .randomUUID ().toString ();
274+ String oldHost = "10.0.0.1" ;
275+ String newHost = "10.0.0.2" ;
276+ String path = "/export/primary" ;
277+ String targetPath = "/mnt/" + uuid ;
278+ String poolXml = buildNfsPoolXml (uuid , oldHost , path , targetPath );
279+
280+ Connect conn = Mockito .mock (Connect .class );
281+ StoragePool sp = Mockito .mock (StoragePool .class );
282+ Mockito .when (LibvirtConnection .getConnection ()).thenReturn (conn );
283+ Mockito .when (conn .storagePoolLookupByUUIDString (uuid )).thenReturn (sp );
284+ Mockito .when (sp .isActive ()).thenReturn (1 );
285+ Mockito .when (sp .getXMLDesc (0 )).thenReturn (poolXml );
286+ Mockito .when (sp .isPersistent ()).thenReturn (1 );
287+ Mockito .when (conn .listStoragePools ()).thenReturn (new String []{});
288+ StoragePool newSp = Mockito .mock (StoragePool .class );
289+ Mockito .when (conn .storagePoolCreateXML (anyString (), Mockito .eq (0 ))).thenReturn (newSp );
290+ Mockito .when (Script .runSimpleBashScriptForExitValue (anyString ())).thenReturn (0 );
291+ Mockito .doReturn (Mockito .mock (KVMStoragePool .class )).when (libvirtStorageAdaptor ).getStoragePool (uuid );
292+
293+ libvirtStorageAdaptor .createStoragePool (uuid , newHost , 0 , path , null ,
294+ Storage .StoragePoolType .NetworkFilesystem , null , true );
295+
296+ Mockito .verify (sp ).destroy ();
297+ Mockito .verify (sp ).undefine ();
298+ Mockito .verify (sp ).free ();
299+ }
300+
301+ /**
302+ * RBD pool exists with different source — should NOT destroy (RBD is excluded from this logic).
303+ */
304+ @ Test
305+ public void testCreateStoragePool_RBD_SourceChanged_DoesNotDestroy () throws Exception {
306+ String uuid = UUID .randomUUID ().toString ();
307+ String rbdPoolXml = "<pool type='rbd'>\n <name>" + uuid + "</name>\n <uuid>" + uuid + "</uuid>\n " +
308+ "<source>\n <host name='10.0.0.1'/>\n <name>oldpool</name>\n </source>\n </pool>\n " ;
309+
310+ Connect conn = Mockito .mock (Connect .class );
311+ StoragePool sp = Mockito .mock (StoragePool .class );
312+ Mockito .when (LibvirtConnection .getConnection ()).thenReturn (conn );
313+ Mockito .when (conn .storagePoolLookupByUUIDString (uuid )).thenReturn (sp );
314+ Mockito .when (sp .isActive ()).thenReturn (1 );
315+ Mockito .doReturn (Mockito .mock (KVMStoragePool .class )).when (libvirtStorageAdaptor ).getStoragePool (uuid );
316+
317+ libvirtStorageAdaptor .createStoragePool (uuid , "10.0.0.2" , 6789 , "newpool" , "user:secret" ,
318+ Storage .StoragePoolType .RBD , null , true );
319+
320+ Mockito .verify (sp , Mockito .never ()).destroy ();
321+ Mockito .verify (sp , Mockito .never ()).undefine ();
322+ }
323+
324+ /**
325+ * NFS pool exists, destroy fails — pool should be reused (not crash), sp.free() not called.
326+ */
327+ @ Test
328+ public void testCreateStoragePool_NFS_DestroyFails_ReusesExistingPool () throws Exception {
329+ String uuid = UUID .randomUUID ().toString ();
330+ String host = "10.0.0.1" ;
331+ String oldPath = "/export/old" ;
332+ String newPath = "/export/new" ;
333+ String targetPath = "/mnt/" + uuid ;
334+ String poolXml = buildNfsPoolXml (uuid , host , oldPath , targetPath );
335+
336+ Connect conn = Mockito .mock (Connect .class );
337+ StoragePool sp = Mockito .mock (StoragePool .class );
338+ Mockito .when (LibvirtConnection .getConnection ()).thenReturn (conn );
339+ Mockito .when (conn .storagePoolLookupByUUIDString (uuid )).thenReturn (sp );
340+ Mockito .when (sp .isActive ()).thenReturn (1 );
341+ Mockito .when (sp .getXMLDesc (0 )).thenReturn (poolXml );
342+ Mockito .when (sp .isPersistent ()).thenReturn (1 );
343+ LibvirtException libvirtException = createLibvirtException ("pool is busy" );
344+ Mockito .doThrow (libvirtException ).when (sp ).destroy ();
345+ Mockito .when (Script .runSimpleBashScriptForExitValue (anyString ())).thenReturn (0 );
346+ Mockito .doReturn (Mockito .mock (KVMStoragePool .class )).when (libvirtStorageAdaptor ).getStoragePool (uuid );
347+
348+ libvirtStorageAdaptor .createStoragePool (uuid , host , 0 , newPath , null ,
349+ Storage .StoragePoolType .NetworkFilesystem , null , true );
350+
351+ Mockito .verify (sp ).destroy ();
352+ Mockito .verify (sp , Mockito .never ()).free ();
353+ }
179354}
0 commit comments