package org.ovirt.engine.core.bll.pm;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.ovirt.engine.core.bll.DbDependentTestBase;
import org.ovirt.engine.core.common.businessentities.FencingPolicy;
import org.ovirt.engine.core.common.businessentities.NonOperationalReason;
import org.ovirt.engine.core.common.businessentities.VDS;
import org.ovirt.engine.core.common.businessentities.VDSStatus;
import org.ovirt.engine.core.common.businessentities.pm.FenceAgent;
import org.ovirt.engine.core.common.businessentities.pm.FenceProxySourceType;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.compat.Version;
import org.ovirt.engine.core.dal.dbbroker.DbFacade;
import org.ovirt.engine.core.dao.VdsDao;
import org.ovirt.engine.core.utils.pm.VdsFenceOptions;
/**
* This class tests the FenceProxyLocator
*/
@RunWith(MockitoJUnitRunner.class)
public class FenceProxyLocatorTest extends DbDependentTestBase {
private static Guid FENCECD_HOST_ID = new Guid("11111111-1111-1111-1111-111111111111");
private static Guid FENCED_HOST_CLUSTER_ID = new Guid("22222222-2222-2222-2222-222222222222");
private static Guid FENCED_HOST_DATACENTER_ID = new Guid("33333333-3333-3333-3333-333333333333");
private static Guid OTHER_CLUSTER_ID = new Guid("66666666-6666-6666-6666-666666666666");
private static Guid OTHER_CLUSTER_ID_2 = new Guid("88888888-8888-8888-8888-888888888888");
private static Guid OTHER_DATACENTER_ID = new Guid("77777777-7777-7777-7777-777777777777");
@Mock
private DbFacade dbFacade;
@Mock
private VdsDao vdsDao;
private VdsFenceOptions vdsFenceOptions;
private VDS fencedHost;
@Before
public void setup() {
when(dbFacade.getVdsDao()).thenReturn(vdsDao);
mockVdsFenceOptions(true);
mockFencedHost();
}
@After
public void cleanup() {
fencedHost = null;
}
/**
* Checks if locator select the only available host as a proxy.
*/
@Test
public void findProxyHost() {
mockExistingHosts(createHost());
VDS proxyHost = setupLocator().findProxyHost();
assertNotNull(proxyHost);
}
/**
* Checks if the locator excludes fenced host as a proxy host. And because fenced host is the only existing host,
* no proxy is selected
*/
@Test
public void findProxyHostExcludesFencedHost() {
mockExistingHosts(fencedHost);
VDS proxyHost = setupLocator().findProxyHost();
assertNull(proxyHost);
}
/**
* Checks if the locator excludes fenced host as a proxy host and selects different available host.
*/
@Test
public void findProxyHostExcludeFencedHostWhenOtherHostAvailable() {
mockExistingHosts(fencedHost, createHost());
VDS proxyHost = setupLocator().findProxyHost();
assertNotNull(proxyHost);
assertNotEquals(proxyHost.getId(), fencedHost.getId());
}
/**
* Checks if the locator excludes specified host as a proxy host. And because specified host is the only existing
* host, no proxy is selected
*/
@Test
public void findProxyHostExcludesSpecifiedHost() {
VDS excludedHost = createHost();
mockExistingHosts(excludedHost);
VDS proxyHost = setupLocator().findProxyHost(false, excludedHost.getId());
assertNull(proxyHost);
}
/**
* Checks if the locator excludes specified host as a proxy host and select different available host.
*/
@Test
public void findProxyHostExcludeHostAnotherHostAvailable() {
VDS excludedHost = createHost();
mockExistingHosts(excludedHost, createHost());
VDS proxyHost = setupLocator().findProxyHost(false, excludedHost.getId());
assertNotNull(proxyHost);
assertNotEquals(proxyHost.getId(), excludedHost.getId());
}
/**
* Checks if the locator excludes specified host as a proxy host, because it doesn't contain fence agents compatible
* with agents defined for fenced host. And because specified host is the only existing host, no proxy is selected
*/
@Test
public void findProxyHostExcludesHostWithIncompatibleAgents() {
VDS host = createHost();
mockExistingHosts(host);
mockVdsFenceOptions(false);
VDS proxyHost = setupLocator().findProxyHost(false);
assertNull(proxyHost);
}
/**
* Checks if the locator excludes specified host as a proxy host, because its supported cluster level is lower
* than minimal supported cluster level required by fencing policy. And because specified host is the only
* existing host, no proxy is selected
*/
@Test
public void findProxyHostExcludesHostDueToFencingPolicy() {
mockExistingHosts(createHost());
FenceProxyLocator locator = setupLocator(new FencingPolicy());
setMinSupportedVersionForFencingPolicy(locator, Version.v3_6);
VDS proxyHost = locator.findProxyHost(false);
assertNull(proxyHost);
}
/**
* Checks if the locator excludes specified host as a proxy host, because it's unreachable by network. And because
* specified host is the only existing host, no proxy is selected.
*
* Special case when host has NonOperational status and reason NETWORK_UNREACHABLE is tested in different test case!
*/
@Test
public void findProxyHostExcludesUnreachableHosts() {
FenceProxyLocator locator = setupLocator();
for (VDSStatus status : VDSStatus.values()) {
mockExistingHosts(createHost(status));
assertEquals(shouldHostBeUnreachable(status), locator.findProxyHost(false) == null);
}
}
/**
* Checks if the locator excludes NonOperational host as a proxy host, if it's NonOperationalReason is
* NETWORK_UNREACHABLE. And because specified host is the only existing host, no proxy is selected.
*/
@Test
public void findProxyHostExcludesNonOperationalHosts() {
mockExistingHosts(createHost());
FenceProxyLocator locator = setupLocator();
for (NonOperationalReason reason : NonOperationalReason.values()) {
VDS host = createHost();
host.setStatus(VDSStatus.NonOperational);
host.setNonOperationalReason(reason);
mockExistingHosts(host);
assertEquals(reason == NonOperationalReason.NETWORK_UNREACHABLE, locator.findProxyHost(false) == null);
}
}
/**
* Checks if the locator will prefer an host in Up status as proxy over a host in Maintenance status.
*/
@Test
public void findProxyHostPreferUpHost() {
VDS hostInMaintenance = createHost();
hostInMaintenance.setStatus(VDSStatus.Maintenance);
mockExistingHosts(hostInMaintenance, createHost());
VDS proxyHost = setupLocator().findProxyHost(false);
assertNotNull(proxyHost);
assertNotEquals(proxyHost.getId(), hostInMaintenance.getId());
}
/**
* Checks if the locator will select as proxy host in 'Maintenance'
*/
@Test
public void findProxyHostSelectSomeHostIfNoneUp() {
VDS hostInMaintenance1 = createHost();
hostInMaintenance1.setStatus(VDSStatus.Maintenance);
VDS hostInMaintenance2 = createHost();
hostInMaintenance2.setStatus(VDSStatus.Maintenance);
mockExistingHosts(hostInMaintenance1, hostInMaintenance2);
VDS proxyHost = setupLocator().findProxyHost(false);
assertNotNull(proxyHost);
}
/**
* Checks that the proxy locator will prefer a host from the same cluster as the fenced-host over a host from a
* different cluster (assuming default fence proxy sources: "cluster,dc")
*/
@Test
public void preferProxyHostFromSameCluster() {
mockExistingHosts(
createHost(VDSStatus.Up, OTHER_CLUSTER_ID, FENCED_HOST_DATACENTER_ID),
createHost(VDSStatus.Up, FENCED_HOST_CLUSTER_ID, FENCED_HOST_DATACENTER_ID));
VDS proxyHost = setupLocator().findProxyHost(false);
assertNotNull(proxyHost);
assertEquals(proxyHost.getClusterId(), FENCED_HOST_CLUSTER_ID);
}
/**
* Checks that, in a situation where there is no host available from the same cluster, the locator will select a host
* from the same data center as the fenced host over a host from a different data center (assuming default fence
* proxy sources: "cluster,dc")
*/
@Test
public void preferProxyHostFromSameDC() {
mockExistingHosts(
createHost(VDSStatus.Up, OTHER_CLUSTER_ID_2, OTHER_DATACENTER_ID),
createHost(VDSStatus.Up, OTHER_CLUSTER_ID, FENCED_HOST_DATACENTER_ID));
VDS proxyHost = setupLocator().findProxyHost(false);
assertNotNull(proxyHost);
assertEquals(proxyHost.getStoragePoolId(), FENCED_HOST_DATACENTER_ID);
}
/**
* Checks that if fence proxy sources are set as "other_dc,cluster,dc", then available host from other DC is selected
* as proxy although there's also an available host in the same cluster (BZ1131411)
*/
@Test
public void findProxyHostAccordingToOtherDcClusterDc() {
mockProxySourcesForFencedHost(
Arrays.asList(
FenceProxySourceType.OTHER_DC,
FenceProxySourceType.CLUSTER,
FenceProxySourceType.DC));
mockExistingHosts(
createHost(VDSStatus.Up, FENCED_HOST_CLUSTER_ID, FENCED_HOST_DATACENTER_ID),
createHost(VDSStatus.Up, OTHER_CLUSTER_ID_2, OTHER_DATACENTER_ID));
VDS proxyHost = setupLocator().findProxyHost(false);
assertNotNull(proxyHost);
assertEquals(proxyHost.getStoragePoolId(), OTHER_DATACENTER_ID);
}
/**
* Checks comparison of host supported cluster level with minimal version requirement for fencing policy.
*/
@Test
public void testProxyCompatibilityWithFencingPolicy() {
VDS host = createHost();
host.setSupportedClusterLevels("3.6");
FenceProxyLocator locator = setupLocator();
assertTrue(locator.isFencingPolicySupported(host, Version.v3_6));
assertFalse(locator.isFencingPolicySupported(host, Version.v4_0));
}
private void mockFencedHost() {
fencedHost = mock(VDS.class);
when(fencedHost.getId()).thenReturn(FENCECD_HOST_ID);
when(fencedHost.getClusterId()).thenReturn(FENCED_HOST_CLUSTER_ID);
when(fencedHost.getStoragePoolId()).thenReturn(FENCED_HOST_DATACENTER_ID);
when(fencedHost.getHostName()).thenReturn("fencedHost");
when(fencedHost.getFenceAgents()).thenReturn(Collections.singletonList(createFenceAgent(FENCECD_HOST_ID, "ipmilan")));
}
private void mockProxySourcesForFencedHost(List<FenceProxySourceType> fenceProxySources) {
when(fencedHost.getFenceProxySources()).thenReturn(fenceProxySources);
}
private FenceAgent createFenceAgent(Guid hostId, String type) {
FenceAgent agent = new FenceAgent();
agent.setId(Guid.newGuid());
agent.setHostId(hostId);
agent.setType(type);
return agent;
}
private FenceProxyLocator setupLocator() {
return setupLocator(null);
}
private FenceProxyLocator setupLocator(FencingPolicy fencingPolicy) {
FenceProxyLocator fenceProxyLocator = spy(new FenceProxyLocator(fencedHost, fencingPolicy));
when(fenceProxyLocator.getDbFacade()).thenReturn(dbFacade);
doReturn(vdsFenceOptions).when(fenceProxyLocator).createVdsFenceOptions(anyString());
doReturn(0L).when(fenceProxyLocator).getDelayBetweenRetries();
doReturn(1).when(fenceProxyLocator).getFindFenceProxyRetries();
doReturn(Arrays.asList(FenceProxySourceType.CLUSTER, FenceProxySourceType.DC))
.when(fenceProxyLocator).getDefaultFenceProxySources();
mockVdsFenceOptions(true);
return fenceProxyLocator;
}
private void setMinSupportedVersionForFencingPolicy(FenceProxyLocator locator, Version version) {
when(locator.getMinSupportedVersionForFencingPolicy()).thenReturn(version);
}
private VDS createHost() {
return createHost(VDSStatus.Up);
}
private VDS createHost(VDSStatus status) {
return createHost(status, FENCED_HOST_CLUSTER_ID, FENCED_HOST_DATACENTER_ID);
}
private VDS createHost(VDSStatus status, Guid clusterId, Guid dcId) {
VDS host = new VDS();
host.setId(Guid.newGuid());
host.setClusterId(clusterId);
host.setStoragePoolId(dcId);
host.setClusterCompatibilityVersion(Version.v3_6);
host.setStatus(status);
host.setHostName("host-" + host.getId());
return host;
}
private void mockExistingHosts(final VDS... hosts) {
// we need to recreate the list on each call, because the list is altered in FenceProxyLocator
when(vdsDao.getAll()).thenAnswer(invocationOnMock -> Arrays.asList(hosts));
}
private void mockVdsFenceOptions(boolean agentsCompatibleWithProxy) {
vdsFenceOptions = mock(VdsFenceOptions.class);
when(vdsFenceOptions.isAgentSupported(anyString())).thenReturn(agentsCompatibleWithProxy);
}
private boolean shouldHostBeUnreachable(VDSStatus status) {
boolean unreachable;
switch(status) {
case Down:
case Reboot:
case Kdumping:
case NonResponsive:
case PendingApproval:
unreachable = true;
break;
default:
unreachable = false;
}
return unreachable;
}
}