Skip to content

Commit 0155d18

Browse files
authored
Merge pull request kubernetes#84485 from tallclair/mirror-owner
Mirror owner
2 parents 0afc842 + dca464d commit 0155d18

File tree

7 files changed

+209
-11
lines changed

7 files changed

+209
-11
lines changed

pkg/kubelet/kubelet.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,8 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
592592
}
593593
}
594594
// podManager is also responsible for keeping secretManager and configMapManager contents up-to-date.
595-
klet.podManager = kubepod.NewBasicPodManager(kubepod.NewBasicMirrorClient(klet.kubeClient), secretManager, configMapManager, checkpointManager)
595+
mirrorPodClient := kubepod.NewBasicMirrorClient(klet.kubeClient, string(nodeName), nodeLister)
596+
klet.podManager = kubepod.NewBasicPodManager(mirrorPodClient, secretManager, configMapManager, checkpointManager)
596597

597598
klet.statusManager = status.NewManager(klet.kubeClient, klet.podManager, klet)
598599

pkg/kubelet/kubelet_getters.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ import (
2424
"path/filepath"
2525

2626
cadvisorapiv1 "github.com/google/cadvisor/info/v1"
27+
v1 "k8s.io/api/core/v1"
2728
"k8s.io/klog"
2829

29-
"k8s.io/api/core/v1"
3030
"k8s.io/apimachinery/pkg/types"
3131
"k8s.io/kubernetes/pkg/kubelet/cm"
3232
"k8s.io/kubernetes/pkg/kubelet/config"

pkg/kubelet/pod/BUILD

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ go_test(
4545
"//staging/src/k8s.io/api/core/v1:go_default_library",
4646
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
4747
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
48+
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
49+
"//vendor/github.com/stretchr/testify/assert:go_default_library",
50+
"//vendor/github.com/stretchr/testify/require:go_default_library",
51+
"//vendor/k8s.io/utils/pointer:go_default_library",
4852
],
4953
)
5054

pkg/kubelet/pod/mirror_client.go

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ limitations under the License.
1717
package pod
1818

