diff --git a/pkg/nodeagent/node_agent.go b/pkg/nodeagent/node_agent.go index a57379f37f..7c1b5b7e77 100644 --- a/pkg/nodeagent/node_agent.go +++ b/pkg/nodeagent/node_agent.go @@ -34,7 +34,8 @@ import ( const ( // daemonSet is the name of the Velero node agent daemonset. - daemonSet = "node-agent" + daemonSet = "node-agent" + daemonsetWindows = "node-agent-windows" ) var ( @@ -91,6 +92,18 @@ type Configs struct { // IsRunning checks if the node agent daemonset is running properly. If not, return the error found func IsRunning(ctx context.Context, kubeClient kubernetes.Interface, namespace string) error { + if err := isRunningOnLinux(ctx, kubeClient, namespace); err != nil { + return err + } + + if err := isRunningOnWindows(ctx, kubeClient, namespace); err != nil { + return err + } + + return nil +} + +func isRunningOnLinux(ctx context.Context, kubeClient kubernetes.Interface, namespace string) error { if _, err := kubeClient.AppsV1().DaemonSets(namespace).Get(ctx, daemonSet, metav1.GetOptions{}); apierrors.IsNotFound(err) { return ErrDaemonSetNotFound } else if err != nil { @@ -100,6 +113,16 @@ func IsRunning(ctx context.Context, kubeClient kubernetes.Interface, namespace s } } +func isRunningOnWindows(ctx context.Context, kubeClient kubernetes.Interface, namespace string) error { + if _, err := kubeClient.AppsV1().DaemonSets(namespace).Get(ctx, daemonsetWindows, metav1.GetOptions{}); apierrors.IsNotFound(err) { + return ErrDaemonSetNotFound + } else if err != nil { + return err + } else { + return nil + } +} + // KbClientIsRunningInNode checks if the node agent pod is running properly in a specified node through kube client. If not, return the error found func KbClientIsRunningInNode(ctx context.Context, namespace string, nodeName string, kubeClient kubernetes.Interface) error { return isRunningInNode(ctx, namespace, nodeName, nil, kubeClient) @@ -116,7 +139,7 @@ func isRunningInNode(ctx context.Context, namespace string, nodeName string, crC } pods := new(v1.PodList) - parsedSelector, err := labels.Parse(fmt.Sprintf("name=%s", daemonSet)) + parsedSelector, err := labels.Parse(fmt.Sprintf("role=%s", daemonSet)) if err != nil { return errors.Wrap(err, "fail to parse selector") } diff --git a/pkg/podvolume/backupper.go b/pkg/podvolume/backupper.go index 0a0c63eff1..29452344e2 100644 --- a/pkg/podvolume/backupper.go +++ b/pkg/podvolume/backupper.go @@ -206,6 +206,12 @@ func (b *backupper) BackupPodVolumes(backup *velerov1api.Backup, pod *corev1api. return nil, pvcSummary, nil } + if err := kube.IsLinuxNode(b.ctx, pod.Spec.NodeName, b.crClient); err != nil { + err := errors.Wrapf(err, "Pod %s/%s is not running in linux node(%s), skip", pod.Namespace, pod.Name, pod.Spec.NodeName) + skipAllPodVolumes(pod, volumesToBackup, err, pvcSummary, log) + return nil, pvcSummary, []error{err} + } + err := nodeagent.IsRunningInNode(b.ctx, backup.Namespace, pod.Spec.NodeName, b.crClient) if err != nil { skipAllPodVolumes(pod, volumesToBackup, err, pvcSummary, log) diff --git a/pkg/podvolume/restorer.go b/pkg/podvolume/restorer.go index 4b3e4354dd..c8f557aae7 100644 --- a/pkg/podvolume/restorer.go +++ b/pkg/podvolume/restorer.go @@ -213,6 +213,11 @@ func (r *restorer) RestorePodVolumes(data RestoreData, tracker *volume.RestoreVo } else if err != nil { r.log.WithError(err).Error("Failed to check node-agent pod status, disengage") } else { + if err := kube.IsLinuxNode(checkCtx, nodeName, r.crClient); err != nil { + r.log.WithField("node", nodeName).WithError(err).Error("Restored pod is not running in linux node") + r.nodeAgentCheck <- errors.Wrapf(err, "restored pod %s/%s is not running in linux node %s", data.Pod.Namespace, data.Pod.Name, nodeName) + } + err = nodeagent.IsRunningInNode(checkCtx, data.Restore.Namespace, nodeName, r.crClient) if err != nil { r.log.WithField("node", nodeName).WithError(err).Error("node-agent pod is not running in node, abort the restore") diff --git a/pkg/util/kube/node.go b/pkg/util/kube/node.go new file mode 100644 index 0000000000..118d097a6f --- /dev/null +++ b/pkg/util/kube/node.go @@ -0,0 +1,40 @@ +/* +Copyright The Velero Contributors. + +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 kube + +import ( + "context" + + "github.com/pkg/errors" + corev1api "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func IsLinuxNode(ctx context.Context, nodeName string, client client.Client) error { + node := &corev1api.Node{} + if err := client.Get(ctx, types.NamespacedName{Name: nodeName}, node); err != nil { + return errors.Wrapf(err, "error getting node %s", nodeName) + } + + if os, found := node.Labels["kubernetes.io/os"]; !found { + return errors.Errorf("no os type label for node %s", nodeName) + } else if os != "linux" { + return errors.Errorf("os type %s for node %s is not linux", os, nodeName) + } else { + return nil + } +} diff --git a/pkg/util/kube/node_test.go b/pkg/util/kube/node_test.go new file mode 100644 index 0000000000..a0ba6d72db --- /dev/null +++ b/pkg/util/kube/node_test.go @@ -0,0 +1,85 @@ +/* +Copyright The Velero Contributors. + +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 kube + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/vmware-tanzu/velero/pkg/builder" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + + clientFake "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestIsLinuxNode(t *testing.T) { + nodeNoOSLabel := builder.ForNode("fake-node").Result() + nodeWindows := builder.ForNode("fake-node").Labels(map[string]string{"kubernetes.io/os": "windows"}).Result() + nodeLinux := builder.ForNode("fake-node").Labels(map[string]string{"kubernetes.io/os": "linux"}).Result() + + scheme := runtime.NewScheme() + corev1.AddToScheme(scheme) + + tests := []struct { + name string + kubeClientObj []runtime.Object + err string + }{ + { + name: "error getting node", + err: "error getting node fake-node: nodes \"fake-node\" not found", + }, + { + name: "no os label", + kubeClientObj: []runtime.Object{ + nodeNoOSLabel, + }, + err: "no os type label for node fake-node", + }, + { + name: "os label does not match", + kubeClientObj: []runtime.Object{ + nodeWindows, + }, + err: "os type windows for node fake-node is not linux", + }, + { + name: "suceed", + kubeClientObj: []runtime.Object{ + nodeLinux, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeClientBuilder := clientFake.NewClientBuilder() + fakeClientBuilder = fakeClientBuilder.WithScheme(scheme) + + fakeClient := fakeClientBuilder.WithRuntimeObjects(test.kubeClientObj...).Build() + + err := IsLinuxNode(context.TODO(), "fake-node", fakeClient) + if err != nil { + assert.EqualError(t, err, test.err) + } else { + assert.NoError(t, err) + } + }) + } +}