@Override
  public V compute(K key, BiFunction<K, V, V> recomputeFunction) {
    checkState(!destroyed, destroyedMessage);
    checkNotNull(key, ERROR_NULL_KEY);
    checkNotNull(recomputeFunction, "Recompute function cannot be null");

    AtomicBoolean updated = new AtomicBoolean(false);
    AtomicReference<MapValue<V>> previousValue = new AtomicReference<>();
    MapValue<V> computedValue =
        items.compute(
            serializer.copy(key),
            (k, mv) -> {
              previousValue.set(mv);
              V newRawValue = recomputeFunction.apply(key, mv == null ? null : mv.get());
              if (mv != null && Objects.equals(newRawValue, mv.get())) {
                // value was not updated
                return mv;
              }
              MapValue<V> newValue =
                  new MapValue<>(newRawValue, timestampProvider.apply(key, newRawValue));
              if (mv == null || newValue.isNewerThan(mv)) {
                updated.set(true);
                // We return a copy to ensure updates to peers can be serialized.
                // This prevents replica divergence due to serialization failures.
                return serializer.copy(newValue);
              } else {
                return mv;
              }
            });
    if (updated.get()) {
      notifyPeers(
          new UpdateEntry<>(key, computedValue),
          peerUpdateFunction.apply(key, computedValue.get()));
      EventuallyConsistentMapEvent.Type updateType = computedValue.isTombstone() ? REMOVE : PUT;
      V value =
          computedValue.isTombstone()
              ? previousValue.get() == null ? null : previousValue.get().get()
              : computedValue.get();
      if (value != null) {
        notifyListeners(new EventuallyConsistentMapEvent<>(mapName, updateType, key, value));
      }
    }
    return computedValue.get();
  }
 private StoreSerializer createSerializer(KryoNamespace ns) {
   return StoreSerializer.using(
       KryoNamespace.newBuilder()
           .register(ns)
           // not so robust way to avoid collision with other
           // user supplied registrations
           .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID + 100)
           .register(KryoNamespaces.BASIC)
           .register(LogicalTimestamp.class)
           .register(WallClockTimestamp.class)
           .register(AntiEntropyAdvertisement.class)
           .register(AntiEntropyResponse.class)
           .register(UpdateEntry.class)
           .register(MapValue.class)
           .register(MapValue.Digest.class)
           .register(UpdateRequest.class)
           .build(name() + "-ecmap"));
 }
