From 8451937c9c7bb6f580f7d18d730787f9704db8f0 Mon Sep 17 00:00:00 2001 From: Karthik K N Date: Wed, 29 Jun 2022 17:49:11 +0530 Subject: [PATCH] Fetch internal IP from dhcp server --- cloud/scope/powervs_machine.go | 95 ++++++ cloud/scope/powervs_machine_test.go | 308 ++++++++++++++++++ controllers/ibmpowervsmachine_controller.go | 10 +- .../ibmpowervsmachine_controller_test.go | 8 +- main.go | 5 + .../powervs/mock/powervs_generated.go | 30 ++ pkg/cloud/services/powervs/powervs.go | 2 + pkg/cloud/services/powervs/service.go | 10 + pkg/options/caching.go | 36 ++ 9 files changed, 502 insertions(+), 2 deletions(-) create mode 100644 pkg/options/caching.go diff --git a/cloud/scope/powervs_machine.go b/cloud/scope/powervs_machine.go index c33ee42d1a..f8439a6ed1 100644 --- a/cloud/scope/powervs_machine.go +++ b/cloud/scope/powervs_machine.go @@ -33,6 +33,7 @@ import ( powerVSUtils "github.com/ppc64le-cloud/powervs-utils" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/cache" "k8s.io/klog/v2/klogr" "k8s.io/utils/pointer" capiv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1" @@ -60,6 +61,7 @@ type PowerVSMachineScopeParams struct { IBMPowerVSMachine *infrav1beta1.IBMPowerVSMachine IBMPowerVSImage *infrav1beta1.IBMPowerVSImage ServiceEndpoint []endpoints.ServiceEndpoint + DHCPIPCacheStore cache.Store } // PowerVSMachineScope defines a scope defined around a Power VS Machine. @@ -75,6 +77,7 @@ type PowerVSMachineScope struct { IBMPowerVSMachine *infrav1beta1.IBMPowerVSMachine IBMPowerVSImage *infrav1beta1.IBMPowerVSImage ServiceEndpoint []endpoints.ServiceEndpoint + DHCPIPCacheStore cache.Store } // NewPowerVSMachineScope creates a new PowerVSMachineScope from the supplied parameters. @@ -185,6 +188,7 @@ func NewPowerVSMachineScope(params PowerVSMachineScopeParams) (scope *PowerVSMac return } scope.IBMPowerVSClient = c + scope.DHCPIPCacheStore = params.DHCPIPCacheStore return scope, nil } @@ -435,6 +439,97 @@ func (m *PowerVSMachineScope) SetAddresses(instance *models.PVMInstance) { } } m.IBMPowerVSMachine.Status.Addresses = addresses + if len(addresses) > 2 { + // If the address length is more than 2 means either NodeInternalIP or NodeExternalIP is updated so return + return + } + // In this case there is no IP found under instance.Networks, So try to fetch the IP from cache or DHCP server + // Look for DHCP IP from the cache + obj, exists, err := m.DHCPIPCacheStore.GetByKey(*instance.ServerName) + if err != nil { + m.Error(err, "failed to fetch the DHCP IP address from cache store", "VM", *instance.ServerName) + } + if exists { + m.Info("found IP for VM from DHCP cache", "IP", obj.(options.VMip).IP, "VM", *instance.ServerName) + addresses = append(addresses, corev1.NodeAddress{ + Type: corev1.NodeInternalIP, + Address: obj.(options.VMip).IP, + }) + m.IBMPowerVSMachine.Status.Addresses = addresses + return + } + // Fetch the VM network ID + networkID, err := getNetworkID(m.IBMPowerVSMachine.Spec.Network, m) + if err != nil { + m.Error(err, "failed to fetch network id from network resource", "VM", *instance.ServerName) + return + } + // Fetch the details of the network attached to the VM + var pvmNetwork *models.PVMInstanceNetwork + for _, network := range instance.Networks { + if network.NetworkID == *networkID { + pvmNetwork = network + m.Info("found network attached to VM", "Network ID", network.NetworkID, "VM", *instance.ServerName) + } + } + if pvmNetwork == nil { + m.Info("failed to get network attached to VM", "VM", *instance.ServerName, "Network ID", *networkID) + return + } + // Get all the DHCP servers + dhcpServer, err := m.IBMPowerVSClient.GetDHCPServers() + if err != nil { + m.Error(err, "failed to get DHCP server") + return + } + // Get the Details of DHCP server associated with the network + var dhcpServerDetails *models.DHCPServerDetail + for _, server := range dhcpServer { + if *server.Network.ID == *networkID { + m.Info("found DHCP server for network", "DHCP server ID", *server.ID, "network ID", *networkID) + dhcpServerDetails, err = m.IBMPowerVSClient.GetDHCPServerByID(*server.ID) + if err != nil { + m.Error(err, "failed to get DHCP server details", "DHCP server ID", *server.ID) + return + } + break + } + } + if dhcpServerDetails == nil { + errStr := fmt.Errorf("DHCP server details is nil") + m.Error(errStr, "DHCP server associated with network is nil", "Network ID", *networkID) + return + } + + // Fetch the VM IP using VM's mac from DHCP server lease + var internalIP *string + for _, lease := range dhcpServerDetails.Leases { + if *lease.InstanceMacAddress == pvmNetwork.MacAddress { + m.Info("found internal ip for VM from DHCP lease", "IP", *lease.InstanceIP, "VM", *instance.ServerName) + internalIP = lease.InstanceIP + break + } + } + if internalIP == nil { + errStr := fmt.Errorf("internal IP is nil") + m.Error(errStr, "failed to get internal IP, DHCP lease not found for VM with MAC in DHCP network", "VM", *instance.ServerName, + "MAC", pvmNetwork.MacAddress, "DHCP server ID", *dhcpServerDetails.ID) + return + } + m.Info("found internal IP for VM from DHCP lease", "IP", *internalIP, "VM", *instance.ServerName) + addresses = append(addresses, corev1.NodeAddress{ + Type: corev1.NodeInternalIP, + Address: *internalIP, + }) + // Update the cache with the ip and VM name + err = m.DHCPIPCacheStore.Add(options.VMip{ + Name: *instance.ServerName, + IP: *internalIP, + }) + if err != nil { + m.Error(err, "failed to update the DHCP cache store with the IP", "VM", *instance.ServerName, "IP", *internalIP) + } + m.IBMPowerVSMachine.Status.Addresses = addresses } // SetInstanceState will set the state for the machine. diff --git a/cloud/scope/powervs_machine_test.go b/cloud/scope/powervs_machine_test.go index 69a60ce008..7a9957f757 100644 --- a/cloud/scope/powervs_machine_test.go +++ b/cloud/scope/powervs_machine_test.go @@ -18,7 +18,9 @@ package scope import ( "errors" + "fmt" "testing" + "time" "github.com/IBM-Cloud/power-go-client/power/models" "github.com/IBM/go-sdk-core/v5/core" @@ -27,7 +29,10 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/cache" "k8s.io/klog/v2/klogr" + "k8s.io/utils/pointer" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/options" capiv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -88,6 +93,42 @@ func setupPowerVSMachineScope(clusterName string, machineName string, imageID *s Machine: machine, IBMPowerVSCluster: powervsCluster, IBMPowerVSMachine: powervsMachine, + DHCPIPCacheStore: cache.NewTTLStore(options.CacheKeyFunc, options.CacheTTL), + } +} + +func newPowerVSInstance(name, networkID, mac string) *models.PVMInstance { + return &models.PVMInstance{ + ServerName: pointer.StringPtr(name), + Networks: []*models.PVMInstanceNetwork{ + { + NetworkID: networkID, + MacAddress: mac, + }, + }, + } +} + +func newDHCPServer(serverID, networkID string) models.DHCPServers { + return models.DHCPServers{ + &models.DHCPServer{ + ID: pointer.StringPtr(serverID), + Network: &models.DHCPServerNetwork{ + ID: pointer.StringPtr(networkID), + }, + }, + } +} + +func newDHCPServerDetails(serverID, leaseIP, instanceMac string) *models.DHCPServerDetail { + return &models.DHCPServerDetail{ + ID: pointer.StringPtr(serverID), + Leases: []*models.DHCPServerLeases{ + { + InstanceIP: pointer.StringPtr(leaseIP), + InstanceMacAddress: pointer.StringPtr(instanceMac), + }, + }, } } @@ -348,3 +389,270 @@ func TestDeleteMachinePVS(t *testing.T) { }) }) } + +func TestSetAddresses(t *testing.T) { + instanceName := "test_vm" + networkID := "test-net-ID" + leaseIP := "192.168.0.10" + instanceMac := "ff:11:33:dd:00:22" + dhcpServerID := "test-server-id" + defaultExpectedMachineAddress := []corev1.NodeAddress{ + { + Type: corev1.NodeInternalDNS, + Address: instanceName, + }, + { + Type: corev1.NodeHostName, + Address: instanceName, + }, + } + + defaultDhcpCacheStoreFunc := func() cache.Store { + return cache.NewTTLStore(options.CacheKeyFunc, options.CacheTTL) + } + + testCases := []struct { + testcase string + powerVSClientFunc func(*gomock.Controller) *mock.MockPowerVS + pvmInstance *models.PVMInstance + expectedNodeAddress []corev1.NodeAddress + expectedError error + dhcpCacheStoreFunc func() cache.Store + setNetworkID bool + }{ + { + testcase: "should set external IP address from instance network", + powerVSClientFunc: func(ctrl *gomock.Controller) *mock.MockPowerVS { + mockPowerVSClient := mock.NewMockPowerVS(ctrl) + return mockPowerVSClient + }, + pvmInstance: &models.PVMInstance{ + Networks: []*models.PVMInstanceNetwork{ + { + ExternalIP: "10.11.2.3", + }, + }, + ServerName: pointer.StringPtr(instanceName), + }, + expectedNodeAddress: append(defaultExpectedMachineAddress, corev1.NodeAddress{ + Type: corev1.NodeExternalIP, + Address: "10.11.2.3", + }), + dhcpCacheStoreFunc: defaultDhcpCacheStoreFunc, + }, + { + testcase: "should set internal IP address from instance network", + powerVSClientFunc: func(ctrl *gomock.Controller) *mock.MockPowerVS { + mockPowerVSClient := mock.NewMockPowerVS(ctrl) + return mockPowerVSClient + }, + pvmInstance: &models.PVMInstance{ + Networks: []*models.PVMInstanceNetwork{ + { + IPAddress: "192.168.10.3", + }, + }, + ServerName: pointer.StringPtr(instanceName), + }, + expectedNodeAddress: append(defaultExpectedMachineAddress, corev1.NodeAddress{ + Type: corev1.NodeInternalIP, + Address: "192.168.10.3", + }), + dhcpCacheStoreFunc: defaultDhcpCacheStoreFunc, + }, + { + testcase: "should set both internal and external IP address from instance network", + powerVSClientFunc: func(ctrl *gomock.Controller) *mock.MockPowerVS { + mockPowerVSClient := mock.NewMockPowerVS(ctrl) + return mockPowerVSClient + }, + pvmInstance: &models.PVMInstance{ + Networks: []*models.PVMInstanceNetwork{ + { + IPAddress: "192.168.10.3", + ExternalIP: "10.11.2.3", + }, + }, + ServerName: pointer.StringPtr(instanceName), + }, + expectedNodeAddress: append(defaultExpectedMachineAddress, []corev1.NodeAddress{ + { + Type: corev1.NodeInternalIP, + Address: "192.168.10.3", + }, + { + Type: corev1.NodeExternalIP, + Address: "10.11.2.3", + }, + }...), + dhcpCacheStoreFunc: defaultDhcpCacheStoreFunc, + }, + { + testcase: "error while getting network id", + powerVSClientFunc: func(ctrl *gomock.Controller) *mock.MockPowerVS { + mockPowerVSClient := mock.NewMockPowerVS(ctrl) + mockPowerVSClient.EXPECT().GetAllNetwork().Return(nil, fmt.Errorf("intentional error")) + return mockPowerVSClient + }, + pvmInstance: &models.PVMInstance{ + ServerName: pointer.StringPtr(instanceName), + }, + expectedNodeAddress: defaultExpectedMachineAddress, + dhcpCacheStoreFunc: defaultDhcpCacheStoreFunc, + }, + { + testcase: "no network id associated with network name", + powerVSClientFunc: func(ctrl *gomock.Controller) *mock.MockPowerVS { + mockPowerVSClient := mock.NewMockPowerVS(ctrl) + networks := &models.Networks{ + Networks: []*models.NetworkReference{ + { + NetworkID: pointer.StringPtr("test-ID"), + Name: pointer.StringPtr("test-name"), + }, + }, + } + mockPowerVSClient.EXPECT().GetAllNetwork().Return(networks, nil) + return mockPowerVSClient + }, + pvmInstance: newPowerVSInstance(instanceName, networkID, instanceMac), + expectedNodeAddress: defaultExpectedMachineAddress, + dhcpCacheStoreFunc: defaultDhcpCacheStoreFunc, + }, + { + testcase: "provided network id not attached to vm", + powerVSClientFunc: func(ctrl *gomock.Controller) *mock.MockPowerVS { + mockPowerVSClient := mock.NewMockPowerVS(ctrl) + return mockPowerVSClient + }, + pvmInstance: newPowerVSInstance(instanceName, "test-net", instanceMac), + expectedNodeAddress: defaultExpectedMachineAddress, + dhcpCacheStoreFunc: defaultDhcpCacheStoreFunc, + setNetworkID: true, + }, + { + testcase: "error while getting DHCP servers", + powerVSClientFunc: func(ctrl *gomock.Controller) *mock.MockPowerVS { + mockPowerVSClient := mock.NewMockPowerVS(ctrl) + mockPowerVSClient.EXPECT().GetDHCPServers().Return(nil, fmt.Errorf("intentional error")) + return mockPowerVSClient + }, + pvmInstance: newPowerVSInstance(instanceName, networkID, instanceMac), + expectedNodeAddress: defaultExpectedMachineAddress, + dhcpCacheStoreFunc: defaultDhcpCacheStoreFunc, + setNetworkID: true, + }, + { + testcase: "dhcp server details not found associated to network id", + powerVSClientFunc: func(ctrl *gomock.Controller) *mock.MockPowerVS { + mockPowerVSClient := mock.NewMockPowerVS(ctrl) + mockPowerVSClient.EXPECT().GetDHCPServers().Return(newDHCPServer(dhcpServerID, "test-network"), nil) + return mockPowerVSClient + }, + pvmInstance: newPowerVSInstance(instanceName, networkID, instanceMac), + expectedNodeAddress: defaultExpectedMachineAddress, + dhcpCacheStoreFunc: defaultDhcpCacheStoreFunc, + setNetworkID: true, + }, + { + testcase: "error on getting DHCP server details", + powerVSClientFunc: func(ctrl *gomock.Controller) *mock.MockPowerVS { + mockPowerVSClient := mock.NewMockPowerVS(ctrl) + mockPowerVSClient.EXPECT().GetDHCPServers().Return(newDHCPServer(dhcpServerID, networkID), nil) + mockPowerVSClient.EXPECT().GetDHCPServerByID(dhcpServerID).Return(nil, fmt.Errorf("intentnional error")) + return mockPowerVSClient + }, + pvmInstance: newPowerVSInstance(instanceName, networkID, instanceMac), + expectedNodeAddress: defaultExpectedMachineAddress, + dhcpCacheStoreFunc: defaultDhcpCacheStoreFunc, + setNetworkID: true, + }, + { + testcase: "dhcp server lease does not have lease for instance", + powerVSClientFunc: func(ctrl *gomock.Controller) *mock.MockPowerVS { + mockPowerVSClient := mock.NewMockPowerVS(ctrl) + mockPowerVSClient.EXPECT().GetDHCPServers().Return(newDHCPServer(dhcpServerID, networkID), nil) + mockPowerVSClient.EXPECT().GetDHCPServerByID(dhcpServerID).Return(newDHCPServerDetails(dhcpServerID, leaseIP, "ff:11:33:dd:00:33"), nil) + return mockPowerVSClient + }, + pvmInstance: newPowerVSInstance(instanceName, networkID, instanceMac), + expectedNodeAddress: defaultExpectedMachineAddress, + dhcpCacheStoreFunc: defaultDhcpCacheStoreFunc, + setNetworkID: true, + }, + { + testcase: "success in getting ip address from dhcp server", + powerVSClientFunc: func(ctrl *gomock.Controller) *mock.MockPowerVS { + mockPowerVSClient := mock.NewMockPowerVS(ctrl) + mockPowerVSClient.EXPECT().GetDHCPServers().Return(newDHCPServer(dhcpServerID, networkID), nil) + mockPowerVSClient.EXPECT().GetDHCPServerByID(dhcpServerID).Return(newDHCPServerDetails(dhcpServerID, leaseIP, instanceMac), nil) + return mockPowerVSClient + }, + pvmInstance: newPowerVSInstance(instanceName, networkID, instanceMac), + expectedNodeAddress: append(defaultExpectedMachineAddress, corev1.NodeAddress{ + Type: corev1.NodeInternalIP, + Address: leaseIP, + }), + dhcpCacheStoreFunc: defaultDhcpCacheStoreFunc, + setNetworkID: true, + }, + { + testcase: "ip stored in cache expired, fetch from dhcp server", + powerVSClientFunc: func(ctrl *gomock.Controller) *mock.MockPowerVS { + mockPowerVSClient := mock.NewMockPowerVS(ctrl) + mockPowerVSClient.EXPECT().GetDHCPServers().Return(newDHCPServer(dhcpServerID, networkID), nil) + mockPowerVSClient.EXPECT().GetDHCPServerByID(dhcpServerID).Return(newDHCPServerDetails(dhcpServerID, leaseIP, instanceMac), nil) + return mockPowerVSClient + }, + pvmInstance: newPowerVSInstance(instanceName, networkID, instanceMac), + expectedNodeAddress: append(defaultExpectedMachineAddress, corev1.NodeAddress{ + Type: corev1.NodeInternalIP, + Address: leaseIP, + }), + dhcpCacheStoreFunc: func() cache.Store { + cacheStore := cache.NewTTLStore(options.CacheKeyFunc, time.Millisecond) + _ = cacheStore.Add(options.VMip{ + Name: instanceName, + IP: "192.168.99.98", + }) + time.Sleep(time.Millisecond) + return cacheStore + }, + setNetworkID: true, + }, + { + testcase: "success in fetching DHCP IP from cache", + powerVSClientFunc: func(ctrl *gomock.Controller) *mock.MockPowerVS { + mockPowerVSClient := mock.NewMockPowerVS(ctrl) + return mockPowerVSClient + }, + pvmInstance: newPowerVSInstance(instanceName, networkID, instanceMac), + expectedNodeAddress: append(defaultExpectedMachineAddress, corev1.NodeAddress{ + Type: corev1.NodeInternalIP, + Address: leaseIP, + }), + dhcpCacheStoreFunc: func() cache.Store { + cacheStore := cache.NewTTLStore(options.CacheKeyFunc, options.CacheTTL) + _ = cacheStore.Add(options.VMip{ + Name: instanceName, + IP: leaseIP, + }) + return cacheStore + }, + setNetworkID: true, + }, + } + for _, tc := range testCases { + t.Run(tc.testcase, func(t *testing.T) { + g := NewWithT(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockPowerVSClient := tc.powerVSClientFunc(ctrl) + scope := setupPowerVSMachineScope("test-cluster", "test-machine-0", pointer.StringPtr("test-image-ID"), &networkID, tc.setNetworkID, mockPowerVSClient) + scope.DHCPIPCacheStore = tc.dhcpCacheStoreFunc() + scope.SetAddresses(tc.pvmInstance) + g.Expect(scope.IBMPowerVSMachine.Status.Addresses).To(Equal(tc.expectedNodeAddress)) + }) + } +} diff --git a/controllers/ibmpowervsmachine_controller.go b/controllers/ibmpowervsmachine_controller.go index 528e065421..e4a46d607e 100644 --- a/controllers/ibmpowervsmachine_controller.go +++ b/controllers/ibmpowervsmachine_controller.go @@ -25,7 +25,9 @@ import ( "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/options" capiv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1" capierrors "sigs.k8s.io/cluster-api/errors" "sigs.k8s.io/cluster-api/util" @@ -47,6 +49,7 @@ type IBMPowerVSMachineReconciler struct { Recorder record.EventRecorder ServiceEndpoint []endpoints.ServiceEndpoint Scheme *runtime.Scheme + CacheStore cache.Store } // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=ibmpowervsmachines,verbs=get;list;watch;create;update;patch;delete @@ -117,6 +120,7 @@ func (r *IBMPowerVSMachineReconciler) Reconcile(ctx context.Context, req ctrl.Re IBMPowerVSMachine: ibmPowerVSMachine, IBMPowerVSImage: ibmPowerVSImage, ServiceEndpoint: r.ServiceEndpoint, + DHCPIPCacheStore: r.CacheStore, }) if err != nil { return ctrl.Result{}, errors.Errorf("failed to create scope: %+v", err) @@ -163,7 +167,11 @@ func (r *IBMPowerVSMachineReconciler) reconcileDelete(scope *scope.PowerVSMachin scope.Info("error deleting IBMPowerVSMachine") return ctrl.Result{}, errors.Wrapf(err, "error deleting IBMPowerVSMachine %s/%s", scope.IBMPowerVSMachine.Namespace, scope.IBMPowerVSMachine.Name) } - + // Remove the cached VM IP + err := scope.DHCPIPCacheStore.Delete(options.VMip{Name: scope.IBMPowerVSMachine.Name}) + if err != nil { + scope.Error(err, "failed to delete the VM entry from DHCP cache store", "VM", scope.IBMPowerVSMachine.Name) + } return ctrl.Result{}, nil } diff --git a/controllers/ibmpowervsmachine_controller_test.go b/controllers/ibmpowervsmachine_controller_test.go index bb020e9a81..5d2ed96a7e 100644 --- a/controllers/ibmpowervsmachine_controller_test.go +++ b/controllers/ibmpowervsmachine_controller_test.go @@ -24,13 +24,15 @@ import ( "github.com/IBM-Cloud/power-go-client/power/models" "github.com/golang/mock/gomock" - . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/klog/v2/klogr" "k8s.io/utils/pointer" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/options" capiv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util" "sigs.k8s.io/cluster-api/util/conditions" @@ -41,6 +43,8 @@ import ( infrav1beta1 "sigs.k8s.io/cluster-api-provider-ibmcloud/api/v1beta1" "sigs.k8s.io/cluster-api-provider-ibmcloud/cloud/scope" "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/powervs/mock" + + . "github.com/onsi/gomega" ) func TestIBMPowerVSMachineReconciler_Reconcile(t *testing.T) { @@ -297,6 +301,7 @@ func TestIBMPowerVSMachineReconciler_Delete(t *testing.T) { InstanceID: "powervs-instance-id", }, }, + DHCPIPCacheStore: cache.NewTTLStore(options.CacheKeyFunc, options.CacheTTL), } mockpowervs.EXPECT().DeleteInstance(machineScope.IBMPowerVSMachine.Status.InstanceID).Return(nil) _, err := reconciler.reconcileDelete(machineScope) @@ -475,6 +480,7 @@ func TestIBMPowerVSMachineReconciler_ReconcileOperations(t *testing.T) { }, }, IBMPowerVSClient: mockpowervs, + DHCPIPCacheStore: cache.NewTTLStore(options.CacheKeyFunc, options.CacheTTL), } instanceReferences := &models.PVMInstances{ diff --git a/main.go b/main.go index 0e36edcb56..9dfbb17011 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + "k8s.io/client-go/tools/cache" cgrecord "k8s.io/client-go/tools/record" "k8s.io/klog/v2" "k8s.io/klog/v2/klogr" @@ -84,6 +85,9 @@ func main() { os.Exit(1) } + // Create the cache store to hold the DHCP IP + dhcpIPCacheStore := cache.NewTTLStore(options.CacheKeyFunc, options.CacheTTL) + if watchNamespace != "" { setupLog.Info("Watching cluster-api objects only in namespace for reconciliation", "namespace", watchNamespace) } @@ -149,6 +153,7 @@ func main() { Recorder: mgr.GetEventRecorderFor("ibmpowervsmachine-controller"), ServiceEndpoint: serviceEndpoint, Scheme: mgr.GetScheme(), + CacheStore: dhcpIPCacheStore, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "IBMPowerVSMachine") os.Exit(1) diff --git a/pkg/cloud/services/powervs/mock/powervs_generated.go b/pkg/cloud/services/powervs/mock/powervs_generated.go index dfcbc3c09d..2690c791c3 100644 --- a/pkg/cloud/services/powervs/mock/powervs_generated.go +++ b/pkg/cloud/services/powervs/mock/powervs_generated.go @@ -181,6 +181,36 @@ func (mr *MockPowerVSMockRecorder) GetCosImages(id interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCosImages", reflect.TypeOf((*MockPowerVS)(nil).GetCosImages), id) } +// GetDHCPServerByID mocks base method. +func (m *MockPowerVS) GetDHCPServerByID(id string) (*models.DHCPServerDetail, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDHCPServerByID", id) + ret0, _ := ret[0].(*models.DHCPServerDetail) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDHCPServerByID indicates an expected call of GetDHCPServerByID. +func (mr *MockPowerVSMockRecorder) GetDHCPServerByID(id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDHCPServerByID", reflect.TypeOf((*MockPowerVS)(nil).GetDHCPServerByID), id) +} + +// GetDHCPServers mocks base method. +func (m *MockPowerVS) GetDHCPServers() (models.DHCPServers, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDHCPServers") + ret0, _ := ret[0].(models.DHCPServers) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDHCPServers indicates an expected call of GetDHCPServers. +func (mr *MockPowerVSMockRecorder) GetDHCPServers() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDHCPServers", reflect.TypeOf((*MockPowerVS)(nil).GetDHCPServers)) +} + // GetImage mocks base method. func (m *MockPowerVS) GetImage(id string) (*models.Image, error) { m.ctrl.T.Helper() diff --git a/pkg/cloud/services/powervs/powervs.go b/pkg/cloud/services/powervs/powervs.go index 04a8004161..f8bf868549 100644 --- a/pkg/cloud/services/powervs/powervs.go +++ b/pkg/cloud/services/powervs/powervs.go @@ -37,4 +37,6 @@ type PowerVS interface { GetCosImages(id string) (*models.Job, error) GetJob(id string) (*models.Job, error) DeleteJob(id string) error + GetDHCPServers() (models.DHCPServers, error) + GetDHCPServerByID(id string) (*models.DHCPServerDetail, error) } diff --git a/pkg/cloud/services/powervs/service.go b/pkg/cloud/services/powervs/service.go index 306b7d3e62..f14c758f58 100644 --- a/pkg/cloud/services/powervs/service.go +++ b/pkg/cloud/services/powervs/service.go @@ -36,6 +36,7 @@ type Service struct { networkClient *instance.IBMPINetworkClient imageClient *instance.IBMPIImageClient jobClient *instance.IBMPIJobClient + dhcpClient *instance.IBMPIDhcpClient } // ServiceOptions holds the PowerVS Service Options specific information. @@ -110,6 +111,14 @@ func (s *Service) GetAllNetwork() (*models.Networks, error) { return s.networkClient.GetAll() } +func (s *Service) GetDHCPServers() (models.DHCPServers, error) { + return s.dhcpClient.GetAll() +} + +func (s *Service) GetDHCPServerByID(id string) (*models.DHCPServerDetail, error) { + return s.dhcpClient.Get(id) +} + // NewService returns a new service for the Power VS api client. func NewService(options ServiceOptions) (PowerVS, error) { auth, err := authenticator.GetAuthenticator() @@ -130,5 +139,6 @@ func NewService(options ServiceOptions) (PowerVS, error) { networkClient: instance.NewIBMPINetworkClient(ctx, session, options.CloudInstanceID), imageClient: instance.NewIBMPIImageClient(ctx, session, options.CloudInstanceID), jobClient: instance.NewIBMPIJobClient(ctx, session, options.CloudInstanceID), + dhcpClient: instance.NewIBMPIDhcpClient(ctx, session, options.CloudInstanceID), }, nil } diff --git a/pkg/options/caching.go b/pkg/options/caching.go new file mode 100644 index 0000000000..8a72bc811e --- /dev/null +++ b/pkg/options/caching.go @@ -0,0 +1,36 @@ +/* +Copyright 2022 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 options + +import "time" + +// CacheTTL is duration of time to store the vm ip in cache +// Currently the default sync period is 10 minutes that means every 10 minutes +// there will be a reconcilation, So setting cache timeout to 20 minutes so the cache updates will happen +// once in 2 reconcilations. +const CacheTTL = time.Duration(20) * time.Minute + +// VMip holds the vm name and corresponding dhcp ip used to cache the dhcp ip +type VMip struct { + Name string + IP string +} + +// CacheKeyFunc defines the key function required in TTLStore. +func CacheKeyFunc(obj interface{}) (string, error) { + return obj.(VMip).Name, nil +}