public void testDeleteIsAppliedFirst() {
    DiscoverySettings discoverySettings = getDiscoverySettings();

    assertEquals(
        discoverySettings.getPublishTimeout(),
        DiscoverySettings.PUBLISH_TIMEOUT_SETTING.get(Settings.EMPTY));
    assertTrue(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.get(Settings.EMPTY));

    ClusterUpdateSettingsResponse response =
        client()
            .admin()
            .cluster()
            .prepareUpdateSettings()
            .setTransientSettings(
                Settings.builder()
                    .put(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.getKey(), false)
                    .put(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey(), "1s")
                    .build())
            .get();

    assertAcked(response);
    assertEquals(
        response
            .getTransientSettings()
            .getAsMap()
            .get(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()),
        "1s");
    assertTrue(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.get(Settings.EMPTY));
    assertFalse(
        response
            .getTransientSettings()
            .getAsBoolean(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.getKey(), null));

    response =
        client()
            .admin()
            .cluster()
            .prepareUpdateSettings()
            .setTransientSettings(
                Settings.builder()
                    .putNull((randomBoolean() ? "discovery.zen.*" : "*"))
                    .put(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey(), "2s"))
            .get();
    assertEquals(
        response
            .getTransientSettings()
            .getAsMap()
            .get(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()),
        "2s");
    assertNull(
        response
            .getTransientSettings()
            .getAsBoolean(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.getKey(), null));
  }
 private void removePublishTimeout() {
   // to test that the acknowledgement mechanism is working we better disable the wait for publish
   // otherwise the operation is most likely acknowledged even if it doesn't support ack
   assertAcked(
       client()
           .admin()
           .cluster()
           .prepareUpdateSettings()
           .setTransientSettings(
               Settings.builder()
                   .put(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey(), "0")
                   .put(DiscoverySettings.COMMIT_TIMEOUT_SETTING.getKey(), "30s")));
 }
  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"));
    }
  }
  public void testUpdateDiscoveryPublishTimeout() {

    DiscoverySettings discoverySettings = getDiscoverySettings();

    assertThat(
        discoverySettings.getPublishTimeout(),
        equalTo(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.get(Settings.EMPTY)));

    ClusterUpdateSettingsResponse response =
        client()
            .admin()
            .cluster()
            .prepareUpdateSettings()
            .setTransientSettings(
                Settings.builder()
                    .put(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey(), "1s")
                    .build())
            .get();

    assertAcked(response);
    assertThat(
        response
            .getTransientSettings()
            .getAsMap()
            .get(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()),
        equalTo("1s"));
    assertThat(discoverySettings.getPublishTimeout().seconds(), equalTo(1L));

    try {
      client()
          .admin()
          .cluster()
          .prepareUpdateSettings()
          .setTransientSettings(
              Settings.builder()
                  .put(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey(), "whatever")
                  .build())
          .get();
      fail("bogus value");
    } catch (IllegalArgumentException ex) {
      assertEquals(
          ex.getMessage(),
          "failed to parse setting [discovery.zen.publish_timeout] with value [whatever] as a time value: unit is missing or unrecognized");
    }

    assertThat(discoverySettings.getPublishTimeout().seconds(), equalTo(1L));

    try {
      client()
          .admin()
          .cluster()
          .prepareUpdateSettings()
          .setTransientSettings(
              Settings.builder()
                  .put(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey(), -1)
                  .build())
          .get();
      fail("bogus value");
    } catch (IllegalArgumentException ex) {
      assertEquals(
          ex.getMessage(),
          "Failed to parse value [-1] for setting [discovery.zen.publish_timeout] must be >= 0s");
    }

    assertThat(discoverySettings.getPublishTimeout().seconds(), equalTo(1L));
  }
  public void testResetClusterSetting() {
    DiscoverySettings discoverySettings = getDiscoverySettings();

    assertThat(
        discoverySettings.getPublishTimeout(),
        equalTo(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.get(Settings.EMPTY)));
    assertThat(
        discoverySettings.getPublishDiff(),
        equalTo(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.get(Settings.EMPTY)));

    ClusterUpdateSettingsResponse response =
        client()
            .admin()
            .cluster()
            .prepareUpdateSettings()
            .setTransientSettings(
                Settings.builder()
                    .put(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey(), "1s")
                    .build())
            .get();

    assertAcked(response);
    assertThat(
        response
            .getTransientSettings()
            .getAsMap()
            .get(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()),
        equalTo("1s"));
    assertThat(discoverySettings.getPublishTimeout().seconds(), equalTo(1L));
    assertThat(
        discoverySettings.getPublishDiff(),
        equalTo(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.get(Settings.EMPTY)));

    response =
        client()
            .admin()
            .cluster()
            .prepareUpdateSettings()
            .setTransientSettings(
                Settings.builder().putNull(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()))
            .get();

    assertAcked(response);
    assertNull(
        response
            .getTransientSettings()
            .getAsMap()
            .get(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()));
    assertThat(
        discoverySettings.getPublishTimeout(),
        equalTo(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.get(Settings.EMPTY)));
    assertThat(
        discoverySettings.getPublishDiff(),
        equalTo(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.get(Settings.EMPTY)));

    response =
        client()
            .admin()
            .cluster()
            .prepareUpdateSettings()
            .setTransientSettings(
                Settings.builder()
                    .put(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey(), "1s")
                    .put(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.getKey(), false)
                    .build())
            .get();

    assertAcked(response);
    assertThat(
        response
            .getTransientSettings()
            .getAsMap()
            .get(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()),
        equalTo("1s"));
    assertThat(discoverySettings.getPublishTimeout().seconds(), equalTo(1L));
    assertFalse(discoverySettings.getPublishDiff());
    response =
        client()
            .admin()
            .cluster()
            .prepareUpdateSettings()
            .setTransientSettings(
                Settings.builder().putNull((randomBoolean() ? "discovery.zen.*" : "*")))
            .get();

    assertNull(
        response
            .getTransientSettings()
            .getAsMap()
            .get(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()));
    assertNull(
        response
            .getTransientSettings()
            .getAsMap()
            .get(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.getKey()));
    assertThat(
        discoverySettings.getPublishTimeout(),
        equalTo(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.get(Settings.EMPTY)));
    assertThat(
        discoverySettings.getPublishDiff(),
        equalTo(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.get(Settings.EMPTY)));

    // now persistent
    response =
        client()
            .admin()
            .cluster()
            .prepareUpdateSettings()
            .setPersistentSettings(
                Settings.builder()
                    .put(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey(), "1s")
                    .build())
            .get();

    assertAcked(response);
    assertThat(
        response
            .getPersistentSettings()
            .getAsMap()
            .get(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()),
        equalTo("1s"));
    assertThat(discoverySettings.getPublishTimeout().seconds(), equalTo(1L));
    assertThat(
        discoverySettings.getPublishDiff(),
        equalTo(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.get(Settings.EMPTY)));

    response =
        client()
            .admin()
            .cluster()
            .prepareUpdateSettings()
            .setPersistentSettings(
                Settings.builder().putNull((DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey())))
            .get();

    assertAcked(response);
    assertNull(
        response
            .getPersistentSettings()
            .getAsMap()
            .get(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()));
    assertThat(
        discoverySettings.getPublishTimeout(),
        equalTo(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.get(Settings.EMPTY)));
    assertThat(
        discoverySettings.getPublishDiff(),
        equalTo(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.get(Settings.EMPTY)));

    response =
        client()
            .admin()
            .cluster()
            .prepareUpdateSettings()
            .setPersistentSettings(
                Settings.builder()
                    .put(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey(), "1s")
                    .put(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.getKey(), false)
                    .build())
            .get();

    assertAcked(response);
    assertThat(
        response
            .getPersistentSettings()
            .getAsMap()
            .get(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()),
        equalTo("1s"));
    assertThat(discoverySettings.getPublishTimeout().seconds(), equalTo(1L));
    assertFalse(discoverySettings.getPublishDiff());
    response =
        client()
            .admin()
            .cluster()
            .prepareUpdateSettings()
            .setPersistentSettings(
                Settings.builder().putNull((randomBoolean() ? "discovery.zen.*" : "*")))
            .get();

    assertNull(
        response
            .getPersistentSettings()
            .getAsMap()
            .get(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey()));
    assertNull(
        response
            .getPersistentSettings()
            .getAsMap()
            .get(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.getKey()));
    assertThat(
        discoverySettings.getPublishTimeout(),
        equalTo(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.get(Settings.EMPTY)));
    assertThat(
        discoverySettings.getPublishDiff(),
        equalTo(DiscoverySettings.PUBLISH_DIFF_ENABLE_SETTING.get(Settings.EMPTY)));
  }