/** Distributed packet store implementation allowing packets to be sent to remote instances. */
@Component(immediate = true)
@Service
public class DistributedPacketStore extends AbstractStore<PacketEvent, PacketStoreDelegate>
    implements PacketStore {

  private final Logger log = getLogger(getClass());

  private static final String FORMAT = "Setting: messageHandlerThreadPoolSize={}";

  @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
  protected MastershipService mastershipService;

  @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
  protected ClusterService clusterService;

  @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
  protected ClusterCommunicationService communicationService;

  @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
  protected StorageService storageService;

  private PacketRequestTracker tracker;

  private static final MessageSubject PACKET_OUT_SUBJECT = new MessageSubject("packet-out");

  private static final StoreSerializer SERIALIZER = StoreSerializer.using(KryoNamespaces.API);

  private ExecutorService messageHandlingExecutor;

  private static final int DEFAULT_MESSAGE_HANDLER_THREAD_POOL_SIZE = 4;

  @Property(
      name = "messageHandlerThreadPoolSize",
      intValue = DEFAULT_MESSAGE_HANDLER_THREAD_POOL_SIZE,
      label = "Size of thread pool to assign message handler")
  private static int messageHandlerThreadPoolSize = DEFAULT_MESSAGE_HANDLER_THREAD_POOL_SIZE;

  private static final int MAX_BACKOFF = 50;

  @Activate
  public void activate() {
    messageHandlingExecutor =
        Executors.newFixedThreadPool(
            messageHandlerThreadPoolSize,
            groupedThreads("onos/store/packet", "message-handlers", log));

    communicationService.<OutboundPacket>addSubscriber(
        PACKET_OUT_SUBJECT,
        SERIALIZER::decode,
        packet -> notifyDelegate(new PacketEvent(Type.EMIT, packet)),
        messageHandlingExecutor);

    tracker = new PacketRequestTracker();

    log.info("Started");
  }

  @Deactivate
  public void deactivate() {
    communicationService.removeSubscriber(PACKET_OUT_SUBJECT);
    messageHandlingExecutor.shutdown();
    tracker = null;
    log.info("Stopped");
  }

  @Modified
  public void modified(ComponentContext context) {
    Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();

    int newMessageHandlerThreadPoolSize;

    try {
      String s = get(properties, "messageHandlerThreadPoolSize");

      newMessageHandlerThreadPoolSize =
          isNullOrEmpty(s) ? messageHandlerThreadPoolSize : Integer.parseInt(s.trim());

    } catch (NumberFormatException e) {
      log.warn(e.getMessage());
      newMessageHandlerThreadPoolSize = messageHandlerThreadPoolSize;
    }

    // Any change in the following parameters implies thread pool restart
    if (newMessageHandlerThreadPoolSize != messageHandlerThreadPoolSize) {
      setMessageHandlerThreadPoolSize(newMessageHandlerThreadPoolSize);
      restartMessageHandlerThreadPool();
    }

    log.info(FORMAT, messageHandlerThreadPoolSize);
  }

  @Override
  public void emit(OutboundPacket packet) {
    NodeId myId = clusterService.getLocalNode().id();
    NodeId master = mastershipService.getMasterFor(packet.sendThrough());

    if (master == null) {
      return;
    }

    if (myId.equals(master)) {
      notifyDelegate(new PacketEvent(Type.EMIT, packet));
      return;
    }

    communicationService
        .unicast(packet, PACKET_OUT_SUBJECT, SERIALIZER::encode, master)
        .whenComplete(
            (r, error) -> {
              if (error != null) {
                log.warn("Failed to send packet-out to {}", master, error);
              }
            });
  }

  @Override
  public void requestPackets(PacketRequest request) {
    tracker.add(request);
  }

  @Override
  public void cancelPackets(PacketRequest request) {
    tracker.remove(request);
  }

  @Override
  public List<PacketRequest> existingRequests() {
    return tracker.requests();
  }

  private final class PacketRequestTracker {

    private ConsistentMap<TrafficSelector, Set<PacketRequest>> requests;

    private PacketRequestTracker() {
      requests =
          storageService
              .<TrafficSelector, Set<PacketRequest>>consistentMapBuilder()
              .withName("onos-packet-requests")
              .withPartitionsDisabled()
              .withSerializer(Serializer.using(KryoNamespaces.API))
              .build();
    }

    private void add(PacketRequest request) {
      AtomicBoolean firstRequest =
          retryable(
                  this::addInternal,
                  ConsistentMapException.ConcurrentModification.class,
                  Integer.MAX_VALUE,
                  MAX_BACKOFF)
              .apply(request);
      if (firstRequest.get() && delegate != null) {
        // The instance that makes the first request will push to all devices
        delegate.requestPackets(request);
      }
    }

    private AtomicBoolean addInternal(PacketRequest request) {
      AtomicBoolean firstRequest = new AtomicBoolean(false);
      requests.compute(
          request.selector(),
          (s, existingRequests) -> {
            if (existingRequests == null) {
              firstRequest.set(true);
              return ImmutableSet.of(request);
            } else if (!existingRequests.contains(request)) {
              return ImmutableSet.<PacketRequest>builder()
                  .addAll(existingRequests)
                  .add(request)
                  .build();
            } else {
              return existingRequests;
            }
          });
      return firstRequest;
    }

    private void remove(PacketRequest request) {
      AtomicBoolean removedLast =
          retryable(
                  this::removeInternal,
                  ConsistentMapException.ConcurrentModification.class,
                  Integer.MAX_VALUE,
                  MAX_BACKOFF)
              .apply(request);
      if (removedLast.get() && delegate != null) {
        // The instance that removes the last request will remove from all devices
        delegate.cancelPackets(request);
      }
    }

    private AtomicBoolean removeInternal(PacketRequest request) {
      AtomicBoolean removedLast = new AtomicBoolean(false);
      requests.computeIfPresent(
          request.selector(),
          (s, existingRequests) -> {
            if (existingRequests.contains(request)) {
              Set<PacketRequest> newRequests = Sets.newHashSet(existingRequests);
              newRequests.remove(request);
              if (newRequests.size() > 0) {
                return ImmutableSet.copyOf(newRequests);
              } else {
                removedLast.set(true);
                return null;
              }
            } else {
              return existingRequests;
            }
          });
      return removedLast;
    }

    private List<PacketRequest> requests() {
      List<PacketRequest> list = Lists.newArrayList();
      requests.values().forEach(v -> list.addAll(v.value()));
      list.sort((o1, o2) -> o1.priority().priorityValue() - o2.priority().priorityValue());
      return list;
    }
  }

  /**
   * Sets thread pool size of message handler.
   *
   * @param poolSize
   */
  private void setMessageHandlerThreadPoolSize(int poolSize) {
    checkArgument(poolSize >= 0, "Message handler pool size must be 0 or more");
    messageHandlerThreadPoolSize = poolSize;
  }

  /** Restarts thread pool of message handler. */
  private void restartMessageHandlerThreadPool() {
    ExecutorService prevExecutor = messageHandlingExecutor;
    messageHandlingExecutor = Executors.newFixedThreadPool(getMessageHandlerThreadPoolSize());
    prevExecutor.shutdown();
  }

  /**
   * Gets current thread pool size of message handler.
   *
   * @return messageHandlerThreadPoolSize
   */
  private int getMessageHandlerThreadPoolSize() {
    return messageHandlerThreadPoolSize;
  }
}
/** Maintains flow statistics using RPC calls to collect stats from remote instances on demand. */
@Component(immediate = true)
@Service
public class DistributedFlowStatisticStore implements FlowStatisticStore {
  private final Logger log = getLogger(getClass());

