diff options
author | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2019-11-15 16:13:32 +0100 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2019-11-15 16:13:32 +0100 |
commit | 45bd4ebef6e08ae4bceb4eef8c47f2fe4416a49a (patch) | |
tree | 83cd6647bd6e8b1cb3143d7840cd3e67804ef23a /controller-server | |
parent | 1e688268d9b55dcd3387c99d6d53c0b7a3c92201 (diff) |
Store exceptions from FlagClient as error in deploy result
Diffstat (limited to 'controller-server')
3 files changed, 177 insertions, 30 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployResult.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployResult.java index 29f7b136542..3809bf6b400 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployResult.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployResult.java @@ -7,6 +7,7 @@ import com.yahoo.vespa.flags.json.FlagData; import com.yahoo.vespa.hosted.controller.api.systemflags.v1.FlagsTarget; import com.yahoo.vespa.hosted.controller.api.systemflags.v1.wire.WireSystemFlagsDeployResult; import com.yahoo.vespa.hosted.controller.api.systemflags.v1.wire.WireSystemFlagsDeployResult.WireFlagDataChange; +import com.yahoo.vespa.hosted.controller.api.systemflags.v1.wire.WireSystemFlagsDeployResult.WireOperationFailure; import java.util.ArrayList; import java.util.HashMap; @@ -25,17 +26,31 @@ import static java.util.stream.Collectors.toList; class SystemFlagsDeployResult { private final List<FlagDataChange> flagChanges; + private final List<OperationError> errors; - SystemFlagsDeployResult(List<FlagDataChange> flagChanges) { this.flagChanges = flagChanges; } + SystemFlagsDeployResult(List<FlagDataChange> flagChanges, List<OperationError> errors) { + this.flagChanges = flagChanges; + this.errors = errors; + } + + SystemFlagsDeployResult(List<OperationError> errors) { + this(List.of(), errors); + } List<FlagDataChange> flagChanges() { return flagChanges; } + List<OperationError> errors() { + return errors; + } + static SystemFlagsDeployResult merge(List<SystemFlagsDeployResult> results) { Map<FlagDataOperation, Set<FlagsTarget>> targetsForOperation = new HashMap<>(); + List<OperationError> errors = new ArrayList<>(); for (SystemFlagsDeployResult result : results) { + errors.addAll(result.errors); for (FlagDataChange change : result.flagChanges()) { FlagDataOperation operation = new FlagDataOperation(change); targetsForOperation.computeIfAbsent(operation, k -> new HashSet<>()) @@ -46,7 +61,7 @@ class SystemFlagsDeployResult { List<FlagDataChange> mergedResult = new ArrayList<>(); targetsForOperation.forEach( (operation, targets) -> mergedResult.add(operation.toFlagDataChange(targets))); - return new SystemFlagsDeployResult(mergedResult); + return new SystemFlagsDeployResult(mergedResult, errors); } WireSystemFlagsDeployResult toWire() { @@ -60,6 +75,15 @@ class SystemFlagsDeployResult { wireChange.data = change.data().map(FlagData::toWire).orElse(null); wireChange.previousData = change.previousData().map(FlagData::toWire).orElse(null); } + wireResult.errors = new ArrayList<>(); + for (OperationError error : errors) { + var wireError = new WireOperationFailure(); + wireError.message = error.message(); + wireError.operation = error.operation().asString(); + wireError.target = error.target().asString(); + wireError.flagId = error.flagId().map(FlagId::toString).orElse(null); + wireError.data = error.flagData().map(FlagData::toWire).orElse(null); + } return wireResult; } @@ -141,8 +165,73 @@ class SystemFlagsDeployResult { } } + static class OperationError { + + final String message; + final FlagsTarget target; + final OperationType operation; + final FlagId flagId; + final FlagData flagData; + + private OperationError( + String message, FlagsTarget target, OperationType operation, FlagId flagId, FlagData flagData) { + this.message = message; + this.target = target; + this.operation = operation; + this.flagId = flagId; + this.flagData = flagData; + } + + static OperationError listFailed(String message, FlagsTarget target) { + return new OperationError(message, target, OperationType.LIST, null, null); + } + + static OperationError createFailed(String message, FlagsTarget target, FlagData flagData) { + return new OperationError(message, target, OperationType.CREATE, flagData.id(), flagData); + } + + static OperationError updateFailed(String message, FlagsTarget target, FlagData flagData) { + return new OperationError(message, target, OperationType.UPDATE, flagData.id(), flagData); + } + + static OperationError deleteFailed(String message, FlagsTarget target, FlagId id) { + return new OperationError(message, target, OperationType.UPDATE, id, null); + } + + String message() { return message; } + FlagsTarget target() { return target; } + OperationType operation() { return operation; } + Optional<FlagId> flagId() { return Optional.ofNullable(flagId); } + Optional<FlagData> flagData() { return Optional.ofNullable(flagData); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OperationError that = (OperationError) o; + return Objects.equals(message, that.message) && + Objects.equals(target, that.target) && + operation == that.operation && + Objects.equals(flagId, that.flagId) && + Objects.equals(flagData, that.flagData); + } + + @Override public int hashCode() { return Objects.hash(message, target, operation, flagId, flagData); } + + @Override + public String toString() { + return "OperationFailure{" + + "message='" + message + '\'' + + ", target=" + target + + ", operation=" + operation + + ", flagId=" + flagId + + ", flagData=" + flagData + + '}'; + } + } + enum OperationType { - CREATE("create"), DELETE("delete"), UPDATE("update"); + CREATE("create"), DELETE("delete"), UPDATE("update"), LIST("list"); private final String stringValue; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployer.java index 349db16292e..d4d529ac7d1 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployer.java @@ -2,11 +2,13 @@ package com.yahoo.vespa.hosted.controller.restapi.systemflags; import com.yahoo.concurrent.DaemonThreadFactory; +import com.yahoo.log.LogLevel; import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; import com.yahoo.vespa.flags.FlagId; import com.yahoo.vespa.flags.json.FlagData; import com.yahoo.vespa.hosted.controller.api.systemflags.v1.FlagsTarget; import com.yahoo.vespa.hosted.controller.api.systemflags.v1.SystemFlagsDataArchive; +import com.yahoo.vespa.hosted.controller.restapi.systemflags.SystemFlagsDeployResult.OperationError; import java.util.ArrayList; import java.util.Collection; @@ -19,6 +21,7 @@ import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Function; +import java.util.logging.Logger; import java.util.stream.Collectors; import static com.yahoo.vespa.hosted.controller.restapi.systemflags.SystemFlagsDeployResult.FlagDataChange; @@ -30,6 +33,8 @@ import static com.yahoo.vespa.hosted.controller.restapi.systemflags.SystemFlagsD */ class SystemFlagsDeployer { + private static final Logger log = Logger.getLogger(SystemFlagsDeployer.class.getName()); + private final FlagsClient client; private final Set<FlagsTarget> targets; private final ExecutorCompletionService<SystemFlagsDeployResult> completionService = @@ -66,34 +71,47 @@ class SystemFlagsDeployer { } } - // TODO Handle http status code 4xx/5xx (e.g for unknown flag id) private SystemFlagsDeployResult deployFlags(FlagsTarget target, Set<FlagData> flagData, boolean dryRun) { Map<FlagId, FlagData> wantedFlagData = lookupTable(flagData); - Map<FlagId, FlagData> currentFlagData = lookupTable(client.listFlagData(target)); + Map<FlagId, FlagData> currentFlagData; + try { + currentFlagData = lookupTable(client.listFlagData(target)); + } catch (Exception e) { + log.log(LogLevel.WARNING, String.format("Failed to list flag data for target '%s': %s", target, e.getMessage()), e); + return new SystemFlagsDeployResult(List.of(OperationError.listFailed(e.getMessage(), target))); + } - List<FlagDataChange> result = new ArrayList<>(); + List<OperationError> errors = new ArrayList<>(); + List<FlagDataChange> results = new ArrayList<>(); - createNewFlagData(target, dryRun, wantedFlagData, currentFlagData, result); - updateExistingFlagData(target, dryRun, wantedFlagData, currentFlagData, result); - removeOldFlagData(target, dryRun, wantedFlagData, currentFlagData, result); + createNewFlagData(target, dryRun, wantedFlagData, currentFlagData, results, errors); + updateExistingFlagData(target, dryRun, wantedFlagData, currentFlagData, results, errors); + removeOldFlagData(target, dryRun, wantedFlagData, currentFlagData, results, errors); - return new SystemFlagsDeployResult(result); + return new SystemFlagsDeployResult(results, errors); } private void createNewFlagData(FlagsTarget target, boolean dryRun, Map<FlagId, FlagData> wantedFlagData, Map<FlagId, FlagData> currentFlagData, - List<FlagDataChange> result) { + List<FlagDataChange> results, + List<OperationError> errors) { wantedFlagData.forEach((id, data) -> { FlagData currentData = currentFlagData.get(id); if (currentData != null) { return; // not a new flag } if (!dryRun) { - client.putFlagData(target, data); + try { + client.putFlagData(target, data); + } catch (Exception e) { + log.log(LogLevel.WARNING, String.format("Failed to put flag '%s' for target '%s': %s", data.id(), target, e.getMessage()), e); + errors.add(OperationError.createFailed(e.getMessage(), target, data)); + return; + } + results.add(FlagDataChange.created(id, target, data)); } - result.add(FlagDataChange.created(id, target, data)); }); } @@ -101,16 +119,23 @@ class SystemFlagsDeployer { boolean dryRun, Map<FlagId, FlagData> wantedFlagData, Map<FlagId, FlagData> currentFlagData, - List<FlagDataChange> result) { + List<FlagDataChange> results, + List<OperationError> errors) { wantedFlagData.forEach((id, wantedData) -> { FlagData currentData = currentFlagData.get(id); if (currentData == null || isEqual(currentData, wantedData)) { return; // not an flag data update } if (!dryRun) { - client.putFlagData(target, wantedData); + try { + client.putFlagData(target, wantedData); + } catch (Exception e) { + log.log(LogLevel.WARNING, String.format("Failed to update flag '%s' for target '%s': %s", wantedData.id(), target, e.getMessage()), e); + errors.add(OperationError.updateFailed(e.getMessage(), target, wantedData)); + return; + } } - result.add(FlagDataChange.updated(id, target, wantedData, currentData)); + results.add(FlagDataChange.updated(id, target, wantedData, currentData)); }); } @@ -118,15 +143,22 @@ class SystemFlagsDeployer { boolean dryRun, Map<FlagId, FlagData> wantedFlagData, Map<FlagId, FlagData> currentFlagData, - List<FlagDataChange> result) { + List<FlagDataChange> results, + List<OperationError> errors) { currentFlagData.forEach((id, data) -> { if (wantedFlagData.containsKey(id)) { return; // not a removed flag } if (!dryRun) { - client.deleteFlagData(target, id); + try { + client.deleteFlagData(target, id); + } catch (Exception e) { + log.log(LogLevel.WARNING, String.format("Failed to delete flag '%s' for target '%s': %s", id, target, e.getMessage()), e); + errors.add(OperationError.deleteFailed(e.getMessage(), target, id)); + return; + } } - result.add(FlagDataChange.deleted(id, target)); + results.add(FlagDataChange.deleted(id, target)); }); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployerTest.java index b09a2e3087b..9b876b1d07b 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployerTest.java @@ -11,9 +11,11 @@ import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock; import org.junit.Test; import java.io.IOException; +import java.io.UncheckedIOException; import java.util.List; import java.util.Set; +import static com.yahoo.vespa.hosted.controller.restapi.systemflags.SystemFlagsDeployResult.*; import static com.yahoo.vespa.hosted.controller.restapi.systemflags.SystemFlagsDeployResult.FlagDataChange; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -27,17 +29,18 @@ import static org.mockito.Mockito.when; public class SystemFlagsDeployerTest { private static final SystemName SYSTEM = SystemName.main; + private static final FlagId FLAG_ID = new FlagId("my-flag"); - @Test - public void deploys_flag_data_to_targets() throws IOException { - ZoneApiMock prodUsWest1Zone = ZoneApiMock.fromId("prod.us-west-1"); - ZoneApiMock prodUsEast3Zone = ZoneApiMock.fromId("prod.us-east-3"); - ZoneRegistryMock registry = new ZoneRegistryMock(SYSTEM).setZones(prodUsWest1Zone, prodUsEast3Zone); + private final ZoneApiMock prodUsWest1Zone = ZoneApiMock.fromId("prod.us-west-1"); + private final ZoneApiMock prodUsEast3Zone = ZoneApiMock.fromId("prod.us-east-3"); + private final ZoneRegistryMock registry = new ZoneRegistryMock(SYSTEM).setZones(prodUsWest1Zone, prodUsEast3Zone); - FlagsTarget controllerTarget = FlagsTarget.forController(SYSTEM); - FlagsTarget prodUsWest1Target = FlagsTarget.forConfigServer(registry, prodUsWest1Zone.getId()); - FlagsTarget prodUsEast3Target = FlagsTarget.forConfigServer(registry, prodUsEast3Zone.getId()); + private final FlagsTarget controllerTarget = FlagsTarget.forController(SYSTEM); + private final FlagsTarget prodUsWest1Target = FlagsTarget.forConfigServer(registry, prodUsWest1Zone.getId()); + private final FlagsTarget prodUsEast3Target = FlagsTarget.forConfigServer(registry, prodUsEast3Zone.getId()); + @Test + public void deploys_flag_data_to_targets() throws IOException { FlagsClient flagsClient = mock(FlagsClient.class); when(flagsClient.listFlagData(controllerTarget)).thenReturn(List.of()); when(flagsClient.listFlagData(prodUsWest1Target)).thenReturn(List.of(flagData("existing-prod.us-west-1.json"))); @@ -61,11 +64,34 @@ public class SystemFlagsDeployerTest { verify(flagsClient).putFlagData(prodUsEast3Target, prodUsEast3Data); verify(flagsClient, never()).putFlagData(prodUsWest1Target, defaultData); List<FlagDataChange> changes = result.flagChanges(); - FlagId flagId = new FlagId("my-flag"); + assertThat(changes).containsOnly( - FlagDataChange.created(flagId, controllerTarget, defaultData), - FlagDataChange.updated(flagId, prodUsEast3Target, prodUsEast3Data, existingProdUsEast3Data)); + FlagDataChange.created(FLAG_ID, controllerTarget, defaultData), + FlagDataChange.updated(FLAG_ID, prodUsEast3Target, prodUsEast3Data, existingProdUsEast3Data)); + } + + @Test + public void creates_error_entries_in_result_if_flag_data_operations_fail() throws IOException { + FlagsClient flagsClient = mock(FlagsClient.class); + UncheckedIOException exception = new UncheckedIOException(new IOException("I/O error message")); + when(flagsClient.listFlagData(prodUsWest1Target)).thenThrow(exception); + when(flagsClient.listFlagData(prodUsEast3Target)).thenReturn(List.of()); + + FlagData defaultData = flagData("flags/my-flag/main.json"); + SystemFlagsDataArchive archive = new SystemFlagsDataArchive.Builder() + .addFile("main.json", defaultData) + .build(); + + SystemFlagsDeployer deployer = new SystemFlagsDeployer(flagsClient, Set.of(prodUsWest1Target, prodUsEast3Target)); + + SystemFlagsDeployResult result = deployer.deployFlags(archive, false); + + System.out.println(result); + assertThat(result.errors()).containsOnly( + OperationError.listFailed(exception.getMessage(), prodUsWest1Target)); + assertThat(result.flagChanges()).containsOnly( + FlagDataChange.created(FLAG_ID, prodUsEast3Target, defaultData)); } private static FlagData flagData(String filename) throws IOException { |