Example #6
0
public class MetaData
    implements Iterable<IndexMetaData>,
        Diffable<MetaData>,
        FromXContentBuilder<MetaData>,
        ToXContent {

  public static final MetaData PROTO = builder().build();

  public static final String ALL = "_all";

  public enum XContentContext {
    /* Custom metadata should be returns as part of API call */
    API,

    /* Custom metadata should be stored as part of the persistent cluster state */
    GATEWAY,

    /* Custom metadata should be stored as part of a snapshot */
    SNAPSHOT
  }

  public static EnumSet<XContentContext> API_ONLY = EnumSet.of(XContentContext.API);
  public static EnumSet<XContentContext> API_AND_GATEWAY =
      EnumSet.of(XContentContext.API, XContentContext.GATEWAY);
  public static EnumSet<XContentContext> API_AND_SNAPSHOT =
      EnumSet.of(XContentContext.API, XContentContext.SNAPSHOT);

  public interface Custom extends Diffable<Custom>, ToXContent {

    String type();

    Custom fromXContent(XContentParser parser) throws IOException;

    EnumSet<XContentContext> context();
  }

  public static Map<String, Custom> customPrototypes = new HashMap<>();

  static {
    // register non plugin custom metadata
    registerPrototype(RepositoriesMetaData.TYPE, RepositoriesMetaData.PROTO);
    registerPrototype(IngestMetadata.TYPE, IngestMetadata.PROTO);
  }

  /** Register a custom index meta data factory. Make sure to call it from a static block. */
  public static void registerPrototype(String type, Custom proto) {
    customPrototypes.put(type, proto);
  }

  @Nullable
  public static <T extends Custom> T lookupPrototype(String type) {
    //noinspection unchecked
    return (T) customPrototypes.get(type);
  }

  public static <T extends Custom> T lookupPrototypeSafe(String type) {
    //noinspection unchecked
    T proto = (T) customPrototypes.get(type);
    if (proto == null) {
      throw new IllegalArgumentException(
          "No custom metadata prototype registered for type ["
              + type
              + "], node likely missing plugins");
    }
    return proto;
  }

  public static final Setting<Boolean> SETTING_READ_ONLY_SETTING =
      Setting.boolSetting("cluster.blocks.read_only", false, true, Setting.Scope.CLUSTER);

  public static final ClusterBlock CLUSTER_READ_ONLY_BLOCK =
      new ClusterBlock(
          6,
          "cluster read-only (api)",
          false,
          false,
          RestStatus.FORBIDDEN,
          EnumSet.of(ClusterBlockLevel.WRITE, ClusterBlockLevel.METADATA_WRITE));

  public static final MetaData EMPTY_META_DATA = builder().build();

  public static final String CONTEXT_MODE_PARAM = "context_mode";

  public static final String CONTEXT_MODE_SNAPSHOT = XContentContext.SNAPSHOT.toString();

  public static final String CONTEXT_MODE_GATEWAY = XContentContext.GATEWAY.toString();

  private final String clusterUUID;
  private final long version;

  private final Settings transientSettings;
  private final Settings persistentSettings;
  private final Settings settings;
  private final ImmutableOpenMap<String, IndexMetaData> indices;
  private final ImmutableOpenMap<String, IndexTemplateMetaData> templates;
  private final ImmutableOpenMap<String, Custom> customs;

  private final transient int totalNumberOfShards; // Transient ? not serializable anyway?
  private final int numberOfShards;

  private final String[] allIndices;
  private final String[] allOpenIndices;
  private final String[] allClosedIndices;

  private final SortedMap<String, AliasOrIndex> aliasAndIndexLookup;

  @SuppressWarnings("unchecked")
  MetaData(
      String clusterUUID,
      long version,
      Settings transientSettings,
      Settings persistentSettings,
      ImmutableOpenMap<String, IndexMetaData> indices,
      ImmutableOpenMap<String, IndexTemplateMetaData> templates,
      ImmutableOpenMap<String, Custom> customs,
      String[] allIndices,
      String[] allOpenIndices,
      String[] allClosedIndices,
      SortedMap<String, AliasOrIndex> aliasAndIndexLookup) {
    this.clusterUUID = clusterUUID;
    this.version = version;
    this.transientSettings = transientSettings;
    this.persistentSettings = persistentSettings;
    this.settings =
        Settings.settingsBuilder().put(persistentSettings).put(transientSettings).build();
    this.indices = indices;
    this.customs = customs;
    this.templates = templates;
    int totalNumberOfShards = 0;
    int numberOfShards = 0;
    for (ObjectCursor<IndexMetaData> cursor : indices.values()) {
      totalNumberOfShards += cursor.value.getTotalNumberOfShards();
      numberOfShards += cursor.value.getNumberOfShards();
    }
    this.totalNumberOfShards = totalNumberOfShards;
    this.numberOfShards = numberOfShards;

    this.allIndices = allIndices;
    this.allOpenIndices = allOpenIndices;
    this.allClosedIndices = allClosedIndices;
    this.aliasAndIndexLookup = aliasAndIndexLookup;
  }

  public long version() {
    return this.version;
  }

  public String clusterUUID() {
    return this.clusterUUID;
  }

  /** Returns the merged transient and persistent settings. */
  public Settings settings() {
    return this.settings;
  }

  public Settings transientSettings() {
    return this.transientSettings;
  }

  public Settings persistentSettings() {
    return this.persistentSettings;
  }

  public boolean hasAlias(String alias) {
    AliasOrIndex aliasOrIndex = getAliasAndIndexLookup().get(alias);
    if (aliasOrIndex != null) {
      return aliasOrIndex.isAlias();
    } else {
      return false;
    }
  }

  public boolean equalsAliases(MetaData other) {
    for (ObjectCursor<IndexMetaData> cursor : other.indices().values()) {
      IndexMetaData otherIndex = cursor.value;
      IndexMetaData thisIndex = index(otherIndex.getIndex());
      if (thisIndex == null) {
        return false;
      }
      if (otherIndex.getAliases().equals(thisIndex.getAliases()) == false) {
        return false;
      }
    }

    return true;
  }

  public SortedMap<String, AliasOrIndex> getAliasAndIndexLookup() {
    return aliasAndIndexLookup;
  }

  /**
   * Finds the specific index aliases that match with the specified aliases directly or partially
   * via wildcards and that point to the specified concrete indices or match partially with the
   * indices via wildcards.
   *
   * @param aliases The names of the index aliases to find
   * @param concreteIndices The concrete indexes the index aliases must point to order to be
   *     returned.
   * @return the found index aliases grouped by index
   */
  public ImmutableOpenMap<String, List<AliasMetaData>> findAliases(
      final String[] aliases, String[] concreteIndices) {
    assert aliases != null;
    assert concreteIndices != null;
    if (concreteIndices.length == 0) {
      return ImmutableOpenMap.of();
    }

    boolean matchAllAliases = matchAllAliases(aliases);
    ImmutableOpenMap.Builder<String, List<AliasMetaData>> mapBuilder = ImmutableOpenMap.builder();
    Iterable<String> intersection =
        HppcMaps.intersection(ObjectHashSet.from(concreteIndices), indices.keys());
    for (String index : intersection) {
      IndexMetaData indexMetaData = indices.get(index);
      List<AliasMetaData> filteredValues = new ArrayList<>();
      for (ObjectCursor<AliasMetaData> cursor : indexMetaData.getAliases().values()) {
        AliasMetaData value = cursor.value;
        if (matchAllAliases || Regex.simpleMatch(aliases, value.alias())) {
          filteredValues.add(value);
        }
      }

      if (!filteredValues.isEmpty()) {
        // Make the list order deterministic
        CollectionUtil.timSort(
            filteredValues,
            new Comparator<AliasMetaData>() {
              @Override
              public int compare(AliasMetaData o1, AliasMetaData o2) {
                return o1.alias().compareTo(o2.alias());
              }
            });
        mapBuilder.put(index, Collections.unmodifiableList(filteredValues));
      }
    }
    return mapBuilder.build();
  }

  private static boolean matchAllAliases(final String[] aliases) {
    for (String alias : aliases) {
      if (alias.equals(ALL)) {
        return true;
      }
    }
    return aliases.length == 0;
  }

  /**
   * Checks if at least one of the specified aliases exists in the specified concrete indices.
   * Wildcards are supported in the alias names for partial matches.
   *
   * @param aliases The names of the index aliases to find
   * @param concreteIndices The concrete indexes the index aliases must point to order to be
   *     returned.
   * @return whether at least one of the specified aliases exists in one of the specified concrete
   *     indices.
   */
  public boolean hasAliases(final String[] aliases, String[] concreteIndices) {
    assert aliases != null;
    assert concreteIndices != null;
    if (concreteIndices.length == 0) {
      return false;
    }

    Iterable<String> intersection =
        HppcMaps.intersection(ObjectHashSet.from(concreteIndices), indices.keys());
    for (String index : intersection) {
      IndexMetaData indexMetaData = indices.get(index);
      List<AliasMetaData> filteredValues = new ArrayList<>();
      for (ObjectCursor<AliasMetaData> cursor : indexMetaData.getAliases().values()) {
        AliasMetaData value = cursor.value;
        if (Regex.simpleMatch(aliases, value.alias())) {
          filteredValues.add(value);
        }
      }
      if (!filteredValues.isEmpty()) {
        return true;
      }
    }
    return false;
  }

  /*
   * Finds all mappings for types and concrete indices. Types are expanded to
   * include all types that match the glob patterns in the types array. Empty
   * types array, null or {"_all"} will be expanded to all types available for
   * the given indices.
   */
  public ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> findMappings(
      String[] concreteIndices, final String[] types) {
    assert types != null;
    assert concreteIndices != null;
    if (concreteIndices.length == 0) {
      return ImmutableOpenMap.of();
    }

    ImmutableOpenMap.Builder<String, ImmutableOpenMap<String, MappingMetaData>> indexMapBuilder =
        ImmutableOpenMap.builder();
    Iterable<String> intersection =
        HppcMaps.intersection(ObjectHashSet.from(concreteIndices), indices.keys());
    for (String index : intersection) {
      IndexMetaData indexMetaData = indices.get(index);
      ImmutableOpenMap.Builder<String, MappingMetaData> filteredMappings;
      if (isAllTypes(types)) {
        indexMapBuilder.put(
            index, indexMetaData.getMappings()); // No types specified means get it all

      } else {
        filteredMappings = ImmutableOpenMap.builder();
        for (ObjectObjectCursor<String, MappingMetaData> cursor : indexMetaData.getMappings()) {
          if (Regex.simpleMatch(types, cursor.key)) {
            filteredMappings.put(cursor.key, cursor.value);
          }
        }
        if (!filteredMappings.isEmpty()) {
          indexMapBuilder.put(index, filteredMappings.build());
        }
      }
    }
    return indexMapBuilder.build();
  }

  /** Returns all the concrete indices. */
  public String[] concreteAllIndices() {
    return allIndices;
  }

  public String[] getConcreteAllIndices() {
    return concreteAllIndices();
  }

  public String[] concreteAllOpenIndices() {
    return allOpenIndices;
  }

  public String[] getConcreteAllOpenIndices() {
    return allOpenIndices;
  }

  public String[] concreteAllClosedIndices() {
    return allClosedIndices;
  }

  public String[] getConcreteAllClosedIndices() {
    return allClosedIndices;
  }

  /** Returns indexing routing for the given index. */
  // TODO: This can be moved to IndexNameExpressionResolver too, but this means that we will support
  // wildcards and other expressions
  // in the index,bulk,update and delete apis.
  public String resolveIndexRouting(
      @Nullable String parent, @Nullable String routing, String aliasOrIndex) {
    if (aliasOrIndex == null) {
      if (routing == null) {
        return parent;
      }
      return routing;
    }

    AliasOrIndex result = getAliasAndIndexLookup().get(aliasOrIndex);
    if (result == null || result.isAlias() == false) {
      if (routing == null) {
        return parent;
      }
      return routing;
    }
    AliasOrIndex.Alias alias = (AliasOrIndex.Alias) result;
    if (result.getIndices().size() > 1) {
      String[] indexNames = new String[result.getIndices().size()];
      int i = 0;
      for (IndexMetaData indexMetaData : result.getIndices()) {
        indexNames[i++] = indexMetaData.getIndex().getName();
      }
      throw new IllegalArgumentException(
          "Alias ["
              + aliasOrIndex
              + "] has more than one index associated with it ["
              + Arrays.toString(indexNames)
              + "], can't execute a single index op");
    }
    AliasMetaData aliasMd = alias.getFirstAliasMetaData();
    if (aliasMd.indexRouting() != null) {
      if (aliasMd.indexRouting().indexOf(',') != -1) {
        throw new IllegalArgumentException(
            "index/alias ["
                + aliasOrIndex
                + "] provided with routing value ["
                + aliasMd.getIndexRouting()
                + "] that resolved to several routing values, rejecting operation");
      }
      if (routing != null) {
        if (!routing.equals(aliasMd.indexRouting())) {
          throw new IllegalArgumentException(
              "Alias ["
                  + aliasOrIndex
                  + "] has index routing associated with it ["
                  + aliasMd.indexRouting()
                  + "], and was provided with routing value ["
                  + routing
                  + "], rejecting operation");
        }
      }
      // Alias routing overrides the parent routing (if any).
      return aliasMd.indexRouting();
    }
    if (routing == null) {
      return parent;
    }
    return routing;
  }

  public boolean hasIndex(String index) {
    return indices.containsKey(index);
  }

  public boolean hasConcreteIndex(String index) {
    return getAliasAndIndexLookup().containsKey(index);
  }

  public IndexMetaData index(String index) {
    return indices.get(index);
  }

  public IndexMetaData index(Index index) {
    return index(index.getName());
  }

  public ImmutableOpenMap<String, IndexMetaData> indices() {
    return this.indices;
  }

  public ImmutableOpenMap<String, IndexMetaData> getIndices() {
    return indices();
  }

  public ImmutableOpenMap<String, IndexTemplateMetaData> templates() {
    return this.templates;
  }

  public ImmutableOpenMap<String, IndexTemplateMetaData> getTemplates() {
    return this.templates;
  }

  public ImmutableOpenMap<String, Custom> customs() {
    return this.customs;
  }

  public ImmutableOpenMap<String, Custom> getCustoms() {
    return this.customs;
  }

  public <T extends Custom> T custom(String type) {
    return (T) customs.get(type);
  }

  public int totalNumberOfShards() {
    return this.totalNumberOfShards;
  }

  public int getTotalNumberOfShards() {
    return totalNumberOfShards();
  }

  public int numberOfShards() {
    return this.numberOfShards;
  }

  public int getNumberOfShards() {
    return numberOfShards();
  }

  /**
   * Identifies whether the array containing type names given as argument refers to all types The
   * empty or null array identifies all types
   *
   * @param types the array containing types
   * @return true if the provided array maps to all types, false otherwise
   */
  public static boolean isAllTypes(String[] types) {
    return types == null || types.length == 0 || isExplicitAllType(types);
  }

  /**
   * Identifies whether the array containing type names given as argument explicitly refers to all
   * types The empty or null array doesn't explicitly map to all types
   *
   * @param types the array containing index names
   * @return true if the provided array explicitly maps to all types, false otherwise
   */
  public static boolean isExplicitAllType(String[] types) {
    return types != null && types.length == 1 && ALL.equals(types[0]);
  }

  /**
   * @param concreteIndex The concrete index to check if routing is required
   * @param type The type to check if routing is required
   * @return Whether routing is required according to the mapping for the specified index and type
   */
  public boolean routingRequired(String concreteIndex, String type) {
    IndexMetaData indexMetaData = indices.get(concreteIndex);
    if (indexMetaData != null) {
      MappingMetaData mappingMetaData = indexMetaData.getMappings().get(type);
      if (mappingMetaData != null) {
        return mappingMetaData.routing().required();
      }
    }
    return false;
  }

  @Override
  public Iterator<IndexMetaData> iterator() {
    return indices.valuesIt();
  }

  public static boolean isGlobalStateEquals(MetaData metaData1, MetaData metaData2) {
    if (!metaData1.persistentSettings.equals(metaData2.persistentSettings)) {
      return false;
    }
    if (!metaData1.templates.equals(metaData2.templates())) {
      return false;
    }
    // Check if any persistent metadata needs to be saved
    int customCount1 = 0;
    for (ObjectObjectCursor<String, Custom> cursor : metaData1.customs) {
      if (customPrototypes.get(cursor.key).context().contains(XContentContext.GATEWAY)) {
        if (!cursor.value.equals(metaData2.custom(cursor.key))) return false;
        customCount1++;
      }
    }
    int customCount2 = 0;
    for (ObjectObjectCursor<String, Custom> cursor : metaData2.customs) {
      if (customPrototypes.get(cursor.key).context().contains(XContentContext.GATEWAY)) {
        customCount2++;
      }
    }
    if (customCount1 != customCount2) return false;
    return true;
  }

  @Override
  public Diff<MetaData> diff(MetaData previousState) {
    return new MetaDataDiff(previousState, this);
  }

  @Override
  public Diff<MetaData> readDiffFrom(StreamInput in) throws IOException {
    return new MetaDataDiff(in);
  }

  @Override
  public MetaData fromXContent(XContentParser parser, ParseFieldMatcher parseFieldMatcher)
      throws IOException {
    return Builder.fromXContent(parser);
  }

  @Override
  public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
    Builder.toXContent(this, builder, params);
    return builder;
  }

  private static class MetaDataDiff implements Diff<MetaData> {

    private long version;

    private String clusterUUID;

    private Settings transientSettings;
    private Settings persistentSettings;
    private Diff<ImmutableOpenMap<String, IndexMetaData>> indices;
    private Diff<ImmutableOpenMap<String, IndexTemplateMetaData>> templates;
    private Diff<ImmutableOpenMap<String, Custom>> customs;

    public MetaDataDiff(MetaData before, MetaData after) {
      clusterUUID = after.clusterUUID;
      version = after.version;
      transientSettings = after.transientSettings;
      persistentSettings = after.persistentSettings;
      indices =
          DiffableUtils.diff(before.indices, after.indices, DiffableUtils.getStringKeySerializer());
      templates =
          DiffableUtils.diff(
              before.templates, after.templates, DiffableUtils.getStringKeySerializer());
      customs =
          DiffableUtils.diff(before.customs, after.customs, DiffableUtils.getStringKeySerializer());
    }

    public MetaDataDiff(StreamInput in) throws IOException {
      clusterUUID = in.readString();
      version = in.readLong();
      transientSettings = Settings.readSettingsFromStream(in);
      persistentSettings = Settings.readSettingsFromStream(in);
      indices =
          DiffableUtils.readImmutableOpenMapDiff(
              in, DiffableUtils.getStringKeySerializer(), IndexMetaData.PROTO);
      templates =
          DiffableUtils.readImmutableOpenMapDiff(
              in, DiffableUtils.getStringKeySerializer(), IndexTemplateMetaData.PROTO);
      customs =
          DiffableUtils.readImmutableOpenMapDiff(
              in,
              DiffableUtils.getStringKeySerializer(),
              new DiffableUtils.DiffableValueSerializer<String, Custom>() {
                @Override
                public Custom read(StreamInput in, String key) throws IOException {
                  return lookupPrototypeSafe(key).readFrom(in);
                }

                @Override
                public Diff<Custom> readDiff(StreamInput in, String key) throws IOException {
                  return lookupPrototypeSafe(key).readDiffFrom(in);
                }
              });
    }

    @Override
    public void writeTo(StreamOutput out) throws IOException {
      out.writeString(clusterUUID);
      out.writeLong(version);
      Settings.writeSettingsToStream(transientSettings, out);
      Settings.writeSettingsToStream(persistentSettings, out);
      indices.writeTo(out);
      templates.writeTo(out);
      customs.writeTo(out);
    }

    @Override
    public MetaData apply(MetaData part) {
      Builder builder = builder();
      builder.clusterUUID(clusterUUID);
      builder.version(version);
      builder.transientSettings(transientSettings);
      builder.persistentSettings(persistentSettings);
      builder.indices(indices.apply(part.indices));
      builder.templates(templates.apply(part.templates));
      builder.customs(customs.apply(part.customs));
      return builder.build();
    }
  }

  @Override
  public MetaData readFrom(StreamInput in) throws IOException {
    Builder builder = new Builder();
    builder.version = in.readLong();
    builder.clusterUUID = in.readString();
    builder.transientSettings(readSettingsFromStream(in));
    builder.persistentSettings(readSettingsFromStream(in));
    int size = in.readVInt();
    for (int i = 0; i < size; i++) {
      builder.put(IndexMetaData.Builder.readFrom(in), false);
    }
    size = in.readVInt();
    for (int i = 0; i < size; i++) {
      builder.put(IndexTemplateMetaData.Builder.readFrom(in));
    }
    int customSize = in.readVInt();
    for (int i = 0; i < customSize; i++) {
      String type = in.readString();
      Custom customIndexMetaData = lookupPrototypeSafe(type).readFrom(in);
      builder.putCustom(type, customIndexMetaData);
    }
    return builder.build();
  }

  @Override
  public void writeTo(StreamOutput out) throws IOException {
    out.writeLong(version);
    out.writeString(clusterUUID);
    writeSettingsToStream(transientSettings, out);
    writeSettingsToStream(persistentSettings, out);
    out.writeVInt(indices.size());
    for (IndexMetaData indexMetaData : this) {
      indexMetaData.writeTo(out);
    }
    out.writeVInt(templates.size());
    for (ObjectCursor<IndexTemplateMetaData> cursor : templates.values()) {
      cursor.value.writeTo(out);
    }
    out.writeVInt(customs.size());
    for (ObjectObjectCursor<String, Custom> cursor : customs) {
      out.writeString(cursor.key);
      cursor.value.writeTo(out);
    }
  }

  public static Builder builder() {
    return new Builder();
  }

  public static Builder builder(MetaData metaData) {
    return new Builder(metaData);
  }

  /** All known byte-sized cluster settings. */
  public static final Set<String> CLUSTER_BYTES_SIZE_SETTINGS =
      unmodifiableSet(
          newHashSet(
              IndexStoreConfig.INDICES_STORE_THROTTLE_MAX_BYTES_PER_SEC_SETTING.getKey(),
              RecoverySettings.INDICES_RECOVERY_MAX_BYTES_PER_SEC_SETTING.getKey()));

  /** All known time cluster settings. */
  public static final Set<String> CLUSTER_TIME_SETTINGS =
      unmodifiableSet(
          newHashSet(
              IndicesTTLService.INDICES_TTL_INTERVAL_SETTING.getKey(),
              RecoverySettings.INDICES_RECOVERY_RETRY_DELAY_STATE_SYNC_SETTING.getKey(),
              RecoverySettings.INDICES_RECOVERY_RETRY_DELAY_NETWORK_SETTING.getKey(),
              RecoverySettings.INDICES_RECOVERY_ACTIVITY_TIMEOUT_SETTING.getKey(),
              RecoverySettings.INDICES_RECOVERY_INTERNAL_ACTION_TIMEOUT_SETTING.getKey(),
              RecoverySettings.INDICES_RECOVERY_INTERNAL_LONG_ACTION_TIMEOUT_SETTING.getKey(),
              DiskThresholdDecider.CLUSTER_ROUTING_ALLOCATION_REROUTE_INTERVAL_SETTING.getKey(),
              InternalClusterInfoService.INTERNAL_CLUSTER_INFO_UPDATE_INTERVAL_SETTING.getKey(),
              InternalClusterInfoService.INTERNAL_CLUSTER_INFO_TIMEOUT_SETTING.getKey(),
              DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey(),
              InternalClusterService.CLUSTER_SERVICE_SLOW_TASK_LOGGING_THRESHOLD_SETTING.getKey()));

  /**
   * As of 2.0 we require units for time and byte-sized settings. This methods adds default units to
   * any cluster settings that don't specify a unit.
   */
  public static MetaData addDefaultUnitsIfNeeded(ESLogger logger, MetaData metaData) {
    Settings.Builder newPersistentSettings = null;
    for (Map.Entry<String, String> ent : metaData.persistentSettings().getAsMap().entrySet()) {
      String settingName = ent.getKey();
      String settingValue = ent.getValue();
      if (CLUSTER_BYTES_SIZE_SETTINGS.contains(settingName)) {
        try {
          Long.parseLong(settingValue);
        } catch (NumberFormatException nfe) {
          continue;
        }
        // It's a naked number that previously would be interpreted as default unit (bytes); now we
        // add it:
        logger.warn(
            "byte-sized cluster setting [{}] with value [{}] is missing units; assuming default units (b) but in future versions this will be a hard error",
            settingName,
            settingValue);
        if (newPersistentSettings == null) {
          newPersistentSettings = Settings.builder();
          newPersistentSettings.put(metaData.persistentSettings());
        }
        newPersistentSettings.put(settingName, settingValue + "b");
      }
      if (CLUSTER_TIME_SETTINGS.contains(settingName)) {
        try {
          Long.parseLong(settingValue);
        } catch (NumberFormatException nfe) {
          continue;
        }
        // It's a naked number that previously would be interpreted as default unit (ms); now we add
        // it:
        logger.warn(
            "time cluster setting [{}] with value [{}] is missing units; assuming default units (ms) but in future versions this will be a hard error",
            settingName,
            settingValue);
        if (newPersistentSettings == null) {
          newPersistentSettings = Settings.builder();
          newPersistentSettings.put(metaData.persistentSettings());
        }
        newPersistentSettings.put(settingName, settingValue + "ms");
      }
    }

    if (newPersistentSettings != null) {
      return new MetaData(
          metaData.clusterUUID(),
          metaData.version(),
          metaData.transientSettings(),
          newPersistentSettings.build(),
          metaData.getIndices(),
          metaData.getTemplates(),
          metaData.getCustoms(),
          metaData.concreteAllIndices(),
          metaData.concreteAllOpenIndices(),
          metaData.concreteAllClosedIndices(),
          metaData.getAliasAndIndexLookup());
    } else {
      // No changes:
      return metaData;
    }
  }

  public static class Builder {

    private String clusterUUID;
    private long version;

    private Settings transientSettings = Settings.Builder.EMPTY_SETTINGS;
    private Settings persistentSettings = Settings.Builder.EMPTY_SETTINGS;

    private final ImmutableOpenMap.Builder<String, IndexMetaData> indices;
    private final ImmutableOpenMap.Builder<String, IndexTemplateMetaData> templates;
    private final ImmutableOpenMap.Builder<String, Custom> customs;

    public Builder() {
      clusterUUID = "_na_";
      indices = ImmutableOpenMap.builder();
      templates = ImmutableOpenMap.builder();
      customs = ImmutableOpenMap.builder();
    }

    public Builder(MetaData metaData) {
      this.clusterUUID = metaData.clusterUUID;
      this.transientSettings = metaData.transientSettings;
      this.persistentSettings = metaData.persistentSettings;
      this.version = metaData.version;
      this.indices = ImmutableOpenMap.builder(metaData.indices);
      this.templates = ImmutableOpenMap.builder(metaData.templates);
      this.customs = ImmutableOpenMap.builder(metaData.customs);
    }

    public Builder put(IndexMetaData.Builder indexMetaDataBuilder) {
      // we know its a new one, increment the version and store
      indexMetaDataBuilder.version(indexMetaDataBuilder.version() + 1);
      IndexMetaData indexMetaData = indexMetaDataBuilder.build();
      indices.put(indexMetaData.getIndex().getName(), indexMetaData);
      return this;
    }

    public Builder put(IndexMetaData indexMetaData, boolean incrementVersion) {
      if (indices.get(indexMetaData.getIndex().getName()) == indexMetaData) {
        return this;
      }
      // if we put a new index metadata, increment its version
      if (incrementVersion) {
        indexMetaData =
            IndexMetaData.builder(indexMetaData).version(indexMetaData.getVersion() + 1).build();
      }
      indices.put(indexMetaData.getIndex().getName(), indexMetaData);
      return this;
    }

    public IndexMetaData get(String index) {
      return indices.get(index);
    }

    public Builder remove(String index) {
      indices.remove(index);
      return this;
    }

    public Builder removeAllIndices() {
      indices.clear();
      return this;
    }

    public Builder indices(ImmutableOpenMap<String, IndexMetaData> indices) {
      this.indices.putAll(indices);
      return this;
    }

    public Builder put(IndexTemplateMetaData.Builder template) {
      return put(template.build());
    }

    public Builder put(IndexTemplateMetaData template) {
      templates.put(template.name(), template);
      return this;
    }

    public Builder removeTemplate(String templateName) {
      templates.remove(templateName);
      return this;
    }

    public Builder templates(ImmutableOpenMap<String, IndexTemplateMetaData> templates) {
      this.templates.putAll(templates);
      return this;
    }

    public Custom getCustom(String type) {
      return customs.get(type);
    }

    public Builder putCustom(String type, Custom custom) {
      customs.put(type, custom);
      return this;
    }

    public Builder removeCustom(String type) {
      customs.remove(type);
      return this;
    }

    public Builder customs(ImmutableOpenMap<String, Custom> customs) {
      this.customs.putAll(customs);
      return this;
    }

    public Builder updateSettings(Settings settings, String... indices) {
      if (indices == null || indices.length == 0) {
        indices = this.indices.keys().toArray(String.class);
      }
      for (String index : indices) {
        IndexMetaData indexMetaData = this.indices.get(index);
        if (indexMetaData == null) {
          throw new IndexNotFoundException(index);
        }
        put(
            IndexMetaData.builder(indexMetaData)
                .settings(settingsBuilder().put(indexMetaData.getSettings()).put(settings)));
      }
      return this;
    }

    public Builder updateNumberOfReplicas(int numberOfReplicas, String... indices) {
      if (indices == null || indices.length == 0) {
        indices = this.indices.keys().toArray(String.class);
      }
      for (String index : indices) {
        IndexMetaData indexMetaData = this.indices.get(index);
        if (indexMetaData == null) {
          throw new IndexNotFoundException(index);
        }
        put(IndexMetaData.builder(indexMetaData).numberOfReplicas(numberOfReplicas));
      }
      return this;
    }

    public Settings transientSettings() {
      return this.transientSettings;
    }

    public Builder transientSettings(Settings settings) {
      this.transientSettings = settings;
      return this;
    }

    public Settings persistentSettings() {
      return this.persistentSettings;
    }

    public Builder persistentSettings(Settings settings) {
      this.persistentSettings = settings;
      return this;
    }

    public Builder version(long version) {
      this.version = version;
      return this;
    }

    public Builder clusterUUID(String clusterUUID) {
      this.clusterUUID = clusterUUID;
      return this;
    }

    public Builder generateClusterUuidIfNeeded() {
      if (clusterUUID.equals("_na_")) {
        clusterUUID = Strings.randomBase64UUID();
      }
      return this;
    }

    public MetaData build() {
      // TODO: We should move these datastructures to IndexNameExpressionResolver, this will give
      // the following benefits:
      // 1) The datastructures will only be rebuilded when needed. Now during serializing we rebuild
      // these datastructures
      //    while these datastructures aren't even used.
      // 2) The aliasAndIndexLookup can be updated instead of rebuilding it all the time.

      // build all concrete indices arrays:
      // TODO: I think we can remove these arrays. it isn't worth the effort, for operations on all
      // indices.
      // When doing an operation across all indices, most of the time is spent on actually going to
      // all shards and
      // do the required operations, the bottleneck isn't resolving expressions into concrete
      // indices.
      List<String> allIndicesLst = new ArrayList<>();
      for (ObjectCursor<IndexMetaData> cursor : indices.values()) {
        allIndicesLst.add(cursor.value.getIndex().getName());
      }
      String[] allIndices = allIndicesLst.toArray(new String[allIndicesLst.size()]);

      List<String> allOpenIndicesLst = new ArrayList<>();
      List<String> allClosedIndicesLst = new ArrayList<>();
      for (ObjectCursor<IndexMetaData> cursor : indices.values()) {
        IndexMetaData indexMetaData = cursor.value;
        if (indexMetaData.getState() == IndexMetaData.State.OPEN) {
          allOpenIndicesLst.add(indexMetaData.getIndex().getName());
        } else if (indexMetaData.getState() == IndexMetaData.State.CLOSE) {
          allClosedIndicesLst.add(indexMetaData.getIndex().getName());
        }
      }
      String[] allOpenIndices = allOpenIndicesLst.toArray(new String[allOpenIndicesLst.size()]);
      String[] allClosedIndices =
          allClosedIndicesLst.toArray(new String[allClosedIndicesLst.size()]);

      // build all indices map
      SortedMap<String, AliasOrIndex> aliasAndIndexLookup = new TreeMap<>();
      for (ObjectCursor<IndexMetaData> cursor : indices.values()) {
        IndexMetaData indexMetaData = cursor.value;
        aliasAndIndexLookup.put(
            indexMetaData.getIndex().getName(), new AliasOrIndex.Index(indexMetaData));

        for (ObjectObjectCursor<String, AliasMetaData> aliasCursor : indexMetaData.getAliases()) {
          AliasMetaData aliasMetaData = aliasCursor.value;
          AliasOrIndex aliasOrIndex = aliasAndIndexLookup.get(aliasMetaData.getAlias());
          if (aliasOrIndex == null) {
            aliasOrIndex = new AliasOrIndex.Alias(aliasMetaData, indexMetaData);
            aliasAndIndexLookup.put(aliasMetaData.getAlias(), aliasOrIndex);
          } else if (aliasOrIndex instanceof AliasOrIndex.Alias) {
            AliasOrIndex.Alias alias = (AliasOrIndex.Alias) aliasOrIndex;
            alias.addIndex(indexMetaData);
          } else if (aliasOrIndex instanceof AliasOrIndex.Index) {
            AliasOrIndex.Index index = (AliasOrIndex.Index) aliasOrIndex;
            throw new IllegalStateException(
                "index and alias names need to be unique, but alias ["
                    + aliasMetaData.getAlias()
                    + "] and index "
                    + index.getIndex().getIndex()
                    + " have the same name");
          } else {
            throw new IllegalStateException(
                "unexpected alias [" + aliasMetaData.getAlias() + "][" + aliasOrIndex + "]");
          }
        }
      }
      aliasAndIndexLookup = Collections.unmodifiableSortedMap(aliasAndIndexLookup);
      return new MetaData(
          clusterUUID,
          version,
          transientSettings,
          persistentSettings,
          indices.build(),
          templates.build(),
          customs.build(),
          allIndices,
          allOpenIndices,
          allClosedIndices,
          aliasAndIndexLookup);
    }

    public static String toXContent(MetaData metaData) throws IOException {
      XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
      builder.startObject();
      toXContent(metaData, builder, ToXContent.EMPTY_PARAMS);
      builder.endObject();
      return builder.string();
    }

    public static void toXContent(
        MetaData metaData, XContentBuilder builder, ToXContent.Params params) throws IOException {
      XContentContext context = XContentContext.valueOf(params.param(CONTEXT_MODE_PARAM, "API"));

      builder.startObject("meta-data");

      builder.field("version", metaData.version());
      builder.field("cluster_uuid", metaData.clusterUUID);

      if (!metaData.persistentSettings().getAsMap().isEmpty()) {
        builder.startObject("settings");
        for (Map.Entry<String, String> entry :
            metaData.persistentSettings().getAsMap().entrySet()) {
          builder.field(entry.getKey(), entry.getValue());
        }
        builder.endObject();
      }

      if (context == XContentContext.API && !metaData.transientSettings().getAsMap().isEmpty()) {
        builder.startObject("transient_settings");
        for (Map.Entry<String, String> entry : metaData.transientSettings().getAsMap().entrySet()) {
          builder.field(entry.getKey(), entry.getValue());
        }
        builder.endObject();
      }

      builder.startObject("templates");
      for (ObjectCursor<IndexTemplateMetaData> cursor : metaData.templates().values()) {
        IndexTemplateMetaData.Builder.toXContent(cursor.value, builder, params);
      }
      builder.endObject();

      if (context == XContentContext.API && !metaData.indices().isEmpty()) {
        builder.startObject("indices");
        for (IndexMetaData indexMetaData : metaData) {
          IndexMetaData.Builder.toXContent(indexMetaData, builder, params);
        }
        builder.endObject();
      }

      for (ObjectObjectCursor<String, Custom> cursor : metaData.customs()) {
        Custom proto = lookupPrototypeSafe(cursor.key);
        if (proto.context().contains(context)) {
          builder.startObject(cursor.key);
          cursor.value.toXContent(builder, params);
          builder.endObject();
        }
      }
      builder.endObject();
    }

    public static MetaData fromXContent(XContentParser parser) throws IOException {
      Builder builder = new Builder();

      // we might get here after the meta-data element, or on a fresh parser
      XContentParser.Token token = parser.currentToken();
      String currentFieldName = parser.currentName();
      if (!"meta-data".equals(currentFieldName)) {
        token = parser.nextToken();
        if (token == XContentParser.Token.START_OBJECT) {
          // move to the field name (meta-data)
          token = parser.nextToken();
          if (token != XContentParser.Token.FIELD_NAME) {
            throw new IllegalArgumentException("Expected a field name but got " + token);
          }
          // move to the next object
          token = parser.nextToken();
        }
        currentFieldName = parser.currentName();
      }

      if (!"meta-data".equals(parser.currentName())) {
        throw new IllegalArgumentException(
            "Expected [meta-data] as a field name but got " + currentFieldName);
      }
      if (token != XContentParser.Token.START_OBJECT) {
        throw new IllegalArgumentException("Expected a START_OBJECT but got " + token);
      }

      while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
        if (token == XContentParser.Token.FIELD_NAME) {
          currentFieldName = parser.currentName();
        } else if (token == XContentParser.Token.START_OBJECT) {
          if ("settings".equals(currentFieldName)) {
            builder.persistentSettings(
                Settings.settingsBuilder()
                    .put(SettingsLoader.Helper.loadNestedFromMap(parser.mapOrdered()))
                    .build());
          } else if ("indices".equals(currentFieldName)) {
            while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
              builder.put(IndexMetaData.Builder.fromXContent(parser), false);
            }
          } else if ("templates".equals(currentFieldName)) {
            while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
              builder.put(IndexTemplateMetaData.Builder.fromXContent(parser, parser.currentName()));
            }
          } else {
            // check if its a custom index metadata
            Custom proto = lookupPrototype(currentFieldName);
            if (proto == null) {
              // TODO warn
              parser.skipChildren();
            } else {
              Custom custom = proto.fromXContent(parser);
              builder.putCustom(custom.type(), custom);
            }
          }
        } else if (token.isValue()) {
          if ("version".equals(currentFieldName)) {
            builder.version = parser.longValue();
          } else if ("cluster_uuid".equals(currentFieldName) || "uuid".equals(currentFieldName)) {
            builder.clusterUUID = parser.text();
          } else {
            throw new IllegalArgumentException("Unexpected field [" + currentFieldName + "]");
          }
        } else {
          throw new IllegalArgumentException("Unexpected token " + token);
        }
      }
      return builder.build();
    }

    public static MetaData readFrom(StreamInput in) throws IOException {
      return PROTO.readFrom(in);
    }
  }
}