  private static final String FORMAT = "Setting: messageHandlerThreadPoolSize={}";

  @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
  protected MastershipService mastershipService;

  @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
  protected ClusterCommunicationService clusterCommunicator;

  @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
  protected ClusterService clusterService;

  @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
  protected ComponentConfigService cfgService;

  private Map<ConnectPoint, Set<FlowEntry>> previous = new ConcurrentHashMap<>();

  private Map<ConnectPoint, Set<FlowEntry>> current = new ConcurrentHashMap<>();

  public static final MessageSubject GET_CURRENT = new MessageSubject("peer-return-current");
  public static final MessageSubject GET_PREVIOUS = new MessageSubject("peer-return-previous");

  protected static final StoreSerializer SERIALIZER = StoreSerializer.using(KryoNamespaces.API);

  private NodeId local;
  private ExecutorService messageHandlingExecutor;

  private static final int DEFAULT_MESSAGE_HANDLER_THREAD_POOL_SIZE = 4;

  @Property(
      name = "messageHandlerThreadPoolSize",
      intValue = DEFAULT_MESSAGE_HANDLER_THREAD_POOL_SIZE,
      label = "Size of thread pool to assign message handler")
  private static int messageHandlerThreadPoolSize = DEFAULT_MESSAGE_HANDLER_THREAD_POOL_SIZE;

  private static final long STATISTIC_STORE_TIMEOUT_MILLIS = 3000;

  @Activate
  public void activate(ComponentContext context) {
    cfgService.registerProperties(getClass());

    modified(context);

    local = clusterService.getLocalNode().id();

    messageHandlingExecutor =
        Executors.newFixedThreadPool(
            messageHandlerThreadPoolSize,
            groupedThreads("onos/store/statistic", "message-handlers", log));

    clusterCommunicator.addSubscriber(
        GET_CURRENT,
        SERIALIZER::decode,
        this::getCurrentStatisticInternal,
        SERIALIZER::encode,
        messageHandlingExecutor);

    clusterCommunicator.addSubscriber(
        GET_CURRENT,
        SERIALIZER::decode,
        this::getPreviousStatisticInternal,
        SERIALIZER::encode,
        messageHandlingExecutor);

    log.info("Started");
  }

