Skip to content

Commit edc2a39

Browse files
author
Daan Hoogland
committed
ssvm: prevent sql constraint violation then a SVM was removed during network oiutage
1 parent 376c1b4 commit edc2a39

5 files changed

Lines changed: 81 additions & 0 deletions

File tree

‎engine/schema/src/main/java/com/cloud/host/HostVO.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,10 @@ public Date getRemoved() {
600600
return removed;
601601
}
602602

603+
public void setRemoved(Date removed) {
604+
this.removed = removed;
605+
}
606+
603607
@Override
604608
public String getVersion() {
605609
return version;

‎engine/schema/src/main/java/com/cloud/host/dao/HostDao.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ public interface HostDao extends GenericDao<HostVO, Long>, StateDao<Status, Stat
7979

8080
HostVO findByGuid(String guid);
8181

82+
HostVO findByGuidIncludingRemoved(String guid);
83+
8284
HostVO findByTypeNameAndZoneId(long zoneId, String name, Host.Type type);
8385

8486
List<HostVO> findHypervisorHostInCluster(long clusterId);

‎engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,12 @@ public HostVO findByGuid(String guid) {
609609
return findOneBy(sc);
610610
}
611611

612+
@Override
613+
public HostVO findByGuidIncludingRemoved(String guid) {
614+
SearchCriteria<HostVO> sc = GuidSearch.create("guid", guid);
615+
return findOneIncludingRemovedBy(sc);
616+
}
617+
612618
/*
613619
* Find hosts which is in Disconnected, Down, Alert and ping timeout and server is not null, set server to null
614620
*/

‎server/src/main/java/com/cloud/resource/ResourceManagerImpl.java‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2281,10 +2281,32 @@ private HostVO getNewHost(StartupCommand[] startupCommands) {
22812281
return host;
22822282
}
22832283

2284+
host = reviveRemovedHostByGuid(fullGuid);
2285+
if (host != null) {
2286+
return host;
2287+
}
2288+
22842289
logger.debug(String.format("Could not find Host by guid %s", fullGuid));
22852290
return null;
22862291
}
22872292

2293+
/**
2294+
* A host can be marked removed (e.g. a system VM force-stopped and destroyed from the UI) while the
2295+
* underlying resource is still alive and later reconnects with the same guid. The `guid` column is
2296+
* uniquely constrained regardless of removal, so blindly inserting a new row for it fails with a
2297+
* SQLIntegrityConstraintViolationException. Revive the removed row instead of creating a duplicate.
2298+
*/
2299+
private HostVO reviveRemovedHostByGuid(String guid) {
2300+
HostVO removedHost = _hostDao.findByGuidIncludingRemoved(guid);
2301+
if (removedHost == null || removedHost.getRemoved() == null) {
2302+
return null;
2303+
}
2304+
logger.warn(String.format("Host with guid %s reconnected but was previously removed (id: %s); reviving the removed host record instead of creating a duplicate",
2305+
guid, removedHost.getId()));
2306+
removedHost.setRemoved(null);
2307+
return removedHost;
2308+
}
2309+
22882310
protected void validateExistingHostLocationImmutable(final HostVO host, final boolean newHost,
22892311
final long dcId, final Long podId, final Long clusterId, final StartupCommand startup) {
22902312
if (newHost || host == null || host.getType() != Host.Type.Routing) {

‎server/src/test/java/com/cloud/resource/ResourceManagerImplTest.java‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,11 @@
6464
import org.mockito.junit.MockitoJUnitRunner;
6565

6666
import java.lang.reflect.Field;
67+
import java.lang.reflect.Method;
6768
import java.util.ArrayList;
6869
import java.util.Arrays;
6970
import java.util.Collections;
71+
import java.util.Date;
7072
import java.util.List;
7173
import java.util.UUID;
7274

@@ -683,4 +685,49 @@ public void testDestroyLocalStoragePoolVolumesNoDisks() {
683685
resourceManager.destroyLocalStoragePoolVolumes(poolId);
684686
verify(volumeDao, never()).updateAndRemoveVolume(any(VolumeVO.class));
685687
}
688+
689+
private HostVO invokeGetNewHost(StartupCommand[] cmds) throws Exception {
690+
Method method = ResourceManagerImpl.class.getDeclaredMethod("getNewHost", StartupCommand[].class);
691+
method.setAccessible(true);
692+
return (HostVO) method.invoke(resourceManager, (Object) cmds);
693+
}
694+
695+
@Test
696+
public void testGetNewHostRevivesRemovedHostOnGuidCollision() throws Exception {
697+
String guid = "s-14-VM-PremiumSecondaryStorageResource";
698+
String guidPrefix = "s-14-VM";
699+
StartupCommand cmd = Mockito.mock(StartupCommand.class);
700+
when(cmd.getGuid()).thenReturn(guid);
701+
when(cmd.getGuidWithoutResource()).thenReturn(guidPrefix);
702+
703+
Mockito.doReturn(null).when(resourceManager).findHostByGuid(guid);
704+
Mockito.doReturn(null).when(resourceManager).findHostByGuidPrefix(guidPrefix);
705+
706+
HostVO removedHost = Mockito.mock(HostVO.class);
707+
when(removedHost.getId()).thenReturn(99L);
708+
when(removedHost.getRemoved()).thenReturn(new Date());
709+
when(hostDao.findByGuidIncludingRemoved(guid)).thenReturn(removedHost);
710+
711+
HostVO result = invokeGetNewHost(new StartupCommand[]{cmd});
712+
713+
Assert.assertSame(removedHost, result);
714+
Mockito.verify(removedHost).setRemoved(null);
715+
}
716+
717+
@Test
718+
public void testGetNewHostReturnsNullWhenNoHostAndNoRemovedMatch() throws Exception {
719+
String guid = "unknown-guid";
720+
String guidPrefix = "unknown";
721+
StartupCommand cmd = Mockito.mock(StartupCommand.class);
722+
when(cmd.getGuid()).thenReturn(guid);
723+
when(cmd.getGuidWithoutResource()).thenReturn(guidPrefix);
724+
725+
Mockito.doReturn(null).when(resourceManager).findHostByGuid(guid);
726+
Mockito.doReturn(null).when(resourceManager).findHostByGuidPrefix(guidPrefix);
727+
when(hostDao.findByGuidIncludingRemoved(guid)).thenReturn(null);
728+
729+
HostVO result = invokeGetNewHost(new StartupCommand[]{cmd});
730+
731+
Assert.assertNull(result);
732+
}
686733
}

0 commit comments

Comments
 (0)