diff --git a/pkg/kubelet/api/services.go b/pkg/kubelet/api/services.go index 0111e7fc354..d19701af59b 100644 --- a/pkg/kubelet/api/services.go +++ b/pkg/kubelet/api/services.go @@ -81,6 +81,8 @@ type RuntimeService interface { // UpdateRuntimeConfig updates runtime configuration if specified UpdateRuntimeConfig(runtimeConfig *runtimeApi.RuntimeConfig) error + // Status returns the status of the runtime. + Status() (*runtimeApi.RuntimeStatus, error) } // ImageManagerService interface should be implemented by a container image diff --git a/pkg/kubelet/api/testing/fake_runtime_service.go b/pkg/kubelet/api/testing/fake_runtime_service.go index 239c27b35df..8e4edfff3a8 100644 --- a/pkg/kubelet/api/testing/fake_runtime_service.go +++ b/pkg/kubelet/api/testing/fake_runtime_service.go @@ -50,6 +50,7 @@ type FakeRuntimeService struct { Called []string + FakeStatus *runtimeApi.RuntimeStatus Containers map[string]*FakeContainer Sandboxes map[string]*FakePodSandbox } @@ -109,6 +110,15 @@ func (r *FakeRuntimeService) Version(apiVersion string) (*runtimeApi.VersionResp }, nil } +func (r *FakeRuntimeService) Status() (*runtimeApi.RuntimeStatus, error) { + r.Lock() + defer r.Unlock() + + r.Called = append(r.Called, "Status") + + return r.FakeStatus, nil +} + func (r *FakeRuntimeService) RunPodSandbox(config *runtimeApi.PodSandboxConfig) (string, error) { r.Lock() defer r.Unlock() diff --git a/pkg/kubelet/api/v1alpha1/runtime/constants.go b/pkg/kubelet/api/v1alpha1/runtime/constants.go new file mode 100644 index 00000000000..27c42c5c516 --- /dev/null +++ b/pkg/kubelet/api/v1alpha1/runtime/constants.go @@ -0,0 +1,27 @@ +/* +Copyright 2016 The Kubernetes Authors. + +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 runtime + +// This file contains all constants defined in CRI. + +// Required runtime condition type. +const ( + // RuntimeReady means the runtime is up and ready to accept basic containers. + RuntimeReady = "RuntimeReady" + // NetworkReady means the runtime network is up and ready to accept containers which require network. + NetworkReady = "NetworkReady" +) diff --git a/pkg/kubelet/dockershim/docker_service.go b/pkg/kubelet/dockershim/docker_service.go index 5c303226830..4399bc398a4 100644 --- a/pkg/kubelet/dockershim/docker_service.go +++ b/pkg/kubelet/dockershim/docker_service.go @@ -21,6 +21,8 @@ import ( "io" "github.com/golang/glog" + "github.com/golang/protobuf/proto" + "k8s.io/kubernetes/pkg/apis/componentconfig" internalApi "k8s.io/kubernetes/pkg/kubelet/api" runtimeApi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime" @@ -222,3 +224,24 @@ type dockerNetworkHost struct { func (ds *dockerService) Start() error { return ds.containerManager.Start() } + +// Status returns the status of the runtime. +// TODO(random-liu): Set network condition accordingly here. +func (ds *dockerService) Status() (*runtimeApi.RuntimeStatus, error) { + runtimeReady := &runtimeApi.RuntimeCondition{ + Type: proto.String(runtimeApi.RuntimeReady), + Status: proto.Bool(true), + } + networkReady := &runtimeApi.RuntimeCondition{ + Type: proto.String(runtimeApi.NetworkReady), + Status: proto.Bool(true), + } + conditions := []*runtimeApi.RuntimeCondition{runtimeReady, networkReady} + _, err := ds.client.Version() + if err != nil { + runtimeReady.Status = proto.Bool(false) + runtimeReady.Reason = proto.String("DockerDaemonNotReady") + runtimeReady.Message = proto.String(fmt.Sprintf("docker: failed to get docker version: %v", err)) + } + return &runtimeApi.RuntimeStatus{Conditions: conditions}, nil +} diff --git a/pkg/kubelet/dockershim/docker_service_test.go b/pkg/kubelet/dockershim/docker_service_test.go index 3314c176fd3..ac77b2d93eb 100644 --- a/pkg/kubelet/dockershim/docker_service_test.go +++ b/pkg/kubelet/dockershim/docker_service_test.go @@ -17,10 +17,14 @@ limitations under the License. package dockershim import ( - "github.com/golang/mock/gomock" + "errors" "testing" "time" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + + runtimeApi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime" containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" "k8s.io/kubernetes/pkg/kubelet/dockertools" "k8s.io/kubernetes/pkg/kubelet/network" @@ -39,3 +43,37 @@ func newTestDockerService() (*dockerService, *dockertools.FakeDockerClient, *clo c := dockertools.NewFakeDockerClientWithClock(fakeClock) return &dockerService{client: c, os: &containertest.FakeOS{}, networkPlugin: &network.NoopNetworkPlugin{}}, c, fakeClock } + +// TestStatus tests the runtime status logic. +func TestStatus(t *testing.T) { + ds, fDocker, _ := newTestDockerService() + + assertStatus := func(expected map[string]bool, status *runtimeApi.RuntimeStatus) { + conditions := status.GetConditions() + assert.Equal(t, len(expected), len(conditions)) + for k, v := range expected { + for _, c := range conditions { + if k == c.GetType() { + assert.Equal(t, v, c.GetStatus()) + } + } + } + } + + // Should report ready status if version returns no error. + status, err := ds.Status() + assert.NoError(t, err) + assertStatus(map[string]bool{ + runtimeApi.RuntimeReady: true, + runtimeApi.NetworkReady: true, + }, status) + + // Should not report ready status if version returns error. + fDocker.InjectError("version", errors.New("test error")) + status, err = ds.Status() + assert.NoError(t, err) + assertStatus(map[string]bool{ + runtimeApi.RuntimeReady: false, + runtimeApi.NetworkReady: true, + }, status) +} diff --git a/pkg/kubelet/dockershim/remote/docker_service.go b/pkg/kubelet/dockershim/remote/docker_service.go index d9bfa8af239..fdca2f1ec65 100644 --- a/pkg/kubelet/dockershim/remote/docker_service.go +++ b/pkg/kubelet/dockershim/remote/docker_service.go @@ -52,7 +52,11 @@ func (d *dockerService) Version(ctx context.Context, r *runtimeApi.VersionReques } func (d *dockerService) Status(ctx context.Context, r *runtimeApi.StatusRequest) (*runtimeApi.StatusResponse, error) { - return nil, fmt.Errorf("not implemented") + status, err := d.runtimeService.Status() + if err != nil { + return nil, err + } + return &runtimeApi.StatusResponse{Status: status}, nil } func (d *dockerService) RunPodSandbox(ctx context.Context, r *runtimeApi.RunPodSandboxRequest) (*runtimeApi.RunPodSandboxResponse, error) { diff --git a/pkg/kubelet/kuberuntime/helpers.go b/pkg/kubelet/kuberuntime/helpers.go index a289f2adbf5..caef101b6b8 100644 --- a/pkg/kubelet/kuberuntime/helpers.go +++ b/pkg/kubelet/kuberuntime/helpers.go @@ -216,3 +216,14 @@ func buildFullContainerLogsPath(podUID types.UID, containerName string, restartC func buildPodLogsDirectory(podUID types.UID) string { return filepath.Join(podLogsRootDirectory, string(podUID)) } + +// getRuntimeCondition gets specified runtime condition from the runtime status. +func getRuntimeCondition(status *runtimeApi.RuntimeStatus, t string) *runtimeApi.RuntimeCondition { + conditions := status.GetConditions() + for _, condition := range conditions { + if condition.GetType() == t { + return condition + } + } + return nil +} diff --git a/pkg/kubelet/kuberuntime/instrumented_services.go b/pkg/kubelet/kuberuntime/instrumented_services.go index 0275bd09f6c..2b335415730 100644 --- a/pkg/kubelet/kuberuntime/instrumented_services.go +++ b/pkg/kubelet/kuberuntime/instrumented_services.go @@ -68,6 +68,15 @@ func (in instrumentedRuntimeService) Version(apiVersion string) (*runtimeApi.Ver return out, err } +func (in instrumentedRuntimeService) Status() (*runtimeApi.RuntimeStatus, error) { + const operation = "status" + defer recordOperation(operation, time.Now()) + + out, err := in.service.Status() + recordError(operation, err) + return out, err +} + func (in instrumentedRuntimeService) CreateContainer(podSandboxID string, config *runtimeApi.ContainerConfig, sandboxConfig *runtimeApi.PodSandboxConfig) (string, error) { const operation = "create_container" defer recordOperation(operation, time.Now()) diff --git a/pkg/kubelet/kuberuntime/kuberuntime_manager.go b/pkg/kubelet/kuberuntime/kuberuntime_manager.go index f91bf041e7a..d3db63f989a 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_manager.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_manager.go @@ -269,13 +269,23 @@ func (m *kubeGenericRuntimeManager) APIVersion() (kubecontainer.Version, error) } // Status returns error if the runtime is unhealthy; nil otherwise. +// TODO(random-liu): Change the Status in runtime interface to return runtime status. +// TODO(random-liu): Add unit test for this function after addressing the TODO above. func (m *kubeGenericRuntimeManager) Status() error { - _, err := m.runtimeService.Version(kubeRuntimeAPIVersion) + status, err := m.runtimeService.Status() if err != nil { - glog.Errorf("Checkout remote runtime status failed: %v", err) - return err + return fmt.Errorf("failed to checkout runtime status: %v", err) + } + networkReady := getRuntimeCondition(status, runtimeApi.NetworkReady) + if networkReady == nil || !networkReady.GetStatus() { + return fmt.Errorf("runtime network not ready: reason: %q, message: %q", + networkReady.GetReason(), networkReady.GetMessage()) + } + runtimeReady := getRuntimeCondition(status, runtimeApi.RuntimeReady) + if runtimeReady == nil || !runtimeReady.GetStatus() { + return fmt.Errorf("runtime not ready: reason: %q, message: %q", + runtimeReady.GetReason(), runtimeReady.GetMessage()) } - return nil } diff --git a/pkg/kubelet/remote/remote_runtime.go b/pkg/kubelet/remote/remote_runtime.go index 17aa3d0f49c..3005fd1d778 100644 --- a/pkg/kubelet/remote/remote_runtime.go +++ b/pkg/kubelet/remote/remote_runtime.go @@ -339,3 +339,17 @@ func (r *RemoteRuntimeService) UpdateRuntimeConfig(runtimeConfig *runtimeApi.Run return nil } + +// Status returns the status of the runtime. +func (r *RemoteRuntimeService) Status() (*runtimeApi.RuntimeStatus, error) { + ctx, cancel := getContextWithTimeout(r.timeout) + defer cancel() + + resp, err := r.runtimeClient.Status(ctx, &runtimeApi.StatusRequest{}) + if err != nil { + glog.Errorf("Status from runtime service failed: %v", err) + return nil, err + } + + return resp.Status, nil +}