  @Deactivate
  public void deactivate() {
    cfgService.unregisterProperties(getClass(), false);
    clusterCommunicator.removeSubscriber(GET_PREVIOUS);
    clusterCommunicator.removeSubscriber(GET_CURRENT);
    messageHandlingExecutor.shutdown();
    log.info("Stopped");
  }

  @Modified
  public void modified(ComponentContext context) {
    Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();

    int newMessageHandlerThreadPoolSize;

    try {
      String s = get(properties, "messageHandlerThreadPoolSize");

      newMessageHandlerThreadPoolSize =
          isNullOrEmpty(s) ? messageHandlerThreadPoolSize : Integer.parseInt(s.trim());

    } catch (NumberFormatException e) {
      log.warn(e.getMessage());
      newMessageHandlerThreadPoolSize = messageHandlerThreadPoolSize;
    }

    // Any change in the following parameters implies thread pool restart
    if (newMessageHandlerThreadPoolSize != messageHandlerThreadPoolSize) {
      setMessageHandlerThreadPoolSize(newMessageHandlerThreadPoolSize);
      restartMessageHandlerThreadPool();
    }

    log.info(FORMAT, messageHandlerThreadPoolSize);
  }

  @Override
  public synchronized void removeFlowStatistic(FlowRule rule) {
    ConnectPoint cp = buildConnectPoint(rule);
    if (cp == null) {
      return;
    }

    // remove this rule if present from current map
    current.computeIfPresent(
        cp,
        (c, e) -> {
          e.remove(rule);
          return e;
        });

    // remove this on if present from previous map
    previous.computeIfPresent(
        cp,
        (c, e) -> {
          e.remove(rule);
          return e;
        });
  }

  @Override
  public synchronized void addFlowStatistic(FlowEntry rule) {
    ConnectPoint cp = buildConnectPoint(rule);
    if (cp == null) {
      return;
    }

    // create one if absent and add this rule
    current.putIfAbsent(cp, new HashSet<>());
    current.computeIfPresent(
        cp,
        (c, e) -> {
          e.add(rule);
          return e;
        });

    // remove previous one if present
    previous.computeIfPresent(
        cp,
        (c, e) -> {
          e.remove(rule);
          return e;
        });
  }

  @Override
  public synchronized void updateFlowStatistic(FlowEntry rule) {
    ConnectPoint cp = buildConnectPoint(rule);
    if (cp == null) {
      return;
    }

    Set<FlowEntry> curr = current.get(cp);
    if (curr == null) {
      addFlowStatistic(rule);
    } else {
      Optional<FlowEntry> f = curr.stream().filter(c -> rule.equals(c)).findAny();
      if (f.isPresent() && rule.bytes() < f.get().bytes()) {
        log.debug(
            "DistributedFlowStatisticStore:updateFlowStatistic():"
                + " Invalid Flow Update! Will be removed!!"
                + " curr flowId="
                + Long.toHexString(rule.id().value())
                + ", prev flowId="
                + Long.toHexString(f.get().id().value())
                + ", curr bytes="
                + rule.bytes()
                + ", prev bytes="
                + f.get().bytes()
                + ", curr life="
                + rule.life()
                + ", prev life="
                + f.get().life()
                + ", curr lastSeen="
                + rule.lastSeen()
                + ", prev lastSeen="
                + f.get().lastSeen());
        // something is wrong! invalid flow entry, so delete it
        removeFlowStatistic(rule);
        return;
      }
      Set<FlowEntry> prev = previous.get(cp);
      if (prev == null) {
        prev = new HashSet<>();
        previous.put(cp, prev);
      }

      // previous one is exist
      if (f.isPresent()) {
        // remove old one and add new one
        prev.remove(rule);
        if (!prev.add(f.get())) {
          log.debug(
              "DistributedFlowStatisticStore:updateFlowStatistic():"
                  + " flowId={}, add failed into previous.",
              Long.toHexString(rule.id().value()));
        }
      }

      // remove old one and add new one
      curr.remove(rule);
      if (!curr.add(rule)) {
        log.debug(
            "DistributedFlowStatisticStore:updateFlowStatistic():"
                + " flowId={}, add failed into current.",
            Long.toHexString(rule.id().value()));
      }
    }
  }

