Skip to content

Commit fc9f2e9

Browse files
committed
Reconcile SecurityGroups <-> Instances separately
Originally, we were adding the SecurityGroups to the instanceSpec. Now, the openstackmachine controller will first create the instances without security groups. Then we have a loop that will reconcile the desired security groups that we want to apply in the cluster[1] with the ones that are added to the instance's ports. [1] for now, we have 2 sets of rules: the one that can be added to `OpenStackMachine.Spec.SecurityGroups` and the one managed by `OpenStackCluster.Spec.ManagedSecurityGroups`.
1 parent 85aed76 commit fc9f2e9

12 files changed

+263
-96
lines changed

api/v1alpha5/conversion.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,3 +456,14 @@ func Convert_v1alpha7_Bastion_To_v1alpha5_Bastion(in *infrav1.Bastion, out *Bast
456456
in.FloatingIP = out.Instance.FloatingIP
457457
return nil
458458
}
459+
460+
func Convert_v1alpha7_OpenStackMachineStatus_To_v1alpha5_OpenStackMachineStatus(in *infrav1.OpenStackMachineStatus, out *OpenStackMachineStatus, s conversion.Scope) error {
461+
err := autoConvert_v1alpha7_OpenStackMachineStatus_To_v1alpha5_OpenStackMachineStatus(in, out, s)
462+
if err != nil {
463+
return err
464+
}
465+
466+
in.SecurityGroups = nil
467+
468+
return nil
469+
}

api/v1alpha5/zz_generated.conversion.go

Lines changed: 1 addition & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1alpha6/conversion.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,3 +682,14 @@ func Convert_v1alpha7_Bastion_To_v1alpha6_Bastion(in *infrav1.Bastion, out *Bast
682682
in.FloatingIP = out.Instance.FloatingIP
683683
return nil
684684
}
685+
686+
func Convert_v1alpha7_OpenStackMachineStatus_To_v1alpha6_OpenStackMachineStatus(in *infrav1.OpenStackMachineStatus, out *OpenStackMachineStatus, s apiconversion.Scope) error {
687+
err := autoConvert_v1alpha7_OpenStackMachineStatus_To_v1alpha6_OpenStackMachineStatus(in, out, s)
688+
if err != nil {
689+
return err
690+
}
691+
692+
in.SecurityGroups = nil
693+
694+
return nil
695+
}

api/v1alpha6/zz_generated.conversion.go

Lines changed: 1 addition & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1alpha7/conditions_consts.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ const (
4242
InstanceDeleteFailedReason = "InstanceDeleteFailed"
4343
// OpenstackErrorReason used when there is an error communicating with OpenStack.
4444
OpenStackErrorReason = "OpenStackError"
45+
// SecurityGroupAppliedFailedCondition used when the security group could not be applied.
46+
SecurityGroupApplyFailedReason = "SecurityGroupAppliedFailed"
4547
)
4648

