Fix: Volumes on lost local storage cannot be removed (#7594)

This commit is contained in:
Nicolas Vazquez 2023-06-23 07:22:15 -03:00 committed by GitHub
parent 0acc66f51d
commit c809201247
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 129 additions and 1 deletions

View File

@ -147,4 +147,6 @@ public interface VolumeDao extends GenericDao<VolumeVO, Long>, StateDao<Volume.S
*/ */
List<VolumeVO> findByDiskOfferingId(long diskOfferingId); List<VolumeVO> findByDiskOfferingId(long diskOfferingId);
VolumeVO getInstanceRootVolume(long instanceId); VolumeVO getInstanceRootVolume(long instanceId);
void updateAndRemoveVolume(VolumeVO volume);
} }

View File

@ -766,4 +766,15 @@ public class VolumeDaoImpl extends GenericDaoBase<VolumeVO, Long> implements Vol
sc.setParameters("vType", Volume.Type.ROOT); sc.setParameters("vType", Volume.Type.ROOT);
return findOneBy(sc); return findOneBy(sc);
} }
@Override
public void updateAndRemoveVolume(VolumeVO volume) {
if (volume.getState() != Volume.State.Destroy) {
volume.setState(Volume.State.Destroy);
volume.setPoolId(null);
volume.setInstanceId(null);
update(volume.getId(), volume);
remove(volume.getId());
}
}
} }

View File

@ -38,6 +38,9 @@ import javax.naming.ConfigurationException;
import com.cloud.exception.StorageConflictException; import com.cloud.exception.StorageConflictException;
import com.cloud.exception.StorageUnavailableException; import com.cloud.exception.StorageUnavailableException;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VolumeDao;
import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants;
@ -294,6 +297,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
private UserVmDetailsDao userVmDetailsDao; private UserVmDetailsDao userVmDetailsDao;
@Inject @Inject
private AnnotationDao annotationDao; private AnnotationDao annotationDao;
@Inject
private VolumeDao volumeDao;
private final long _nodeId = ManagementServerNode.getManagementServerId(); private final long _nodeId = ManagementServerNode.getManagementServerId();
@ -979,6 +984,7 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
final Long poolId = pool.getPoolId(); final Long poolId = pool.getPoolId();
final StoragePoolVO storagePool = _storagePoolDao.findById(poolId); final StoragePoolVO storagePool = _storagePoolDao.findById(poolId);
if (storagePool.isLocal() && isForceDeleteStorage) { if (storagePool.isLocal() && isForceDeleteStorage) {
destroyLocalStoragePoolVolumes(poolId);
storagePool.setUuid(null); storagePool.setUuid(null);
storagePool.setClusterId(null); storagePool.setClusterId(null);
_storagePoolDao.update(poolId, storagePool); _storagePoolDao.update(poolId, storagePool);
@ -1011,6 +1017,27 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
return true; return true;
} }
private void addVolumesToList(List<VolumeVO> volumes, List<VolumeVO> volumesToAdd) {
if (CollectionUtils.isNotEmpty(volumesToAdd)) {
volumes.addAll(volumesToAdd);
}
}
protected void destroyLocalStoragePoolVolumes(long poolId) {
List<VolumeVO> rootDisks = volumeDao.findByPoolId(poolId);
List<VolumeVO> dataVolumes = volumeDao.findByPoolId(poolId, Volume.Type.DATADISK);
List<VolumeVO> volumes = new ArrayList<>();
addVolumesToList(volumes, rootDisks);
addVolumesToList(volumes, dataVolumes);
if (CollectionUtils.isNotEmpty(volumes)) {
for (VolumeVO volume : volumes) {
volumeDao.updateAndRemoveVolume(volume);
}
}
}
/** /**
* Returns true if host can be deleted.</br> * Returns true if host can be deleted.</br>
* A host can be deleted either if it is in Maintenance or "Degraded" state. * A host can be deleted either if it is in Maintenance or "Degraded" state.

View File

@ -36,6 +36,7 @@ import javax.inject.Inject;
import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy; import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy;
import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd; import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd; import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
@ -88,6 +89,7 @@ import org.apache.cloudstack.storage.command.AttachAnswer;
import org.apache.cloudstack.storage.command.AttachCommand; import org.apache.cloudstack.storage.command.AttachCommand;
import org.apache.cloudstack.storage.command.DettachCommand; import org.apache.cloudstack.storage.command.DettachCommand;
import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand; import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand;
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
@ -273,6 +275,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
@Inject @Inject
private PrimaryDataStoreDao _storagePoolDao; private PrimaryDataStoreDao _storagePoolDao;
@Inject @Inject
private ImageStoreDao imageStoreDao;
@Inject
private DiskOfferingDao _diskOfferingDao; private DiskOfferingDao _diskOfferingDao;
@Inject @Inject
private ServiceOfferingDao _serviceOfferingDao; private ServiceOfferingDao _serviceOfferingDao;
@ -1619,6 +1623,12 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
} }
private void expungeVolumesInPrimaryOrSecondary(VolumeVO volume, DataStoreRole role) throws InterruptedException, ExecutionException { private void expungeVolumesInPrimaryOrSecondary(VolumeVO volume, DataStoreRole role) throws InterruptedException, ExecutionException {
if (!canAccessVolumeStore(volume, role)) {
s_logger.debug(String.format("Cannot access the storage pool with role: %s " +
"for the volume: %s, skipping expunge from storage",
role.name(), volume.getName()));
return;
}
VolumeInfo volOnStorage = volFactory.getVolume(volume.getId(), role); VolumeInfo volOnStorage = volFactory.getVolume(volume.getId(), role);
if (volOnStorage != null) { if (volOnStorage != null) {
s_logger.info("Expunging volume " + volume.getId() + " from " + role + " data store"); s_logger.info("Expunging volume " + volume.getId() + " from " + role + " data store");
@ -1638,6 +1648,17 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
} }
} }
} }
private boolean canAccessVolumeStore(VolumeVO volume, DataStoreRole role) {
if (volume == null) {
throw new CloudRuntimeException("No volume given, cannot check access to volume store");
}
InternalIdentity pool = role == DataStoreRole.Primary ?
_storagePoolDao.findById(volume.getPoolId()) :
imageStoreDao.findById(volume.getPoolId());
return pool != null;
}
/** /**
* Clean volumes cache entries (if they exist). * Clean volumes cache entries (if they exist).
*/ */

View File

@ -34,9 +34,13 @@ import static org.mockito.Mockito.when;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VolumeDao;
import org.apache.cloudstack.api.command.admin.host.CancelHostAsDegradedCmd; import org.apache.cloudstack.api.command.admin.host.CancelHostAsDegradedCmd;
import org.apache.cloudstack.api.command.admin.host.DeclareHostAsDegradedCmd; import org.apache.cloudstack.api.command.admin.host.DeclareHostAsDegradedCmd;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@ -98,6 +102,8 @@ public class ResourceManagerImplTest {
private VMInstanceDao vmInstanceDao; private VMInstanceDao vmInstanceDao;
@Mock @Mock
private ConfigurationDao configurationDao; private ConfigurationDao configurationDao;
@Mock
private VolumeDao volumeDao;
@Spy @Spy
@InjectMocks @InjectMocks
@ -119,6 +125,13 @@ public class ResourceManagerImplTest {
@Mock @Mock
private GetVncPortCommand getVncPortCommandVm2; private GetVncPortCommand getVncPortCommandVm2;
@Mock
private VolumeVO rootDisk1;
@Mock
private VolumeVO rootDisk2;
@Mock
private VolumeVO dataDisk;
@Mock @Mock
private Connection sshConnection; private Connection sshConnection;
@ -138,6 +151,10 @@ public class ResourceManagerImplTest {
private static String vm2VncAddress = "10.2.2.2"; private static String vm2VncAddress = "10.2.2.2";
private static int vm2VncPort = 5901; private static int vm2VncPort = 5901;
private static long poolId = 1L;
private List<VolumeVO> rootDisks;
private List<VolumeVO> dataDisks;
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
@ -179,6 +196,11 @@ public class ResourceManagerImplTest {
willReturn(new SSHCmdHelper.SSHCmdResult(0,"","")); willReturn(new SSHCmdHelper.SSHCmdResult(0,"",""));
when(configurationDao.getValue(ResourceManager.KvmSshToAgentEnabled.key())).thenReturn("true"); when(configurationDao.getValue(ResourceManager.KvmSshToAgentEnabled.key())).thenReturn("true");
rootDisks = Arrays.asList(rootDisk1, rootDisk2);
dataDisks = Collections.singletonList(dataDisk);
when(volumeDao.findByPoolId(poolId)).thenReturn(rootDisks);
when(volumeDao.findByPoolId(poolId, Volume.Type.DATADISK)).thenReturn(dataDisks);
} }
@Test @Test
@ -527,4 +549,33 @@ public class ResourceManagerImplTest {
return new HostVO(1L, "host01", Host.Type.Routing, "192.168.1.1", "255.255.255.0", null, null, null, null, null, null, null, null, null, null, UUID.randomUUID().toString(), return new HostVO(1L, "host01", Host.Type.Routing, "192.168.1.1", "255.255.255.0", null, null, null, null, null, null, null, null, null, null, UUID.randomUUID().toString(),
hostStatus, "1.0", null, null, 1L, null, 0, 0, null, 0, null); hostStatus, "1.0", null, null, 1L, null, 0, 0, null, 0, null);
} }
@Test
public void testDestroyLocalStoragePoolVolumesBothRootDisksAndDataDisks() {
resourceManager.destroyLocalStoragePoolVolumes(poolId);
verify(volumeDao, times(rootDisks.size() + dataDisks.size()))
.updateAndRemoveVolume(any(VolumeVO.class));
}
@Test
public void testDestroyLocalStoragePoolVolumesOnlyRootDisks() {
when(volumeDao.findByPoolId(poolId, Volume.Type.DATADISK)).thenReturn(null);
resourceManager.destroyLocalStoragePoolVolumes(poolId);
verify(volumeDao, times(rootDisks.size())).updateAndRemoveVolume(any(VolumeVO.class));
}
@Test
public void testDestroyLocalStoragePoolVolumesOnlyDataDisks() {
when(volumeDao.findByPoolId(poolId)).thenReturn(null);
resourceManager.destroyLocalStoragePoolVolumes(poolId);
verify(volumeDao, times(dataDisks.size())).updateAndRemoveVolume(any(VolumeVO.class));
}
@Test
public void testDestroyLocalStoragePoolVolumesNoDisks() {
when(volumeDao.findByPoolId(poolId)).thenReturn(null);
when(volumeDao.findByPoolId(poolId, Volume.Type.DATADISK)).thenReturn(null);
resourceManager.destroyLocalStoragePoolVolumes(poolId);
verify(volumeDao, never()).updateAndRemoveVolume(any(VolumeVO.class));
}
} }

