Today the stable-master health indicator reports `yellow` health if it sees the elected master change 4 times in 30 minutes. However, this could be caused by planned activity such as a rolling restart, and in that case we should not consider a sequence of master failovers as a sign of ill health.

I think we can solve this by refining this check to disregard master nodes that have restarted since they were master, or are no longer in the cluster at all. Restarting the master will always trigger a failover, and the stable-master indicator need not worry about whether the node restart was planned or not; separately, unplanned node restarts are definitely a sign of ill health, but that applies whether the node was the elected master or not. Similarly, if a node is no longer in the cluster then either it is no longer running (again, whether that's planned or not is irrelevant to the stable-master indicator) or else it is running and failing to join the cluster (and hence reporting its own ill health).
server/src/test/java/org/elasticsearch/cluster/coordination/MasterHistoryTests.java
masterHistory.clusterChanged(new ClusterChangedEvent(TEST_SOURCE, nullMasterClusterState, node1MasterClusterState));
        masterHistory.clusterChanged(new ClusterChangedEvent(TEST_SOURCE, nullMasterClusterState, nullMasterClusterState));
        assertTrue(masterHistory.hasSeenMasterInLastNSeconds(5));
        masterHistory.clusterChanged(new ClusterChangedEvent(TEST_SOURCE, node1MasterClusterState, nullMasterClusterState));
        assertTrue(masterHistory.hasSeenMasterInLastNSeconds(5));
        /*
         * 6 seconds ago we get these master changes (it had been set to node1 previously):
         * null -> null
         * Even though the last non-null master was more
         * than 5 seconds ago (and more than the age of history we keep, 30 minutes), the transition from it to null was just now, so we
         * still say that there has been a master recently.
         */
        long sixSecondsAgo = System.currentTimeMillis() - new TimeValue(6, TimeUnit.SECONDS).getMillis();
        when(threadPool.relativeTimeInMillis()).thenReturn(sixSecondsAgo);
        masterHistory.clusterChanged(new ClusterChangedEvent(TEST_SOURCE, nullMasterClusterState, node1MasterClusterState));
        assertTrue(masterHistory.hasSeenMasterInLastNSeconds(5));
        masterHistory.clusterChanged(new ClusterChangedEvent(TEST_SOURCE, nullMasterClusterState, nullMasterClusterState));
        assertTrue(masterHistory.hasSeenMasterInLastNSeconds(5));
        /*
         * Right now we get these master changes (the master was null when this section began):
         * null -> node1
         * Even before the first transition to null, we have no longer seen a non-null master within the last 5 seconds (because we last
         * transitioned from a non-null master 6 seconds ago). After the transition to node1, we again have seen a non-null master in the
         *  last 5 seconds.
         */
        long now = System.currentTimeMillis();
        when(threadPool.relativeTimeInMillis()).thenReturn(now);
        assertFalse(masterHistory.hasSeenMasterInLastNSeconds(5));
        masterHistory.clusterChanged(new ClusterChangedEvent(TEST_SOURCE, nullMasterClusterState, nullMasterClusterState));
        assertFalse(masterHistory.hasSeenMasterInLastNSeconds(5));