package com.sequenceiq.cloudbreak.orchestrator.salt;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.verifyNew;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.whenNew;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import com.google.common.collect.Sets;
import com.sequenceiq.cloudbreak.orchestrator.OrchestratorBootstrapRunner;
import com.sequenceiq.cloudbreak.orchestrator.exception.CloudbreakOrchestratorFailedException;
import com.sequenceiq.cloudbreak.orchestrator.executor.ParallelOrchestratorComponentRunner;
import com.sequenceiq.cloudbreak.orchestrator.model.GatewayConfig;
import com.sequenceiq.cloudbreak.orchestrator.model.GenericResponse;
import com.sequenceiq.cloudbreak.orchestrator.model.Node;
import com.sequenceiq.cloudbreak.orchestrator.model.SaltPillarConfig;
import com.sequenceiq.cloudbreak.orchestrator.salt.client.SaltConnector;
import com.sequenceiq.cloudbreak.orchestrator.salt.poller.PillarSave;
import com.sequenceiq.cloudbreak.orchestrator.salt.poller.SaltBootstrap;
import com.sequenceiq.cloudbreak.orchestrator.salt.poller.SaltCommandTracker;
import com.sequenceiq.cloudbreak.orchestrator.salt.poller.SaltJobIdTracker;
import com.sequenceiq.cloudbreak.orchestrator.salt.poller.checker.GrainAddRunner;
import com.sequenceiq.cloudbreak.orchestrator.salt.poller.checker.HighStateRunner;
import com.sequenceiq.cloudbreak.orchestrator.salt.poller.checker.SyncGrainsRunner;
import com.sequenceiq.cloudbreak.orchestrator.salt.service.HostDiscoveryService;
import com.sequenceiq.cloudbreak.orchestrator.salt.states.SaltStates;
import com.sequenceiq.cloudbreak.orchestrator.state.ExitCriteria;
import com.sequenceiq.cloudbreak.orchestrator.state.ExitCriteriaModel;
@RunWith(PowerMockRunner.class)
@PrepareForTest({SaltOrchestrator.class, SaltStates.class})
public class SaltOrchestratorTest {
private GatewayConfig gatewayConfig;
private Set<Node> targets;
private ExitCriteria exitCriteria;
private ParallelOrchestratorComponentRunner parallelOrchestratorComponentRunner;
private SaltConnector saltConnector;
@Captor
private ArgumentCaptor<Set<String>> ipSet;
private ExitCriteriaModel exitCriteriaModel;
@Mock
private HostDiscoveryService hostDiscoveryService;
@InjectMocks
private SaltOrchestrator saltOrchestrator;
@Before
public void setUp() throws Exception {
gatewayConfig = new GatewayConfig("1.1.1.1", "10.0.0.1", "172.16.252.43", "10-0-0-1", 9443, "/certdir", "servercert", "clientcert", "clientkey",
"saltpasswd", "saltbootpassword", "signkey", false, true, null, null);
targets = new HashSet<>();
targets.add(new Node("10.0.0.1", "1.1.1.1", "10-0-0-1.example.com"));
targets.add(new Node("10.0.0.2", "1.1.1.2", "10-0-0-2.example.com"));
targets.add(new Node("10.0.0.3", "1.1.1.3", "10-0-0-3.example.com"));
saltConnector = mock(SaltConnector.class);
PowerMockito.whenNew(SaltConnector.class).withAnyArguments().thenReturn(saltConnector);
parallelOrchestratorComponentRunner = mock(ParallelOrchestratorComponentRunner.class);
when(parallelOrchestratorComponentRunner.submit(any())).thenReturn(CompletableFuture.completedFuture(true));
when(hostDiscoveryService.determineDomain()).thenReturn(".example.com");
exitCriteria = mock(ExitCriteria.class);
exitCriteriaModel = mock(ExitCriteriaModel.class);
}
@Test
public void bootstrapTest() throws Exception {
saltOrchestrator.init(parallelOrchestratorComponentRunner, exitCriteria);
PowerMockito.whenNew(OrchestratorBootstrapRunner.class).withAnyArguments().thenReturn(mock(OrchestratorBootstrapRunner.class));
PowerMockito.whenNew(SaltBootstrap.class).withAnyArguments().thenReturn(mock(SaltBootstrap.class));
saltOrchestrator.bootstrap(Collections.singletonList(gatewayConfig), targets, exitCriteriaModel);
verify(parallelOrchestratorComponentRunner, times(2)).submit(any(OrchestratorBootstrapRunner.class));
verifyNew(OrchestratorBootstrapRunner.class, times(2))
.withArguments(any(PillarSave.class), eq(exitCriteria), eq(exitCriteriaModel), any(), anyInt(), anyInt());
verifyNew(OrchestratorBootstrapRunner.class, times(2))
.withArguments(any(SaltBootstrap.class), eq(exitCriteria), eq(exitCriteriaModel), any(), anyInt(), anyInt());
verifyNew(SaltBootstrap.class, times(1)).withArguments(eq(saltConnector), eq(Collections.singletonList(gatewayConfig)), eq(targets), eq(".example.com"));
}
@Test
public void bootstrapNewNodesTest() throws Exception {
PowerMockito.whenNew(SaltBootstrap.class).withAnyArguments().thenReturn(mock(SaltBootstrap.class));
PowerMockito.whenNew(OrchestratorBootstrapRunner.class).withAnyArguments().thenReturn(mock(OrchestratorBootstrapRunner.class));
saltOrchestrator.init(parallelOrchestratorComponentRunner, exitCriteria);
saltOrchestrator.bootstrapNewNodes(Collections.singletonList(gatewayConfig), targets, targets, exitCriteriaModel);
verifyNew(OrchestratorBootstrapRunner.class, times(1))
.withArguments(any(SaltBootstrap.class), eq(exitCriteria), eq(exitCriteriaModel), any(), anyInt(), anyInt());
verifyNew(SaltBootstrap.class, times(1)).withArguments(eq(saltConnector), eq(Collections.singletonList(gatewayConfig)), eq(targets), eq(".example.com"));
}
@Test
public void runServiceTest() throws Exception {
PowerMockito.whenNew(SaltBootstrap.class).withAnyArguments().thenReturn(mock(SaltBootstrap.class));
PowerMockito.whenNew(OrchestratorBootstrapRunner.class).withAnyArguments().thenReturn(mock(OrchestratorBootstrapRunner.class));
PillarSave pillarSave = mock(PillarSave.class);
whenNew(PillarSave.class).withAnyArguments().thenReturn(pillarSave);
GrainAddRunner addRemoveGrainRunner = mock(GrainAddRunner.class);
whenNew(GrainAddRunner.class).withAnyArguments().thenReturn(addRemoveGrainRunner);
SaltCommandTracker roleCheckerSaltCommandTracker = mock(SaltCommandTracker.class);
whenNew(SaltCommandTracker.class).withArguments(eq(saltConnector), eq(addRemoveGrainRunner)).thenReturn(roleCheckerSaltCommandTracker);
SyncGrainsRunner syncGrainsRunner = mock(SyncGrainsRunner.class);
whenNew(SyncGrainsRunner.class).withAnyArguments().thenReturn(syncGrainsRunner);
SaltCommandTracker syncGrainsCheckerSaltCommandTracker = mock(SaltCommandTracker.class);
whenNew(SaltCommandTracker.class).withArguments(eq(saltConnector), eq(syncGrainsRunner)).thenReturn(syncGrainsCheckerSaltCommandTracker);
HighStateRunner highStateRunner = mock(HighStateRunner.class);
whenNew(HighStateRunner.class).withAnyArguments().thenReturn(highStateRunner);
SaltJobIdTracker saltJobIdTracker = mock(SaltJobIdTracker.class);
whenNew(SaltJobIdTracker.class).withAnyArguments().thenReturn(saltJobIdTracker);
saltOrchestrator.init(parallelOrchestratorComponentRunner, exitCriteria);
SaltPillarConfig saltPillarConfig = new SaltPillarConfig();
saltOrchestrator.runService(Collections.singletonList(gatewayConfig), targets, saltPillarConfig, exitCriteriaModel);
// verify pillar save
verifyNew(OrchestratorBootstrapRunner.class, times(1))
.withArguments(eq(pillarSave), eq(exitCriteria), eq(exitCriteriaModel), any(), anyInt(), anyInt());
// verify ambari server role
verifyNew(GrainAddRunner.class, times(1)).withArguments(eq(Sets.newHashSet(gatewayConfig.getPrivateAddress())),
eq(targets), eq("ambari_server"));
// verify ambari agent role
Set<String> allNodes = targets.stream().map(Node::getPrivateIp).collect(Collectors.toSet());
verifyNew(GrainAddRunner.class, times(1)).withArguments(eq(allNodes),
eq(targets), eq("ambari_agent"));
// verify two role command (amabari server, ambari agent)
verifyNew(SaltCommandTracker.class, times(2)).withArguments(eq(saltConnector), eq(addRemoveGrainRunner));
// verify two OrchestratorBootstrapRunner call with rolechecker command tracker
verifyNew(OrchestratorBootstrapRunner.class, times(2))
.withArguments(eq(roleCheckerSaltCommandTracker), eq(exitCriteria), eq(exitCriteriaModel), any(), anyInt(), anyInt());
// verify syncgrains command
verifyNew(SyncGrainsRunner.class, times(1)).withArguments(eq(allNodes), eq(targets));
verifyNew(SaltCommandTracker.class, times(1)).withArguments(eq(saltConnector), eq(syncGrainsRunner));
verifyNew(OrchestratorBootstrapRunner.class, times(1))
.withArguments(eq(syncGrainsCheckerSaltCommandTracker), eq(exitCriteria), eq(exitCriteriaModel), any(), anyInt(), anyInt());
// verify run new service
verifyNew(HighStateRunner.class, atLeastOnce()).withArguments(eq(allNodes),
eq(targets));
verifyNew(SaltJobIdTracker.class, atLeastOnce()).withArguments(eq(saltConnector), eq(highStateRunner), eq(true));
}
@Test
public void tearDownTest() throws Exception {
SaltOrchestrator saltOrchestrator = new SaltOrchestrator();
saltOrchestrator.init(parallelOrchestratorComponentRunner, exitCriteria);
Map<String, String> privateIpsByFQDN = new HashMap<>();
privateIpsByFQDN.put("10-0-0-1.example.com", "10.0.0.1");
privateIpsByFQDN.put("10-0-0-2.example.com", "10.0.0.2");
privateIpsByFQDN.put("10-0-0-3.example.com", "10.0.0.3");
mockStatic(SaltStates.class);
SaltStates.stopMinions(eq(saltConnector), eq(privateIpsByFQDN));
saltOrchestrator.tearDown(Collections.singletonList(gatewayConfig), privateIpsByFQDN);
verifyStatic();
SaltStates.stopMinions(eq(saltConnector), eq(privateIpsByFQDN));
}
@Test
public void tearDownFailTest() throws Exception {
SaltOrchestrator saltOrchestrator = new SaltOrchestrator();
saltOrchestrator.init(parallelOrchestratorComponentRunner, exitCriteria);
Map<String, String> privateIpsByFQDN = new HashMap<>();
privateIpsByFQDN.put("10-0-0-1.example.com", "10.0.0.1");
privateIpsByFQDN.put("10-0-0-2.example.com", "10.0.0.2");
privateIpsByFQDN.put("10-0-0-3.example.com", "10.0.0.3");
mockStatic(SaltStates.class);
PowerMockito.doThrow(new NullPointerException()).when(SaltStates.class);
SaltStates.stopMinions(eq(saltConnector), eq(privateIpsByFQDN));
try {
saltOrchestrator.tearDown(Collections.singletonList(gatewayConfig), privateIpsByFQDN);
fail();
} catch (CloudbreakOrchestratorFailedException e) {
assertTrue(NullPointerException.class.isInstance(e.getCause()));
}
}
@Test
public void getMissingNodesTest() {
SaltOrchestrator saltOrchestrator = new SaltOrchestrator();
saltOrchestrator.init(parallelOrchestratorComponentRunner, exitCriteria);
assertThat(saltOrchestrator.getMissingNodes(gatewayConfig, targets), hasSize(0));
}
@Test
public void getAvailableNodesTest() {
SaltOrchestrator saltOrchestrator = new SaltOrchestrator();
saltOrchestrator.init(parallelOrchestratorComponentRunner, exitCriteria);
assertThat(saltOrchestrator.getAvailableNodes(gatewayConfig, targets), hasSize(0));
}
@Test
public void isBootstrapApiAvailableTest() {
SaltOrchestrator saltOrchestrator = new SaltOrchestrator();
saltOrchestrator.init(parallelOrchestratorComponentRunner, exitCriteria);
GenericResponse response = new GenericResponse();
response.setStatusCode(200);
when(saltConnector.health()).thenReturn(response);
boolean bootstrapApiAvailable = saltOrchestrator.isBootstrapApiAvailable(gatewayConfig);
assertTrue(bootstrapApiAvailable);
}
@Test
public void isBootstrapApiAvailableFailTest() {
SaltOrchestrator saltOrchestrator = new SaltOrchestrator();
saltOrchestrator.init(parallelOrchestratorComponentRunner, exitCriteria);
GenericResponse response = new GenericResponse();
response.setStatusCode(404);
when(saltConnector.health()).thenReturn(response);
boolean bootstrapApiAvailable = saltOrchestrator.isBootstrapApiAvailable(gatewayConfig);
assertFalse(bootstrapApiAvailable);
}
}