From 181e08ef5eeadf2c6c552c06e9369a712d4b93e0 Mon Sep 17 00:00:00 2001 From: Frank Mai Date: Tue, 2 Jun 2020 14:24:11 +0800 Subject: [PATCH] refactor(limb): adjust adaptor reconciling logic - add the devicelink's predication - move the adaptor name observed status from `status.adaptor.name` to `status.adaptorName` - enrich test case --- api/v1alpha1/devicelink_types.go | 4 + deploy/e2e/all_in_one.yaml | 3 + deploy/e2e/all_in_one_without_webhook.yaml | 3 + .../crd/base/edge.cattle.io_devicelinks.yaml | 3 + pkg/limb/controller/devicelink.go | 50 ++- pkg/limb/index/devicelink_by_adaptor.go | 8 +- pkg/limb/index/devicelink_by_adaptor_test.go | 7 +- pkg/limb/predicate/devicelink_changed.go | 103 ++++++ pkg/limb/predicate/devicelink_changed_test.go | 312 ++++++++++++++++++ pkg/suctioncup/neurons.go | 22 +- pkg/util/object/runtime_is.go | 10 + pkg/util/object/runtime_is_test.go | 26 ++ 12 files changed, 522 insertions(+), 29 deletions(-) create mode 100644 pkg/limb/predicate/devicelink_changed.go create mode 100644 pkg/limb/predicate/devicelink_changed_test.go diff --git a/api/v1alpha1/devicelink_types.go b/api/v1alpha1/devicelink_types.go index b3f125f5..f9f34a32 100644 --- a/api/v1alpha1/devicelink_types.go +++ b/api/v1alpha1/devicelink_types.go @@ -138,6 +138,10 @@ type DeviceLinkStatus struct { // Represents the observed model of the device. // +optional Model metav1.TypeMeta `json:"model,omitempty"` + + // Represents the observed adaptor name of the device. + // +optional + AdaptorName string `json:"adaptorName,omitempty"` } // +kubebuilder:object:root=true diff --git a/deploy/e2e/all_in_one.yaml b/deploy/e2e/all_in_one.yaml index 8ddc6533..c7cb22cf 100644 --- a/deploy/e2e/all_in_one.yaml +++ b/deploy/e2e/all_in_one.yaml @@ -149,6 +149,9 @@ spec: type: object x-kubernetes-preserve-unknown-fields: true type: object + adaptorName: + description: Represents the observed adaptor name of the device. + type: string conditions: description: Represents the latest available observations of the device's current state. diff --git a/deploy/e2e/all_in_one_without_webhook.yaml b/deploy/e2e/all_in_one_without_webhook.yaml index 89af607c..7d6bc9bc 100644 --- a/deploy/e2e/all_in_one_without_webhook.yaml +++ b/deploy/e2e/all_in_one_without_webhook.yaml @@ -136,6 +136,9 @@ spec: type: object x-kubernetes-preserve-unknown-fields: true type: object + adaptorName: + description: Represents the observed adaptor name of the device. + type: string conditions: description: Represents the latest available observations of the device's current state. diff --git a/deploy/manifests/crd/base/edge.cattle.io_devicelinks.yaml b/deploy/manifests/crd/base/edge.cattle.io_devicelinks.yaml index 1a900fdb..cb2feb30 100644 --- a/deploy/manifests/crd/base/edge.cattle.io_devicelinks.yaml +++ b/deploy/manifests/crd/base/edge.cattle.io_devicelinks.yaml @@ -127,6 +127,9 @@ spec: type: object x-kubernetes-preserve-unknown-fields: true type: object + adaptorName: + description: Represents the observed adaptor name of the device. + type: string conditions: description: Represents the latest available observations of the device's current state. diff --git a/pkg/limb/controller/devicelink.go b/pkg/limb/controller/devicelink.go index 33b7606f..dc0dc179 100644 --- a/pkg/limb/controller/devicelink.go +++ b/pkg/limb/controller/devicelink.go @@ -19,6 +19,7 @@ import ( edgev1alpha1 "github.com/rancher/octopus/api/v1alpha1" "github.com/rancher/octopus/pkg/limb/index" + "github.com/rancher/octopus/pkg/limb/predicate" "github.com/rancher/octopus/pkg/metrics" "github.com/rancher/octopus/pkg/status/devicelink" "github.com/rancher/octopus/pkg/suctioncup" @@ -63,13 +64,21 @@ func (r *DeviceLinkReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) return ctrl.Result{}, nil } - // validates application + // rejects if not the requested node if link.Status.NodeName != r.NodeName { + // NB(thxCode) disconnects the link to avoid connection leak when the requested node has been changed + if exist := r.SuctionCup.Disconnect(&link); exist { + metricsRecorder.DecreaseConnections(link.Status.AdaptorName) + } return ctrl.Result{}, nil } - // validates mode existing or not + // rejects if the conditions are not met if devicelink.GetModelExistedStatus(&link.Status) != metav1.ConditionTrue { + // NB(thxCode) disconnects the link to avoid connection leak when the model has been changed or removed + if exist := r.SuctionCup.Disconnect(&link); exist { + metricsRecorder.DecreaseConnections(link.Status.AdaptorName) + } return ctrl.Result{}, nil } @@ -78,12 +87,12 @@ func (r *DeviceLinkReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) return ctrl.Result{}, nil } - // disconnect + // disconnects if exist := r.SuctionCup.Disconnect(&link); exist { - metricsRecorder.DecreaseConnections(link.Status.Adaptor.Name) + metricsRecorder.DecreaseConnections(link.Status.AdaptorName) } - // remove finalizer + // removes finalizer link.Finalizers = collection.StringSliceRemove(link.Finalizers, ReconcilingDeviceLink) if err := r.Update(ctx, &link); err != nil { log.Error(err, "Unable to remove finalizer from DeviceLink") @@ -93,7 +102,7 @@ func (r *DeviceLinkReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) return ctrl.Result{}, nil } - // add finalizer if needed + // adds finalizer if needed if !collection.StringSliceContain(link.Finalizers, ReconcilingDeviceLink) { link.Finalizers = append(link.Finalizers, ReconcilingDeviceLink) if err := r.Update(ctx, &link); err != nil { @@ -107,7 +116,7 @@ func (r *DeviceLinkReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) switch devicelink.GetAdaptorExistedStatus(&link.Status) { case metav1.ConditionFalse: if r.SuctionCup.ExistAdaptor(link.Spec.Adaptor.Name) || - link.Spec.Adaptor.Name != link.Status.Adaptor.Name || + link.Status.AdaptorName != link.Spec.Adaptor.Name || compareAdaptorParameters(link.Spec.Adaptor, link.Status.Adaptor) { devicelink.ToCheckAdaptorExisted(&link.Status) if err := r.Status().Update(ctx, &link); err != nil { @@ -118,8 +127,12 @@ func (r *DeviceLinkReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) return ctrl.Result{}, nil case metav1.ConditionTrue: if !r.SuctionCup.ExistAdaptor(link.Spec.Adaptor.Name) || - link.Spec.Adaptor.Name != link.Status.Adaptor.Name || + link.Status.AdaptorName != link.Spec.Adaptor.Name || compareAdaptorParameters(link.Spec.Adaptor, link.Status.Adaptor) { + // NB(thxCode) disconnects the link to avoid connection leak when the requested adaptor has been changed + if exist := r.SuctionCup.Disconnect(&link); exist { + metricsRecorder.DecreaseConnections(link.Status.AdaptorName) + } devicelink.ToCheckAdaptorExisted(&link.Status) if err := r.Status().Update(ctx, &link); err != nil { log.Error(err, "Unable to change the status of DeviceLink") @@ -134,7 +147,7 @@ func (r *DeviceLinkReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) devicelink.FailOnAdaptorExisted(&link.Status, "the adaptor isn't existed") } - link.Status.Adaptor.Name = link.Spec.Adaptor.Name + link.Status.AdaptorName = link.Spec.Adaptor.Name link.Status.Adaptor.Parameters = link.Spec.Adaptor.Parameters if err := r.Status().Update(ctx, &link); err != nil { log.Error(err, "Unable to change the status of DeviceLink") @@ -143,7 +156,7 @@ func (r *DeviceLinkReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) return ctrl.Result{}, nil } - // validate device created or not + // validates device created or not var device unstructured.Unstructured switch devicelink.GetDeviceCreatedStatus(&link.Status) { case metav1.ConditionFalse: @@ -176,7 +189,7 @@ func (r *DeviceLinkReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) return ctrl.Result{}, nil } - // update device + // updates device updated, err := updateDevice(&link, &device) if err != nil { devicelink.FailOnDeviceCreated(&link.Status, "unable to update device from template") @@ -194,7 +207,7 @@ func (r *DeviceLinkReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) } } default: - // create device + // creates device if device, err := constructDevice(&link, r.Scheme); err != nil { devicelink.FailOnDeviceCreated(&link.Status, "unable to construct device from template") r.Eventf(&link, "Warning", "FailedCreated", "cannot create device from template: %v", err) @@ -222,7 +235,7 @@ func (r *DeviceLinkReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) return ctrl.Result{}, nil } - // validate device connected or not + // validates device connected or not switch devicelink.GetDeviceConnectedStatus(&link.Status) { case metav1.ConditionFalse: // NB(thxCode) could not send any data to unhealthy connection, @@ -231,11 +244,11 @@ func (r *DeviceLinkReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) case metav1.ConditionTrue: sendStartTS := time.Now() defer func() { - metricsRecorder.ObserveSendLatency(link.Status.Adaptor.Name, time.Since(sendStartTS)) + metricsRecorder.ObserveSendLatency(link.Status.AdaptorName, time.Since(sendStartTS)) }() if err := r.SuctionCup.Send(&device, &link); err != nil { - metricsRecorder.IncreaseSendErrors(link.Status.Adaptor.Name) + metricsRecorder.IncreaseSendErrors(link.Status.AdaptorName) devicelink.FailOnDeviceConnected(&link.Status, "cannot send data to adaptor") r.Eventf(&link, "Warning", "FailedSent", "cannot send data to adaptor: %v", err) @@ -248,13 +261,13 @@ func (r *DeviceLinkReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) return ctrl.Result{}, nil default: if overwrite, err := r.SuctionCup.Connect(&link); err != nil { - metricsRecorder.IncreaseConnectErrors(link.Status.Adaptor.Name) + metricsRecorder.IncreaseConnectErrors(link.Status.AdaptorName) devicelink.FailOnDeviceConnected(&link.Status, "unable to connect to adaptor") r.Eventf(&link, "Warning", "FailedConnected", "cannot connect to adaptor: %v", err) } else { if !overwrite { - metricsRecorder.IncreaseConnections(link.Status.Adaptor.Name) + metricsRecorder.IncreaseConnections(link.Status.AdaptorName) } devicelink.SuccessOnDeviceConnected(&link.Status) @@ -274,7 +287,7 @@ func (r *DeviceLinkReconciler) SetupWithManager(ctrlMgr ctrl.Manager, suctionCup suctionCupMgr.RegisterAdaptorHandler(r) suctionCupMgr.RegisterConnectionHandler(r) - // indexes DeviceLink by `spec.adaptor.name` + // indexes DeviceLink by `status.adaptorName` if err := ctrlMgr.GetFieldIndexer().IndexField( &edgev1alpha1.DeviceLink{}, index.DeviceLinkByAdaptorField, @@ -286,6 +299,7 @@ func (r *DeviceLinkReconciler) SetupWithManager(ctrlMgr ctrl.Manager, suctionCup return ctrl.NewControllerManagedBy(ctrlMgr). Named("limb_dl"). For(&edgev1alpha1.DeviceLink{}). + WithEventFilter(predicate.DeviceLinkChangedPredicate{NodeName: r.NodeName}). Complete(r) } diff --git a/pkg/limb/index/devicelink_by_adaptor.go b/pkg/limb/index/devicelink_by_adaptor.go index 7b8f52f1..d1e8a998 100644 --- a/pkg/limb/index/devicelink_by_adaptor.go +++ b/pkg/limb/index/devicelink_by_adaptor.go @@ -14,13 +14,13 @@ var deviceLinkByAdaptorIndexLog = ctrl.Log.WithName("index").WithName(DeviceLink func DeviceLinkByAdaptorFunc(rawObj runtime.Object) []string { var link = object.ToDeviceLinkObject(rawObj) if link == nil { - deviceLinkByAdaptorIndexLog.Error(nil, "Received runtime object is not DeviceLink", "object", rawObj) return nil } - var name = link.Status.Adaptor.Name - if name != "" { - return []string{name} + var adaptorName = link.Status.AdaptorName + if adaptorName != "" { + deviceLinkByAdaptorIndexLog.V(0).Info("Index DeviceLink by Adaptor", "adaptorName", adaptorName, "object", object.GetNamespacedName(link)) + return []string{adaptorName} } return nil } diff --git a/pkg/limb/index/devicelink_by_adaptor_test.go b/pkg/limb/index/devicelink_by_adaptor_test.go index f43815fd..40f746e1 100644 --- a/pkg/limb/index/devicelink_by_adaptor_test.go +++ b/pkg/limb/index/devicelink_by_adaptor_test.go @@ -28,17 +28,20 @@ func TestDeviceLinkByAdaptorFunc(t *testing.T) { }, { given: &edgev1alpha1.DeviceLink{ - Status: edgev1alpha1.DeviceLinkStatus{ + Spec: edgev1alpha1.DeviceLinkSpec{ Adaptor: edgev1alpha1.DeviceAdaptor{ Name: "adaptors.test.io/dummy", }, }, + Status: edgev1alpha1.DeviceLinkStatus{ + AdaptorName: "adaptors.test.io/dummy", + }, }, expect: []string{ "adaptors.test.io/dummy", }, }, - { + { // non-DeviceLink object given: &corev1.Node{}, expect: nil, }, diff --git a/pkg/limb/predicate/devicelink_changed.go b/pkg/limb/predicate/devicelink_changed.go new file mode 100644 index 00000000..0643114e --- /dev/null +++ b/pkg/limb/predicate/devicelink_changed.go @@ -0,0 +1,103 @@ +package predicate + +import ( + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + "github.com/rancher/octopus/pkg/util/object" +) + +var deviceLinkChangedPredicateLog = ctrl.Log.WithName("predicate").WithName("deviceLinkChanged") + +type DeviceLinkChangedPredicate struct { + predicate.Funcs + + NodeName string +} + +func (p DeviceLinkChangedPredicate) Create(e event.CreateEvent) bool { + if e.Meta == nil || e.Object == nil { + return false + } + + // doesn't handle non-DeviceLink object + if !object.IsDeviceLinkObject(e.Object) { + return true + } + + var dl = object.ToDeviceLinkObject(e.Object) + + // handles if the requested node + if p.NodeName == dl.Spec.Adaptor.Node { + deviceLinkChangedPredicateLog.V(0).Info("Accept CreateEvent as requested the same node", "key", object.GetNamespacedName(e.Meta)) + return true + } + + return false +} + +func (p DeviceLinkChangedPredicate) Delete(e event.DeleteEvent) bool { + if e.Meta == nil || e.Object == nil { + return false + } + + // doesn't handle non-DeviceLink object + if !object.IsDeviceLinkObject(e.Object) { + return true + } + + // NB(thxCode) there is a finalizer to handler the DeviceLink deletion event, + // so with the finalizer, the deletion event can be changed to an update event. + return false +} + +func (p DeviceLinkChangedPredicate) Update(e event.UpdateEvent) bool { + if e.MetaOld == nil || e.MetaNew == nil || e.ObjectNew == nil || e.ObjectOld == nil { + return false + } + + // doesn't handle non-DeviceLink object + if !object.IsDeviceLinkObject(e.ObjectOld) { + return true + } + + var dlOld = object.ToDeviceLinkObject(e.ObjectOld) + var dlNew = object.ToDeviceLinkObject(e.ObjectNew) + + // handles if the object is requesting the same node + if p.NodeName == dlNew.Status.NodeName { + deviceLinkChangedPredicateLog.V(0).Info("Accept UpdateEvent as the object is requesting the same node", "key", object.GetNamespacedName(e.MetaNew)) + return true + } + + // handles if the object has requested the same node previously + if p.NodeName == dlOld.Status.NodeName { + // NB(thxCode) help the reconciling logic to close the previous connection + deviceLinkChangedPredicateLog.V(0).Info("Accept UpdateEvent as the object has requested the same node previously", "key", object.GetNamespacedName(e.MetaOld)) + return true + } + + return false +} + +func (p DeviceLinkChangedPredicate) Generic(e event.GenericEvent) bool { + if e.Meta == nil || e.Object == nil { + return false + } + + // doesn't handle non-DeviceLink object + if !object.IsDeviceLinkObject(e.Object) { + return true + } + + var dl = object.ToDeviceLinkObject(e.Object) + + // handles if the requested node + if p.NodeName == dl.Spec.Adaptor.Node { + deviceLinkChangedPredicateLog.V(0).Info("Accept GenericEvent as requested the same node", "key", object.GetNamespacedName(e.Meta)) + return true + } + + return false +} diff --git a/pkg/limb/predicate/devicelink_changed_test.go b/pkg/limb/predicate/devicelink_changed_test.go new file mode 100644 index 00000000..f3aecebd --- /dev/null +++ b/pkg/limb/predicate/devicelink_changed_test.go @@ -0,0 +1,312 @@ +package predicate + +import ( + "testing" + + "github.com/davecgh/go-spew/spew" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/event" + + edgev1alpha1 "github.com/rancher/octopus/api/v1alpha1" +) + +func TestDeviceLinkChangedPredicate_Update(t *testing.T) { + var testCases = []struct { + name string + given event.UpdateEvent + expect bool + }{ + { + name: "without MetaOld", + given: event.UpdateEvent{ + MetaOld: nil, + ObjectOld: &edgev1alpha1.DeviceLink{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: edgev1alpha1.DeviceLinkSpec{ + Adaptor: edgev1alpha1.DeviceAdaptor{ + Name: "adaptors.test.io/dummy", + Node: "edge-worker", + }, + }, + Status: edgev1alpha1.DeviceLinkStatus{ + AdaptorName: "adaptors.test.io/dummy", + NodeName: "edge-worker", + }, + }, + MetaNew: &edgev1alpha1.DeviceLink{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: edgev1alpha1.DeviceLinkSpec{ + Adaptor: edgev1alpha1.DeviceAdaptor{ + Name: "adaptors.test.io/dummy", + Node: "edge-worker", + }, + }, + Status: edgev1alpha1.DeviceLinkStatus{ + AdaptorName: "adaptors.test.io/dummy", + NodeName: "edge-worker", + }, + }, + ObjectNew: &edgev1alpha1.DeviceLink{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: edgev1alpha1.DeviceLinkSpec{ + Adaptor: edgev1alpha1.DeviceAdaptor{ + Name: "adaptors.test.io/dummy", + Node: "edge-worker", + }, + }, + Status: edgev1alpha1.DeviceLinkStatus{ + AdaptorName: "adaptors.test.io/dummy", + NodeName: "edge-worker", + }, + }, + }, + expect: false, + }, + { + name: "non-DeviceLink instance", + given: event.UpdateEvent{ + MetaOld: &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummyspecialdevices.edge.cattle.io", + }, + }, + ObjectOld: &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummyspecialdevices.edge.cattle.io", + }, + }, + MetaNew: &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummyspecialdevices.edge.cattle.io", + }, + }, + ObjectNew: &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummyspecialdevices.edge.cattle.io", + }, + }, + }, + expect: true, + }, + { + name: "request the same node", + given: event.UpdateEvent{ + MetaOld: &edgev1alpha1.DeviceLink{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: edgev1alpha1.DeviceLinkSpec{ + Adaptor: edgev1alpha1.DeviceAdaptor{ + Name: "adaptors.test.io/dummy", + Node: "edge-worker", + }, + }, + }, + ObjectOld: &edgev1alpha1.DeviceLink{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: edgev1alpha1.DeviceLinkSpec{ + Adaptor: edgev1alpha1.DeviceAdaptor{ + Name: "adaptors.test.io/dummy", + Node: "edge-worker", + }, + }, + }, + MetaNew: &edgev1alpha1.DeviceLink{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: edgev1alpha1.DeviceLinkSpec{ + Adaptor: edgev1alpha1.DeviceAdaptor{ + Name: "adaptors.test.io/dummy", + Node: "edge-worker", + }, + }, + Status: edgev1alpha1.DeviceLinkStatus{ + AdaptorName: "adaptors.test.io/dummy", + NodeName: "edge-worker", + }, + }, + ObjectNew: &edgev1alpha1.DeviceLink{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: edgev1alpha1.DeviceLinkSpec{ + Adaptor: edgev1alpha1.DeviceAdaptor{ + Name: "adaptors.test.io/dummy", + Node: "edge-worker", + }, + }, + Status: edgev1alpha1.DeviceLinkStatus{ + AdaptorName: "adaptors.test.io/dummy", + NodeName: "edge-worker", + }, + }, + }, + expect: true, + }, + { + name: "requested the same node previously", + given: event.UpdateEvent{ + MetaOld: &edgev1alpha1.DeviceLink{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: edgev1alpha1.DeviceLinkSpec{ + Adaptor: edgev1alpha1.DeviceAdaptor{ + Name: "adaptors.test.io/dummy", + Node: "edge-worker", + }, + }, + Status: edgev1alpha1.DeviceLinkStatus{ + AdaptorName: "adaptors.test.io/dummy", + NodeName: "edge-worker", + }, + }, + ObjectOld: &edgev1alpha1.DeviceLink{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: edgev1alpha1.DeviceLinkSpec{ + Adaptor: edgev1alpha1.DeviceAdaptor{ + Name: "adaptors.test.io/dummy", + Node: "edge-worker", + }, + }, + Status: edgev1alpha1.DeviceLinkStatus{ + AdaptorName: "adaptors.test.io/dummy", + NodeName: "edge-worker", + }, + }, + MetaNew: &edgev1alpha1.DeviceLink{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: edgev1alpha1.DeviceLinkSpec{ + Adaptor: edgev1alpha1.DeviceAdaptor{ + Name: "adaptors.test.io/dummy", + Node: "edge-worker1", + }, + }, + Status: edgev1alpha1.DeviceLinkStatus{ + AdaptorName: "adaptors.test.io/dummy", + NodeName: "edge-worker1", + }, + }, + ObjectNew: &edgev1alpha1.DeviceLink{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: edgev1alpha1.DeviceLinkSpec{ + Adaptor: edgev1alpha1.DeviceAdaptor{ + Name: "adaptors.test.io/dummy", + Node: "edge-worker1", + }, + }, + Status: edgev1alpha1.DeviceLinkStatus{ + AdaptorName: "adaptors.test.io/dummy", + NodeName: "edge-worker1", + }, + }, + }, + expect: true, + }, + { + name: "request another node", + given: event.UpdateEvent{ + MetaOld: &edgev1alpha1.DeviceLink{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: edgev1alpha1.DeviceLinkSpec{ + Adaptor: edgev1alpha1.DeviceAdaptor{ + Name: "adaptors.test.io/dummy", + Node: "edge-worker1", + }, + }, + Status: edgev1alpha1.DeviceLinkStatus{ + AdaptorName: "adaptors.test.io/dummy", + NodeName: "edge-worker1", + }, + }, + ObjectOld: &edgev1alpha1.DeviceLink{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: edgev1alpha1.DeviceLinkSpec{ + Adaptor: edgev1alpha1.DeviceAdaptor{ + Name: "adaptors.test.io/dummy", + Node: "edge-worker1", + }, + }, + Status: edgev1alpha1.DeviceLinkStatus{ + AdaptorName: "adaptors.test.io/dummy", + NodeName: "edge-worker1", + }, + }, + MetaNew: &edgev1alpha1.DeviceLink{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: edgev1alpha1.DeviceLinkSpec{ + Adaptor: edgev1alpha1.DeviceAdaptor{ + Name: "adaptors.test.io/dummy", + Node: "edge-worker1", + }, + }, + Status: edgev1alpha1.DeviceLinkStatus{ + AdaptorName: "adaptors.test.io/dummy", + NodeName: "edge-worker1", + }, + }, + ObjectNew: &edgev1alpha1.DeviceLink{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test", + }, + Spec: edgev1alpha1.DeviceLinkSpec{ + Adaptor: edgev1alpha1.DeviceAdaptor{ + Name: "adaptors.test.io/dummy", + Node: "edge-worker1", + }, + }, + Status: edgev1alpha1.DeviceLinkStatus{ + AdaptorName: "adaptors.test.io/dummy", + NodeName: "edge-worker1", + }, + }, + }, + expect: false, + }, + } + + var predication = DeviceLinkChangedPredicate{NodeName: "edge-worker"} + for _, tc := range testCases { + var ret = predication.Update(tc.given) + if ret != tc.expect { + t.Errorf("case %v: expected %s, got %s", tc.name, spew.Sprintf("%#v", tc.expect), spew.Sprintf("%#v", ret)) + } + } +} diff --git a/pkg/suctioncup/neurons.go b/pkg/suctioncup/neurons.go index 1bf97ca2..279afff5 100644 --- a/pkg/suctioncup/neurons.go +++ b/pkg/suctioncup/neurons.go @@ -13,9 +13,13 @@ func (m *manager) ExistAdaptor(name string) bool { } func (m *manager) Connect(by *edgev1alpha1.DeviceLink) (bool, error) { - var adaptor = m.adaptors.Get(by.Spec.Adaptor.Name) + var adaptorName = by.Status.AdaptorName + if adaptorName == "" { + return false, errors.New("adaptor name is empty") + } + var adaptor = m.adaptors.Get(adaptorName) if adaptor == nil { - return false, errors.Errorf("could not find adaptor %s", by.Spec.Adaptor.Name) + return false, errors.Errorf("could not find adaptor %s", adaptorName) } var name = object.GetNamespacedName(by) @@ -27,7 +31,11 @@ func (m *manager) Connect(by *edgev1alpha1.DeviceLink) (bool, error) { } func (m *manager) Disconnect(by *edgev1alpha1.DeviceLink) bool { - var adaptor = m.adaptors.Get(by.Spec.Adaptor.Name) + var adaptorName = by.Status.AdaptorName + if adaptorName == "" { + return false + } + var adaptor = m.adaptors.Get(adaptorName) if adaptor == nil { return false } @@ -37,9 +45,13 @@ func (m *manager) Disconnect(by *edgev1alpha1.DeviceLink) bool { } func (m *manager) Send(data *unstructured.Unstructured, by *edgev1alpha1.DeviceLink) error { - var adaptor = m.adaptors.Get(by.Spec.Adaptor.Name) + var adaptorName = by.Status.AdaptorName + if adaptorName == "" { + return errors.New("could not find blank name adaptor") + } + var adaptor = m.adaptors.Get(adaptorName) if adaptor == nil { - return errors.Errorf("could not find adaptor %s", by.Spec.Adaptor.Name) + return errors.Errorf("could not find adaptor %s", adaptorName) } var name = object.GetNamespacedName(by) diff --git a/pkg/util/object/runtime_is.go b/pkg/util/object/runtime_is.go index 61aabbc4..d8906129 100644 --- a/pkg/util/object/runtime_is.go +++ b/pkg/util/object/runtime_is.go @@ -4,6 +4,8 @@ import ( corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/runtime" + + edgev1alpha1 "github.com/rancher/octopus/api/v1alpha1" ) func IsNodeObject(obj runtime.Object) bool { @@ -21,3 +23,11 @@ func IsCustomResourceDefinitionObject(obj runtime.Object) bool { _, ok := obj.(*apiextensionsv1.CustomResourceDefinition) return ok } + +func IsDeviceLinkObject(obj runtime.Object) bool { + if obj == nil { + return false + } + _, ok := obj.(*edgev1alpha1.DeviceLink) + return ok +} diff --git a/pkg/util/object/runtime_is_test.go b/pkg/util/object/runtime_is_test.go index 5bc69937..5233f860 100644 --- a/pkg/util/object/runtime_is_test.go +++ b/pkg/util/object/runtime_is_test.go @@ -62,3 +62,29 @@ func TestIsCustomResourceDefinitionObject(t *testing.T) { } } } + +func TestIsDeviceLinkObject(t *testing.T) { + var testCases = []struct { + name string + given runtime.Object + expect bool + }{ + { + name: "DeviceLink instance", + given: &edgev1alpha1.DeviceLink{}, + expect: true, + }, + { + name: "non-DeviceLink instance", + given: &corev1.Node{}, + expect: false, + }, + } + + for _, tc := range testCases { + var ret = IsDeviceLinkObject(tc.given) + if ret != tc.expect { + t.Errorf("case %v: expected %s, got %s", tc.name, spew.Sprintf("%#v", tc.expect), spew.Sprintf("%#v", ret)) + } + } +}