public void testSerializationFailureDuringDiffPublishing() throws Exception {
    MockNode nodeA =
        createMockNode(
                "nodeA",
                Settings.EMPTY,
                new ClusterStateListener() {
                  @Override
                  public void clusterChanged(ClusterChangedEvent event) {
                    fail("Shouldn't send cluster state to myself");
                  }
                })
            .setAsMaster();

    MockNode nodeB = createMockNode("nodeB");

    // Initial cluster state with both states - the second node still shouldn't get
    // diff even though it's present in the previous cluster state
    DiscoveryNodes discoveryNodes =
        DiscoveryNodes.builder(nodeA.nodes()).add(nodeB.discoveryNode).build();
    ClusterState previousClusterState =
        ClusterState.builder(CLUSTER_NAME).nodes(discoveryNodes).build();
    ClusterState clusterState =
        ClusterState.builder(previousClusterState).incrementVersion().build();
    publishStateAndWait(nodeA.action, clusterState, previousClusterState);
    assertSameStateFromFull(nodeB.clusterState, clusterState);

    // cluster state update - add block
    previousClusterState = clusterState;
    clusterState =
        ClusterState.builder(clusterState)
            .blocks(ClusterBlocks.builder().addGlobalBlock(MetaData.CLUSTER_READ_ONLY_BLOCK))
            .incrementVersion()
            .build();

    ClusterState unserializableClusterState =
        new ClusterState(clusterState.version(), clusterState.stateUUID(), clusterState) {
          @Override
          public Diff<ClusterState> diff(ClusterState previousState) {
            return new Diff<ClusterState>() {
              @Override
              public ClusterState apply(ClusterState part) {
                fail("this diff shouldn't be applied");
                return part;
              }

              @Override
              public void writeTo(StreamOutput out) throws IOException {
                throw new IOException("Simulated failure of diff serialization");
              }
            };
          }
        };
    try {
      publishStateAndWait(nodeA.action, unserializableClusterState, previousClusterState);
      fail("cluster state published despite of diff errors");
    } catch (Discovery.FailedToCommitClusterStateException e) {
      assertThat(e.getCause(), notNullValue());
      assertThat(e.getCause().getMessage(), containsString("failed to serialize"));
    }
  }
  public void testPublishingWithSendingErrors() throws Exception {
    int goodNodes = randomIntBetween(2, 5);
    int errorNodes = randomIntBetween(1, 5);
    int timeOutNodes =
        randomBoolean()
            ? 0
            : randomIntBetween(1, 5); // adding timeout nodes will force timeout errors
    final int numberOfMasterNodes = goodNodes + errorNodes + timeOutNodes + 1; // master
    final boolean expectingToCommit = randomBoolean();
    Settings.Builder settings = Settings.builder();
    // make sure we have a reasonable timeout if we expect to timeout, o.w. one that will make the
    // test "hang"
    settings
        .put(
            DiscoverySettings.COMMIT_TIMEOUT_SETTING.getKey(),
            expectingToCommit == false && timeOutNodes > 0 ? "100ms" : "1h")
        .put(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey(), "5ms"); // test is about committing

    MockNode master = createMockNode("master", settings.build(), null);

    // randomize things a bit
    int[] nodeTypes = new int[goodNodes + errorNodes + timeOutNodes];
    for (int i = 0; i < goodNodes; i++) {
      nodeTypes[i] = 0;
    }
    for (int i = goodNodes; i < goodNodes + errorNodes; i++) {
      nodeTypes[i] = 1;
    }
    for (int i = goodNodes + errorNodes; i < nodeTypes.length; i++) {
      nodeTypes[i] = 2;
    }
    Collections.shuffle(Arrays.asList(nodeTypes), random());

    DiscoveryNodes.Builder discoveryNodesBuilder =
        DiscoveryNodes.builder().add(master.discoveryNode);
    for (int i = 0; i < nodeTypes.length; i++) {
      final MockNode mockNode = createMockNode("node" + i);
      discoveryNodesBuilder.add(mockNode.discoveryNode);
      switch (nodeTypes[i]) {
        case 1:
          mockNode.action.errorOnSend.set(true);
          break;
        case 2:
          mockNode.action.timeoutOnSend.set(true);
          break;
      }
    }
    final int dataNodes = randomIntBetween(0, 3); // data nodes don't matter
    for (int i = 0; i < dataNodes; i++) {
      final MockNode mockNode =
          createMockNode(
              "data_" + i,
              Settings.builder().put(Node.NODE_MASTER_SETTING.getKey(), false).build(),
              null);
      discoveryNodesBuilder.add(mockNode.discoveryNode);
      if (randomBoolean()) {
        // we really don't care - just chaos monkey
        mockNode.action.errorOnCommit.set(randomBoolean());
        mockNode.action.errorOnSend.set(randomBoolean());
        mockNode.action.timeoutOnCommit.set(randomBoolean());
        mockNode.action.timeoutOnSend.set(randomBoolean());
      }
    }

    final int minMasterNodes;
    final String expectedBehavior;
    if (expectingToCommit) {
      minMasterNodes = randomIntBetween(0, goodNodes + 1); // count master
      expectedBehavior = "succeed";
    } else {
      minMasterNodes = randomIntBetween(goodNodes + 2, numberOfMasterNodes); // +2 because of master
      expectedBehavior = timeOutNodes > 0 ? "timeout" : "fail";
    }
    logger.info(
        "--> expecting commit to {}. good nodes [{}], errors [{}], timeouts [{}]. min_master_nodes [{}]",
        expectedBehavior,
        goodNodes + 1,
        errorNodes,
        timeOutNodes,
        minMasterNodes);

    discoveryNodesBuilder
        .localNodeId(master.discoveryNode.getId())
        .masterNodeId(master.discoveryNode.getId());
    DiscoveryNodes discoveryNodes = discoveryNodesBuilder.build();
    MetaData metaData = MetaData.EMPTY_META_DATA;
    ClusterState clusterState =
        ClusterState.builder(CLUSTER_NAME).metaData(metaData).nodes(discoveryNodes).build();
    ClusterState previousState = master.clusterState;
    try {
      publishState(master.action, clusterState, previousState, minMasterNodes);
      if (expectingToCommit == false) {
        fail("cluster state publishing didn't fail despite of not have enough nodes");
      }
    } catch (Discovery.FailedToCommitClusterStateException exception) {
      logger.debug("failed to publish as expected", exception);
      if (expectingToCommit) {
        throw exception;
      }
      assertThat(exception.getMessage(), containsString(timeOutNodes > 0 ? "timed out" : "failed"));
    }
  }