4749
const (

api/v1alpha7/openstackmachine_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ type OpenStackMachineStatus struct {
107107
// +optional
108108
InstanceState *InstanceState `json:"instanceState,omitempty"`
109109

110+
// SecurityGroups is the list of security groups that were applied to the ports
111+
// attached to the instance.
112+
// +optional
113+
SecurityGroups []SecurityGroup `json:"securityGroups,omitempty"`
114+
110115
FailureReason *errors.MachineStatusError `json:"failureReason,omitempty"`
111116

112117
// FailureMessage will be set in the event that there is a terminal problem

api/v1alpha7/zz_generated.deepcopy.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/infrastructure.cluster.x-k8s.io_openstackmachines.yaml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,61 @@ spec:
16011601
ready:
16021602
description: Ready is true when the provider resource is ready.
16031603
type: boolean
1604+
securityGroups:
1605+
description: SecurityGroups is the list of security groups that were
1606+
applied to the ports attached to the instance.
1607+
items:
1608+
description: SecurityGroup represents the basic information of the
1609+
associated OpenStack Neutron Security Group.
1610+
properties:
1611+
id:
1612+
type: string
1613+
name:
1614+
type: string
1615+
rules:
1616+
items:
1617+
description: SecurityGroupRule represent the basic information
1618+
of the associated OpenStack Security Group Role.
1619+
properties:
1620+
description:
1621+
type: string
1622+
direction:
1623+
type: string
1624+
etherType:
1625+
type: string
1626+
name:
1627+
type: string
1628+
portRangeMax:
1629+
type: integer
1630+
portRangeMin:
1631+
type: integer
1632+
protocol:
1633+
type: string
1634+
remoteGroupID:
1635+
type: string
1636+
remoteIPPrefix:
1637+
type: string
1638+
securityGroupID:
1639+
type: string
1640+
required:
1641+
- description
1642+
- direction
1643+
- etherType
1644+
- name
1645+
- portRangeMax
1646+
- portRangeMin
1647+
- protocol
1648+
- remoteGroupID
1649+
- remoteIPPrefix
1650+
- securityGroupID
1651+
type: object
1652+
type: array
1653+
required:
1654+
- id
1655+
- name
1656+
- rules
1657+
type: object
1658+
type: array
16041659
type: object
16051660
type: object
16061661
served: true

controllers/openstackmachine_controller.go

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,12 @@ func (r *OpenStackMachineReconciler) reconcileNormal(ctx context.Context, scope
334334
return ctrl.Result{}, err
335335
}
336336

337+
err = r.reconcileSecurityGroupsToInstance(openStackCluster, machine, openStackMachine, instanceStatus, computeService, networkingService)
338+
if err != nil {
339+
conditions.MarkFalse(openStackMachine, infrav1.InstanceReadyCondition, infrav1.SecurityGroupApplyFailedReason, clusterv1.ConditionSeverityError, "Applying security groups failed: %v", err)
340+
return ctrl.Result{}, fmt.Errorf("add security groups to instance: %w", err)
341+
}
342+
337343
// TODO(sbueringer) From CAPA: TODO(ncdc): move this validation logic into a validating webhook (for us: create validation logic in webhook)
338344

339345
openStackMachine.Spec.ProviderID = pointer.String(fmt.Sprintf("openstack:///%s", instanceStatus.ID()))
@@ -430,6 +436,53 @@ func (r *OpenStackMachineReconciler) reconcileNormal(ctx context.Context, scope
430436
return ctrl.Result{}, nil
431437
}
432438

439+
func (r *OpenStackMachineReconciler) reconcileSecurityGroupsToInstance(openStackCluster *infrav1.OpenStackCluster, machine *clusterv1.Machine, openStackMachine *infrav1.OpenStackMachine, instanceStatus *compute.InstanceStatus, computeService *compute.Service, networkingService *networking.Service) error {
440+
desiredSecurityGroups := []*infrav1.SecurityGroup{}
441+
442+
// These security groups are currently not managed by CAPO and provided by the user
443+
// Later we'll have an API to manage them.
444+
for _, sg := range openStackMachine.Spec.SecurityGroups {
445+
if sg.ID == "" {
446+
// lookup security group by name
447+
securityGroup, err := networkingService.GetSecurityGroupByName(sg.Name)
448+
if err != nil {
449+
return fmt.Errorf("get security group %q: %w", sg.Name, err)
450+
}
451+
sg.ID = securityGroup.ID
452+
}
453+
desiredSecurityGroups = append(desiredSecurityGroups, &infrav1.SecurityGroup{
454+
ID: sg.ID,
455+
})
456+
}
457+
458+
if openStackCluster.Spec.ManagedSecurityGroups {
459+
if util.IsControlPlaneMachine(machine) && openStackCluster.Status.ControlPlaneSecurityGroup != nil {
460+
desiredSecurityGroups = append(desiredSecurityGroups, openStackCluster.Status.ControlPlaneSecurityGroup)
461+
} else if openStackCluster.Status.WorkerSecurityGroup != nil {
462+
desiredSecurityGroups = append(desiredSecurityGroups, openStackCluster.Status.WorkerSecurityGroup)
463+
}
464+
}
465+
466+
if len(desiredSecurityGroups) > 0 {
467+
instanceID := instanceStatus.ID()
468+
desiredSecurityGroupsIDs := make([]string, len(desiredSecurityGroups))
469+
for i, sg := range desiredSecurityGroups {
470+
desiredSecurityGroupsIDs[i] = sg.ID
471+
}
472+
// Security Groups are attached to ports, not instances so we need to get the ports first
473+
attachedInterfaces, err := computeService.GetAttachedInterfaces(instanceID)
474+
if err != nil {
475+
return fmt.Errorf("get attached interfaces for instance %q: %w", instanceStatus.Name(), err)
476+
}
477+
err = networkingService.ReconcileSecurityGroupsToInstanceAttachedInterfaces(openStackMachine, attachedInterfaces, desiredSecurityGroupsIDs)
478+
if err != nil {
479+
return fmt.Errorf("reconcile security groups %q to instance %q: %w", desiredSecurityGroupsIDs, instanceStatus.Name(), err)
480+
}
481+
}
482+
483+
return nil
484+
}
485+
433486
func (r *OpenStackMachineReconciler) getOrCreate(logger logr.Logger, cluster *clusterv1.Cluster, openStackCluster *infrav1.OpenStackCluster, machine *clusterv1.Machine, openStackMachine *infrav1.OpenStackMachine, computeService *compute.Service, userData string) (*compute.InstanceStatus, error) {
434487
instanceStatus, err := computeService.GetInstanceStatusByName(openStackMachine, openStackMachine.Name)
435488
if err != nil {
@@ -496,20 +549,6 @@ func machineToInstanceSpec(openStackCluster *infrav1.OpenStackCluster, machine *
496549

497550
instanceSpec.Tags = machineTags
498551

499-
instanceSpec.SecurityGroups = openStackMachine.Spec.SecurityGroups
500-
if openStackCluster.Spec.ManagedSecurityGroups {
501-
var managedSecurityGroup string
502-
if util.IsControlPlaneMachine(machine) && openStackCluster.Status.ControlPlaneSecurityGroup != nil {
503-
managedSecurityGroup = openStackCluster.Status.ControlPlaneSecurityGroup.ID
504-
} else if openStackCluster.Status.WorkerSecurityGroup != nil {
505-
managedSecurityGroup = openStackCluster.Status.WorkerSecurityGroup.ID
506-
}
507-
508-
instanceSpec.SecurityGroups = append(instanceSpec.SecurityGroups, infrav1.SecurityGroupFilter{
509-
ID: managedSecurityGroup,
510-
})
511-
}
512-
513552
instanceSpec.Ports = openStackMachine.Spec.Ports
514553

515554
return &instanceSpec

controllers/openstackmachine_controller_test.go

Lines changed: 45 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,14 @@ import (
2626

2727
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha7"
2828
"sigs.k8s.io/cluster-api-provider-openstack/pkg/cloud/services/compute"
29+
"sigs.k8s.io/cluster-api-provider-openstack/pkg/cloud/services/networking"
2930
)
3031

3132
const (
32-
networkUUID = "d412171b-9fd7-41c1-95a6-c24e5953974d"
33-
subnetUUID = "d2d8d98d-b234-477e-a547-868b7cb5d6a5"
34-
extraSecurityGroupUUID = "514bb2d8-3390-4a3b-86a7-7864ba57b329"
35-
controlPlaneSecurityGroupUUID = "c9817a91-4821-42db-8367-2301002ab659"
36-
workerSecurityGroupUUID = "9c6c0d28-03c9-436c-815d-58440ac2c1c8"
37-
serverGroupUUID = "7b940d62-68ef-4e42-a76a-1a62e290509c"
33+
networkUUID = "d412171b-9fd7-41c1-95a6-c24e5953974d"
34+
subnetUUID = "d2d8d98d-b234-477e-a547-868b7cb5d6a5"
35+
extraSecurityGroupUUID = "514bb2d8-3390-4a3b-86a7-7864ba57b329"
36+
serverGroupUUID = "7b940d62-68ef-4e42-a76a-1a62e290509c"
3837

3938
openStackMachineName = "test-openstack-machine"
4039
namespace = "test-namespace"
@@ -56,8 +55,6 @@ func getDefaultOpenStackCluster() *infrav1.OpenStackCluster {
5655
{ID: subnetUUID},
5756
},
5857
},
59-
ControlPlaneSecurityGroup: &infrav1.SecurityGroup{ID: controlPlaneSecurityGroupUUID},
60-
WorkerSecurityGroup: &infrav1.SecurityGroup{ID: workerSecurityGroupUUID},
6158
},
6259
}
6360
}
@@ -129,64 +126,6 @@ func Test_machineToInstanceSpec(t *testing.T) {
129126
openStackMachine: getDefaultOpenStackMachine,
130127
wantInstanceSpec: getDefaultInstanceSpec,
131128
},
132-
{
133-
name: "Control plane security group",
134-
openStackCluster: func() *infrav1.OpenStackCluster {
135-
c := getDefaultOpenStackCluster()
136-
c.Spec.ManagedSecurityGroups = true
137-
return c
138-
},
139-
machine: func() *clusterv1.Machine {
140-
m := getDefaultMachine()
141-
m.Labels = map[string]string{
142-
clusterv1.MachineControlPlaneLabel: "true",
143-
}
144-
return m
145-
},
146-
openStackMachine: getDefaultOpenStackMachine,
147-
wantInstanceSpec: func() *compute.InstanceSpec {
148-
i := getDefaultInstanceSpec()
149-
i.SecurityGroups = []infrav1.SecurityGroupFilter{{ID: controlPlaneSecurityGroupUUID}}
150-
return i
151-
},
152-
},
153-
{
154-
name: "Worker security group",
155-
openStackCluster: func() *infrav1.OpenStackCluster {
156-
c := getDefaultOpenStackCluster()
157-
c.Spec.ManagedSecurityGroups = true
158-
return c
159-
},
160-
machine: getDefaultMachine,
161-
openStackMachine: getDefaultOpenStackMachine,
162-
wantInstanceSpec: func() *compute.InstanceSpec {
163-
i := getDefaultInstanceSpec()
164-
i.SecurityGroups = []infrav1.SecurityGroupFilter{{ID: workerSecurityGroupUUID}}
165-
return i
166-
},
167-
},
168-
{
169-
name: "Extra security group",
170-
openStackCluster: func() *infrav1.OpenStackCluster {
171-
c := getDefaultOpenStackCluster()
172-
c.Spec.ManagedSecurityGroups = true
173-
return c
174-
},
175-
machine: getDefaultMachine,
176-
openStackMachine: func() *infrav1.OpenStackMachine {
177-
m := getDefaultOpenStackMachine()
178-
m.Spec.SecurityGroups = []infrav1.SecurityGroupFilter{{ID: extraSecurityGroupUUID}}
179-
return m
180-
},
181-
wantInstanceSpec: func() *compute.InstanceSpec {
182-
i := getDefaultInstanceSpec()
183-
i.SecurityGroups = []infrav1.SecurityGroupFilter{
184-
{ID: extraSecurityGroupUUID},
185-
{ID: workerSecurityGroupUUID},
186-
}
187-
return i
188-
},
189-
},
190129
{
191130
name: "Tags",
192131
openStackCluster: func() *infrav1.OpenStackCluster {
@@ -214,3 +153,43 @@ func Test_machineToInstanceSpec(t *testing.T) {
214153
})
215154
}
216155
}
156+
157+
func Test_reconcileSecurityGroupsToInstance(t *testing.T) {
158+
RegisterTestingT(t)
159+
160+
openStackCluster := getDefaultOpenStackCluster()
161+
machine := getDefaultMachine()
162+
openStackMachine := getDefaultOpenStackMachine()
163+
instanceStatus := &compute.InstanceStatus{}
164+
165+
computeService := &compute.Service{}
166+
networkingService := &networking.Service{}
167+
168+
tests := []struct {
169+
name string
170+
openStackMachine *infrav1.OpenStackMachine
171+
expectedError bool
172+
expectedSecurityGroups []infrav1.SecurityGroup
173+
}{
174+
{
175+
name: "NoSecurityGroups",
176+
openStackMachine: openStackMachine,
177+
expectedError: false,
178+
expectedSecurityGroups: nil,
179+
},
180+
}
181+
// TODO(emilien) Add more tests
182+
183+
for _, tt := range tests {
184+
r := &OpenStackMachineReconciler{}
185+
t.Run(tt.name, func(t *testing.T) {
186+
err := r.reconcileSecurityGroupsToInstance(openStackCluster, machine, tt.openStackMachine, instanceStatus, computeService, networkingService)
187+
if tt.expectedError {
188+
Expect(err).To(HaveOccurred())
189+
} else {
190+
Expect(err).NotTo(HaveOccurred())
191+
}
192+
Expect(tt.openStackMachine.Status.SecurityGroups).To(Equal(tt.expectedSecurityGroups))
193+
})
194+
}
195+
}

0 commit comments

Comments
 (0)