Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vm): Implement publicIP generation with VM's #285

Merged
1 commit merged into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion api/v1beta1/oscmachinetemplate_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ func TestOscMachineTemplate_ValidateUpdate(t *testing.T) {
ImageId: "ami-00000000",
DeviceName: "/dev/xvda",
VmType: "tinav3.c2r4p2",
PublicIp: false,
},
Volumes: []*OscVolume{
{
Expand Down Expand Up @@ -320,7 +321,7 @@ func TestOscMachineTemplate_ValidateUpdate(t *testing.T) {
},
},
},
expValidateUpdateErr: fmt.Errorf("OscMachineTemplate.infrastructure.cluster.x-k8s.io \"webhook-test\" is invalid: OscMachineTemplate.spec.template.spec: Invalid value: v1beta1.OscMachineTemplate{TypeMeta:v1.TypeMeta{Kind:\"\", APIVersion:\"\"}, ObjectMeta:v1.ObjectMeta{Name:\"webhook-test\", GenerateName:\"\", Namespace:\"default\", SelfLink:\"\", UID:\"\", ResourceVersion:\"\", Generation:0, CreationTimestamp:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), DeletionTimestamp:<nil>, DeletionGracePeriodSeconds:(*int64)(nil), Labels:map[string]string(nil), Annotations:map[string]string(nil), OwnerReferences:[]v1.OwnerReference(nil), Finalizers:[]string(nil), ManagedFields:[]v1.ManagedFieldsEntry(nil)}, Spec:v1beta1.OscMachineTemplateSpec{Template:v1beta1.OscMachineTemplateResource{ObjectMeta:v1beta1.ObjectMeta{Labels:map[string]string(nil), Annotations:map[string]string(nil)}, Spec:v1beta1.OscMachineSpec{ProviderID:(*string)(nil), Node:v1beta1.OscNode{Vm:v1beta1.OscVm{Name:\"\", ImageId:\"ami-00000000\", KeypairName:\"test-webhook-2\", VmType:\"tinav3.c2r4p2\", VolumeName:\"\", VolumeDeviceName:\"\", DeviceName:\"/dev/xvda\", SubnetName:\"\", RootDisk:v1beta1.OscRootDisk{RootDiskIops:0, RootDiskSize:0, RootDiskType:\"\"}, LoadBalancerName:\"\", PublicIpName:\"\", SubregionName:\"\", PrivateIps:[]v1beta1.OscPrivateIpElement(nil), SecurityGroupNames:[]v1beta1.OscSecurityGroupElement(nil), ResourceId:\"\", Role:\"\", ClusterName:\"\", Replica:0}, Image:v1beta1.OscImage{Name:\"\", ResourceId:\"\"}, Volumes:[]*v1beta1.OscVolume(nil), KeyPair:v1beta1.OscKeypair{Name:\"\", PublicKey:\"\", ResourceId:\"\", ClusterName:\"\", DeleteKeypair:false}, ClusterName:\"\"}}}}, Status:v1beta1.OscMachineTemplateStatus{Capacity:v1.ResourceList(nil), Conditions:v1beta1.Conditions(nil)}}: OscMachineTemplate spec.template.spec field is immutable."),
expValidateUpdateErr: fmt.Errorf("OscMachineTemplate.infrastructure.cluster.x-k8s.io \"webhook-test\" is invalid: OscMachineTemplate.spec.template.spec: Invalid value: v1beta1.OscMachineTemplate{TypeMeta:v1.TypeMeta{Kind:\"\", APIVersion:\"\"}, ObjectMeta:v1.ObjectMeta{Name:\"webhook-test\", GenerateName:\"\", Namespace:\"default\", SelfLink:\"\", UID:\"\", ResourceVersion:\"\", Generation:0, CreationTimestamp:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), DeletionTimestamp:<nil>, DeletionGracePeriodSeconds:(*int64)(nil), Labels:map[string]string(nil), Annotations:map[string]string(nil), OwnerReferences:[]v1.OwnerReference(nil), Finalizers:[]string(nil), ManagedFields:[]v1.ManagedFieldsEntry(nil)}, Spec:v1beta1.OscMachineTemplateSpec{Template:v1beta1.OscMachineTemplateResource{ObjectMeta:v1beta1.ObjectMeta{Labels:map[string]string(nil), Annotations:map[string]string(nil)}, Spec:v1beta1.OscMachineSpec{ProviderID:(*string)(nil), Node:v1beta1.OscNode{Vm:v1beta1.OscVm{Name:\"\", ImageId:\"ami-00000000\", KeypairName:\"test-webhook-2\", VmType:\"tinav3.c2r4p2\", VolumeName:\"\", VolumeDeviceName:\"\", DeviceName:\"/dev/xvda\", SubnetName:\"\", RootDisk:v1beta1.OscRootDisk{RootDiskIops:0, RootDiskSize:0, RootDiskType:\"\"}, LoadBalancerName:\"\", PublicIpName:\"\", PublicIp:false, SubregionName:\"\", PrivateIps:[]v1beta1.OscPrivateIpElement(nil), SecurityGroupNames:[]v1beta1.OscSecurityGroupElement(nil), ResourceId:\"\", Role:\"\", ClusterName:\"\", Replica:0}, Image:v1beta1.OscImage{Name:\"\", ResourceId:\"\"}, Volumes:[]*v1beta1.OscVolume(nil), KeyPair:v1beta1.OscKeypair{Name:\"\", PublicKey:\"\", ResourceId:\"\", ClusterName:\"\", DeleteKeypair:false}, ClusterName:\"\"}}}}, Status:v1beta1.OscMachineTemplateStatus{Capacity:v1.ResourceList(nil), Conditions:v1beta1.Conditions(nil)}}: OscMachineTemplate spec.template.spec field is immutable."),
},
}
for _, mtc := range machineTestCases {
Expand Down
2 changes: 2 additions & 0 deletions api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ type OscNodeResource struct {
KeypairRef OscResourceReference `json:"keypairRef,omitempty"`
VmRef OscResourceReference `json:"vmRef,omitempty"`
LinkPublicIpRef OscResourceReference `json:"linkPublicIpRef,omitempty"`
PublicIpIdRef OscResourceReference `json:"publicIpIdRef,omitempty"`
}

type OscImage struct {
Expand Down Expand Up @@ -362,6 +363,7 @@ type OscVm struct {
RootDisk OscRootDisk `json:"rootDisk,omitempty"`
LoadBalancerName string `json:"loadBalancerName,omitempty"`
PublicIpName string `json:"publicIpName,omitempty"`
PublicIp bool `json:"publicIp,omitempty"`
SubregionName string `json:"subregionName,omitempty"`
PrivateIps []OscPrivateIpElement `json:"privateIps,omitempty"`
SecurityGroupNames []OscSecurityGroupElement `json:"securityGroupNames,omitempty"`
Expand Down
6 changes: 6 additions & 0 deletions cloud/scope/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"errors"
"fmt"

"github.com/go-logr/logr"
infrastructurev1beta1 "github.com/outscale-dev/cluster-api-provider-outscale.git/api/v1beta1"
osc "github.com/outscale/osc-sdk-go/v2"
Expand Down Expand Up @@ -192,6 +193,11 @@ func (m *MachineScope) GetLinkPublicIpRef() *infrastructurev1beta1.OscResourceRe
return &m.OscMachine.Status.Node.LinkPublicIpRef
}

// GetLinkPublicIpRef get the status of linkPublicIpRef (a Map with tag name with machine uid associate with resource response id)
func (m *MachineScope) GetPublicIpIdRef() *infrastructurev1beta1.OscResourceReference {
return &m.OscMachine.Status.Node.PublicIpIdRef
}

// GetKeyPair return the keypair of the cluster
func (m *MachineScope) GetKeypair() *infrastructurev1beta1.OscKeypair {
return &m.OscMachine.Spec.Node.KeyPair
Expand Down
11 changes: 11 additions & 0 deletions config/crd/bases/infrastructure.cluster.x-k8s.io_oscmachines.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ spec:
- privateIp
type: object
type: array
publicIp:
type: boolean
publicIpName:
type: string
replica:
Expand Down Expand Up @@ -249,6 +251,15 @@ spec:
type: string
type: object
type: object
publicIpIdRef:
description: Map between resourceId and resourceName (tag Name
with cluster UID)
properties:
resourceMap:
additionalProperties:
type: string
type: object
type: object
vmRef:
description: Map between resourceId and resourceName (tag Name
with cluster UID)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ spec:
- privateIp
type: object
type: array
publicIp:
type: boolean
publicIpName:
type: string
replica:
Expand Down
45 changes: 39 additions & 6 deletions controllers/oscmachine_vm_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,18 +149,21 @@ func checkVmSubnetOscAssociateResourceName(machineScope *scope.MachineScope, clu
}
}

// checkVmPublicIpOscAssociateResourceName check that PublicIp dependancies tag name in both resource configuration are the same.
// checkVmPublicIpOscAssociateResourceName check that PublicIp dependencies tag name in both resource configuration are the same.
func checkVmPublicIpOscAssociateResourceName(machineScope *scope.MachineScope, clusterScope *scope.ClusterScope) error {
var resourceNameList []string
vmSpec := machineScope.GetVm()
vmSpec.SetDefaultValue()
if vmSpec.PublicIp {
return nil
}
vmPublicIpName := vmSpec.PublicIpName + "-" + clusterScope.GetUID()
publicIpsSpec := clusterScope.GetPublicIp()
for _, publicIpSpec := range publicIpsSpec {
publicIpName := publicIpSpec.Name + "-" + clusterScope.GetUID()
resourceNameList = append(resourceNameList, publicIpName)
}
machineScope.V(2).Info("Check match publicip with vm")
machineScope.V(2).Info("Check match publicip with vm on cluster")
checkOscAssociate := Contains(resourceNameList, vmPublicIpName)
if checkOscAssociate {
return nil
Expand Down Expand Up @@ -337,11 +340,33 @@ func reconcileVm(ctx context.Context, clusterScope *scope.ClusterScope, machineS
var publicIpId string
var vmPublicIpName string
var linkPublicIpRef *infrastructurev1beta1.OscResourceReference
if vmSpec.PublicIp {
vmSpec.PublicIpName = vmSpec.Name + "-publicIp"
vmPublicIpName = vmSpec.PublicIpName + "-" + clusterScope.GetUID()
var ipFound bool
publicIpIdRef := machineScope.GetPublicIpIdRef()
publicIpId, ipFound = publicIpIdRef.ResourceMap[vmPublicIpName]
if !ipFound {
publicIp, err := publicIpSvc.CreatePublicIp(vmPublicIpName)
if err != nil {
return reconcile.Result{}, fmt.Errorf("%w Can not create publicIp for Vm %s/%s", err, clusterScope.GetNamespace(), clusterScope.GetName())
}
clusterScope.V(4).Info("Get publicIp for Vm", "publicip", publicIp)
publicIpId = publicIp.GetPublicIpId()

if len(publicIpIdRef.ResourceMap) == 0 {
publicIpIdRef.ResourceMap = make(map[string]string)
}
publicIpIdRef.ResourceMap[vmPublicIpName] = publicIpId
}
}
if vmSpec.PublicIpName != "" {
vmPublicIpName = vmSpec.PublicIpName + "-" + clusterScope.GetUID()
publicIpId, err = getPublicIpResourceId(vmPublicIpName, clusterScope)
if err != nil {
return reconcile.Result{}, err
if publicIpId == "" {
publicIpId, err = getPublicIpResourceId(vmPublicIpName, clusterScope)
if err != nil {
return reconcile.Result{}, err
}
}
linkPublicIpRef = machineScope.GetLinkPublicIpRef()
if len(linkPublicIpRef.ResourceMap) == 0 {
Expand Down Expand Up @@ -573,7 +598,15 @@ func reconcileDeleteVm(ctx context.Context, clusterScope *scope.ClusterScope, ma
if err != nil {
return reconcile.Result{}, fmt.Errorf("%w Can not unlink publicIp for OscCluster %s/%s", err, machineScope.GetNamespace(), machineScope.GetName())
}

}
if vmSpec.PublicIp {
publicIpIdRef := machineScope.GetPublicIpIdRef()
publicIpName := vmSpec.PublicIpName + "-" + clusterScope.GetUID()
clusterScope.V(2).Info("Delete the desired Vm publicip", "publicIpName", publicIpName)
err = publicIpSvc.DeletePublicIp(publicIpIdRef.ResourceMap[publicIpName])
if err != nil {
return reconcile.Result{}, fmt.Errorf("%w Can not delete Vm publicIp for Osccluster %s/%s", err, clusterScope.GetNamespace(), clusterScope.GetName())
}
}
if vmSpec.LoadBalancerName != "" {
vmIds := []string{vmId}
Expand Down
132 changes: 131 additions & 1 deletion controllers/oscmachine_vm_controller_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,50 @@ var (
},
}

defaultVmInitializeWithPublicIp = infrastructurev1beta1.OscMachineSpec{
Node: infrastructurev1beta1.OscNode{
Volumes: []*infrastructurev1beta1.OscVolume{
{
Name: "test-volume",
Iops: 1000,
Size: 50,
VolumeType: "io1",
SubregionName: "eu-west-2a",
},
},
Vm: infrastructurev1beta1.OscVm{
ClusterName: "test-cluster",
Name: "test-vm",
ImageId: "ami-00000000",
Role: "controlplane",
DeviceName: "/dev/sda1",
RootDisk: infrastructurev1beta1.OscRootDisk{
RootDiskSize: 30,
RootDiskIops: 1500,
RootDiskType: "gp2",
},
KeypairName: "rke",
SubregionName: "eu-west-2a",
SubnetName: "test-subnet",
LoadBalancerName: "test-loadbalancer",
PublicIpName: "test-publicip",
VmType: "tinav3.c2r4p2",
Replica: 1,
SecurityGroupNames: []infrastructurev1beta1.OscSecurityGroupElement{
{
Name: "test-securitygroup",
},
},
PrivateIps: []infrastructurev1beta1.OscPrivateIpElement{
{
Name: "test-privateip",
PrivateIp: "10.0.0.17",
},
},
},
},
}

defaultMultiVmInitialize = infrastructurev1beta1.OscMachineSpec{
Node: infrastructurev1beta1.OscNode{
Volumes: []*infrastructurev1beta1.OscVolume{
Expand Down Expand Up @@ -408,6 +452,52 @@ var (
},
},
}
defaultVmReconcileWithDedicatedIp = infrastructurev1beta1.OscMachineSpec{
Node: infrastructurev1beta1.OscNode{
Volumes: []*infrastructurev1beta1.OscVolume{
{
Name: "test-volume",
Iops: 1000,
Size: 50,
VolumeType: "io1",
SubregionName: "eu-west-2a",
ResourceId: "volume-test-volume-uid",
},
},
Vm: infrastructurev1beta1.OscVm{
ClusterName: "test-cluster",
Name: "test-vm",
ImageId: "ami-00000000",
Role: "controlplane",
DeviceName: "/dev/xvdb",
KeypairName: "rke",
RootDisk: infrastructurev1beta1.OscRootDisk{

RootDiskSize: 30,
RootDiskIops: 1500,
RootDiskType: "io1",
},
SubregionName: "eu-west-2a",
SubnetName: "test-subnet",
LoadBalancerName: "test-loadbalancer",
VmType: "tinav3.c2r4p2",
ResourceId: "i-test-vm-uid",
PublicIp: true,
Replica: 1,
SecurityGroupNames: []infrastructurev1beta1.OscSecurityGroupElement{
{
Name: "test-securitygroup",
},
},
PrivateIps: []infrastructurev1beta1.OscPrivateIpElement{
{
Name: "test-privateip",
PrivateIp: "10.0.0.17",
},
},
},
},
}
)

// SetupWithVmMock set vmMock with clusterScope, machineScope and oscmachine
Expand Down Expand Up @@ -3443,6 +3533,7 @@ func TestReconcileVmResourceId(t *testing.T) {
expLinkPublicIpFound bool
expSecurityGroupFound bool
expLoadBalancerSecurityGroupFound bool
expCreatePublicIpFound bool
expReadTagErr error
expReconcileVmErr error
}{
Expand All @@ -3457,6 +3548,7 @@ func TestReconcileVmResourceId(t *testing.T) {
expSecurityGroupFound: true,
expLoadBalancerSecurityGroupFound: true,
expTagFound: false,
expCreatePublicIpFound: false,
expReadTagErr: nil,
expReconcileVmErr: fmt.Errorf("test-volume-uid does not exist"),
},
Expand All @@ -3471,11 +3563,12 @@ func TestReconcileVmResourceId(t *testing.T) {
expSecurityGroupFound: true,
expLoadBalancerSecurityGroupFound: true,
expTagFound: false,
expCreatePublicIpFound: false,
expReadTagErr: nil,
expReconcileVmErr: fmt.Errorf("test-subnet-uid does not exist"),
},
{
name: "PublicIp does not exist ",
name: "PublicIp does not exist on clusterScope",
clusterSpec: defaultVmClusterInitialize,
machineSpec: defaultVmInitialize,
expVolumeFound: true,
Expand All @@ -3485,6 +3578,7 @@ func TestReconcileVmResourceId(t *testing.T) {
expSecurityGroupFound: true,
expLoadBalancerSecurityGroupFound: true,
expTagFound: false,
expCreatePublicIpFound: false,
expReadTagErr: nil,
expReconcileVmErr: fmt.Errorf("test-publicip-uid does not exist"),
},
Expand All @@ -3499,6 +3593,7 @@ func TestReconcileVmResourceId(t *testing.T) {
expSecurityGroupFound: false,
expLoadBalancerSecurityGroupFound: false,
expTagFound: false,
expCreatePublicIpFound: false,
expReadTagErr: nil,
expReconcileVmErr: fmt.Errorf("test-securitygroup-uid does not exist"),
},
Expand All @@ -3513,6 +3608,7 @@ func TestReconcileVmResourceId(t *testing.T) {
expSecurityGroupFound: true,
expLoadBalancerSecurityGroupFound: true,
expTagFound: true,
expCreatePublicIpFound: false,
expReadTagErr: fmt.Errorf("ReadTag generic error"),
expReconcileVmErr: fmt.Errorf("ReadTag generic error Can not get tag for OscMachine test-system/test-osc"),
},
Expand Down Expand Up @@ -3621,6 +3717,7 @@ func TestReconcileDeleteVm(t *testing.T) {
expListMachine bool
expDeleteInboundSecurityGroupRuleFound bool
expDeleteOutboundSecurityGroupRuleFound bool
expDeleteDedicatedPublicIpFound bool
expUnlinkLoadBalancerBackendMachineErr error
expDeleteInboundSecurityGroupRuleErr error
expDeleteOutboundSecurityGroupRuleErr error
Expand All @@ -3638,6 +3735,7 @@ func TestReconcileDeleteVm(t *testing.T) {
expListMachine: false,
expDeleteInboundSecurityGroupRuleFound: true,
expDeleteOutboundSecurityGroupRuleFound: true,
expDeleteDedicatedPublicIpFound: false,
expUnlinkLoadBalancerBackendMachineErr: nil,
expDeleteInboundSecurityGroupRuleErr: nil,
expDeleteOutboundSecurityGroupRuleErr: nil,
Expand Down Expand Up @@ -3665,6 +3763,23 @@ func TestReconcileDeleteVm(t *testing.T) {
expCheckUnlinkPublicIpErr: nil,
expReconcileDeleteVmErr: nil,
},
{
name: "delete vm with publicIp",
clusterSpec: defaultVmClusterReconcile,
machineSpec: defaultVmReconcileWithDedicatedIp,
expDeleteInboundSecurityGroupRuleFound: true,
expDeleteOutboundSecurityGroupRuleFound: true,
expDeleteDedicatedPublicIpFound: true,
expUnlinkLoadBalancerBackendMachineErr: nil,
expDeleteInboundSecurityGroupRuleErr: nil,
expDeleteOutboundSecurityGroupRuleErr: nil,
expSecurityGroupRuleFound: true,
expDeleteVmErr: nil,
expGetVmFound: true,
expGetVmErr: nil,
expCheckUnlinkPublicIpErr: nil,
expReconcileDeleteVmErr: nil,
},
{
name: "delete first vm in group",
clusterSpec: defaultVmClusterReconcile,
Expand All @@ -3689,6 +3804,7 @@ func TestReconcileDeleteVm(t *testing.T) {
expListMachine: false,
expDeleteInboundSecurityGroupRuleFound: true,
expDeleteOutboundSecurityGroupRuleFound: true,
expDeleteDedicatedPublicIpFound: false,
expUnlinkLoadBalancerBackendMachineErr: nil,
expSecurityGroupRuleFound: true,
expDeleteVmErr: fmt.Errorf("DeleteVm generic error"),
Expand Down Expand Up @@ -3721,6 +3837,14 @@ func TestReconcileDeleteVm(t *testing.T) {
securityGroupsRef.ResourceMap[securityGroupName] = securityGroupId
securityGroupIds = append(securityGroupIds, securityGroupId)
}

if vtc.expDeleteDedicatedPublicIpFound {
publicIpName := machineScope.GetName() + "-publicIp"
machineScope.OscMachine.Spec.Node.Vm.PublicIpName = publicIpName
vtc.machineSpec.Node.Vm.PublicIpName = publicIpName
machineScope.GetPublicIpIdRef().ResourceMap = map[string]string{publicIpName + "-uid": "eipassoc-" + publicIpName + "-uid"}
}

publicIpName := vtc.machineSpec.Node.Vm.PublicIpName + "-uid"
linkPublicIpId := "eipassoc-" + publicIpName
linkPublicIpRef := machineScope.GetLinkPublicIpRef()
Expand Down Expand Up @@ -3801,6 +3925,12 @@ func TestReconcileDeleteVm(t *testing.T) {
DeleteSecurityGroupRule(gomock.Eq(associateSecurityGroupId), gomock.Eq("Outbound"), gomock.Eq(ipProtocol), "", gomock.Eq(securityGroupIds[0]), gomock.Eq(fromPortRange), gomock.Eq(toPortRange)).
Return(vtc.expDeleteOutboundSecurityGroupRuleErr)
}
if vtc.expDeleteDedicatedPublicIpFound {
mockOscPublicIpInterface.
EXPECT().
DeletePublicIp(gomock.Eq("eipassoc-test-osc-publicIp-uid")).
Return(nil)
}

if vtc.expDeleteInboundSecurityGroupRuleFound {
mockOscSecurityGroupInterface.
Expand Down