/**
   * Tests that incremental metadata changes made on the master are visible (refreshed) when needed
   * on the replicas. Incremental metadata changes occur when not all metadata is known to the DPL
   * initially when the store is opened, and additional metadata is discovered as entities are
   * written. This is scenario A-1 in the [#16655] SR.
   *
   * <p>This is not actually a schema upgrade test, but is conveniently tested here using the
   * upgrade test framework.
   */
  @Test
  public void testIncrementalMetadataChanges() throws Exception {

    open();

    /* Master writes and reads Data entity. */
    masterApp.writeData(0);
    masterApp.readData(0);

    /* Replicas read Data entity. */
    replicaApp1.readData(0);
    replicaApp2.readData(0);

    /* Master writes DataA (subclass), causing a metadata update. */
    masterApp.writeDataA(1);
    masterApp.readDataA(1);

    /* Replicas read DataA and must refresh metadata. */
    replicaApp1.readDataA(1);
    replicaApp2.readDataA(1);

    /* Read Data again for good measure. */
    masterApp.readData(0);
    replicaApp1.readData(0);
    replicaApp2.readData(0);

    close();
  }
  /**
   * Tests that when a replica having stale metadata is elected master, the first metadata update on
   * the new master causes refresh of the stale metadata before the new metadata is written. This is
   * scenario A-2 in the [#16655] SR.
   *
   * <p>This is not actually a schema upgrade test, but is conveniently tested here using the
   * upgrade test framework.
   */
  @Test
  public void testElectedMasterWithStaleMetadata() throws Exception {

    open();

    /* Master writes and reads Data entity. */
    masterApp.writeData(0);
    masterApp.readData(0);

    /* Replicas read Data entity. */
    replicaApp1.readData(0);
    replicaApp2.readData(0);

    /* Master writes DataA (subclass), causing a metadata update. */
    masterApp.writeDataA(1);
    masterApp.readDataA(1);

    /*
     * Master is bounced (but not upgraded), replica1 switches roles with
     * master.
     */
    bounceMaster(0);

    /*
     * Master writes DataB, which requires a metadata change.  Before this
     * new metadata change, it must refresh metadata from disk to get the
     * definition of DataA.
     */
    masterApp.writeDataB(2);

    /*
     * Reading DataA would cause a ClassCastException if refresh did not
     * occur above, because the format ID for DataA would be incorrect.
     */
    masterApp.readDataA(1);

    /* Read all again for good measure. */
    masterApp.readData(0);
    masterApp.readDataA(1);
    masterApp.readDataB(2);
    replicaApp1.readData(0);
    replicaApp1.readDataA(1);
    replicaApp1.readDataB(2);
    replicaApp2.readData(0);
    replicaApp2.readDataA(1);
    replicaApp2.readDataB(2);

    close();
  }
  /** Tests scenarios B-1 and B-2 in the [#16655] SR. */
  @Test
  public void testUpgrade() throws Exception {

    open();

    /* Master writes and reads v0 entities. */
    masterApp.writeData(0);
    masterApp.writeDataA(1);
    masterApp.writeDataB(2);
    masterApp.readData(0);
    masterApp.readDataA(1);
    masterApp.readDataB(2);

    /* Replicas read v0 entities. */
    replicaApp1.readData(0);
    replicaApp1.readDataA(1);
    replicaApp1.readDataB(2);
    replicaApp2.readData(0);
    replicaApp2.readDataA(1);
    replicaApp2.readDataB(2);

    /* Replica1 is upgraded to v1, upgrades metadata in memory. */
    bounceReplica1(1);

    /* Upgraded replica1 reads v0 entities, can't get new index. */
    try {
      replicaApp1.readData(0);
      fail();
    } catch (IndexNotAvailableException e) {
    }
    try {
      replicaApp1.readDataB(2);
      fail();
    } catch (IndexNotAvailableException e) {
    }

    /* Upgraded replica1 can't get index for new entity NewData2. */
    try {
      replicaApp1.readData2(14);
      fail();
    } catch (IndexNotAvailableException e) {
    }

    /* Replica1 can read v0 DataA, because it has no new indexes. */
    replicaApp1.readDataA(1);

    /* Replica2 (not yet upgraded) reads v0 entities without errors. */
    replicaApp2.readData(0);
    replicaApp2.readDataA(1);
    replicaApp2.readDataB(2);

    /* Replica2 is upgraded to v1, upgrades metadata in memory. */
    bounceReplica2(1);

    /* Upgraded replicas read v0 entities, can't get new index. */
    try {
      replicaApp1.readData(0);
      fail();
    } catch (IndexNotAvailableException e) {
    }
    try {
      replicaApp1.readDataB(2);
      fail();
    } catch (IndexNotAvailableException e) {
    }
    try {
      replicaApp2.readData(0);
      fail();
    } catch (IndexNotAvailableException e) {
    }
    try {
      replicaApp2.readDataB(2);
      fail();
    } catch (IndexNotAvailableException e) {
    }

    /* Upgraded replicas can't get index for new entity NewData2. */
    try {
      replicaApp1.readData2(14);
      fail();
    } catch (IndexNotAvailableException e) {
    }
    try {
      replicaApp2.readData2(14);
      fail();
    } catch (IndexNotAvailableException e) {
    }

    /* Upgraded replicas can read v0 DataA, it has no new indexes. */
    replicaApp1.readDataA(1);
    replicaApp2.readDataA(1);

    /* Read again on master for good measure. */
    masterApp.readData(0);
    masterApp.readDataA(1);
    masterApp.readDataB(2);

    /* Master is upgraded to v1, replica1 switches roles with master. */
    bounceMaster(1);

    /* Metadata is refreshed when new indexes are requested. */
    try {
      masterApp.readData(0);
      fail();
    } catch (DatabasePreemptedException expected) {
      masterApp.close();
      masterApp.open(masterEnv);
      masterApp.readData(0);
    }
    masterApp.readDataA(1);
    masterApp.readDataB(2);
    try {
      replicaApp1.readData(0);
      fail();
    } catch (DatabasePreemptedException expected) {
      replicaApp1.close();
      replicaApp1.open(replicaEnv1);
      replicaApp1.readData(0);
    }
    replicaApp1.readDataA(1);
    replicaApp1.readDataB(2);
    try {
      replicaApp2.readData(0);
      fail();
    } catch (DatabasePreemptedException expected) {
      replicaApp2.close();
      replicaApp2.open(replicaEnv2);
      replicaApp2.readData(0);
    }
    replicaApp2.readDataA(1);
    replicaApp2.readDataB(2);

    /* Master writes v1 entities. */
    masterApp.writeData(10);
    masterApp.writeDataA(11);
    masterApp.writeDataB(12);

    /* Master reads v0 and v1 entities. */
    masterApp.readData(0);
    masterApp.readData(10);
    masterApp.readDataA(1);
    masterApp.readDataA(11);
    masterApp.readDataB(2);
    masterApp.readDataB(12);

    /* Replicas read v0 and v1 entities. */
    replicaApp1.readData(0);
    replicaApp1.readData(10);
    replicaApp1.readDataA(1);
    replicaApp1.readDataA(11);
    replicaApp1.readDataB(2);
    replicaApp1.readDataB(12);
    replicaApp2.readData(0);
    replicaApp2.readData(10);
    replicaApp2.readDataA(1);
    replicaApp2.readDataA(11);
    replicaApp2.readDataB(2);
    replicaApp2.readDataB(12);

    /* Master writes new NewDataC subclass, all can read. */
    masterApp.writeDataC(13);
    masterApp.readDataC(13);
    replicaApp1.readDataC(13);
    replicaApp2.readDataC(13);

    /* Master writes new NewData2 entity class, all can read. */
    masterApp.writeData2(14);
    masterApp.readData2(14);
    replicaApp1.readData2(14);
    replicaApp2.readData2(14);

    close();
  }