1919
import (
20-
"k8s.io/api/core/v1"
21-
"k8s.io/apimachinery/pkg/api/errors"
20+
"fmt"
21+
22+
v1 "k8s.io/api/core/v1"
23+
apierrors "k8s.io/apimachinery/pkg/api/errors"
2224
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2325
"k8s.io/apimachinery/pkg/types"
2426
clientset "k8s.io/client-go/kubernetes"
@@ -39,16 +41,28 @@ type MirrorClient interface {
3941
DeleteMirrorPod(podFullName string, uid *types.UID) (bool, error)
4042
}
4143

44+
// nodeGetter is a subset a NodeLister, simplified for testing.
45+
type nodeGetter interface {
46+
// Get retrieves the Node for a given name.
47+
Get(name string) (*v1.Node, error)
48+
}
49+
4250
// basicMirrorClient is a functional MirrorClient. Mirror pods are stored in
4351
// the kubelet directly because they need to be in sync with the internal
4452
// pods.
4553
type basicMirrorClient struct {
4654
apiserverClient clientset.Interface
55+
nodeGetter nodeGetter
56+
nodeName string
4757
}
4858

4959
// NewBasicMirrorClient returns a new MirrorClient.
50-
func NewBasicMirrorClient(apiserverClient clientset.Interface) MirrorClient {
51-
return &basicMirrorClient{apiserverClient: apiserverClient}
60+
func NewBasicMirrorClient(apiserverClient clientset.Interface, nodeName string, nodeGetter nodeGetter) MirrorClient {
61+
return &basicMirrorClient{
62+
apiserverClient: apiserverClient,
63+
nodeName: nodeName,
64+
nodeGetter: nodeGetter,
65+
}
5266
}
5367

5468
func (mc *basicMirrorClient) CreateMirrorPod(pod *v1.Pod) error {
@@ -64,8 +78,25 @@ func (mc *basicMirrorClient) CreateMirrorPod(pod *v1.Pod) error {
6478
}
6579
hash := getPodHash(pod)
6680
copyPod.Annotations[kubetypes.ConfigMirrorAnnotationKey] = hash
81+
82+
// With the MirrorPodNodeRestriction feature, mirror pods are required to have an owner reference
83+
// to the owning node.
84+
// See http://git.k8s.io/enhancements/keps/sig-auth/20190916-noderestriction-pods.md
85+
nodeUID, err := mc.getNodeUID()
86+
if err != nil {
87+
return fmt.Errorf("failed to get node UID: %v", err)
88+
}
89+
controller := true
90+
copyPod.OwnerReferences = []metav1.OwnerReference{{
91+
APIVersion: v1.SchemeGroupVersion.String(),
92+
Kind: "Node",
93+
Name: mc.nodeName,
94+
UID: nodeUID,
95+
Controller: &controller,
96+
}}
97+
6798
apiPod, err := mc.apiserverClient.CoreV1().Pods(copyPod.Namespace).Create(&copyPod)
68-
if err != nil && errors.IsAlreadyExists(err) {
99+
if err != nil && apierrors.IsAlreadyExists(err) {
69100
// Check if the existing pod is the same as the pod we want to create.
70101
if h, ok := apiPod.Annotations[kubetypes.ConfigMirrorAnnotationKey]; ok && h == hash {
71102
return nil
@@ -94,7 +125,7 @@ func (mc *basicMirrorClient) DeleteMirrorPod(podFullName string, uid *types.UID)
94125
var GracePeriodSeconds int64
95126
if err := mc.apiserverClient.CoreV1().Pods(namespace).Delete(name, &metav1.DeleteOptions{GracePeriodSeconds: &GracePeriodSeconds, Preconditions: &metav1.Preconditions{UID: uid}}); err != nil {
96127
// Unfortunately, there's no generic error for failing a precondition
97-
if !(errors.IsNotFound(err) || errors.IsConflict(err)) {
128+
if !(apierrors.IsNotFound(err) || apierrors.IsConflict(err)) {
98129
// We should return the error here, but historically this routine does
99130
// not return an error unless it can't parse the pod name
100131
klog.Errorf("Failed deleting a mirror pod %q: %v", podFullName, err)
@@ -104,6 +135,17 @@ func (mc *basicMirrorClient) DeleteMirrorPod(podFullName string, uid *types.UID)
104135
return true, nil
105136
}
106137

138+
func (mc *basicMirrorClient) getNodeUID() (types.UID, error) {
139+
node, err := mc.nodeGetter.Get(mc.nodeName)
140+
if err != nil {
141+
return "", err
142+
}
143+
if node.UID == "" {
144+
return "", fmt.Errorf("UID unset for node %s", mc.nodeName)
145+
}
146+
return node.UID, nil
147+
}
148+
107149
// IsStaticPod returns true if the passed Pod is static.
108150
func IsStaticPod(pod *v1.Pod) bool {
109151
source, err := kubetypes.GetPodSource(pod)

pkg/kubelet/pod/mirror_client_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,18 @@ limitations under the License.
1717
package pod
1818

1919
import (
20+
"errors"
2021
"testing"
2122

23+
"github.com/stretchr/testify/assert"
24+
"github.com/stretchr/testify/require"
25+
v1 "k8s.io/api/core/v1"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/types"
28+
"k8s.io/client-go/kubernetes/fake"
2229
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
30+
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
31+
"k8s.io/utils/pointer"
2332
)
2433

2534
func TestParsePodFullName(t *testing.T) {
@@ -52,3 +61,97 @@ func TestParsePodFullName(t *testing.T) {
5261
}
5362
}
5463
}
64+
65+
func TestCreateMirrorPod(t *testing.T) {
66+
const (
67+
testNodeName = "test-node-name"
68+
testNodeUID = types.UID("test-node-uid-1234")
69+
testPodName = "test-pod-name"
70+
testPodNS = "test-pod-ns"
71+
testPodHash = "123456789"
72+
)
73+
testcases := []struct {
74+
desc string
75+
node *v1.Node
76+
nodeErr error
77+
expectSuccess bool
78+
}{{
79+
desc: "cannot get node",
80+
nodeErr: errors.New("expected: cannot get node"),
81+
expectSuccess: false,
82+
}, {
83+
desc: "node missing UID",
84+
node: &v1.Node{
85+
ObjectMeta: metav1.ObjectMeta{
86+
Name: testNodeName,
87+
},
88+
},
89+
expectSuccess: false,
90+
}, {
91+
desc: "successfully fetched node",
92+
node: &v1.Node{
93+
ObjectMeta: metav1.ObjectMeta{
94+
Name: testNodeName,
95+
UID: testNodeUID,
96+
},
97+
},
98+
expectSuccess: true,
99+
}}
100+
101+
for _, test := range testcases {
102+
t.Run(test.desc, func(t *testing.T) {
103+
clientset := fake.NewSimpleClientset()
104+
nodeGetter := &fakeNodeGetter{
105+
t: t,
106+
expectNodeName: testNodeName,
107+
node: test.node,
108+
err: test.nodeErr,
109+
}
110+
mc := NewBasicMirrorClient(clientset, testNodeName, nodeGetter)
111+
112+
pod := &v1.Pod{
113+
ObjectMeta: metav1.ObjectMeta{
114+
Name: testPodName,
115+
Namespace: testPodNS,
116+
Annotations: map[string]string{
117+
kubetypes.ConfigHashAnnotationKey: testPodHash,
118+
},
119+
},
120+
}
121+
122+
err := mc.CreateMirrorPod(pod)
123+
if !test.expectSuccess {
124+
assert.Error(t, err)
125+
return
126+
}
127+
128+
createdPod, err := clientset.CoreV1().Pods(testPodNS).Get(testPodName, metav1.GetOptions{})
129+
require.NoError(t, err)
130+
131+
// Validate created pod
132+
assert.Equal(t, testPodHash, createdPod.Annotations[kubetypes.ConfigMirrorAnnotationKey])
133+
assert.Len(t, createdPod.OwnerReferences, 1)
134+
expectedOwnerRef := metav1.OwnerReference{
135+
APIVersion: "v1",
136+
Kind: "Node",
137+
Name: testNodeName,
138+
UID: testNodeUID,
139+
Controller: pointer.BoolPtr(true),
140+
}
141+
assert.Equal(t, expectedOwnerRef, createdPod.OwnerReferences[0])
142+
})
143+
}
144+
}
145+
146+
type fakeNodeGetter struct {
147+
t *testing.T
148+
expectNodeName string
149+
150+
node *v1.Node
151+
err error
152+
}
153+
154+
func (f *fakeNodeGetter) Get(nodeName string) (*v1.Node, error) {
155+
require.Equal(f.t, f.expectNodeName, nodeName)
156+
return f.node, f.err
157+
}

test/e2e_node/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ go_test(
201201
"//vendor/github.com/blang/semver:go_default_library",
202202
"//vendor/github.com/coreos/go-systemd/util:go_default_library",
203203
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
204+
"//vendor/github.com/google/go-cmp/cmp:go_default_library",
204205
"//vendor/github.com/onsi/ginkgo:go_default_library",
205206
"//vendor/github.com/onsi/gomega:go_default_library",
206207
"//vendor/github.com/onsi/gomega/gstruct:go_default_library",

test/e2e_node/mirror_pod_test.go

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,20 @@ import (
2323
"path/filepath"
2424
"time"
2525

26-
"k8s.io/api/core/v1"
26+
v1 "k8s.io/api/core/v1"
27+
apiequality "k8s.io/apimachinery/pkg/api/equality"
2728
"k8s.io/apimachinery/pkg/api/errors"
2829
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2930
"k8s.io/apimachinery/pkg/types"
3031
"k8s.io/apimachinery/pkg/util/uuid"
3132
clientset "k8s.io/client-go/kubernetes"
33+
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
3234
"k8s.io/kubernetes/test/e2e/framework"
35+
imageutils "k8s.io/kubernetes/test/utils/image"
3336

37+
"github.com/google/go-cmp/cmp"
3438
"github.com/onsi/ginkgo"
3539
"github.com/onsi/gomega"
36-
imageutils "k8s.io/kubernetes/test/utils/image"
3740
)
3841

3942
var _ = framework.KubeDescribe("MirrorPod", func() {
@@ -188,7 +191,7 @@ func checkMirrorPodRunning(cl clientset.Interface, name, namespace string) error
188191
if pod.Status.Phase != v1.PodRunning {
189192
return fmt.Errorf("expected the mirror pod %q to be running, got %q", name, pod.Status.Phase)
190193
}
191-
return nil
194+
return validateMirrorPod(cl, pod)
192195
}
193196

194197
func checkMirrorPodRecreatedAndRunning(cl clientset.Interface, name, namespace string, oUID types.UID) error {
@@ -202,5 +205,49 @@ func checkMirrorPodRecreatedAndRunning(cl clientset.Interface, name, namespace s
202205
if pod.Status.Phase != v1.PodRunning {
203206
return fmt.Errorf("expected the mirror pod %q to be running, got %q", name, pod.Status.Phase)
204207
}
208+
return validateMirrorPod(cl, pod)
209+
}
210+
211+
func validateMirrorPod(cl clientset.Interface, mirrorPod *v1.Pod) error {
212+
hash, ok := mirrorPod.Annotations[kubetypes.ConfigHashAnnotationKey]
213+
if !ok || hash == "" {
214+
return fmt.Errorf("expected mirror pod %q to have a hash annotation", mirrorPod.Name)
215+
}
216+
mirrorHash, ok := mirrorPod.Annotations[kubetypes.ConfigMirrorAnnotationKey]
217+
if !ok || mirrorHash == "" {
218+
return fmt.Errorf("expected mirror pod %q to have a mirror pod annotation", mirrorPod.Name)
219+
}
220+
if hash != mirrorHash {
221+
return fmt.Errorf("expected mirror pod %q to have a matching mirror pod hash: got %q; expected %q", mirrorPod.Name, mirrorHash, hash)
222+
}
223+
source, ok := mirrorPod.Annotations[kubetypes.ConfigSourceAnnotationKey]
224+
if !ok {
225+
return fmt.Errorf("expected mirror pod %q to have a source annotation", mirrorPod.Name)
226+
}
227+
if source == kubetypes.ApiserverSource {
228+
return fmt.Errorf("expected mirror pod %q source to not be 'api'; got: %q", mirrorPod.Name, source)
229+
}
230+
231+
if len(mirrorPod.OwnerReferences) != 1 {
232+
return fmt.Errorf("expected mirror pod %q to have a single owner reference: got %d", mirrorPod.Name, len(mirrorPod.OwnerReferences))
233+
}
234+
node, err := cl.CoreV1().Nodes().Get(framework.TestContext.NodeName, metav1.GetOptions{})
235+
if err != nil {
236+
return fmt.Errorf("failed to fetch test node: %v", err)
237+
}
238+
239+
controller := true
240+
expectedOwnerRef := metav1.OwnerReference{
241+
APIVersion: "v1",
242+
Kind: "Node",
243+
Name: framework.TestContext.NodeName,
244+
UID: node.UID,
245+
Controller: &controller,
246+
}
247+
ref := mirrorPod.OwnerReferences[0]
248+
if !apiequality.Semantic.DeepEqual(ref, expectedOwnerRef) {
249+
return fmt.Errorf("unexpected mirror pod %q owner ref: %v", mirrorPod.Name, cmp.Diff(expectedOwnerRef, ref))
250+
}
251+
205252
return nil
206253
}

0 commit comments

Comments
 (0)