Skip to content

Commit

Permalink
- service_network_manager changes for SNVA security group API support
Browse files Browse the repository at this point in the history
  • Loading branch information
Zijun Wang committed Sep 22, 2023
1 parent d716a0f commit e0760f8
Show file tree
Hide file tree
Showing 7 changed files with 445 additions and 96 deletions.
149 changes: 108 additions & 41 deletions pkg/deploy/lattice/service_network_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ import (
lattice_aws "github.com/aws/aws-application-networking-k8s/pkg/aws"
"github.com/aws/aws-application-networking-k8s/pkg/config"
latticemodel "github.com/aws/aws-application-networking-k8s/pkg/model/lattice"
"github.com/aws/aws-application-networking-k8s/pkg/utils"
)

//go:generate mockgen -destination service_network_manager_mock.go -package lattice github.com/aws/aws-application-networking-k8s/pkg/deploy/lattice ServiceNetworkManager

type ServiceNetworkManager interface {
Create(ctx context.Context, service_network *latticemodel.ServiceNetwork) (latticemodel.ServiceNetworkStatus, error)
CreateOrUpdate(ctx context.Context, service_network *latticemodel.ServiceNetwork) (latticemodel.ServiceNetworkStatus, error)
List(ctx context.Context) ([]string, error)
Delete(ctx context.Context, service_network string) error
}
Expand All @@ -39,7 +42,8 @@ type defaultServiceNetworkManager struct {
cloud lattice_aws.Cloud
}