View File

@ -52,6 +52,8 @@ import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext;
import org.apache.cloudstack.framework.jobs.AsyncJobManager; import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.framework.jobs.dao.AsyncJobJoinMapDao; import org.apache.cloudstack.framework.jobs.dao.AsyncJobJoinMapDao;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao; import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
@ -143,6 +145,12 @@ public class VolumeApiServiceImplTest {
@Mock @Mock
private PrimaryDataStoreDao primaryDataStoreDaoMock; private PrimaryDataStoreDao primaryDataStoreDaoMock;
@Mock @Mock
private ImageStoreDao imageStoreDao;
@Mock
private ImageStoreVO imageStoreVO;
@Mock
private StoragePoolVO storagePoolVO;
@Mock
private VMSnapshotDao _vmSnapshotDao; private VMSnapshotDao _vmSnapshotDao;
@Mock @Mock
private AsyncJobManager _jobMgr; private AsyncJobManager _jobMgr;
@ -214,6 +222,7 @@ public class VolumeApiServiceImplTest {
private long volumeMockId = 12313l; private long volumeMockId = 12313l;
private long vmInstanceMockId = 1123l; private long vmInstanceMockId = 1123l;
private long volumeSizeMock = 456789921939l; private long volumeSizeMock = 456789921939l;
private static long imageStoreId = 10L;
private String projectMockUuid = "projectUuid"; private String projectMockUuid = "projectUuid";
private long projecMockId = 13801801923810L; private long projecMockId = 13801801923810L;
@ -239,6 +248,10 @@ public class VolumeApiServiceImplTest {
Mockito.when(storagePoolMock.getId()).thenReturn(storagePoolMockId); Mockito.when(storagePoolMock.getId()).thenReturn(storagePoolMockId);
Mockito.when(volumeVoMock.getPoolId()).thenReturn(storagePoolMockId);
Mockito.when(imageStoreDao.findById(imageStoreId)).thenReturn(imageStoreVO);
Mockito.when(primaryDataStoreDaoMock.findById(storagePoolMockId)).thenReturn(storagePoolVO);
volumeApiServiceImpl._gson = GsonHelper.getGsonLogger(); volumeApiServiceImpl._gson = GsonHelper.getGsonLogger();
// mock caller context // mock caller context
@ -914,7 +927,7 @@ public class VolumeApiServiceImplTest {
@Test @Test
public void expungeVolumesInSecondaryStorageIfNeededTestVolumeNotFoundInSecondaryStorage() throws InterruptedException, ExecutionException { public void expungeVolumesInSecondaryStorageIfNeededTestVolumeNotFoundInSecondaryStorage() throws InterruptedException, ExecutionException {
Mockito.lenient().doReturn(asyncCallFutureVolumeapiResultMock).when(volumeServiceMock).expungeVolumeAsync(volumeInfoMock); Mockito.lenient().doReturn(asyncCallFutureVolumeapiResultMock).when(volumeServiceMock).expungeVolumeAsync(volumeInfoMock);
Mockito.doReturn(null).when(volumeDataFactoryMock).getVolume(volumeMockId, DataStoreRole.Image); Mockito.lenient().doReturn(null).when(imageStoreDao).findById(imageStoreId);
Mockito.lenient().doNothing().when(resourceLimitServiceMock).decrementResourceCount(accountMockId, ResourceType.secondary_storage, volumeSizeMock); Mockito.lenient().doNothing().when(resourceLimitServiceMock).decrementResourceCount(accountMockId, ResourceType.secondary_storage, volumeSizeMock);
Mockito.lenient().doReturn(accountMockId).when(volumeInfoMock).getAccountId(); Mockito.lenient().doReturn(accountMockId).when(volumeInfoMock).getAccountId();
Mockito.lenient().doReturn(volumeSizeMock).when(volumeInfoMock).getSize(); Mockito.lenient().doReturn(volumeSizeMock).when(volumeInfoMock).getSize();
@ -933,6 +946,7 @@ public class VolumeApiServiceImplTest {
Mockito.doNothing().when(resourceLimitServiceMock).decrementResourceCount(accountMockId, ResourceType.secondary_storage, volumeSizeMock); Mockito.doNothing().when(resourceLimitServiceMock).decrementResourceCount(accountMockId, ResourceType.secondary_storage, volumeSizeMock);
Mockito.doReturn(accountMockId).when(volumeInfoMock).getAccountId(); Mockito.doReturn(accountMockId).when(volumeInfoMock).getAccountId();
Mockito.doReturn(volumeSizeMock).when(volumeInfoMock).getSize(); Mockito.doReturn(volumeSizeMock).when(volumeInfoMock).getSize();
Mockito.doReturn(imageStoreId).when(volumeVoMock).getPoolId();
volumeApiServiceImpl.expungeVolumesInSecondaryStorageIfNeeded(volumeVoMock); volumeApiServiceImpl.expungeVolumesInSecondaryStorageIfNeeded(volumeVoMock);
@ -948,6 +962,7 @@ public class VolumeApiServiceImplTest {
Mockito.lenient().doNothing().when(resourceLimitServiceMock).decrementResourceCount(accountMockId, ResourceType.secondary_storage, volumeSizeMock); Mockito.lenient().doNothing().when(resourceLimitServiceMock).decrementResourceCount(accountMockId, ResourceType.secondary_storage, volumeSizeMock);
Mockito.lenient().doReturn(accountMockId).when(volumeInfoMock).getAccountId(); Mockito.lenient().doReturn(accountMockId).when(volumeInfoMock).getAccountId();
Mockito.lenient().doReturn(volumeSizeMock).when(volumeInfoMock).getSize(); Mockito.lenient().doReturn(volumeSizeMock).when(volumeInfoMock).getSize();
Mockito.doReturn(imageStoreId).when(volumeVoMock).getPoolId();
Mockito.doThrow(InterruptedException.class).when(asyncCallFutureVolumeapiResultMock).get(); Mockito.doThrow(InterruptedException.class).when(asyncCallFutureVolumeapiResultMock).get();
@ -962,6 +977,7 @@ public class VolumeApiServiceImplTest {
Mockito.lenient().doNothing().when(resourceLimitServiceMock).decrementResourceCount(accountMockId, ResourceType.secondary_storage, volumeSizeMock); Mockito.lenient().doNothing().when(resourceLimitServiceMock).decrementResourceCount(accountMockId, ResourceType.secondary_storage, volumeSizeMock);
Mockito.lenient().doReturn(accountMockId).when(volumeInfoMock).getAccountId(); Mockito.lenient().doReturn(accountMockId).when(volumeInfoMock).getAccountId();
Mockito.lenient().doReturn(volumeSizeMock).when(volumeInfoMock).getSize(); Mockito.lenient().doReturn(volumeSizeMock).when(volumeInfoMock).getSize();
Mockito.doReturn(imageStoreId).when(volumeVoMock).getPoolId();
Mockito.doThrow(ExecutionException.class).when(asyncCallFutureVolumeapiResultMock).get(); Mockito.doThrow(ExecutionException.class).when(asyncCallFutureVolumeapiResultMock).get();