diff --git a/server/api/api.raml b/server/api/api.raml index 0924348eb54..be32c1e742d 100644 --- a/server/api/api.raml +++ b/server/api/api.raml @@ -13,6 +13,7 @@ types: type: object properties: raft_bootstrap_time?: string + is_initialized: boolean Version: type: object properties: diff --git a/server/api/cluster_test.go b/server/api/cluster_test.go index 7f5d71d0d81..aca5a368361 100644 --- a/server/api/cluster_test.go +++ b/server/api/cluster_test.go @@ -64,9 +64,16 @@ func (s *testClusterInfo) TestGetClusterStatus(c *C) { err := readJSONWithURL(url, &status) c.Assert(err, IsNil) c.Assert(status.RaftBootstrapTime.IsZero(), IsTrue) + c.Assert(status.IsInitialized, IsFalse) now := time.Now() mustBootstrapCluster(c, s.svr) err = readJSONWithURL(url, &status) c.Assert(err, IsNil) c.Assert(status.RaftBootstrapTime.After(now), IsTrue) + c.Assert(status.IsInitialized, IsFalse) + s.svr.SetReplicationConfig(server.ReplicationConfig{MaxReplicas: 1}) + err = readJSONWithURL(url, &status) + c.Assert(err, IsNil) + c.Assert(status.RaftBootstrapTime.After(now), IsTrue) + c.Assert(status.IsInitialized, IsTrue) } diff --git a/server/cluster.go b/server/cluster.go index 03d2e8213db..16e289a2303 100644 --- a/server/cluster.go +++ b/server/cluster.go @@ -65,6 +65,7 @@ type RaftCluster struct { // ClusterStatus saves some state information type ClusterStatus struct { RaftBootstrapTime time.Time `json:"raft_bootstrap_time,omitempty"` + IsInitialized bool `json:"is_initialized"` } func newRaftCluster(s *Server, clusterID uint64) *RaftCluster { @@ -78,18 +79,43 @@ func newRaftCluster(s *Server, clusterID uint64) *RaftCluster { } func (c *RaftCluster) loadClusterStatus() (*ClusterStatus, error) { - data, err := c.s.kv.Load((c.s.kv.ClusterStatePath("raft_bootstrap_time"))) + bootstrapTime, err := c.loadBootstrapTime() if err != nil { return nil, err } - if len(data) == 0 { - return &ClusterStatus{}, nil + var isInitialized bool + if bootstrapTime != zeroTime { + isInitialized = c.isInitialized() } - t, err := parseTimestamp([]byte(data)) + return &ClusterStatus{ + RaftBootstrapTime: bootstrapTime, + IsInitialized: isInitialized, + }, nil +} + +func (c *RaftCluster) isInitialized() bool { + if c.cachedCluster.getRegionCount() > 1 { + return true + } + region := c.cachedCluster.searchRegion(nil) + return region != nil && + len(region.GetVoters()) >= int(c.s.GetReplicationConfig().MaxReplicas) && + len(region.GetPendingPeers()) == 0 +} + +// loadBootstrapTime loads the saved bootstrap time from etcd. It returns zero +// value of time.Time when there is error or the cluster is not bootstrapped +// yet. +func (c *RaftCluster) loadBootstrapTime() (time.Time, error) { + var t time.Time + data, err := c.s.kv.Load(c.s.kv.ClusterStatePath("raft_bootstrap_time")) if err != nil { - return nil, err + return t, err + } + if data == "" { + return t, nil } - return &ClusterStatus{RaftBootstrapTime: t}, nil + return parseTimestamp([]byte(data)) } func (c *RaftCluster) start() error {