// Create will try to create a service_network and associate the service_network with vpc
// CreateOrUpdate will try to create a service_network and associate the service_network with vpc.
// Or try to update the security groups for the serviceNetworkVpcAssociation if security groups are changed.
// return error when:
//
// ListServiceNetworkesWithContext returns error
Expand All @@ -53,66 +57,72 @@ type defaultServiceNetworkManager struct {
// return errors.New(LATTICE_RETRY) when:
//
// CreateServiceNetworkVpcAssociationInput returns ServiceNetworkVpcAssociationStatusFailed/ServiceNetworkVpcAssociationStatusCreateInProgress/MeshVpcAssociationStatusDeleteInProgress
func (m *defaultServiceNetworkManager) Create(ctx context.Context, service_network *latticemodel.ServiceNetwork) (latticemodel.ServiceNetworkStatus, error) {
func (m *defaultServiceNetworkManager) CreateOrUpdate(ctx context.Context, stackSn *latticemodel.ServiceNetwork) (latticemodel.ServiceNetworkStatus, error) {
// check if exists
service_networkSummary, err := m.cloud.Lattice().FindServiceNetwork(ctx, service_network.Spec.Name, "")
service_networkSummary, err := m.cloud.Lattice().FindServiceNetwork(ctx, stackSn.Spec.Name, "")
if err != nil && !services.IsNotFoundError(err) {
return latticemodel.ServiceNetworkStatus{ServiceNetworkARN: "", ServiceNetworkID: ""}, err
}

// pre declaration
var service_networkID string
var service_networkArn string
var isServiceNetworkAssociatedWithVPC bool
var service_networkAssociatedWithCurrentVPCId *string
var isServiceNetworkAlreadyAssociatedWithCurrentVpc bool
var snvaAssociatedWithCurrentVPC *vpclattice.ServiceNetworkVpcAssociationSummary
vpcLatticeSess := m.cloud.Lattice()
if service_networkSummary == nil {
glog.V(2).Infof("Create ServiceNetwork, service_network[%v] and tag it with vpciID[%s]\n", service_network, config.VpcID)
glog.V(2).Infof("CreateOrUpdate ServiceNetwork, stackSn[%v] and tag it with vpciID[%s]\n", stackSn, config.VpcID)
// Add tag to show this is the VPC create this servicenetwork
// This means, the servicenetwork can only be deleted by the controller running in this VPC

service_networkInput := vpclattice.CreateServiceNetworkInput{
Name: &service_network.Spec.Name,
serviceNetworkInput := vpclattice.CreateServiceNetworkInput{
Name: &stackSn.Spec.Name,
Tags: make(map[string]*string),
}
service_networkInput.Tags[latticemodel.K8SServiceNetworkOwnedByVPC] = &config.VpcID
serviceNetworkInput.Tags[latticemodel.K8SServiceNetworkOwnedByVPC] = &config.VpcID

glog.V(2).Infof("Create service_network >>>> req[%v]", service_networkInput)
resp, err := vpcLatticeSess.CreateServiceNetworkWithContext(ctx, &service_networkInput)
glog.V(2).Infof("Create service_network >>>> resp[%v], err : %v", resp, err)
glog.V(2).Infof("CreateOrUpdate stackSn >>>> req[%v]", serviceNetworkInput)
resp, err := vpcLatticeSess.CreateServiceNetworkWithContext(ctx, &serviceNetworkInput)
glog.V(2).Infof("CreateOrUpdate stackSn >>>> resp[%v], err : %v", resp, err)
if err != nil {
glog.V(2).Infof("Failed to create service_network[%v], err: %v", service_network, err)
glog.V(2).Infof("Failed to create stackSn[%v], err: %v", stackSn, err)
return latticemodel.ServiceNetworkStatus{ServiceNetworkARN: "", ServiceNetworkID: ""}, err
}
service_networkID = aws.StringValue(resp.Id)
service_networkArn = aws.StringValue(resp.Arn)
isServiceNetworkAssociatedWithVPC = false
glog.V(6).Infof(" ServiceNetwork Create API resp [%v]\n", resp)
isServiceNetworkAlreadyAssociatedWithCurrentVpc = false
glog.V(6).Infof(" ServiceNetwork CreateOrUpdate API resp [%v]\n", resp)

} else {
glog.V(6).Infof("service_network[%v] exists, further check association", service_network)
} else { //service_networkSummary != nil
glog.V(6).Infof("stackSn[%v] exists, further check association", stackSn)
service_networkID = aws.StringValue(service_networkSummary.SvcNetwork.Id)
service_networkArn = aws.StringValue(service_networkSummary.SvcNetwork.Arn)
isServiceNetworkAssociatedWithVPC, service_networkAssociatedWithCurrentVPCId, _, err = m.isServiceNetworkAssociatedWithVPC(ctx, service_networkID)
isServiceNetworkAlreadyAssociatedWithCurrentVpc, snvaAssociatedWithCurrentVPC, _, err = m.isServiceNetworkAlreadyAssociatedWithVPC(ctx, service_networkID)
if err != nil {
return latticemodel.ServiceNetworkStatus{ServiceNetworkARN: "", ServiceNetworkID: ""}, err
}
if stackSn.Spec.AssociateToVPC == true && isServiceNetworkAlreadyAssociatedWithCurrentVpc == true &&
snvaAssociatedWithCurrentVPC.Status != nil && aws.StringValue(snvaAssociatedWithCurrentVPC.Status) == vpclattice.ServiceNetworkVpcAssociationStatusActive {
return m.UpdateServiceNetworkVpcAssociationIfNeeded(ctx, service_networkID, service_networkArn, stackSn, snvaAssociatedWithCurrentVPC.Id)

}
}

if service_network.Spec.AssociateToVPC == true {
if isServiceNetworkAssociatedWithVPC == false {
if stackSn.Spec.AssociateToVPC == true {
if isServiceNetworkAlreadyAssociatedWithCurrentVpc == false {
// current state: service network is associated to VPC
// desired state: associate this service network to VPC
createServiceNetworkVpcAssociationInput := vpclattice.CreateServiceNetworkVpcAssociationInput{
ServiceNetworkIdentifier: &service_networkID,
VpcIdentifier: &config.VpcID,
SecurityGroupIds: stackSn.Spec.SecurityGroupIds,
}
glog.V(2).Infof("Create service_network/vpc association >>>> req[%v]", createServiceNetworkVpcAssociationInput)
glog.V(2).Infof("CreateOrUpdate stackSn/vpc association >>>> req[%v]", createServiceNetworkVpcAssociationInput)
resp, err := vpcLatticeSess.CreateServiceNetworkVpcAssociationWithContext(ctx, &createServiceNetworkVpcAssociationInput)
glog.V(2).Infof("Create service_network and vpc association here >>>> resp[%v] err [%v]\n", resp, err)
// Associate service_network with vpc
glog.V(2).Infof("CreateOrUpdate stackSn and vpc association here >>>> resp[%v] err [%v]\n", resp, err)
// Associate stackSn with vpc
if err != nil {
glog.V(2).Infof("Failed to associate service_network[%v] and vpc, err: %v", service_network, err)
glog.V(2).Infof("Failed to associate stackSn[%v] and vpc, err: %v", stackSn, err)
return latticemodel.ServiceNetworkStatus{ServiceNetworkARN: "", ServiceNetworkID: ""}, err
} else {
service_networkVPCAssociationStatus := aws.StringValue(resp.Status)
Expand All @@ -131,27 +141,28 @@ func (m *defaultServiceNetworkManager) Create(ctx context.Context, service_netwo
}
}
} else {
if isServiceNetworkAssociatedWithVPC == true {
// stackSn.Spec.AssociateToVPC == false
if isServiceNetworkAlreadyAssociatedWithCurrentVpc == true {
// current state: service network is associated to VPC
// desired state: not to associate this service network to VPC
glog.V(6).Infof("Disassociate service_network(%v) from vpc association", service_network.Spec.Name)
glog.V(6).Infof("Disassociate stackSn(%v) from vpc association", stackSn.Spec.Name)

deleteServiceNetworkVpcAssociationInput := vpclattice.DeleteServiceNetworkVpcAssociationInput{
ServiceNetworkVpcAssociationIdentifier: service_networkAssociatedWithCurrentVPCId,
ServiceNetworkVpcAssociationIdentifier: snvaAssociatedWithCurrentVPC.Id,
}

glog.V(2).Infof("Delete service_network association >>>> req[%v]", deleteServiceNetworkVpcAssociationInput)
glog.V(2).Infof("Delete stackSn association >>>> req[%v]", deleteServiceNetworkVpcAssociationInput)
resp, err := vpcLatticeSess.DeleteServiceNetworkVpcAssociationWithContext(ctx, &deleteServiceNetworkVpcAssociationInput)
glog.V(2).Infof("Delete service_network association >>>> resp[%v],err [%v]", resp, err)
glog.V(2).Infof("Delete stackSn association >>>> resp[%v],err [%v]", resp, err)
if err != nil {
glog.V(2).Infof("Failed to delete association for %v err=%v , resp = %v\n", service_network.Spec.Name, err, resp)
glog.V(2).Infof("Failed to delete association for %v err=%v , resp = %v\n", stackSn.Spec.Name, err, resp)
}

// return retry and check later if disassociation workflow finishes
return latticemodel.ServiceNetworkStatus{ServiceNetworkARN: "", ServiceNetworkID: ""}, errors.New(LATTICE_RETRY)

}
glog.V(2).Infof("Created service_network(%v) without vpc association", service_network.Spec.Name)
glog.V(2).Infof("Created stackSn(%v) without vpc association", stackSn.Spec.Name)
}
return latticemodel.ServiceNetworkStatus{ServiceNetworkARN: service_networkArn, ServiceNetworkID: service_networkID}, nil
}
Expand Down Expand Up @@ -187,16 +198,16 @@ func (m *defaultServiceNetworkManager) Delete(ctx context.Context, snName string
vpcLatticeSess := m.cloud.Lattice()
service_networkID := aws.StringValue(service_networkSummary.SvcNetwork.Id)

_, service_networkAssociatedWithCurrentVPCId, assocResp, err := m.isServiceNetworkAssociatedWithVPC(ctx, service_networkID)
_, snvaAssociatedWithCurrentVPC, assocResp, err := m.isServiceNetworkAlreadyAssociatedWithVPC(ctx, service_networkID)
if err != nil {
return err
}
if service_networkAssociatedWithCurrentVPCId != nil {
if snvaAssociatedWithCurrentVPC != nil && snvaAssociatedWithCurrentVPC.Id != nil {
// current VPC is associated with this service network

// Happy case, disassociate the VPC from service network
deleteServiceNetworkVpcAssociationInput := vpclattice.DeleteServiceNetworkVpcAssociationInput{
ServiceNetworkVpcAssociationIdentifier: service_networkAssociatedWithCurrentVPCId,
ServiceNetworkVpcAssociationIdentifier: snvaAssociatedWithCurrentVPC.Id,
}
glog.V(2).Infof("DeleteServiceNetworkVpcAssociationInput >>>> %v\n", deleteServiceNetworkVpcAssociationInput)
resp, err := vpcLatticeSess.DeleteServiceNetworkVpcAssociationWithContext(ctx, &deleteServiceNetworkVpcAssociationInput)
Expand Down Expand Up @@ -251,7 +262,7 @@ func (m *defaultServiceNetworkManager) Delete(ctx context.Context, snName string
}

// If service_network exists, check if service_network has already associated with VPC
func (m *defaultServiceNetworkManager) isServiceNetworkAssociatedWithVPC(ctx context.Context, service_networkID string) (bool, *string, []*vpclattice.ServiceNetworkVpcAssociationSummary, error) {
func (m *defaultServiceNetworkManager) isServiceNetworkAlreadyAssociatedWithVPC(ctx context.Context, service_networkID string) (bool, *vpclattice.ServiceNetworkVpcAssociationSummary, []*vpclattice.ServiceNetworkVpcAssociationSummary, error) {
vpcLatticeSess := m.cloud.Lattice()
// TODO: can pass vpc id to ListServiceNetworkVpcAssociationsInput, could return err if no associations
associationStatusInput := vpclattice.ListServiceNetworkVpcAssociationsInput{
Expand All @@ -274,22 +285,78 @@ func (m *defaultServiceNetworkManager) isServiceNetworkAssociatedWithVPC(ctx con
switch associationStatus {
case vpclattice.ServiceNetworkVpcAssociationStatusActive:
glog.V(6).Infoln("Mesh and Vpc association is active.")
return true, r.Id, resp, nil
return true, r, resp, nil
case vpclattice.ServiceNetworkVpcAssociationStatusCreateFailed:
glog.V(6).Infoln("Mesh and Vpc association does not exists, start creating service_network and vpc association")
return false, r.Id, resp, nil
return false, r, resp, nil
case vpclattice.ServiceNetworkVpcAssociationStatusDeleteFailed:
glog.V(6).Infoln("Mesh and Vpc association failed to delete")
return true, r.Id, resp, nil
return true, r, resp, nil
case vpclattice.ServiceNetworkVpcAssociationStatusDeleteInProgress:
glog.V(6).Infoln("ServiceNetwork and Vpc association is being deleted, please retry later")
return true, r.Id, resp, errors.New(LATTICE_RETRY)
return true, r, resp, errors.New(LATTICE_RETRY)
case vpclattice.ServiceNetworkVpcAssociationStatusCreateInProgress:
glog.V(6).Infoln("ServiceNetwork and Vpc association is being created, please retry later")
return true, r.Id, resp, errors.New(LATTICE_RETRY)
return true, r, resp, errors.New(LATTICE_RETRY)
}
}
}
}
return false, nil, resp, err
}

func (m *defaultServiceNetworkManager) UpdateServiceNetworkVpcAssociationIfNeeded(ctx context.Context, snId string, snArn string, desiredSN *latticemodel.ServiceNetwork, existingSnvaId *string) (latticemodel.ServiceNetworkStatus, error) {
retrievedSnva, err := m.cloud.Lattice().GetServiceNetworkVpcAssociationWithContext(ctx, &vpclattice.GetServiceNetworkVpcAssociationInput{
ServiceNetworkVpcAssociationIdentifier: existingSnvaId,
})
if err != nil {
return latticemodel.ServiceNetworkStatus{}, err
}

if len(desiredSN.Spec.SecurityGroupIds) == 0 && retrievedSnva.SecurityGroupIds != nil && len(retrievedSnva.SecurityGroupIds) > 0 {
// VPC latiice API have a limitation that the user can't remove all security groups if current snva already has security groups.
// If that case happen, the controller will not delete snva. User should manually delete it.
//https://docs.aws.amazon.com/vpc-lattice/latest/ug/security-groups.html#security-groups-rules

// TODO: Add validation webhook or update gateway (or VpcAssociationPolicy) status to indicate that error.
// https://gateway-api.sigs.k8s.io/geps/gep-713/#standard-status-condition
return latticemodel.ServiceNetworkStatus{
ServiceNetworkID: snId,
ServiceNetworkARN: snArn,
SnvaSecurityGroupIds: nil,
}, errors.New(LATTICE_RETRY)
}

twoSlicesElementsEqual := utils.PointerStringSlicesElementsEqual(desiredSN.Spec.SecurityGroupIds, retrievedSnva.SecurityGroupIds)

if twoSlicesElementsEqual {
// desiredSN's security group ids are same with retrievedSnva's security group ids, don't need to update
return latticemodel.ServiceNetworkStatus{
ServiceNetworkID: snId,
ServiceNetworkARN: snArn,
SnvaSecurityGroupIds: retrievedSnva.SecurityGroupIds,
}, nil
}

updateSnvaResp, err := m.cloud.Lattice().UpdateServiceNetworkVpcAssociationWithContext(ctx, &vpclattice.UpdateServiceNetworkVpcAssociationInput{
ServiceNetworkVpcAssociationIdentifier: existingSnvaId,
SecurityGroupIds: desiredSN.Spec.SecurityGroupIds,
})
retSnStatus := latticemodel.ServiceNetworkStatus{
ServiceNetworkID: snId,
ServiceNetworkARN: snArn,
SnvaSecurityGroupIds: updateSnvaResp.SecurityGroupIds,
}
if err != nil {
return retSnStatus, err
}
switch updateSnvaResp.Status {
case aws.String(vpclattice.ServiceNetworkVpcAssociationStatusActive):
return retSnStatus, nil
case aws.String(vpclattice.ServiceNetworkVpcAssociationStatusUpdateInProgress):
default:
return retSnStatus, errors.New(LATTICE_RETRY)
}
return latticemodel.ServiceNetworkStatus{}, errors.New(LATTICE_RETRY)

}
30 changes: 15 additions & 15 deletions pkg/deploy/lattice/service_network_manager_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit e0760f8

Please sign in to comment.