kubelet: Update engine version parsing to handle semantic versioning

This updates the dockertools.dockerVersion to use a semantic versioning
library to more gracefully support engine versions which include
additional version fields.

Previously, go-dockerclient's APIVersion struct was use which only
handles plain numeric x.y.z version strings. With #19675, the library
was now used on the Docker engine string, however it is possible for the
engine string to include include additional information for beta, rc, or
distro specific builds.

This PR also enables the TestDockerRuntimeVersion test which was
previously just a FIXME and updates it to pass, and be used to test the
version string that cause #20005.

This negates the need for fsouza/go-dockerclient#451, since even with
that change, if a user was running Docker 1.10.0-rc1, this would cause
the kubelet to report it as simply 1.10.0.
This commit is contained in:
Ken Robertson 2016-01-22 12:54:23 -08:00
parent 0b00928c74
commit fff8a7c371
2 changed files with 94 additions and 24 deletions

View File

@ -31,6 +31,7 @@ import (
"sync"
"time"
"github.com/coreos/go-semver/semver"
docker "github.com/fsouza/go-dockerclient"
"github.com/golang/glog"
"github.com/golang/groupcache/lru"
@ -944,18 +945,47 @@ func (dm *DockerManager) podInfraContainerChanged(pod *api.Pod, podInfraContaine
return podInfraContainerStatus.Hash != kubecontainer.HashContainer(expectedPodInfraContainer), nil
}
type dockerVersion docker.APIVersion
func NewVersion(input string) (dockerVersion, error) {
version, err := docker.NewAPIVersion(input)
return dockerVersion(version), err
// dockerVersion implementes kubecontainer.Version interface by implementing
// Compare() and String() (which is implemented by the underlying semver.Version)
// TODO: this code is the same as rktVersion and may make sense to be moved to
// somewhere shared.
type dockerVersion struct {
*semver.Version
}
func (dv dockerVersion) String() string {
func newDockerVersion(version string) (dockerVersion, error) {
sem, err := semver.NewVersion(version)
if err != nil {
return dockerVersion{}, err
}
return dockerVersion{sem}, nil
}
func (r dockerVersion) Compare(other string) (int, error) {
v, err := semver.NewVersion(other)
if err != nil {
return -1, err
}
if r.LessThan(*v) {
return -1, nil
}
if v.LessThan(*r.Version) {
return 1, nil
}
return 0, nil
}
// dockerVersion implementes kubecontainer.Version interface by implementing
// Compare() and String() on top og go-dockerclient's APIVersion. This version
// string doesn't conform to semantic versioning, as it is only "x.y"
type dockerAPIVersion docker.APIVersion
func (dv dockerAPIVersion) String() string {
return docker.APIVersion(dv).String()
}
func (dv dockerVersion) Compare(other string) (int, error) {
func (dv dockerAPIVersion) Compare(other string) (int, error) {
a := docker.APIVersion(dv)
b, err := docker.NewAPIVersion(other)
if err != nil {
@ -981,12 +1011,12 @@ func (dm *DockerManager) Version() (kubecontainer.Version, error) {
}
engineVersion := env.Get("Version")
version, err := docker.NewAPIVersion(engineVersion)
version, err := newDockerVersion(engineVersion)
if err != nil {
glog.Errorf("docker: failed to parse docker server version %q: %v", engineVersion, err)
return nil, fmt.Errorf("docker: failed to parse docker server version %q: %v", engineVersion, err)
}
return dockerVersion(version), nil
return version, nil
}
func (dm *DockerManager) APIVersion() (kubecontainer.Version, error) {
@ -1001,7 +1031,7 @@ func (dm *DockerManager) APIVersion() (kubecontainer.Version, error) {
glog.Errorf("docker: failed to parse docker api version %q: %v", apiVersion, err)
return nil, fmt.Errorf("docker: failed to parse docker api version %q: %v", apiVersion, err)
}
return dockerVersion(version), nil
return dockerAPIVersion(version), nil
}
// The first version of docker that supports exec natively is 1.3.0 == API 1.15

View File

@ -2719,26 +2719,59 @@ func TestUpdateNewNodeOutOfDiskStatusWithTransitionFrequency(t *testing.T) {
}
}
// FIXME: Enable me..
func testDockerRuntimeVersion(t *testing.T) {
func TestDockerRuntimeVersion(t *testing.T) {
testKubelet := newTestKubelet(t)
kubelet := testKubelet.kubelet
fakeRuntime := testKubelet.fakeRuntime
fakeRuntime.RuntimeType = "docker"
fakeRuntime.VersionInfo = "1.5.0"
fakeRuntime.APIVersionInfo = "1.18"
fakeRuntime.VersionInfo = "1.10.0-rc1-fc24"
fakeRuntime.APIVersionInfo = "1.22"
kubeClient := testKubelet.fakeKubeClient
kubeClient.ReactionChain = testclient.NewSimpleFake(&api.NodeList{Items: []api.Node{
{ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}},
{
ObjectMeta: api.ObjectMeta{Name: testKubeletHostname},
Spec: api.NodeSpec{},
Status: api.NodeStatus{
Conditions: []api.NodeCondition{
{
Type: api.NodeOutOfDisk,
Status: api.ConditionFalse,
Reason: "KubeletHasSufficientDisk",
Message: fmt.Sprintf("kubelet has sufficient disk space available"),
LastHeartbeatTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
LastTransitionTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
},
{
Type: api.NodeReady,
Status: api.ConditionTrue,
Reason: "KubeletReady",
Message: fmt.Sprintf("kubelet is posting ready status"),
LastHeartbeatTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
LastTransitionTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
Capacity: api.ResourceList{
api.ResourceCPU: *resource.NewMilliQuantity(3000, resource.DecimalSI),
api.ResourceMemory: *resource.NewQuantity(20E9, resource.BinarySI),
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
},
Allocatable: api.ResourceList{
api.ResourceCPU: *resource.NewMilliQuantity(2800, resource.DecimalSI),
api.ResourceMemory: *resource.NewQuantity(19900E6, resource.BinarySI),
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
},
},
},
}}).ReactionChain
mockCadvisor := testKubelet.fakeCadvisor
mockCadvisor.On("Start").Return(nil)
machineInfo := &cadvisorapi.MachineInfo{
MachineID: "123",
SystemUUID: "abc",
BootID: "1b3",
NumCores: 2,
MemoryCapacity: 1024,
MemoryCapacity: 20E9,
}
mockCadvisor := testKubelet.fakeCadvisor
mockCadvisor.On("MachineInfo").Return(machineInfo, nil)
versionInfo := &cadvisorapi.VersionInfo{
KernelVersion: "3.16.0-0.bpo.4-amd64",
@ -2779,13 +2812,18 @@ func testDockerRuntimeVersion(t *testing.T) {
BootID: "1b3",
KernelVersion: "3.16.0-0.bpo.4-amd64",
OSImage: "Debian GNU/Linux 7 (wheezy)",
ContainerRuntimeVersion: "docker://1.5.0",
ContainerRuntimeVersion: "docker://1.10.0-rc1-fc24",
KubeletVersion: version.Get().String(),
KubeProxyVersion: version.Get().String(),
},
Capacity: api.ResourceList{
api.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
api.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
api.ResourceMemory: *resource.NewQuantity(20E9, resource.BinarySI),
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
},
Allocatable: api.ResourceList{
api.ResourceCPU: *resource.NewMilliQuantity(1800, resource.DecimalSI),
api.ResourceMemory: *resource.NewQuantity(19900E6, resource.BinarySI),
api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
},
Addresses: []api.NodeAddress{
@ -2805,6 +2843,7 @@ func testDockerRuntimeVersion(t *testing.T) {
},
}
kubelet.runtimeState = newRuntimeState(maxWaitForContainerRuntime, false, "", kubelet.isContainerRuntimeVersionCompatible)
kubelet.updateRuntimeUp()
if err := kubelet.updateNodeStatus(); err != nil {
t.Errorf("unexpected error: %v", err)
@ -2836,13 +2875,14 @@ func testDockerRuntimeVersion(t *testing.T) {
t.Errorf("unexpected node condition order. NodeReady should be last.")
}
if !reflect.DeepEqual(expectedNode, updatedNode) {
t.Errorf("unexpected objects: %s", util.ObjectDiff(expectedNode, updatedNode))
if !api.Semantic.DeepEqual(expectedNode, updatedNode) {
t.Errorf("expected \n%v\n, got \n%v", expectedNode, updatedNode)
}
// Downgrade docker version, node should be NotReady
fakeRuntime.RuntimeType = "docker"
fakeRuntime.VersionInfo = "1.17"
fakeRuntime.VersionInfo = "1.5.0"
fakeRuntime.APIVersionInfo = "1.17"
kubelet.updateRuntimeUp()
if err := kubelet.updateNodeStatus(); err != nil {
t.Errorf("unexpected error: %v", err)
@ -2858,8 +2898,8 @@ func testDockerRuntimeVersion(t *testing.T) {
if !ok {
t.Errorf("unexpected object type")
}
if updatedNode.Status.Conditions[0].Reason != "KubeletNotReady" &&
!strings.Contains(updatedNode.Status.Conditions[0].Message, "container runtime version is older than") {
if updatedNode.Status.Conditions[1].Reason != "KubeletNotReady" &&
!strings.Contains(updatedNode.Status.Conditions[1].Message, "container runtime version is older than") {
t.Errorf("unexpect NodeStatus due to container runtime version")
}
}