2015-02-12 01:03:14 +03:00
|
|
|
// Copyright 2015 CoreOS, Inc.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package etcdserver
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"sort"
|
|
|
|
"time"
|
|
|
|
|
*: add cluster version and cluster version detection.
Cluster version is the min major.minor of all members in
the etcd cluster. Cluster version is set to the min version
that a etcd member is compatible with when first bootstrapp.
During a rolling upgrades, the cluster version will be updated
automatically.
For example:
```
Cluster [a:1, b:1 ,c:1] -> clusterVersion 1
update a -> 2, b -> 2
after a detection
Cluster [a:2, b:2 ,c:1] -> clusterVersion 1, since c is still 1
update c -> 2
after a detection
Cluster [a:2, b:2 ,c:2] -> clusterVersion 2
```
The API/raft component can utilize clusterVersion to determine if
it can accept a client request or a raft RPC.
We choose polling rather than pushing since we want to use the same
logic for cluster version detection and (TODO) cluster version checking.
Before a member actually joins a etcd cluster, it should check the version
of the cluster. Push does not work since the other members cannot push
version info to it before it actually joins. Moreover, we do not want our
raft RPC system (which is doing the heartbeat pushing) to coordinate cluster version.
2015-04-29 20:56:34 +03:00
|
|
|
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/coreos/go-semver/semver"
|
2015-02-12 01:03:14 +03:00
|
|
|
"github.com/coreos/etcd/pkg/types"
|
*: add cluster version and cluster version detection.
Cluster version is the min major.minor of all members in
the etcd cluster. Cluster version is set to the min version
that a etcd member is compatible with when first bootstrapp.
During a rolling upgrades, the cluster version will be updated
automatically.
For example:
```
Cluster [a:1, b:1 ,c:1] -> clusterVersion 1
update a -> 2, b -> 2
after a detection
Cluster [a:2, b:2 ,c:1] -> clusterVersion 1, since c is still 1
update c -> 2
after a detection
Cluster [a:2, b:2 ,c:2] -> clusterVersion 2
```
The API/raft component can utilize clusterVersion to determine if
it can accept a client request or a raft RPC.
We choose polling rather than pushing since we want to use the same
logic for cluster version detection and (TODO) cluster version checking.
Before a member actually joins a etcd cluster, it should check the version
of the cluster. Push does not work since the other members cannot push
version info to it before it actually joins. Moreover, we do not want our
raft RPC system (which is doing the heartbeat pushing) to coordinate cluster version.
2015-04-29 20:56:34 +03:00
|
|
|
"github.com/coreos/etcd/version"
|
2015-02-12 01:03:14 +03:00
|
|
|
)
|
|
|
|
|
2015-02-12 01:18:10 +03:00
|
|
|
// isMemberBootstrapped tries to check if the given member has been bootstrapped
|
2015-02-12 01:03:14 +03:00
|
|
|
// in the given cluster.
|
2015-05-13 03:22:06 +03:00
|
|
|
func isMemberBootstrapped(cl *cluster, member string, tr *http.Transport) bool {
|
2015-02-14 06:05:29 +03:00
|
|
|
rcl, err := getClusterFromRemotePeers(getRemotePeerURLs(cl, member), false, tr)
|
2015-02-12 01:03:14 +03:00
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
id := cl.MemberByName(member).ID
|
|
|
|
m := rcl.Member(id)
|
|
|
|
if m == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if len(m.ClientURLs) > 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2015-02-14 06:05:29 +03:00
|
|
|
// GetClusterFromRemotePeers takes a set of URLs representing etcd peers, and
|
2015-02-12 01:03:14 +03:00
|
|
|
// attempts to construct a Cluster by accessing the members endpoint on one of
|
|
|
|
// these URLs. The first URL to provide a response is used. If no URLs provide
|
|
|
|
// a response, or a Cluster cannot be successfully created from a received
|
|
|
|
// response, an error is returned.
|
2015-05-13 03:22:06 +03:00
|
|
|
func GetClusterFromRemotePeers(urls []string, tr *http.Transport) (*cluster, error) {
|
2015-02-14 06:05:29 +03:00
|
|
|
return getClusterFromRemotePeers(urls, true, tr)
|
2015-02-12 01:03:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// If logerr is true, it prints out more error messages.
|
2015-05-13 03:22:06 +03:00
|
|
|
func getClusterFromRemotePeers(urls []string, logerr bool, tr *http.Transport) (*cluster, error) {
|
2015-02-12 01:03:14 +03:00
|
|
|
cc := &http.Client{
|
|
|
|
Transport: tr,
|
|
|
|
Timeout: time.Second,
|
|
|
|
}
|
|
|
|
for _, u := range urls {
|
|
|
|
resp, err := cc.Get(u + "/members")
|
|
|
|
if err != nil {
|
|
|
|
if logerr {
|
|
|
|
log.Printf("etcdserver: could not get cluster response from %s: %v", u, err)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
b, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
if logerr {
|
|
|
|
log.Printf("etcdserver: could not read the body of cluster response: %v", err)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
var membs []*Member
|
|
|
|
if err := json.Unmarshal(b, &membs); err != nil {
|
|
|
|
if logerr {
|
|
|
|
log.Printf("etcdserver: could not unmarshal cluster response: %v", err)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
id, err := types.IDFromString(resp.Header.Get("X-Etcd-Cluster-ID"))
|
|
|
|
if err != nil {
|
|
|
|
if logerr {
|
|
|
|
log.Printf("etcdserver: could not parse the cluster ID from cluster res: %v", err)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
2015-05-13 03:22:06 +03:00
|
|
|
return newClusterFromMembers("", id, membs), nil
|
2015-02-12 01:03:14 +03:00
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("etcdserver: could not retrieve cluster information from the given urls")
|
|
|
|
}
|
|
|
|
|
2015-02-14 05:56:45 +03:00
|
|
|
// getRemotePeerURLs returns peer urls of remote members in the cluster. The
|
2015-02-12 01:03:14 +03:00
|
|
|
// returned list is sorted in ascending lexicographical order.
|
2015-05-13 03:22:06 +03:00
|
|
|
func getRemotePeerURLs(cl Cluster, local string) []string {
|
2015-02-12 01:03:14 +03:00
|
|
|
us := make([]string, 0)
|
|
|
|
for _, m := range cl.Members() {
|
2015-02-14 05:56:45 +03:00
|
|
|
if m.Name == local {
|
2015-02-12 01:03:14 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
us = append(us, m.PeerURLs...)
|
|
|
|
}
|
|
|
|
sort.Strings(us)
|
|
|
|
return us
|
|
|
|
}
|
*: add cluster version and cluster version detection.
Cluster version is the min major.minor of all members in
the etcd cluster. Cluster version is set to the min version
that a etcd member is compatible with when first bootstrapp.
During a rolling upgrades, the cluster version will be updated
automatically.
For example:
```
Cluster [a:1, b:1 ,c:1] -> clusterVersion 1
update a -> 2, b -> 2
after a detection
Cluster [a:2, b:2 ,c:1] -> clusterVersion 1, since c is still 1
update c -> 2
after a detection
Cluster [a:2, b:2 ,c:2] -> clusterVersion 2
```
The API/raft component can utilize clusterVersion to determine if
it can accept a client request or a raft RPC.
We choose polling rather than pushing since we want to use the same
logic for cluster version detection and (TODO) cluster version checking.
Before a member actually joins a etcd cluster, it should check the version
of the cluster. Push does not work since the other members cannot push
version info to it before it actually joins. Moreover, we do not want our
raft RPC system (which is doing the heartbeat pushing) to coordinate cluster version.
2015-04-29 20:56:34 +03:00
|
|
|
|
|
|
|
// getVersions returns the versions of the members in the given cluster.
|
|
|
|
// The key of the returned map is the member's ID. The value of the returned map
|
2015-05-14 03:04:46 +03:00
|
|
|
// is the semver versions string, including server and cluster.
|
|
|
|
// If it fails to get the version of a member, the key will be nil.
|
2015-05-14 03:19:32 +03:00
|
|
|
func getVersions(cl Cluster, local types.ID, tr *http.Transport) map[string]*version.Versions {
|
*: add cluster version and cluster version detection.
Cluster version is the min major.minor of all members in
the etcd cluster. Cluster version is set to the min version
that a etcd member is compatible with when first bootstrapp.
During a rolling upgrades, the cluster version will be updated
automatically.
For example:
```
Cluster [a:1, b:1 ,c:1] -> clusterVersion 1
update a -> 2, b -> 2
after a detection
Cluster [a:2, b:2 ,c:1] -> clusterVersion 1, since c is still 1
update c -> 2
after a detection
Cluster [a:2, b:2 ,c:2] -> clusterVersion 2
```
The API/raft component can utilize clusterVersion to determine if
it can accept a client request or a raft RPC.
We choose polling rather than pushing since we want to use the same
logic for cluster version detection and (TODO) cluster version checking.
Before a member actually joins a etcd cluster, it should check the version
of the cluster. Push does not work since the other members cannot push
version info to it before it actually joins. Moreover, we do not want our
raft RPC system (which is doing the heartbeat pushing) to coordinate cluster version.
2015-04-29 20:56:34 +03:00
|
|
|
members := cl.Members()
|
2015-05-14 03:04:46 +03:00
|
|
|
vers := make(map[string]*version.Versions)
|
*: add cluster version and cluster version detection.
Cluster version is the min major.minor of all members in
the etcd cluster. Cluster version is set to the min version
that a etcd member is compatible with when first bootstrapp.
During a rolling upgrades, the cluster version will be updated
automatically.
For example:
```
Cluster [a:1, b:1 ,c:1] -> clusterVersion 1
update a -> 2, b -> 2
after a detection
Cluster [a:2, b:2 ,c:1] -> clusterVersion 1, since c is still 1
update c -> 2
after a detection
Cluster [a:2, b:2 ,c:2] -> clusterVersion 2
```
The API/raft component can utilize clusterVersion to determine if
it can accept a client request or a raft RPC.
We choose polling rather than pushing since we want to use the same
logic for cluster version detection and (TODO) cluster version checking.
Before a member actually joins a etcd cluster, it should check the version
of the cluster. Push does not work since the other members cannot push
version info to it before it actually joins. Moreover, we do not want our
raft RPC system (which is doing the heartbeat pushing) to coordinate cluster version.
2015-04-29 20:56:34 +03:00
|
|
|
for _, m := range members {
|
2015-05-14 03:19:32 +03:00
|
|
|
if m.ID == local {
|
|
|
|
vers[m.ID.String()] = &version.Versions{Server: version.Version, Cluster: cl.Version().String()}
|
|
|
|
continue
|
|
|
|
}
|
*: add cluster version and cluster version detection.
Cluster version is the min major.minor of all members in
the etcd cluster. Cluster version is set to the min version
that a etcd member is compatible with when first bootstrapp.
During a rolling upgrades, the cluster version will be updated
automatically.
For example:
```
Cluster [a:1, b:1 ,c:1] -> clusterVersion 1
update a -> 2, b -> 2
after a detection
Cluster [a:2, b:2 ,c:1] -> clusterVersion 1, since c is still 1
update c -> 2
after a detection
Cluster [a:2, b:2 ,c:2] -> clusterVersion 2
```
The API/raft component can utilize clusterVersion to determine if
it can accept a client request or a raft RPC.
We choose polling rather than pushing since we want to use the same
logic for cluster version detection and (TODO) cluster version checking.
Before a member actually joins a etcd cluster, it should check the version
of the cluster. Push does not work since the other members cannot push
version info to it before it actually joins. Moreover, we do not want our
raft RPC system (which is doing the heartbeat pushing) to coordinate cluster version.
2015-04-29 20:56:34 +03:00
|
|
|
ver, err := getVersion(m, tr)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("etcdserver: cannot get the version of member %s (%v)", m.ID, err)
|
2015-05-14 03:04:46 +03:00
|
|
|
vers[m.ID.String()] = nil
|
*: add cluster version and cluster version detection.
Cluster version is the min major.minor of all members in
the etcd cluster. Cluster version is set to the min version
that a etcd member is compatible with when first bootstrapp.
During a rolling upgrades, the cluster version will be updated
automatically.
For example:
```
Cluster [a:1, b:1 ,c:1] -> clusterVersion 1
update a -> 2, b -> 2
after a detection
Cluster [a:2, b:2 ,c:1] -> clusterVersion 1, since c is still 1
update c -> 2
after a detection
Cluster [a:2, b:2 ,c:2] -> clusterVersion 2
```
The API/raft component can utilize clusterVersion to determine if
it can accept a client request or a raft RPC.
We choose polling rather than pushing since we want to use the same
logic for cluster version detection and (TODO) cluster version checking.
Before a member actually joins a etcd cluster, it should check the version
of the cluster. Push does not work since the other members cannot push
version info to it before it actually joins. Moreover, we do not want our
raft RPC system (which is doing the heartbeat pushing) to coordinate cluster version.
2015-04-29 20:56:34 +03:00
|
|
|
} else {
|
|
|
|
vers[m.ID.String()] = ver
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return vers
|
|
|
|
}
|
|
|
|
|
|
|
|
// decideClusterVersion decides the cluster version based on the versions map.
|
2015-05-14 03:04:46 +03:00
|
|
|
// The returned version is the min server version in the map, or nil if the min
|
*: add cluster version and cluster version detection.
Cluster version is the min major.minor of all members in
the etcd cluster. Cluster version is set to the min version
that a etcd member is compatible with when first bootstrapp.
During a rolling upgrades, the cluster version will be updated
automatically.
For example:
```
Cluster [a:1, b:1 ,c:1] -> clusterVersion 1
update a -> 2, b -> 2
after a detection
Cluster [a:2, b:2 ,c:1] -> clusterVersion 1, since c is still 1
update c -> 2
after a detection
Cluster [a:2, b:2 ,c:2] -> clusterVersion 2
```
The API/raft component can utilize clusterVersion to determine if
it can accept a client request or a raft RPC.
We choose polling rather than pushing since we want to use the same
logic for cluster version detection and (TODO) cluster version checking.
Before a member actually joins a etcd cluster, it should check the version
of the cluster. Push does not work since the other members cannot push
version info to it before it actually joins. Moreover, we do not want our
raft RPC system (which is doing the heartbeat pushing) to coordinate cluster version.
2015-04-29 20:56:34 +03:00
|
|
|
// version in unknown.
|
2015-05-14 03:04:46 +03:00
|
|
|
func decideClusterVersion(vers map[string]*version.Versions) *semver.Version {
|
*: add cluster version and cluster version detection.
Cluster version is the min major.minor of all members in
the etcd cluster. Cluster version is set to the min version
that a etcd member is compatible with when first bootstrapp.
During a rolling upgrades, the cluster version will be updated
automatically.
For example:
```
Cluster [a:1, b:1 ,c:1] -> clusterVersion 1
update a -> 2, b -> 2
after a detection
Cluster [a:2, b:2 ,c:1] -> clusterVersion 1, since c is still 1
update c -> 2
after a detection
Cluster [a:2, b:2 ,c:2] -> clusterVersion 2
```
The API/raft component can utilize clusterVersion to determine if
it can accept a client request or a raft RPC.
We choose polling rather than pushing since we want to use the same
logic for cluster version detection and (TODO) cluster version checking.
Before a member actually joins a etcd cluster, it should check the version
of the cluster. Push does not work since the other members cannot push
version info to it before it actually joins. Moreover, we do not want our
raft RPC system (which is doing the heartbeat pushing) to coordinate cluster version.
2015-04-29 20:56:34 +03:00
|
|
|
var cv *semver.Version
|
|
|
|
lv := semver.Must(semver.NewVersion(version.Version))
|
|
|
|
|
|
|
|
for mid, ver := range vers {
|
2015-05-14 03:04:46 +03:00
|
|
|
if ver == nil {
|
*: add cluster version and cluster version detection.
Cluster version is the min major.minor of all members in
the etcd cluster. Cluster version is set to the min version
that a etcd member is compatible with when first bootstrapp.
During a rolling upgrades, the cluster version will be updated
automatically.
For example:
```
Cluster [a:1, b:1 ,c:1] -> clusterVersion 1
update a -> 2, b -> 2
after a detection
Cluster [a:2, b:2 ,c:1] -> clusterVersion 1, since c is still 1
update c -> 2
after a detection
Cluster [a:2, b:2 ,c:2] -> clusterVersion 2
```
The API/raft component can utilize clusterVersion to determine if
it can accept a client request or a raft RPC.
We choose polling rather than pushing since we want to use the same
logic for cluster version detection and (TODO) cluster version checking.
Before a member actually joins a etcd cluster, it should check the version
of the cluster. Push does not work since the other members cannot push
version info to it before it actually joins. Moreover, we do not want our
raft RPC system (which is doing the heartbeat pushing) to coordinate cluster version.
2015-04-29 20:56:34 +03:00
|
|
|
return nil
|
|
|
|
}
|
2015-05-14 03:04:46 +03:00
|
|
|
v, err := semver.NewVersion(ver.Server)
|
*: add cluster version and cluster version detection.
Cluster version is the min major.minor of all members in
the etcd cluster. Cluster version is set to the min version
that a etcd member is compatible with when first bootstrapp.
During a rolling upgrades, the cluster version will be updated
automatically.
For example:
```
Cluster [a:1, b:1 ,c:1] -> clusterVersion 1
update a -> 2, b -> 2
after a detection
Cluster [a:2, b:2 ,c:1] -> clusterVersion 1, since c is still 1
update c -> 2
after a detection
Cluster [a:2, b:2 ,c:2] -> clusterVersion 2
```
The API/raft component can utilize clusterVersion to determine if
it can accept a client request or a raft RPC.
We choose polling rather than pushing since we want to use the same
logic for cluster version detection and (TODO) cluster version checking.
Before a member actually joins a etcd cluster, it should check the version
of the cluster. Push does not work since the other members cannot push
version info to it before it actually joins. Moreover, we do not want our
raft RPC system (which is doing the heartbeat pushing) to coordinate cluster version.
2015-04-29 20:56:34 +03:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("etcdserver: cannot understand the version of member %s (%v)", mid, err)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if lv.LessThan(*v) {
|
|
|
|
log.Printf("etcdserver: the etcd version %s is not up-to-date", lv.String())
|
|
|
|
log.Printf("etcdserver: member %s has a higher version %s", mid, ver)
|
|
|
|
}
|
|
|
|
if cv == nil {
|
|
|
|
cv = v
|
|
|
|
} else if v.LessThan(*cv) {
|
|
|
|
cv = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return cv
|
|
|
|
}
|
2015-05-08 21:10:12 +03:00
|
|
|
|
2015-05-14 03:04:46 +03:00
|
|
|
// getVersion returns the Versions of the given member via its
|
2015-05-08 21:10:12 +03:00
|
|
|
// peerURLs. Returns the last error if it fails to get the version.
|
2015-05-14 03:04:46 +03:00
|
|
|
func getVersion(m *Member, tr *http.Transport) (*version.Versions, error) {
|
2015-05-08 21:10:12 +03:00
|
|
|
cc := &http.Client{
|
|
|
|
Transport: tr,
|
|
|
|
Timeout: time.Second,
|
|
|
|
}
|
|
|
|
var (
|
|
|
|
err error
|
|
|
|
resp *http.Response
|
|
|
|
)
|
|
|
|
|
|
|
|
for _, u := range m.PeerURLs {
|
|
|
|
resp, err = cc.Get(u + "/version")
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
2015-05-14 03:04:46 +03:00
|
|
|
// etcd 2.0 does not have version endpoint on peer url.
|
|
|
|
if resp.StatusCode == http.StatusNotFound {
|
|
|
|
resp.Body.Close()
|
|
|
|
return &version.Versions{"2.0.0", "2.0.0"}, nil
|
|
|
|
}
|
|
|
|
|
2015-05-08 21:10:12 +03:00
|
|
|
b, err := ioutil.ReadAll(resp.Body)
|
|
|
|
resp.Body.Close()
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
var vers version.Versions
|
|
|
|
if err := json.Unmarshal(b, &vers); err != nil {
|
|
|
|
continue
|
|
|
|
}
|
2015-05-14 03:04:46 +03:00
|
|
|
return &vers, nil
|
2015-05-08 21:10:12 +03:00
|
|
|
}
|
2015-05-14 03:04:46 +03:00
|
|
|
return nil, err
|
2015-05-08 21:10:12 +03:00
|
|
|
}
|