  @Override
  public Set<FlowEntry> getCurrentFlowStatistic(ConnectPoint connectPoint) {
    final DeviceId deviceId = connectPoint.deviceId();

    NodeId master = mastershipService.getMasterFor(deviceId);
    if (master == null) {
      log.warn("No master for {}", deviceId);
      return Collections.emptySet();
    }

    if (Objects.equal(local, master)) {
      return getCurrentStatisticInternal(connectPoint);
    } else {
      return Tools.futureGetOrElse(
          clusterCommunicator.sendAndReceive(
              connectPoint, GET_CURRENT, SERIALIZER::encode, SERIALIZER::decode, master),
          STATISTIC_STORE_TIMEOUT_MILLIS,
          TimeUnit.MILLISECONDS,
          Collections.emptySet());
    }
  }

  private synchronized Set<FlowEntry> getCurrentStatisticInternal(ConnectPoint connectPoint) {
    return current.get(connectPoint);
  }

  @Override
  public Set<FlowEntry> getPreviousFlowStatistic(ConnectPoint connectPoint) {
    final DeviceId deviceId = connectPoint.deviceId();

    NodeId master = mastershipService.getMasterFor(deviceId);
    if (master == null) {
      log.warn("No master for {}", deviceId);
      return Collections.emptySet();
    }

    if (Objects.equal(local, master)) {
      return getPreviousStatisticInternal(connectPoint);
    } else {
      return Tools.futureGetOrElse(
          clusterCommunicator.sendAndReceive(
              connectPoint, GET_PREVIOUS, SERIALIZER::encode, SERIALIZER::decode, master),
          STATISTIC_STORE_TIMEOUT_MILLIS,
          TimeUnit.MILLISECONDS,
          Collections.emptySet());
    }
  }

  private synchronized Set<FlowEntry> getPreviousStatisticInternal(ConnectPoint connectPoint) {
    return previous.get(connectPoint);
  }

  private ConnectPoint buildConnectPoint(FlowRule rule) {
    PortNumber port = getOutput(rule);

    if (port == null) {
      return null;
    }
    ConnectPoint cp = new ConnectPoint(rule.deviceId(), port);
    return cp;
  }

  private PortNumber getOutput(FlowRule rule) {
    for (Instruction i : rule.treatment().allInstructions()) {
      if (i.type() == Instruction.Type.OUTPUT) {
        Instructions.OutputInstruction out = (Instructions.OutputInstruction) i;
        return out.port();
      }
    }
    return null;
  }

  /**
   * Sets thread pool size of message handler.
   *
   * @param poolSize
   */
  private void setMessageHandlerThreadPoolSize(int poolSize) {
    checkArgument(poolSize >= 0, "Message handler pool size must be 0 or more");
    messageHandlerThreadPoolSize = poolSize;
  }

  /** Restarts thread pool of message handler. */
  private void restartMessageHandlerThreadPool() {
    ExecutorService prevExecutor = messageHandlingExecutor;
    messageHandlingExecutor =
        newFixedThreadPool(
            getMessageHandlerThreadPoolSize(),
            groupedThreads("DistFlowStats", "messageHandling-%d", log));
    prevExecutor.shutdown();
  }

  /**
   * Gets current thread pool size of message handler.
   *
   * @return messageHandlerThreadPoolSize
   */
  private int getMessageHandlerThreadPoolSize() {
    return messageHandlerThreadPoolSize;
  }
}