package io.airlift.airship.coordinator; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Splitter; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import io.airlift.airship.shared.AgentLifecycleState; import io.airlift.airship.shared.AgentStatus; import io.airlift.airship.shared.Assignment; import io.airlift.airship.shared.HttpUriBuilder; import io.airlift.airship.shared.Installation; import io.airlift.airship.shared.InstallationUtils; import io.airlift.airship.shared.Repository; import io.airlift.airship.shared.SlotStatus; import javax.annotation.Nullable; import javax.ws.rs.core.UriInfo; import java.net.URI; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.UUID; import static com.google.common.base.Predicates.notNull; import static io.airlift.airship.coordinator.StringFunctions.startsWith; import static io.airlift.airship.coordinator.StringFunctions.toLowerCase; import static io.airlift.airship.shared.AgentLifecycleState.ONLINE; import static io.airlift.airship.shared.InstallationUtils.getAvailableResources; import static io.airlift.airship.shared.InstallationUtils.resourcesAreAvailable; import static io.airlift.airship.shared.InstallationUtils.toInstallation; import static java.lang.String.format; public class AgentFilterBuilder { public static AgentFilterBuilder builder() { return new AgentFilterBuilder(); } public static Predicate<AgentStatus> build(UriInfo uriInfo, List<String> allAgentUuids, List<UUID> allSlotUuids) { return build(uriInfo, allAgentUuids, allSlotUuids, false, null); } public static Predicate<AgentStatus> build(UriInfo uriInfo, List<String> allAgentUuids, List<UUID> allSlotUuids, boolean allowDuplicateInstallationsOnAnAgent, Repository repository) { AgentFilterBuilder builder = new AgentFilterBuilder(); for (Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) { if ("uuid".equals(entry.getKey())) { for (String uuidFilter : entry.getValue()) { builder.addUuidFilter(uuidFilter); } } if ("!uuid".equals(entry.getKey())) { for (String notUuidFilter : entry.getValue()) { builder.addNotUuidFilter(notUuidFilter); } } if ("state".equals(entry.getKey())) { for (String stateFilter : entry.getValue()) { builder.addStateFilter(stateFilter); } } if ("!state".equals(entry.getKey())) { for (String notStateFilter : entry.getValue()) { builder.addNotStateFilter(notStateFilter); } } else if ("host".equals(entry.getKey())) { for (String hostGlob : entry.getValue()) { builder.addHostGlobFilter(hostGlob); } } else if ("!host".equals(entry.getKey())) { for (String notHostGlob : entry.getValue()) { builder.addNotHostGlobFilter(notHostGlob); } } else if ("machine".equals(entry.getKey())) { for (String machineGlob : entry.getValue()) { builder.addMachineGlobFilter(machineGlob); } } else if ("!machine".equals(entry.getKey())) { for (String notMachineGlob : entry.getValue()) { builder.addNotMachineGlobFilter(notMachineGlob); } } else if ("slotUuid".equals(entry.getKey())) { for (String uuidGlob : entry.getValue()) { builder.addSlotUuidGlobFilter(uuidGlob); } } else if ("!slotUuid".equals(entry.getKey())) { for (String notUuidGlob : entry.getValue()) { builder.addNotSlotUuidGlobFilter(notUuidGlob); } } else if ("assignable".equals(entry.getKey())) { Preconditions.checkArgument(repository != null, "repository is null"); for (String assignment : entry.getValue()) { List<String> split = ImmutableList.copyOf(Splitter.on("@").limit(2).split(assignment)); Preconditions.checkArgument(split.size() == 2, "Invalid canInstall filter %s", assignment); builder.addAssignableFilter(new Assignment(split.get(0), "@" + split.get(1))); } } else if ("all".equals(entry.getKey())) { builder.selectAll(); } } return builder.build(allAgentUuids, allSlotUuids, allowDuplicateInstallationsOnAnAgent, repository); } private final List<String> uuidFilters = Lists.newArrayListWithCapacity(6); private final List<String> notUuidFilters = Lists.newArrayListWithCapacity(6); private final List<AgentLifecycleState> stateFilters = Lists.newArrayListWithCapacity(6); private final List<AgentLifecycleState> notStateFilters = Lists.newArrayListWithCapacity(6); private final List<String> slotUuidGlobs = Lists.newArrayListWithCapacity(6); private final List<String> notSlotUuidGlobs = Lists.newArrayListWithCapacity(6); private final List<String> hostGlobs = Lists.newArrayListWithCapacity(6); private final List<String> notHostGlobs = Lists.newArrayListWithCapacity(6); private final List<String> machineGlobs = Lists.newArrayListWithCapacity(6); private final List<String> notMachineGlobs = Lists.newArrayListWithCapacity(6); private final List<Assignment> assignableFilters = Lists.newArrayListWithCapacity(6); private boolean selectAll; public void addUuidFilter(String uuid) { Preconditions.checkNotNull(uuid, "uuid is null"); uuidFilters.add(uuid); } public void addNotUuidFilter(String notUuid) { Preconditions.checkNotNull(notUuid, "notUuid is null"); notUuidFilters.add(notUuid); } public void addStateFilter(String stateFilter) { Preconditions.checkNotNull(stateFilter, "stateFilter is null"); AgentLifecycleState state = AgentLifecycleState.valueOf(stateFilter.toUpperCase()); Preconditions.checkArgument(state != null, "unknown state %s", stateFilter); stateFilters.add(state); } public void addNotStateFilter(String notStateFilter) { Preconditions.checkNotNull(notStateFilter, "notStateFilter is null"); AgentLifecycleState state = AgentLifecycleState.valueOf(notStateFilter.toUpperCase()); Preconditions.checkArgument(state != null, "unknown state %s", notStateFilter); notStateFilters.add(state); } public void addSlotUuidGlobFilter(String slotUuidGlob) { Preconditions.checkNotNull(slotUuidGlob, "slotUuidGlob is null"); slotUuidGlobs.add(slotUuidGlob); } public void addNotSlotUuidGlobFilter(String notSlotUuidGlob) { Preconditions.checkNotNull(notSlotUuidGlob, "notSlotUuidGlob is null"); notSlotUuidGlobs.add(notSlotUuidGlob); } public void addHostGlobFilter(String hostGlob) { Preconditions.checkNotNull(hostGlob, "hostGlob is null"); hostGlobs.add(hostGlob); } public void addNotHostGlobFilter(String notHostGlob) { Preconditions.checkNotNull(notHostGlob, "notHostGlob is null"); notHostGlobs.add(notHostGlob); } public void addMachineGlobFilter(String machineGlob) { Preconditions.checkNotNull(machineGlob, "machineGlob is null"); machineGlobs.add(machineGlob); } public void addNotMachineGlobFilter(String notMachineGlob) { Preconditions.checkNotNull(notMachineGlob, "notMachineGlob is null"); notMachineGlobs.add(notMachineGlob); } public void addAssignableFilter(Assignment assignment) { Preconditions.checkNotNull(assignment, "assignment is null"); assignableFilters.add(assignment); } public void selectAll() { this.selectAll = true; } public Predicate<AgentStatus> build(final List<String> allAgentUuids, final List<UUID> allSlotUuids, final boolean allowDuplicateInstallationsOnAnAgent, final Repository repository) { Predicate<AgentStatus> include = buildIncludesPredicate(allAgentUuids, allSlotUuids, allowDuplicateInstallationsOnAnAgent, repository); Optional<Predicate<AgentStatus>> excludesPredicate = buildExcludesPredicate(allAgentUuids, allSlotUuids); if (excludesPredicate.isPresent()) { // includes and not excluded return Predicates.and(include, Predicates.not(excludesPredicate.get())); } return include; } private Predicate<AgentStatus> buildIncludesPredicate(final List<String> allAgentUuids, final List<UUID> allSlotUuids, final boolean allowDuplicateInstallationsOnAnAgent, final Repository repository) { List<Predicate<AgentStatus>> andPredicates = Lists.newArrayListWithCapacity(6); if (!uuidFilters.isEmpty()) { Predicate<AgentStatus> predicate = Predicates.or(Lists.transform(uuidFilters, new Function<String, UuidPredicate>() { @Override public UuidPredicate apply(String uuid) { return new UuidPredicate(uuid, allAgentUuids); } })); andPredicates.add(predicate); } if (!stateFilters.isEmpty()) { Predicate<AgentStatus> predicate = Predicates.or(Lists.transform(stateFilters, new Function<AgentLifecycleState, StatePredicate>() { @Override public StatePredicate apply(AgentLifecycleState state) { return new StatePredicate(state); } })); andPredicates.add(predicate); } if (!slotUuidGlobs.isEmpty()) { Predicate<AgentStatus> predicate = Predicates.or(Lists.transform(slotUuidGlobs, new Function<String, SlotUuidPredicate>() { @Override public SlotUuidPredicate apply(String slotUuidGlob) { return new SlotUuidPredicate(slotUuidGlob, allSlotUuids); } })); andPredicates.add(predicate); } if (!hostGlobs.isEmpty()) { Predicate<AgentStatus> predicate = Predicates.or(Lists.transform(hostGlobs, new Function<String, HostPredicate>() { @Override public HostPredicate apply(String hostGlob) { return new HostPredicate(hostGlob); } })); andPredicates.add(predicate); } if (!machineGlobs.isEmpty()) { Predicate<AgentStatus> predicate = Predicates.or(Lists.transform(machineGlobs, new Function<String, MachinePredicate>() { @Override public MachinePredicate apply(String machineGlob) { return new MachinePredicate(machineGlob); } })); andPredicates.add(predicate); } if (!assignableFilters.isEmpty()) { Predicate<AgentStatus> predicate = Predicates.or(Lists.transform(assignableFilters, new Function<Assignment, AssignablePredicate>() { @Override public AssignablePredicate apply(Assignment assignment) { return new AssignablePredicate(assignment, allowDuplicateInstallationsOnAnAgent, repository); } })); andPredicates.add(predicate); } if (selectAll || andPredicates.isEmpty()) { return Predicates.alwaysTrue(); } else { return Predicates.and(andPredicates); } } private Optional<Predicate<AgentStatus>> buildExcludesPredicate(final List<String> allAgentUuids, final List<UUID> allSlotUuids) { List<Predicate<AgentStatus>> excludes = Lists.newArrayListWithCapacity(6); excludes.addAll(Lists.transform(notUuidFilters, new Function<String, UuidPredicate>() { @Override public UuidPredicate apply(String uuid) { return new UuidPredicate(uuid, allAgentUuids); } })); excludes.addAll(Lists.transform(notStateFilters, new Function<AgentLifecycleState, StatePredicate>() { @Override public StatePredicate apply(AgentLifecycleState state) { return new StatePredicate(state); } })); excludes.addAll(Lists.transform(notSlotUuidGlobs, new Function<String, SlotUuidPredicate>() { @Override public SlotUuidPredicate apply(String slotUuidGlob) { return new SlotUuidPredicate(slotUuidGlob, allSlotUuids); } })); excludes.addAll(Lists.transform(notHostGlobs, new Function<String, HostPredicate>() { @Override public HostPredicate apply(String hostGlob) { return new HostPredicate(hostGlob); } })); excludes.addAll(Lists.transform(notMachineGlobs, new Function<String, MachinePredicate>() { @Override public MachinePredicate apply(String machineGlob) { return new MachinePredicate(machineGlob); } })); if (excludes.isEmpty()) { return Optional.absent(); } return Optional.of(Predicates.or(excludes)); } public URI buildUri(URI baseUri) { HttpUriBuilder uriBuilder = HttpUriBuilder.uriBuilderFrom(baseUri); return buildUri(uriBuilder); } public URI buildUri(HttpUriBuilder uriBuilder) { for (String uuidFilter : uuidFilters) { uriBuilder.addParameter("uuid", uuidFilter); } for (String notUuidFilter : notUuidFilters) { uriBuilder.addParameter("!uuid", notUuidFilter); } for (String hostGlob : hostGlobs) { uriBuilder.addParameter("host", hostGlob); } for (String notHostGlob : notHostGlobs) { uriBuilder.addParameter("!host", notHostGlob); } for (String machineGlob : machineGlobs) { uriBuilder.addParameter("machine", machineGlob); } for (String notMachineGlob : notMachineGlobs) { uriBuilder.addParameter("!machine", notMachineGlob); } for (AgentLifecycleState stateFilter : stateFilters) { uriBuilder.addParameter("state", stateFilter.name()); } for (AgentLifecycleState notStateFilter : notStateFilters) { uriBuilder.addParameter("!state", notStateFilter.name()); } for (String shortId : slotUuidGlobs) { uriBuilder.addParameter("slotUuid", shortId); } for (String notShortId : notSlotUuidGlobs) { uriBuilder.addParameter("!slotUuid", notShortId); } for (Assignment assignment : assignableFilters) { uriBuilder.addParameter("assignable", assignment.getBinary() + assignment.getConfig()); } if (selectAll) { uriBuilder.addParameter("all"); } return uriBuilder.build(); } public static class UuidPredicate implements Predicate<AgentStatus> { private final String uuid; public UuidPredicate(String shortId, List<String> allUuids) { allUuids = ImmutableList.copyOf(Iterables.filter(allUuids, notNull())); Predicate<String> startsWithPrefix = Predicates.compose(startsWith(shortId.toLowerCase()), toLowerCase()); Collection<String> matches = Collections2.filter(allUuids, startsWithPrefix); if (matches.size() > 1) { throw new IllegalArgumentException(format("Ambiguous expansion for id '%s': %s", shortId, matches)); } if (matches.isEmpty()) { uuid = null; } else { uuid = matches.iterator().next(); } } @Override public boolean apply(@Nullable AgentStatus agentStatus) { return agentStatus != null && agentStatus.getAgentId() != null && uuid != null && uuid.equals(agentStatus.getAgentId()); } } public static class SlotUuidPredicate implements Predicate<AgentStatus> { private final SlotFilterBuilder.SlotUuidPredicate predicate; public SlotUuidPredicate(UUID slotUuid) { predicate = new SlotFilterBuilder.SlotUuidPredicate(slotUuid); } public SlotUuidPredicate(String slotUuidGlobGlob, List<UUID> allUuids) { predicate = new SlotFilterBuilder.SlotUuidPredicate(slotUuidGlobGlob, allUuids); } @Override public boolean apply(AgentStatus agentStatus) { if (agentStatus == null) { return false; } for (SlotStatus slotStatus : agentStatus.getSlotStatuses()) { if (predicate.apply(slotStatus)) { return true; } } return false; } } public static class HostPredicate implements Predicate<AgentStatus> { private final UriHostPredicate predicate; public HostPredicate(String hostGlob) { predicate = new UriHostPredicate(hostGlob.toLowerCase()); } @Override public boolean apply(@Nullable AgentStatus agentStatus) { return agentStatus != null && (predicate.apply(agentStatus.getExternalUri()) || predicate.apply(agentStatus.getInternalUri())); } } public static class MachinePredicate implements Predicate<AgentStatus> { private final GlobPredicate predicate; public MachinePredicate(String machineGlob) { predicate = new GlobPredicate(machineGlob); } @Override public boolean apply(@Nullable AgentStatus agentStatus) { return agentStatus != null && predicate.apply(agentStatus.getInstanceId()); } } public static class StatePredicate implements Predicate<AgentStatus> { private final AgentLifecycleState state; public StatePredicate(AgentLifecycleState state) { this.state = state; } @Override public boolean apply(@Nullable AgentStatus agentStatus) { return agentStatus != null && agentStatus.getState() == state; } } public static class AssignablePredicate implements Predicate<AgentStatus> { private final Assignment assignment; private final boolean allowDuplicateInstallationsOnAnAgent; private final Repository repository; public AssignablePredicate(Assignment assignment, boolean allowDuplicateInstallationsOnAnAgent, Repository repository) { this.assignment = InstallationUtils.resolveAssignment(repository, assignment); this.allowDuplicateInstallationsOnAnAgent = allowDuplicateInstallationsOnAnAgent; this.repository = repository; } @Override public boolean apply(@Nullable AgentStatus status) { // We can only install on online agents if ((status == null) || (status.getState() != ONLINE)) { return false; } // Constraints: normally we only allow only instance of a binary+config on each agent if (!allowDuplicateInstallationsOnAnAgent) { for (SlotStatus slot : status.getSlotStatuses()) { if ((slot.getAssignment() != null) && assignmentEqualsIgnoreVersion(slot.getAssignment())) { return false; } } } // agents without declared resources are considered to have unlimited resources if (!status.getResources().isEmpty()) { // verify that required resources are available Installation installation = toInstallation(repository, assignment); Map<String, Integer> availableResources = getAvailableResources(status); if (!resourcesAreAvailable(availableResources, installation.getResources())) { return false; } } return true; } private boolean assignmentEqualsIgnoreVersion(Assignment a) { return repository.binaryEqualsIgnoreVersion(assignment.getBinary(), a.getBinary()) && repository.configEqualsIgnoreVersion(assignment.getConfig(), a.getConfig()); } } }