Skip to content

Instantly share code, notes, and snippets.

@michaelgugino
Created October 20, 2020 15:38
Show Gist options
  • Select an option

  • Save michaelgugino/9d20be02b1f0c4810893c6e81963eaf8 to your computer and use it in GitHub Desktop.

Select an option

Save michaelgugino/9d20be02b1f0c4810893c6e81963eaf8 to your computer and use it in GitHub Desktop.
evict-code-cov
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>storage: Go Coverage Report</title>
<style>
body {
background: black;
color: rgb(80, 80, 80);
}
body, pre, #legend span {
font-family: Menlo, monospace;
font-weight: bold;
}
#topbar {
background: black;
position: fixed;
top: 0; left: 0; right: 0;
height: 42px;
border-bottom: 1px solid rgb(80, 80, 80);
}
#content {
margin-top: 50px;
}
#nav, #legend {
float: left;
margin-left: 10px;
}
#legend {
margin-top: 12px;
}
#nav {
margin-top: 10px;
}
#legend span {
margin: 0 5px;
}
.cov0 { color: rgb(192, 0, 0) }
.cov1 { color: rgb(128, 128, 128) }
.cov2 { color: rgb(116, 140, 131) }
.cov3 { color: rgb(104, 152, 134) }
.cov4 { color: rgb(92, 164, 137) }
.cov5 { color: rgb(80, 176, 140) }
.cov6 { color: rgb(68, 188, 143) }
.cov7 { color: rgb(56, 200, 146) }
.cov8 { color: rgb(44, 212, 149) }
.cov9 { color: rgb(32, 224, 152) }
.cov10 { color: rgb(20, 236, 155) }
</style>
</head>
<body>
<div id="topbar">
<div id="nav">
<select id="files">
<option value="file0">k8s.io/kubernetes/pkg/registry/core/pod/storage/eviction.go (79.9%)</option>
<option value="file1">k8s.io/kubernetes/pkg/registry/core/pod/storage/storage.go (45.8%)</option>
</select>
</div>
<div id="legend">
<span>not tracked</span>
<span class="cov0">not covered</span>
<span class="cov8">covered</span>
</div>
</div>
<div id="content">
<pre class="file" id="file0" style="display: none">/*
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 storage
import (
"context"
"fmt"
"reflect"
"time"
policyv1beta1 "k8s.io/api/policy/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/util/dryrun"
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1beta1"
"k8s.io/client-go/util/retry"
podutil "k8s.io/kubernetes/pkg/api/pod"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
)
const (
// MaxDisruptedPodSize is the max size of PodDisruptionBudgetStatus.DisruptedPods. API server eviction
// subresource handler will refuse to evict pods covered by the corresponding PDB
// if the size of the map exceeds this value. It means a large number of
// evictions have been approved by the API server but not noticed by the PDB controller yet.
// This situation should self-correct because the PDB controller removes
// entries from the map automatically after the PDB DeletionTimeout regardless.
MaxDisruptedPodSize = 2000
)
// EvictionsRetry is the retry for a conflict where multiple clients
// are making changes to the same resource.
var EvictionsRetry = wait.Backoff{
Steps: 20,
Duration: 500 * time.Millisecond,
Factor: 1.0,
Jitter: 0.1,
}
func newEvictionStorage(store rest.StandardStorage, podDisruptionBudgetClient policyclient.PodDisruptionBudgetsGetter) *EvictionREST <span class="cov8" title="1">{
return &amp;EvictionREST{store: store, podDisruptionBudgetClient: podDisruptionBudgetClient}
}</span>
// EvictionREST implements the REST endpoint for evicting pods from nodes
type EvictionREST struct {
store rest.StandardStorage
podDisruptionBudgetClient policyclient.PodDisruptionBudgetsGetter
}
var _ = rest.NamedCreater(&amp;EvictionREST{})
var _ = rest.GroupVersionKindProvider(&amp;EvictionREST{})
// GroupVersionKind specifies a particular GroupVersionKind to discovery
func (r *EvictionREST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind <span class="cov0" title="0">{
return schema.GroupVersionKind{Group: "policy", Version: "v1beta1", Kind: "Eviction"}
}</span>
// New creates a new eviction resource
func (r *EvictionREST) New() runtime.Object <span class="cov0" title="0">{
return &amp;policy.Eviction{}
}</span>
// Propagate dry-run takes the dry-run option from the request and pushes it into the eviction object.
// It returns an error if they have non-matching dry-run options.
func propagateDryRun(eviction *policy.Eviction, options *metav1.CreateOptions) (*metav1.DeleteOptions, error) <span class="cov8" title="1">{
if eviction.DeleteOptions == nil </span><span class="cov0" title="0">{
return &amp;metav1.DeleteOptions{DryRun: options.DryRun}, nil
}</span>
<span class="cov8" title="1">if len(eviction.DeleteOptions.DryRun) == 0 </span><span class="cov8" title="1">{
eviction.DeleteOptions.DryRun = options.DryRun
return eviction.DeleteOptions, nil
}</span>
<span class="cov8" title="1">if len(options.DryRun) == 0 </span><span class="cov8" title="1">{
return eviction.DeleteOptions, nil
}</span>
<span class="cov8" title="1">if !reflect.DeepEqual(options.DryRun, eviction.DeleteOptions.DryRun) </span><span class="cov0" title="0">{
return nil, fmt.Errorf("Non-matching dry-run options in request and content: %v and %v", options.DryRun, eviction.DeleteOptions.DryRun)
}</span>
<span class="cov8" title="1">return eviction.DeleteOptions, nil</span>
}
// Create attempts to create a new eviction. That is, it tries to evict a pod.
func (r *EvictionREST) Create(ctx context.Context, name string, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) <span class="cov8" title="1">{
eviction, ok := obj.(*policy.Eviction)
if !ok </span><span class="cov0" title="0">{
return nil, errors.NewBadRequest(fmt.Sprintf("not a Eviction object: %T", obj))
}</span>
<span class="cov8" title="1">if name != eviction.Name </span><span class="cov8" title="1">{
return nil, errors.NewBadRequest("name in URL does not match name in Eviction object")
}</span>
<span class="cov8" title="1">originalDeleteOptions, err := propagateDryRun(eviction, options)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov8" title="1">if createValidation != nil </span><span class="cov0" title="0">{
if err := createValidation(ctx, eviction.DeepCopyObject()); err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
}
<span class="cov8" title="1">var pod *api.Pod
deletedPod := false
// by default, retry conflict errors
shouldRetry := errors.IsConflict
if !resourceVersionIsUnset(originalDeleteOptions) </span><span class="cov8" title="1">{
// if the original options included a resourceVersion precondition, don't retry
shouldRetry = func(err error) bool </span><span class="cov8" title="1">{ return false }</span>
}
<span class="cov8" title="1">err = retry.OnError(EvictionsRetry, shouldRetry, func() error </span><span class="cov8" title="1">{
obj, err = r.store.Get(ctx, eviction.Name, &amp;metav1.GetOptions{})
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov8" title="1">pod = obj.(*api.Pod)
// Evicting a terminal pod should result in direct deletion of pod as it already caused disruption by the time we are evicting.
// There is no need to check for pdb.
if !canIgnorePDB(pod) </span><span class="cov8" title="1">{
// Pod is not in a state where we can skip checking PDBs, exit the loop, and continue to PDB checks.
return nil
}</span>
// the PDB can be ignored, so delete the pod
<span class="cov8" title="1">deletionOptions := originalDeleteOptions.DeepCopy()
// We should check if resourceVersion is already set by the requestor
// as it might be older than the pod we just fetched and should be
// honored.
if shouldEnforceResourceVersion(pod) &amp;&amp; resourceVersionIsUnset(originalDeleteOptions) </span><span class="cov8" title="1">{
// Set deletionOptions.Preconditions.ResourceVersion to ensure we're not
// racing with another PDB-impacting process elsewhere.
setPreconditionsResourceVersion(deletionOptions, &amp;pod.ResourceVersion)
}</span>
<span class="cov8" title="1">_, _, err = r.store.Delete(ctx, eviction.Name, rest.ValidateAllObjectFunc, deletionOptions)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">deletedPod = true
return nil</span>
})
<span class="cov8" title="1">switch </span>{
case err != nil:<span class="cov8" title="1">
// this can happen in cases where the PDB can be ignored, but there was a problem issuing the pod delete:
// maybe we conflicted too many times or we didn't have permission or something else weird.
return nil, err</span>
case deletedPod:<span class="cov8" title="1">
// this happens when we successfully deleted the pod. In this case, we're done executing because we've evicted/deleted the pod
return &amp;metav1.Status{Status: metav1.StatusSuccess}, nil</span>
default:<span class="cov8" title="1"></span>
// this happens when we didn't have an error and we didn't delete the pod. The only branch that happens on is when
// we cannot ignored the PDB for this pod, so this is the fall through case.
}
<span class="cov8" title="1">var rtStatus *metav1.Status
var pdbName string
updateDeletionOptions := false
err = func() error </span><span class="cov8" title="1">{
pdbs, err := r.getPodDisruptionBudgets(ctx, pod)
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov8" title="1">if len(pdbs) &gt; 1 </span><span class="cov0" title="0">{
rtStatus = &amp;metav1.Status{
Status: metav1.StatusFailure,
Message: "This pod has more than one PodDisruptionBudget, which the eviction subresource does not support.",
Code: 500,
}
return nil
}</span>
<span class="cov8" title="1">if len(pdbs) == 0 </span><span class="cov0" title="0">{
return nil
}</span>
<span class="cov8" title="1">pdb := &amp;pdbs[0]
pdbName = pdb.Name
// If the pod is not ready, it doesn't count towards healthy and we should not decrement
if !podutil.IsPodReady(pod) &amp;&amp; pdb.Status.CurrentHealthy &gt;= pdb.Status.DesiredHealthy &amp;&amp; pdb.Status.DesiredHealthy &gt; 0 </span><span class="cov8" title="1">{
updateDeletionOptions = true
return nil
}</span>
<span class="cov8" title="1">refresh := false
err = retry.RetryOnConflict(EvictionsRetry, func() error </span><span class="cov8" title="1">{
if refresh </span><span class="cov0" title="0">{
pdb, err = r.podDisruptionBudgetClient.PodDisruptionBudgets(pod.Namespace).Get(context.TODO(), pdbName, metav1.GetOptions{})
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
}
// Try to verify-and-decrement
// If it was false already, or if it becomes false during the course of our retries,
// raise an error marked as a 429.
<span class="cov8" title="1">if err = r.checkAndDecrement(pod.Namespace, pod.Name, *pdb, dryrun.IsDryRun(originalDeleteOptions.DryRun)); err != nil </span><span class="cov8" title="1">{
refresh = true
return err
}</span>
<span class="cov8" title="1">return nil</span>
})
<span class="cov8" title="1">return err</span>
}()
<span class="cov8" title="1">if err == wait.ErrWaitTimeout </span><span class="cov0" title="0">{
err = errors.NewTimeoutError(fmt.Sprintf("couldn't update PodDisruptionBudget %q due to conflicts", pdbName), 10)
}</span>
<span class="cov8" title="1">if err != nil </span><span class="cov8" title="1">{
return nil, err
}</span>
<span class="cov8" title="1">if rtStatus != nil </span><span class="cov0" title="0">{
return rtStatus, nil
}</span>
// At this point there was either no PDB or we succeeded in decrementing or
// the pod was unready and we have enough healthy replicas
<span class="cov8" title="1">deletionOptions := originalDeleteOptions.DeepCopy()
// Set deletionOptions.Preconditions.ResourceVersion to ensure
// the pod hasn't been considered ready since we calculated
if updateDeletionOptions </span><span class="cov8" title="1">{
setPreconditionsResourceVersion(deletionOptions, &amp;pod.ResourceVersion)
}</span>
// Try the delete
<span class="cov8" title="1">_, _, err = r.store.Delete(ctx, eviction.Name, rest.ValidateAllObjectFunc, deletionOptions.DeepCopy())
if err != nil </span><span class="cov8" title="1">{
if errors.IsConflict(err) &amp;&amp; updateDeletionOptions &amp;&amp;
(originalDeleteOptions.Preconditions == nil || originalDeleteOptions.Preconditions.ResourceVersion == nil) </span><span class="cov8" title="1">{
// If we encounter a resource conflict error, we updated the deletion options to include them,
// and the original deletion options did not specify ResourceVersion, we send back
// TooManyRequests so clients will retry.
return nil, createTooManyRequestsError(pdbName)
}</span>
<span class="cov8" title="1">return nil, err</span>
}
// Success!
<span class="cov8" title="1">return &amp;metav1.Status{Status: metav1.StatusSuccess}, nil</span>
}
func setPreconditionsResourceVersion(deletionOptions *metav1.DeleteOptions, resourceVersion *string) <span class="cov8" title="1">{
if deletionOptions.Preconditions == nil </span><span class="cov8" title="1">{
deletionOptions.Preconditions = &amp;metav1.Preconditions{}
}</span>
<span class="cov8" title="1">deletionOptions.Preconditions.ResourceVersion = resourceVersion</span>
}
// canIgnorePDB returns true for pod conditions that allow the pod to be deleted
// without checking PDBs.
func canIgnorePDB(pod *api.Pod) bool <span class="cov8" title="1">{
if pod.Status.Phase == api.PodSucceeded || pod.Status.Phase == api.PodFailed ||
pod.Status.Phase == api.PodPending || !pod.ObjectMeta.DeletionTimestamp.IsZero() </span><span class="cov8" title="1">{
return true
}</span>
<span class="cov8" title="1">return false</span>
}
func shouldEnforceResourceVersion(pod *api.Pod) bool <span class="cov8" title="1">{
// We don't need to enforce ResourceVersion for terminal pods
if pod.Status.Phase == api.PodSucceeded || pod.Status.Phase == api.PodFailed || !pod.ObjectMeta.DeletionTimestamp.IsZero() </span><span class="cov8" title="1">{
return false
}</span>
// Return true for all other pods to ensure we don't race against a pod becoming
// ready and violating PDBs.
<span class="cov8" title="1">return true</span>
}
func resourceVersionIsUnset(options *metav1.DeleteOptions) bool <span class="cov8" title="1">{
return options.Preconditions == nil || options.Preconditions.ResourceVersion == nil
}</span>
func createTooManyRequestsError(name string) error <span class="cov8" title="1">{
// TODO(mml): Add a Retry-After header. Once there are time-based
// budgets, we can sometimes compute a sensible suggested value. But
// even without that, we can give a suggestion (10 minutes?) that
// prevents well-behaved clients from hammering us.
err := errors.NewTooManyRequests("Cannot evict pod as it would violate the pod's disruption budget.", 0)
err.ErrStatus.Details.Causes = append(err.ErrStatus.Details.Causes, metav1.StatusCause{Type: "DisruptionBudget", Message: fmt.Sprintf("The disruption budget %s is still being processed by the server.", name)})
return err
}</span>
// checkAndDecrement checks if the provided PodDisruptionBudget allows any disruption.
func (r *EvictionREST) checkAndDecrement(namespace string, podName string, pdb policyv1beta1.PodDisruptionBudget, dryRun bool) error <span class="cov8" title="1">{
if pdb.Status.ObservedGeneration &lt; pdb.Generation </span><span class="cov0" title="0">{
return createTooManyRequestsError(pdb.Name)
}</span>
<span class="cov8" title="1">if pdb.Status.DisruptionsAllowed &lt; 0 </span><span class="cov0" title="0">{
return errors.NewForbidden(policy.Resource("poddisruptionbudget"), pdb.Name, fmt.Errorf("pdb disruptions allowed is negative"))
}</span>
<span class="cov8" title="1">if len(pdb.Status.DisruptedPods) &gt; MaxDisruptedPodSize </span><span class="cov0" title="0">{
return errors.NewForbidden(policy.Resource("poddisruptionbudget"), pdb.Name, fmt.Errorf("DisruptedPods map too big - too many evictions not confirmed by PDB controller"))
}</span>
<span class="cov8" title="1">if pdb.Status.DisruptionsAllowed == 0 </span><span class="cov8" title="1">{
err := errors.NewTooManyRequests("Cannot evict pod as it would violate the pod's disruption budget.", 0)
err.ErrStatus.Details.Causes = append(err.ErrStatus.Details.Causes, metav1.StatusCause{Type: "DisruptionBudget", Message: fmt.Sprintf("The disruption budget %s needs %d healthy pods and has %d currently", pdb.Name, pdb.Status.DesiredHealthy, pdb.Status.CurrentHealthy)})
return err
}</span>
<span class="cov8" title="1">pdb.Status.DisruptionsAllowed--
// If this is a dry-run, we don't need to go any further than that.
if dryRun == true </span><span class="cov0" title="0">{
return nil
}</span>
<span class="cov8" title="1">if pdb.Status.DisruptedPods == nil </span><span class="cov8" title="1">{
pdb.Status.DisruptedPods = make(map[string]metav1.Time)
}</span>
// Eviction handler needs to inform the PDB controller that it is about to delete a pod
// so it should not consider it as available in calculations when updating PodDisruptions allowed.
// If the pod is not deleted within a reasonable time limit PDB controller will assume that it won't
// be deleted at all and remove it from DisruptedPod map.
<span class="cov8" title="1">pdb.Status.DisruptedPods[podName] = metav1.Time{Time: time.Now()}
if _, err := r.podDisruptionBudgetClient.PodDisruptionBudgets(namespace).UpdateStatus(context.TODO(), &amp;pdb, metav1.UpdateOptions{}); err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov8" title="1">return nil</span>
}
// getPodDisruptionBudgets returns any PDBs that match the pod or err if there's an error.
func (r *EvictionREST) getPodDisruptionBudgets(ctx context.Context, pod *api.Pod) ([]policyv1beta1.PodDisruptionBudget, error) <span class="cov8" title="1">{
if len(pod.Labels) == 0 </span><span class="cov0" title="0">{
return nil, nil
}</span>
<span class="cov8" title="1">pdbList, err := r.podDisruptionBudgetClient.PodDisruptionBudgets(pod.Namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov8" title="1">var pdbs []policyv1beta1.PodDisruptionBudget
for _, pdb := range pdbList.Items </span><span class="cov8" title="1">{
if pdb.Namespace != pod.Namespace </span><span class="cov0" title="0">{
continue</span>
}
<span class="cov8" title="1">selector, err := metav1.LabelSelectorAsSelector(pdb.Spec.Selector)
if err != nil </span><span class="cov0" title="0">{
continue</span>
}
// If a PDB with a nil or empty selector creeps in, it should match nothing, not everything.
<span class="cov8" title="1">if selector.Empty() || !selector.Matches(labels.Set(pod.Labels)) </span><span class="cov0" title="0">{
continue</span>
}
<span class="cov8" title="1">pdbs = append(pdbs, pdb)</span>
}
<span class="cov8" title="1">return pdbs, nil</span>
}
</pre>
<pre class="file" id="file1" style="display: none">/*
Copyright 2014 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 storage
import (
"context"
"fmt"
"net/http"
"net/url"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage"
storeerr "k8s.io/apiserver/pkg/storage/errors"
"k8s.io/apiserver/pkg/util/dryrun"
utilfeature "k8s.io/apiserver/pkg/util/feature"
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1beta1"
podutil "k8s.io/kubernetes/pkg/api/pod"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubelet/client"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
registrypod "k8s.io/kubernetes/pkg/registry/core/pod"
podrest "k8s.io/kubernetes/pkg/registry/core/pod/rest"
)
// PodStorage includes storage for pods and all sub resources
type PodStorage struct {
Pod *REST
Binding *BindingREST
LegacyBinding *LegacyBindingREST
Eviction *EvictionREST
Status *StatusREST
EphemeralContainers *EphemeralContainersREST
Log *podrest.LogREST
Proxy *podrest.ProxyREST
Exec *podrest.ExecREST
Attach *podrest.AttachREST
PortForward *podrest.PortForwardREST
}
// REST implements a RESTStorage for pods
type REST struct {
*genericregistry.Store
proxyTransport http.RoundTripper
}
// NewStorage returns a RESTStorage object that will work against pods.
func NewStorage(optsGetter generic.RESTOptionsGetter, k client.ConnectionInfoGetter, proxyTransport http.RoundTripper, podDisruptionBudgetClient policyclient.PodDisruptionBudgetsGetter) (PodStorage, error) <span class="cov8" title="1">{
store := &amp;genericregistry.Store{
NewFunc: func() runtime.Object </span><span class="cov8" title="1">{ return &amp;api.Pod{} }</span>,
NewListFunc: func() runtime.Object <span class="cov8" title="1">{ return &amp;api.PodList{} }</span>,
PredicateFunc: registrypod.MatchPod,
DefaultQualifiedResource: api.Resource("pods"),
CreateStrategy: registrypod.Strategy,
UpdateStrategy: registrypod.Strategy,
DeleteStrategy: registrypod.Strategy,
ReturnDeletedObject: true,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
<span class="cov8" title="1">options := &amp;generic.StoreOptions{
RESTOptions: optsGetter,
AttrFunc: registrypod.GetAttrs,
TriggerFunc: map[string]storage.IndexerFunc{"spec.nodeName": registrypod.NodeNameTriggerFunc},
Indexers: registrypod.Indexers(),
}
if err := store.CompleteWithOptions(options); err != nil </span><span class="cov0" title="0">{
return PodStorage{}, err
}</span>
<span class="cov8" title="1">statusStore := *store
statusStore.UpdateStrategy = registrypod.StatusStrategy
ephemeralContainersStore := *store
ephemeralContainersStore.UpdateStrategy = registrypod.EphemeralContainersStrategy
bindingREST := &amp;BindingREST{store: store}
return PodStorage{
Pod: &amp;REST{store, proxyTransport},
Binding: &amp;BindingREST{store: store},
LegacyBinding: &amp;LegacyBindingREST{bindingREST},
Eviction: newEvictionStorage(store, podDisruptionBudgetClient),
Status: &amp;StatusREST{store: &amp;statusStore},
EphemeralContainers: &amp;EphemeralContainersREST{store: &amp;ephemeralContainersStore},
Log: &amp;podrest.LogREST{Store: store, KubeletConn: k},
Proxy: &amp;podrest.ProxyREST{Store: store, ProxyTransport: proxyTransport},
Exec: &amp;podrest.ExecREST{Store: store, KubeletConn: k},
Attach: &amp;podrest.AttachREST{Store: store, KubeletConn: k},
PortForward: &amp;podrest.PortForwardREST{Store: store, KubeletConn: k},
}, nil</span>
}
// Implement Redirector.
var _ = rest.Redirector(&amp;REST{})
// ResourceLocation returns a pods location from its HostIP
func (r *REST) ResourceLocation(ctx context.Context, name string) (*url.URL, http.RoundTripper, error) <span class="cov8" title="1">{
return registrypod.ResourceLocation(ctx, r, r.proxyTransport, name)
}</span>
// Implement ShortNamesProvider
var _ rest.ShortNamesProvider = &amp;REST{}
// ShortNames implements the ShortNamesProvider interface. Returns a list of short names for a resource.
func (r *REST) ShortNames() []string <span class="cov8" title="1">{
return []string{"po"}
}</span>
// Implement CategoriesProvider
var _ rest.CategoriesProvider = &amp;REST{}
// Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of.
func (r *REST) Categories() []string <span class="cov8" title="1">{
return []string{"all"}
}</span>
// BindingREST implements the REST endpoint for binding pods to nodes when etcd is in use.
type BindingREST struct {
store *genericregistry.Store
}
// NamespaceScoped fulfill rest.Scoper
func (r *BindingREST) NamespaceScoped() bool <span class="cov0" title="0">{
return r.store.NamespaceScoped()
}</span>
// New creates a new binding resource
func (r *BindingREST) New() runtime.Object <span class="cov0" title="0">{
return &amp;api.Binding{}
}</span>
var _ = rest.NamedCreater(&amp;BindingREST{})
// Create ensures a pod is bound to a specific host.
func (r *BindingREST) Create(ctx context.Context, name string, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (out runtime.Object, err error) <span class="cov8" title="1">{
binding, ok := obj.(*api.Binding)
if !ok </span><span class="cov0" title="0">{
return nil, errors.NewBadRequest(fmt.Sprintf("not a Binding object: %#v", obj))
}</span>
<span class="cov8" title="1">if name != binding.Name </span><span class="cov8" title="1">{
return nil, errors.NewBadRequest("name in URL does not match name in Binding object")
}</span>
// TODO: move me to a binding strategy
<span class="cov8" title="1">if errs := validation.ValidatePodBinding(binding); len(errs) != 0 </span><span class="cov8" title="1">{
return nil, errs.ToAggregate()
}</span>
<span class="cov8" title="1">if createValidation != nil </span><span class="cov8" title="1">{
if err := createValidation(ctx, binding.DeepCopyObject()); err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
}
<span class="cov8" title="1">err = r.assignPod(ctx, binding.Name, binding.Target.Name, binding.Annotations, dryrun.IsDryRun(options.DryRun))
out = &amp;metav1.Status{Status: metav1.StatusSuccess}
return</span>
}
// setPodHostAndAnnotations sets the given pod's host to 'machine' if and only if it was
// previously 'oldMachine' and merges the provided annotations with those of the pod.
// Returns the current state of the pod, or an error.
func (r *BindingREST) setPodHostAndAnnotations(ctx context.Context, podID, oldMachine, machine string, annotations map[string]string, dryRun bool) (finalPod *api.Pod, err error) <span class="cov8" title="1">{
podKey, err := r.store.KeyFunc(ctx, podID)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov8" title="1">err = r.store.Storage.GuaranteedUpdate(ctx, podKey, &amp;api.Pod{}, false, nil, storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) </span><span class="cov8" title="1">{
pod, ok := obj.(*api.Pod)
if !ok </span><span class="cov0" title="0">{
return nil, fmt.Errorf("unexpected object: %#v", obj)
}</span>
<span class="cov8" title="1">if pod.DeletionTimestamp != nil </span><span class="cov0" title="0">{
return nil, fmt.Errorf("pod %s is being deleted, cannot be assigned to a host", pod.Name)
}</span>
<span class="cov8" title="1">if pod.Spec.NodeName != oldMachine </span><span class="cov8" title="1">{
return nil, fmt.Errorf("pod %v is already assigned to node %q", pod.Name, pod.Spec.NodeName)
}</span>
<span class="cov8" title="1">pod.Spec.NodeName = machine
if pod.Annotations == nil </span><span class="cov8" title="1">{
pod.Annotations = make(map[string]string)
}</span>
<span class="cov8" title="1">for k, v := range annotations </span><span class="cov8" title="1">{
pod.Annotations[k] = v
}</span>
<span class="cov8" title="1">podutil.UpdatePodCondition(&amp;pod.Status, &amp;api.PodCondition{
Type: api.PodScheduled,
Status: api.ConditionTrue,
})
finalPod = pod
return pod, nil</span>
}), dryRun)
<span class="cov8" title="1">return finalPod, err</span>
}
// assignPod assigns the given pod to the given machine.
func (r *BindingREST) assignPod(ctx context.Context, podID string, machine string, annotations map[string]string, dryRun bool) (err error) <span class="cov8" title="1">{
if _, err = r.setPodHostAndAnnotations(ctx, podID, "", machine, annotations, dryRun); err != nil </span><span class="cov8" title="1">{
err = storeerr.InterpretGetError(err, api.Resource("pods"), podID)
err = storeerr.InterpretUpdateError(err, api.Resource("pods"), podID)
if _, ok := err.(*errors.StatusError); !ok </span><span class="cov8" title="1">{
err = errors.NewConflict(api.Resource("pods/binding"), podID, err)
}</span>
}
<span class="cov8" title="1">return</span>
}
var _ = rest.Creater(&amp;LegacyBindingREST{})
// LegacyBindingREST implements the REST endpoint for binding pods to nodes when etcd is in use.
type LegacyBindingREST struct {
bindingRest *BindingREST
}
// NamespaceScoped fulfill rest.Scoper
func (r *LegacyBindingREST) NamespaceScoped() bool <span class="cov0" title="0">{
return r.bindingRest.NamespaceScoped()
}</span>
// New creates a new binding resource
func (r *LegacyBindingREST) New() runtime.Object <span class="cov0" title="0">{
return r.bindingRest.New()
}</span>
// Create ensures a pod is bound to a specific host.
func (r *LegacyBindingREST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (out runtime.Object, err error) <span class="cov0" title="0">{
metadata, err := meta.Accessor(obj)
if err != nil </span><span class="cov0" title="0">{
return nil, errors.NewBadRequest(fmt.Sprintf("not a Binding object: %T", obj))
}</span>
<span class="cov0" title="0">return r.bindingRest.Create(ctx, metadata.GetName(), obj, createValidation, options)</span>
}
// StatusREST implements the REST endpoint for changing the status of a pod.
type StatusREST struct {
store *genericregistry.Store
}
// New creates a new pod resource
func (r *StatusREST) New() runtime.Object <span class="cov0" title="0">{
return &amp;api.Pod{}
}</span>
// Get retrieves the object from the storage. It is required to support Patch.
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) <span class="cov0" title="0">{
return r.store.Get(ctx, name, options)
}</span>
// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) <span class="cov8" title="1">{
// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}</span>
// EphemeralContainersREST implements the REST endpoint for adding EphemeralContainers
type EphemeralContainersREST struct {
store *genericregistry.Store
}
var _ = rest.Patcher(&amp;EphemeralContainersREST{})
// Get of this endpoint will return the list of ephemeral containers in this pod
func (r *EphemeralContainersREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) <span class="cov0" title="0">{
if !utilfeature.DefaultFeatureGate.Enabled(features.EphemeralContainers) </span><span class="cov0" title="0">{
return nil, errors.NewBadRequest("feature EphemeralContainers disabled")
}</span>
<span class="cov0" title="0">obj, err := r.store.Get(ctx, name, options)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">return ephemeralContainersInPod(obj.(*api.Pod)), nil</span>
}
// New creates a new EphemeralContainers resource
func (r *EphemeralContainersREST) New() runtime.Object <span class="cov0" title="0">{
return &amp;api.EphemeralContainers{}
}</span>
// Update alters the EphemeralContainers field in PodSpec
func (r *EphemeralContainersREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) <span class="cov0" title="0">{
if !utilfeature.DefaultFeatureGate.Enabled(features.EphemeralContainers) </span><span class="cov0" title="0">{
return nil, false, errors.NewBadRequest("feature EphemeralContainers disabled")
}</span>
<span class="cov0" title="0">obj, err := r.store.Get(ctx, name, &amp;metav1.GetOptions{})
if err != nil </span><span class="cov0" title="0">{
return nil, false, err
}</span>
<span class="cov0" title="0">pod := obj.(*api.Pod)
// Build an UpdatedObjectInfo to pass to the pod store.
// It is given the currently stored v1.Pod and transforms it to the new pod that should be stored.
updatedPodInfo := rest.DefaultUpdatedObjectInfo(pod, func(ctx context.Context, oldObject, _ runtime.Object) (newObject runtime.Object, err error) </span><span class="cov0" title="0">{
oldPod, ok := oldObject.(*api.Pod)
if !ok </span><span class="cov0" title="0">{
return nil, fmt.Errorf("unexpected type for Pod %T", oldObject)
}</span>
<span class="cov0" title="0">newEphemeralContainersObj, err := objInfo.UpdatedObject(ctx, ephemeralContainersInPod(oldPod))
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">newEphemeralContainers, ok := newEphemeralContainersObj.(*api.EphemeralContainers)
if !ok </span><span class="cov0" title="0">{
return nil, fmt.Errorf("unexpected type for EphemeralContainers %T", newEphemeralContainersObj)
}</span>
// avoid mutating
<span class="cov0" title="0">newPod := oldPod.DeepCopy()
// identity, version (make sure we're working with the right object, instance, and version)
newPod.Name = newEphemeralContainers.Name
newPod.Namespace = newEphemeralContainers.Namespace
newPod.UID = newEphemeralContainers.UID
newPod.ResourceVersion = newEphemeralContainers.ResourceVersion
// ephemeral containers
newPod.Spec.EphemeralContainers = newEphemeralContainers.EphemeralContainers
return newPod, nil</span>
})
// Validation should be passed the API kind (EphemeralContainers) rather than the storage kind.
<span class="cov0" title="0">obj, _, err = r.store.Update(ctx, name, updatedPodInfo, toEphemeralContainersCreateValidation(createValidation), toEphemeralContainersUpdateValidation(updateValidation), false, options)
if err != nil </span><span class="cov0" title="0">{
return nil, false, err
}</span>
<span class="cov0" title="0">return ephemeralContainersInPod(obj.(*api.Pod)), false, err</span>
}
func toEphemeralContainersCreateValidation(f rest.ValidateObjectFunc) rest.ValidateObjectFunc <span class="cov0" title="0">{
return func(ctx context.Context, obj runtime.Object) error </span><span class="cov0" title="0">{
return f(ctx, ephemeralContainersInPod(obj.(*api.Pod)))
}</span>
}
func toEphemeralContainersUpdateValidation(f rest.ValidateObjectUpdateFunc) rest.ValidateObjectUpdateFunc <span class="cov0" title="0">{
return func(ctx context.Context, obj, old runtime.Object) error </span><span class="cov0" title="0">{
return f(ctx, ephemeralContainersInPod(obj.(*api.Pod)), ephemeralContainersInPod(old.(*api.Pod)))
}</span>
}
// Extract the list of Ephemeral Containers from a Pod
func ephemeralContainersInPod(pod *api.Pod) *api.EphemeralContainers <span class="cov0" title="0">{
ephemeralContainers := pod.Spec.EphemeralContainers
if ephemeralContainers == nil </span><span class="cov0" title="0">{
ephemeralContainers = []api.EphemeralContainer{}
}</span>
<span class="cov0" title="0">return &amp;api.EphemeralContainers{
ObjectMeta: metav1.ObjectMeta{
Name: pod.Name,
Namespace: pod.Namespace,
UID: pod.UID,
ResourceVersion: pod.ResourceVersion,
CreationTimestamp: pod.CreationTimestamp,
},
EphemeralContainers: ephemeralContainers,
}</span>
}
</pre>
</div>
</body>
<script>
(function() {
var files = document.getElementById('files');
var visible;
files.addEventListener('change', onChange, false);
function select(part) {
if (visible)
visible.style.display = 'none';
visible = document.getElementById(part);
if (!visible)
return;
files.value = part;
visible.style.display = 'block';
location.hash = part;
}
function onChange() {
select(files.value);
window.scrollTo(0, 0);
}
if (location.hash != "") {
select(location.hash.substr(1));
}
if (!visible) {
select("file0");
}
})();
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment