/* * Copyright 2016 The Simple File Server Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sfs.nodes; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import io.vertx.core.Handler; import io.vertx.core.logging.Logger; import org.sfs.Server; import org.sfs.VertxContext; import org.sfs.rx.Defer; import org.sfs.rx.RxHelper; import org.sfs.rx.ToVoid; import org.sfs.vo.TransientServiceDef; import org.sfs.vo.XVolume; import rx.Observable; import rx.Subscriber; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.NavigableMap; import java.util.Set; import java.util.TreeMap; import static com.google.common.base.Optional.fromNullable; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.FluentIterable.from; import static io.vertx.core.logging.LoggerFactory.getLogger; import static java.lang.Boolean.TRUE; import static java.util.Collections.emptyList; import static java.util.concurrent.TimeUnit.SECONDS; import static org.sfs.filesystem.volume.Volume.Status; import static org.sfs.rx.Defer.aVoid; public class ClusterInfo { private static final Logger LOGGER = getLogger(ClusterInfo.class); private final long refreshInterval = SECONDS.toMillis(1); private boolean started = false; private VertxContext<Server> vertxContext; private volatile List<TransientServiceDef> allNodes; private volatile Map<String, TransientServiceDef> nodesByStartedVolume; private volatile NavigableMap<Long, Set<String>> startedVolumeIdByUseableSpace; private volatile int numberOfStartedVolumes; private volatile TransientServiceDef currentMaintainerNode; private volatile List<TransientServiceDef> masterNodes; private Long timerId; public Observable<Void> open(VertxContext<Server> vertxContext) { this.vertxContext = vertxContext; return aVoid() .flatMap(aVoid -> updateClusterInfo(vertxContext)) .doOnNext(aVoid -> startTimer()) .doOnNext(aVoid -> started = true); } public Observable<Void> close(VertxContext<Server> vertxContext) { return aVoid() .doOnNext(aVoid -> started = false) .doOnNext(aVoid -> stopTimer()) .doOnNext(aVoid -> { if (nodesByStartedVolume != null) { nodesByStartedVolume.clear(); nodesByStartedVolume = null; } currentMaintainerNode = null; masterNodes = null; }); } public List<TransientServiceDef> getAllNodes() { return allNodes; } public long getRefreshInterval() { return refreshInterval; } public int getNumberOfStartedVolumes() { return numberOfStartedVolumes; } public NavigableMap<Long, Set<String>> getStartedVolumeIdByUseableSpace() { NavigableMap<Long, Set<String>> snapshot = startedVolumeIdByUseableSpace; return snapshot != null ? snapshot : Collections.emptyNavigableMap(); } public Observable<Void> forceRefresh(VertxContext<Server> vertxContext) { return aVoid() .doOnNext(aVoid -> checkStarted()) .flatMap(aVoid -> updateClusterInfo(vertxContext)); } public Iterable<TransientServiceDef> getNodesWithStartedVolumes() { checkStarted(); Map<String, TransientServiceDef> snapshot = nodesByStartedVolume; if (snapshot == null) { return emptyList(); } return snapshot.values(); } public Observable<Boolean> isOnline() { return Observable.defer(() -> { try { return Defer.just(getCurrentMasterNode() != null); } catch (Throwable e) { return Defer.just(false); } }); } public Optional<XNode> getNodeForVolume(VertxContext<Server> vertxContext, String volumeId) { Nodes nodes = vertxContext.verticle().nodes(); TransientServiceDef serviceDef = nodesByStartedVolume.get(volumeId); if (serviceDef != null) { return Optional.of(nodes.remoteNode(vertxContext, serviceDef)); } else { return Optional.absent(); } } public Optional<TransientServiceDef> getServiceDefForVolume(String volumeId) { checkStarted(); Map<String, TransientServiceDef> snapshot = nodesByStartedVolume; return snapshot != null ? Optional.fromNullable(snapshot.get(volumeId)) : Optional.absent(); } public Optional<TransientServiceDef> getCurrentMaintainerNode() { checkStarted(); return fromNullable(currentMaintainerNode); } public TransientServiceDef getCurrentMasterNode() { checkStarted(); List<TransientServiceDef> snapshot = masterNodes; Preconditions.checkState(!snapshot.isEmpty(), "no elected master node"); Preconditions.checkState(snapshot.size() <= 1, "more than one elected master node"); return snapshot.get(0); } public Iterable<TransientServiceDef> getDataNodes() { checkStarted(); return from(getNodesWithStartedVolumes()) .filter(input -> { Boolean dataNode = input.getDataNode().orNull(); return TRUE.equals(dataNode); }); } protected void startTimer() { Handler<Long> handler = new Handler<Long>() { Handler<Long> _this = this; @Override public void handle(Long event) { updateClusterInfo(vertxContext) .subscribe(new Subscriber<Void>() { @Override public void onCompleted() { timerId = vertxContext.vertx().setTimer(refreshInterval, _this); } @Override public void onError(Throwable e) { LOGGER.debug("Handling Exception", e); timerId = vertxContext.vertx().setTimer(refreshInterval, _this); } @Override public void onNext(Void aVoid1) { } }); } }; timerId = vertxContext.vertx().setTimer(refreshInterval, handler); } protected void stopTimer() { if (timerId != null) { vertxContext.vertx().cancelTimer(timerId); } } protected void checkStarted() { checkState(started, "Not started"); } protected Observable<Void> updateClusterInfo(VertxContext<Server> vertxContext) { Nodes nodes = vertxContext.verticle().nodes(); List<TransientServiceDef> transientServiceDefs = new ArrayList<>(); return RxHelper.iterate(vertxContext.vertx(), nodes.getClusterHosts(), hostAndPort -> { XNode xNode = nodes.remoteNode(vertxContext, hostAndPort); return xNode.getNodeStats() .doOnNext(transientServiceDefOptional -> { if (transientServiceDefOptional.isPresent()) { transientServiceDefs.add(transientServiceDefOptional.get()); } }) .map(transientServiceDefOptional -> true) .onErrorResumeNext(throwable -> { LOGGER.warn("Handling Connect Error", throwable); return Defer.just(true); }); }) .map(new ToVoid<>()) .doOnNext(aVoid -> { int updatedNumberOfStartedVolumes = 0; Map<String, TransientServiceDef> updatedNodesByStartedVolume = new HashMap<>(); NavigableMap<Long, Set<String>> updatedStartedVolumeIdByUseableSpace = new TreeMap<>(); List<TransientServiceDef> updatedMasterNodes = new ArrayList<>(); TransientServiceDef candidateMaintainerNode = null; for (TransientServiceDef transientServiceDef : transientServiceDefs) { if (Boolean.TRUE.equals(transientServiceDef.getMaster().orNull())) { updatedMasterNodes.add(transientServiceDef); } if (candidateMaintainerNode == null) { candidateMaintainerNode = transientServiceDef; } else { long currentDocumentCount = candidateMaintainerNode.getDocumentCount().or(0L); long candidateDocumentCount = transientServiceDef.getDocumentCount().or(0L); if (candidateDocumentCount < currentDocumentCount) { candidateMaintainerNode = transientServiceDef; } } for (XVolume<?> xVolume : transientServiceDef.getVolumes()) { Optional<String> oVolumeId = xVolume.getId(); Optional<Status> oStatus = xVolume.getStatus(); Optional<Long> oUseableSpace = xVolume.getUsableSpace(); if (oVolumeId.isPresent() && oStatus.isPresent() && oUseableSpace.isPresent()) { Status status = oStatus.get(); if (Status.STARTED.equals(status)) { String volumeId = oVolumeId.get(); long useableSpace = oUseableSpace.get(); updatedNumberOfStartedVolumes++; Set<String> volumeIdsForSpace = updatedStartedVolumeIdByUseableSpace.get(useableSpace); if (volumeIdsForSpace == null) { volumeIdsForSpace = new HashSet<>(); updatedStartedVolumeIdByUseableSpace.put(useableSpace, volumeIdsForSpace); } volumeIdsForSpace.add(volumeId); updatedNodesByStartedVolume.put(volumeId, transientServiceDef); } } } } numberOfStartedVolumes = updatedNumberOfStartedVolumes; startedVolumeIdByUseableSpace = updatedStartedVolumeIdByUseableSpace; nodesByStartedVolume = updatedNodesByStartedVolume; currentMaintainerNode = candidateMaintainerNode; allNodes = transientServiceDefs; masterNodes = updatedMasterNodes; }); } }