Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
import com.openframe.api.dto.force.response.ForceAgentStatus;
import com.openframe.api.dto.force.response.ForceClientUninstallResponse;
import com.openframe.api.dto.force.response.ForceClientUninstallResponseItem;
import com.openframe.data.document.delivery.DeliveryType;
import com.openframe.data.document.device.Machine;
import com.openframe.data.nats.delivery.ClientUninstallDeliverySeed;
import com.openframe.data.nats.publisher.ClientUninstallNatsPublisher;
import com.openframe.data.repository.device.MachineRepository;
import com.openframe.delivery.config.DeliveryProperties;
import com.openframe.delivery.dispatch.DeliveryDispatcher;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand All @@ -25,6 +29,8 @@ public class ForceClientUninstallService {

private final ClientUninstallNatsPublisher clientUninstallNatsPublisher;
private final MachineRepository machineRepository;
private final DeliveryProperties deliveryProperties;
private final DeliveryDispatcher deliveryDispatcher;

public ForceClientUninstallResponse process(ForceClientUninstallRequest request) {
List<String> machineIds = request.getMachineIds();
Expand Down Expand Up @@ -57,7 +63,11 @@ private ForceClientUninstallResponseItem processMachine(String machineId) {
return buildResponseItem(machineId, ForceAgentStatus.FAILED);
}

clientUninstallNatsPublisher.publish(machineId);
if (deliveryProperties.isEnabled(DeliveryType.CLIENT_UNINSTALL)) {
deliveryDispatcher.dispatch(new ClientUninstallDeliverySeed(machineId));
} else {
clientUninstallNatsPublisher.publish(machineId);
}

markPendingDeletion(machine);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.openframe.api.service;

import com.openframe.api.dto.force.request.ForceClientUninstallRequest;
import com.openframe.api.dto.force.response.ForceAgentStatus;
import com.openframe.api.dto.force.response.ForceClientUninstallResponse;
import com.openframe.data.document.delivery.DeliveryType;
import com.openframe.data.document.device.DeviceStatus;
import com.openframe.data.document.device.Machine;
import com.openframe.data.nats.delivery.ClientUninstallDeliverySeed;
import com.openframe.data.nats.publisher.ClientUninstallNatsPublisher;
import com.openframe.data.repository.device.MachineRepository;
import com.openframe.delivery.config.DeliveryProperties;
import com.openframe.delivery.dispatch.DeliveryDispatcher;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class ForceClientUninstallServiceTest {

private static final String MACHINE_ID = "mach-42";

@Mock private ClientUninstallNatsPublisher clientUninstallNatsPublisher;
@Mock private MachineRepository machineRepository;
@Mock private DeliveryProperties deliveryProperties;
@Mock private DeliveryDispatcher deliveryDispatcher;

@Captor private ArgumentCaptor<ClientUninstallDeliverySeed> seedCaptor;

@InjectMocks private ForceClientUninstallService service;

private Machine machine;
private ForceClientUninstallRequest request;

@BeforeEach
void setUp() {
machine = new Machine();
machine.setMachineId(MACHINE_ID);
machine.setStatus(DeviceStatus.ONLINE);
request = new ForceClientUninstallRequest();
request.setMachineIds(List.of(MACHINE_ID));
when(machineRepository.findByMachineId(MACHINE_ID)).thenReturn(Optional.of(machine));
}

@Test
void process_flagOff_publishedToJetStreamAndMarkedPendingDeletion() {
// setup
when(deliveryProperties.isEnabled(DeliveryType.CLIENT_UNINSTALL)).thenReturn(false);

// execution
ForceClientUninstallResponse response = service.process(request);

// verifications
verify(clientUninstallNatsPublisher).publish(MACHINE_ID);
assertThat(machine.getStatus()).isEqualTo(DeviceStatus.PENDING_DELETION);
verify(machineRepository).save(machine);
verifyNoInteractions(deliveryDispatcher);
assertThat(response.getItems().get(0).getStatus()).isEqualTo(ForceAgentStatus.PROCESSED);
}

@Test
void process_flagOn_dispatchedThroughEngineAndMarkedPendingDeletion() {
// setup
when(deliveryProperties.isEnabled(DeliveryType.CLIENT_UNINSTALL)).thenReturn(true);

// execution
ForceClientUninstallResponse response = service.process(request);

// verifications
verify(deliveryDispatcher).dispatch(seedCaptor.capture());
assertThat(seedCaptor.getValue().getMachineId()).isEqualTo(MACHINE_ID);
assertThat(machine.getStatus()).isEqualTo(DeviceStatus.PENDING_DELETION);
verify(machineRepository).save(machine);
verifyNoInteractions(clientUninstallNatsPublisher);
assertThat(response.getItems().get(0).getStatus()).isEqualTo(ForceAgentStatus.PROCESSED);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.openframe.client.service.NatsTopicMachineIdExtractor;
import com.openframe.data.document.delivery.DeliveryType;
import com.openframe.data.nats.delivery.DeliveryResultMessage;
import com.openframe.data.nats.listener.AbstractJetStreamPushListener;
import com.openframe.delivery.metrics.DeliveryMetrics;
Expand Down Expand Up @@ -96,13 +95,10 @@ protected void handleMessage(Message message) {

private void apply(String machineId, DeliveryResultMessage report) {
DeliveryRef delivery = report.getDelivery();
DeliveryType type = delivery.getType();
String targetId = delivery.getTargetId();
String dispatchId = delivery.getDispatchId();
switch (report.getResult()) {
case ACKED -> deliveryTracker.acknowledge(type, targetId, machineId, dispatchId);
case DONE -> deliveryTracker.complete(type, targetId, machineId, dispatchId);
case FAILED -> deliveryTracker.fail(type, targetId, machineId, dispatchId, report.getError());
case ACKED -> deliveryTracker.acknowledge(delivery, machineId);
case DONE -> deliveryTracker.done(delivery, machineId);
case FAILED -> deliveryTracker.fail(delivery, machineId, report.getError());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.openframe.client.service.NatsTopicMachineIdExtractor;
import com.openframe.data.document.delivery.DeliveryType;
import com.openframe.delivery.metrics.DeliveryMetrics;
import com.openframe.delivery.spec.DeliveryRef;
import com.openframe.delivery.track.DeliveryTracker;
import io.nats.client.Connection;
import io.nats.client.Message;
Expand All @@ -27,6 +28,7 @@ class DeliveryResultListenerTest {
private static final String SUBJECT = "machine.mach-42.delivery.result";
private static final String TOOL_AGENT_ID = "fleetmdm-agent";
private static final String DISPATCH_ID = "d-1";
private static final DeliveryRef REF = new DeliveryRef(DeliveryType.TOOL_INSTALLATION, TOOL_AGENT_ID, DISPATCH_ID);
private static final String ERROR = "download failed";
private static final String ACKED =
"{\"delivery\":{\"type\":\"TOOL_INSTALLATION\",\"targetId\":\"fleetmdm-agent\",\"dispatchId\":\"d-1\"},\"result\":\"ACKED\"}";
Expand Down Expand Up @@ -64,7 +66,7 @@ void handleMessage_acked_trackerAcknowledgesDispatch() {
listener.handleMessage(message);

// verifications
verify(deliveryTracker).acknowledge(DeliveryType.TOOL_INSTALLATION, TOOL_AGENT_ID, MACHINE_ID, DISPATCH_ID);
verify(deliveryTracker).acknowledge(REF, MACHINE_ID);
verify(message).ack();
}

Expand All @@ -77,7 +79,7 @@ void handleMessage_done_trackerCompletesDispatch() {
listener.handleMessage(message);

// verifications
verify(deliveryTracker).complete(DeliveryType.TOOL_INSTALLATION, TOOL_AGENT_ID, MACHINE_ID, DISPATCH_ID);
verify(deliveryTracker).done(REF, MACHINE_ID);
verify(message).ack();
}

Expand All @@ -90,7 +92,7 @@ void handleMessage_failed_trackerFailsDispatchWithError() {
listener.handleMessage(message);

// verifications
verify(deliveryTracker).fail(DeliveryType.TOOL_INSTALLATION, TOOL_AGENT_ID, MACHINE_ID, DISPATCH_ID, ERROR);
verify(deliveryTracker).fail(REF, MACHINE_ID, ERROR);
verify(message).ack();
}

Expand Down Expand Up @@ -168,7 +170,7 @@ void handleMessage_malformedPayload_rejectedCountedAndAcked() {
void handleMessage_trackerThrows_leftUnackedForRedelivery() {
// setup
stubMessage(ACKED);
doThrow(new IllegalStateException("mongo down")).when(deliveryTracker).acknowledge(DeliveryType.TOOL_INSTALLATION, TOOL_AGENT_ID, MACHINE_ID, DISPATCH_ID);
doThrow(new IllegalStateException("mongo down")).when(deliveryTracker).acknowledge(REF, MACHINE_ID);

// execution
listener.handleMessage(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ public interface CustomMachineDeliveryRepository {

boolean markDone(String id, String dispatchId, Set<DeliveryStatus> from, Instant finishedAt, Instant expiresAt);

boolean markCancelled(String id, Set<DeliveryStatus> from, Instant finishedAt, Instant expiresAt);

boolean markCancelled(String id, Set<DeliveryStatus> from, Instant dispatchedAt, Instant finishedAt, Instant expiresAt);

boolean markFailed(String id, Set<DeliveryStatus> from, Instant dispatchedAt, DeliveryFailure failure, Instant finishedAt, Instant expiresAt);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,6 @@ public boolean markDone(String id, String dispatchId, Set<DeliveryStatus> from,
return updateOne(thisDispatch(id, from, dispatchId), update);
}

@Override
public boolean markCancelled(String id, Set<DeliveryStatus> from, Instant finishedAt, Instant expiresAt) {
Update update = closed(DeliveryStatus.CANCELLED, finishedAt, expiresAt);
return updateOne(stillIn(id, from), update);
}

@Override
public boolean markCancelled(String id, Set<DeliveryStatus> from, Instant dispatchedAt, Instant finishedAt, Instant expiresAt) {
Update update = closed(DeliveryStatus.CANCELLED, finishedAt, expiresAt);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.openframe.data.nats.delivery;

import com.openframe.data.document.delivery.DeliveryType;
import com.openframe.delivery.spec.DeliverySeed;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class ClientUninstallDeliverySeed implements DeliverySeed {

private static final String TARGET_ID = "openframe-client";

private final String machineId;

@Override
public DeliveryType getType() {
return DeliveryType.CLIENT_UNINSTALL;
}

@Override
public String getTargetId() {
return TARGET_ID;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.openframe.data.nats.delivery;

import com.openframe.data.document.delivery.DeliveryType;
import com.openframe.data.document.device.DeviceStatus;
import com.openframe.data.nats.model.ClientUninstallMessage;
import com.openframe.delivery.spec.DeliveryRequest;
import com.openframe.delivery.spec.DeliverySpec;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.util.EnumSet;
import java.util.Set;

import static java.lang.String.format;

@Component
@ConditionalOnProperty("spring.cloud.stream.enabled")
public class ClientUninstallDeliverySpec implements DeliverySpec<ClientUninstallDeliverySeed, ClientUninstallMessage> {

private static final String SUBJECT_TEMPLATE = "machine.%s.client-uninstall";

@Override
public DeliveryType getType() {
return DeliveryType.CLIENT_UNINSTALL;
}

@Override
public Class<ClientUninstallMessage> getPayloadClass() {
return ClientUninstallMessage.class;
}

@Override
public DeliveryRequest<ClientUninstallMessage> request(ClientUninstallDeliverySeed seed) {
ClientUninstallMessage message = new ClientUninstallMessage();
message.setIssuedAt(Instant.now().toString());
return DeliveryRequest.<ClientUninstallMessage>builder()
.type(DeliveryType.CLIENT_UNINSTALL)
.targetId(seed.getTargetId())
.machineId(seed.getMachineId())
.payload(message)
.build();
}

@Override
public String subject(String machineId) {
return format(SUBJECT_TEMPLATE, machineId);
}

// the uninstall is the one command a machine marked for deletion is still waiting for
@Override
public Set<DeviceStatus> getDeliverableStatuses() {
return EnumSet.of(DeviceStatus.ONLINE, DeviceStatus.OFFLINE, DeviceStatus.PENDING, DeviceStatus.PENDING_DELETION);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ public class ToolInstallationDeliverySeed implements DeliverySeed {
private final boolean reinstall;

@Override
public DeliveryType type() {
public DeliveryType getType() {
return DeliveryType.TOOL_INSTALLATION;
}

@Override
public String getTargetId() {
return toolAgent.getKey();
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.openframe.data.nats.delivery;

import com.openframe.data.document.delivery.DeliveryFailure;
import com.openframe.data.document.delivery.DeliveryType;
import com.openframe.data.document.delivery.MachineDelivery;
import com.openframe.data.document.tool.IntegratedTool;
import com.openframe.data.document.toolagent.IntegratedToolAgent;
import com.openframe.data.document.toolagent.ToolAgentAsset;
Expand Down Expand Up @@ -41,15 +39,13 @@ public Class<ToolInstallationMessage> getPayloadClass() {
return ToolInstallationMessage.class;
}

// targetId must equal the agentType the agent sends in installed-agent, or complete() never finds the row
@Override
public DeliveryRequest<ToolInstallationMessage> request(ToolInstallationDeliverySeed seed) {
IntegratedToolAgent toolAgent = seed.getToolAgent();
ToolInstallationMessage message = buildMessage(toolAgent, seed.getTool(), seed.isReinstall());
String targetId = toolAgent.getKey();
return DeliveryRequest.<ToolInstallationMessage>builder()
.type(DeliveryType.TOOL_INSTALLATION)
.targetId(targetId)
.targetId(seed.getTargetId())
.machineId(seed.getMachineId())
.payload(message)
.build();
Expand All @@ -60,11 +56,6 @@ public String subject(String machineId) {
return format(SUBJECT_TEMPLATE, machineId);
}

@Override
public void onFailed(MachineDelivery delivery, DeliveryFailure failure) {
// intentionally empty: a failed install leaves nothing to compensate
}

private ToolInstallationMessage buildMessage(IntegratedToolAgent toolAgent, IntegratedTool tool, boolean reinstall) {
String version = toolAgent.getVersion();
ToolInstallationMessage message = new ToolInstallationMessage();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package com.openframe.data.nats.model;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.openframe.delivery.spec.DeliveryPayload;
import com.openframe.delivery.spec.DeliveryRef;
import lombok.Data;

@Data
public class ClientUninstallMessage {
public class ClientUninstallMessage implements DeliveryPayload {

@JsonInclude(JsonInclude.Include.NON_NULL)
private DeliveryRef delivery;

/**
* When the command was issued (ISO-8601 instant). Lets the agent ignore
Expand Down
Loading
Loading