diff --git a/federation/pkg/federation-controller/replicaset/pod_helper.go b/federation/pkg/federation-controller/replicaset/pod_helper.go new file mode 100644 index 00000000000..35fa3260085 --- /dev/null +++ b/federation/pkg/federation-controller/replicaset/pod_helper.go @@ -0,0 +1,92 @@ +/* +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 replicaset + +import ( + "fmt" + "time" + + "k8s.io/kubernetes/federation/pkg/federation-controller/util" + api "k8s.io/kubernetes/pkg/api" + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + api_v1 "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + "k8s.io/kubernetes/pkg/labels" +) + +type PodAnalysisResult struct { + // Total number of pods created. + Total int + // Number of pods that are running and ready. + RunningAndReady int + // Number of pods that have been in unschedulable state for UnshedulableThreshold seconds. + Unschedulable int + + // TODO: Handle other scenarios like pod waiting too long for scheduler etc. +} + +const ( + // TODO: make it configurable + UnschedulableThreshold = 60 * time.Second +) + +// A function that calculates how many pods from the list are in one of +// the meaningful (from the replica set perspective) states. This function is +// a temporary workaround against the current lack of ownerRef in pods. +func AnalysePods(replicaSet *v1beta1.ReplicaSet, allPods []util.FederatedObject, currentTime time.Time) (map[string]PodAnalysisResult, error) { + selector, err := labelSelectorAsSelector(replicaSet.Spec.Selector) + if err != nil { + return nil, fmt.Errorf("invalid selector: %v", err) + } + result := make(map[string]PodAnalysisResult) + + for _, fedObject := range allPods { + pod, isPod := fedObject.Object.(*api_v1.Pod) + if !isPod { + return nil, fmt.Errorf("invalid arg content - not a *pod") + } + if !selector.Empty() && selector.Matches(labels.Set(pod.Labels)) { + status := result[fedObject.ClusterName] + status.Total++ + for _, condition := range pod.Status.Conditions { + if pod.Status.Phase == api_v1.PodRunning { + if condition.Type == api_v1.PodReady { + status.RunningAndReady++ + } + } else { + if condition.Type == api_v1.PodScheduled && + condition.Status == api_v1.ConditionFalse && + condition.Reason == "Unschedulable" && + condition.LastTransitionTime.Add(UnschedulableThreshold).Before(currentTime) { + + status.Unschedulable++ + } + } + } + result[fedObject.ClusterName] = status + } + } + return result, nil +} + +func labelSelectorAsSelector(ps *v1beta1.LabelSelector) (labels.Selector, error) { + unversionedSelector := unversioned.LabelSelector{} + if err := api.Scheme.Convert(ps, &unversionedSelector, nil); err != nil { + return nil, err + } + return unversioned.LabelSelectorAsSelector(&unversionedSelector) +} diff --git a/federation/pkg/federation-controller/replicaset/pod_helper_test.go b/federation/pkg/federation-controller/replicaset/pod_helper_test.go new file mode 100644 index 00000000000..3600f516950 --- /dev/null +++ b/federation/pkg/federation-controller/replicaset/pod_helper_test.go @@ -0,0 +1,119 @@ +/* +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 replicaset + +import ( + "testing" + "time" + + "k8s.io/kubernetes/federation/pkg/federation-controller/util" + "k8s.io/kubernetes/pkg/api/unversioned" + api_v1 "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + + "github.com/stretchr/testify/assert" +) + +func TestAnalyze(t *testing.T) { + now := time.Now() + replicaSet := newReplicaSet(map[string]string{"A": "B"}) + replicaSet2 := newReplicaSet(map[string]string{"C": "D"}) + podRunning := newPod("p1", replicaSet, + api_v1.PodStatus{ + Phase: api_v1.PodRunning, + Conditions: []api_v1.PodCondition{ + { + Type: api_v1.PodReady, + Status: api_v1.ConditionTrue, + }, + }, + }) + podUnschedulable := newPod("pU", replicaSet, + api_v1.PodStatus{ + Phase: api_v1.PodPending, + Conditions: []api_v1.PodCondition{ + { + Type: api_v1.PodScheduled, + Status: api_v1.ConditionFalse, + Reason: "Unschedulable", + LastTransitionTime: unversioned.Time{Time: now.Add(-10 * time.Minute)}, + }, + }, + }) + podOther := newPod("pO", replicaSet, + api_v1.PodStatus{ + Phase: api_v1.PodPending, + Conditions: []api_v1.PodCondition{}, + }) + podOtherRS := newPod("pO", replicaSet2, + api_v1.PodStatus{ + Phase: api_v1.PodPending, + Conditions: []api_v1.PodCondition{}, + }) + + federatedObjects := []util.FederatedObject{ + {ClusterName: "c1", Object: podRunning}, + {ClusterName: "c1", Object: podRunning}, + {ClusterName: "c1", Object: podRunning}, + {ClusterName: "c1", Object: podUnschedulable}, + {ClusterName: "c1", Object: podUnschedulable}, + {ClusterName: "c2", Object: podOther}, + {ClusterName: "c2", Object: podOtherRS}, + } + + raport, err := AnalysePods(replicaSet, federatedObjects, now) + assert.NoError(t, err) + assert.Equal(t, 2, len(raport)) + c1Raport := raport["c1"] + c2Raport := raport["c2"] + assert.Equal(t, PodAnalysisResult{ + Total: 5, + RunningAndReady: 3, + Unschedulable: 2, + }, c1Raport) + assert.Equal(t, PodAnalysisResult{ + Total: 1, + RunningAndReady: 0, + Unschedulable: 0, + }, c2Raport) +} + +func newReplicaSet(selectorMap map[string]string) *v1beta1.ReplicaSet { + replicas := int32(3) + rs := &v1beta1.ReplicaSet{ + ObjectMeta: api_v1.ObjectMeta{ + Name: "foobar", + Namespace: "default", + }, + Spec: v1beta1.ReplicaSetSpec{ + Replicas: &replicas, + Selector: &v1beta1.LabelSelector{MatchLabels: selectorMap}, + }, + } + return rs +} + +func newPod(name string, rs *v1beta1.ReplicaSet, status api_v1.PodStatus) *api_v1.Pod { + return &api_v1.Pod{ + ObjectMeta: api_v1.ObjectMeta{ + Name: name, + Namespace: rs.Namespace, + Labels: rs.Spec.Selector.MatchLabels, + }, + Status: status, + } +}