Skip to content
Open
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
92 changes: 75 additions & 17 deletions rest-api/api/pkg/api/handler/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -2581,8 +2581,8 @@ func (uih UpdateInstanceHandler) Handle(c echo.Context) error {
// Collect all Subnet and VPC Prefix IDs for batch query
subnetIDs := []uuid.UUID{}
vpcPrefixIDs := []uuid.UUID{}
subnetIfcMap := map[uuid.UUID]int{}
vpcPrefixIfcMap := map[uuid.UUID]int{}
subnetIfcMap := map[uuid.UUID]uint64{}
vpcPrefixIfcMap := map[uuid.UUID]uint64{}

for _, ifc := range apiRequest.Interfaces {
if ifc.SubnetID != nil {
Expand Down Expand Up @@ -2638,8 +2638,8 @@ func (uih UpdateInstanceHandler) Handle(c echo.Context) error {
return cutil.NewAPIErrorResponse(c, interfaceVpcErr.Code, interfaceVpcErr.Message, interfaceVpcErr.Data)
}

existingSubnetIfcMap := map[uuid.UUID]int{}
existingVpcPrefixIfcMap := map[uuid.UUID]int{}
existingSubnetIfcMap := map[uuid.UUID]uint64{}
existingVpcPrefixIfcMap := map[uuid.UUID]uint64{}
if len(apiRequest.Interfaces) > 0 {
ifcDAO := cdbm.NewInterfaceDAO(uih.dbSession)
existingIfcsForCapacity, _, err := ifcDAO.GetAll(ctx, nil, cdbm.InterfaceFilterInput{InstanceIDs: []uuid.UUID{instance.ID}}, cdbp.PageInput{Limit: cutil.GetPtr(cdbp.TotalLimit)}, nil)
Expand All @@ -2649,6 +2649,10 @@ func (uih UpdateInstanceHandler) Handle(c echo.Context) error {
}
for i := range existingIfcsForCapacity {
eifc := &existingIfcsForCapacity[i]
if eifc.Status == cdbm.InterfaceStatusDeleting {
continue
}

if eifc.SubnetID != nil {
existingSubnetIfcMap[*eifc.SubnetID]++
}
Expand Down Expand Up @@ -2742,9 +2746,13 @@ func (uih UpdateInstanceHandler) Handle(c echo.Context) error {
}

// Check if Subnet is exhausted
incomingInterfaceIPs := subnetIfcMap[subnetID] - existingSubnetIfcMap[subnetID]
incomingInterfaceIPs := uint64(0)
if subnetIfcMap[subnetID] > existingSubnetIfcMap[subnetID] {
incomingInterfaceIPs = subnetIfcMap[subnetID] - existingSubnetIfcMap[subnetID]
}

subnetUsage := subnetUsageMap[subnetID]
if subnetUsage != nil && subnetUsage.AvailableIPs > 0 && subnetUsage.AcquiredIPs+uint64(incomingInterfaceIPs) > subnetUsage.AvailableIPs {
if incomingInterfaceIPs > 0 && subnetUsage != nil && subnetUsage.AvailableIPs > 0 && subnetUsage.AcquiredIPs+incomingInterfaceIPs > subnetUsage.AvailableIPs {
msg := fmt.Sprintf(
"Subnet %v does not have enough IP addresses: %d of %d IP addresses remain available, but the %d additional interface(s) in this request require %d IP address(es)",
subnetID, subnetUsage.AvailableIPs-subnetUsage.AcquiredIPs, subnetUsage.AvailableIPs, incomingInterfaceIPs, incomingInterfaceIPs,
Expand Down Expand Up @@ -2826,9 +2834,13 @@ func (uih UpdateInstanceHandler) Handle(c echo.Context) error {
}

// Check if VPC Prefix is exhausted
incomingInterfaceIPs := max(vpcPrefixIfcMap[vpcPrefixID]-existingVpcPrefixIfcMap[vpcPrefixID], 0)
incomingInterfaceIPs := uint64(0)
if vpcPrefixIfcMap[vpcPrefixID] > existingVpcPrefixIfcMap[vpcPrefixID] {
incomingInterfaceIPs = vpcPrefixIfcMap[vpcPrefixID] - existingVpcPrefixIfcMap[vpcPrefixID]
}

vpUsage := vpcPrefixUsageMap[vpcPrefixID]
if vpUsage != nil && vpUsage.AvailableIPs > 0 && vpUsage.AcquiredIPs+uint64(incomingInterfaceIPs)*2 > vpUsage.AvailableIPs {
if incomingInterfaceIPs > 0 && vpUsage != nil && vpUsage.AvailableIPs > 0 && vpUsage.AcquiredIPs+incomingInterfaceIPs*2 > vpUsage.AvailableIPs {
msg := fmt.Sprintf(
"VPC Prefix %v does not have enough IP addresses: %d of %d IP addresses remain available, but the %d additional interface(s) in this request require %d IP addresses",
vpcPrefixID, vpUsage.AvailableIPs-vpUsage.AcquiredIPs, vpUsage.AvailableIPs, incomingInterfaceIPs, incomingInterfaceIPs*2,
Expand Down Expand Up @@ -3460,8 +3472,8 @@ func (uih UpdateInstanceHandler) Handle(c echo.Context) error {
// return an empty list. Reads after this should reflect the
// auto contract (no explicit interfaces) rather than the
// stale rows that pre-dated the mode switch.
// - Explicit interfaces in the request: create the new rows
// and mark the previous rows as Deleting (existing behavior).
// - Explicit interfaces in the request: reuse matching rows,
// create new rows, and mark only removed rows as Deleting.
// - Neither (no interface change, not switching to auto):
// carry the existing rows forward.
switch {
Expand All @@ -3476,7 +3488,36 @@ func (uih UpdateInstanceHandler) Handle(c echo.Context) error {
}
newdbIfcs = []cdbm.Interface{}
case len(apiRequest.Interfaces) > 0:
existingIfcMap := make(map[cdbm.EthernetInterfaceKey][]cdbm.Interface)

for existingIfcIndex := range existingIfcs {
if existingIfcs[existingIfcIndex].Status == cdbm.InterfaceStatusDeleting {
continue
}

key := existingIfcs[existingIfcIndex].EthernetKey()
existingIfcMap[key] = append(existingIfcMap[key], existingIfcs[existingIfcIndex])
}

reusedIfcIDs := make(map[uuid.UUID]struct{})
for _, dbifc := range dbInterfaces {
key := dbifc.EthernetKey()

existingIfcsForKey := existingIfcMap[key]
if len(existingIfcsForKey) > 0 {
reusedIfc := existingIfcsForKey[0]
if len(existingIfcsForKey) == 1 {
delete(existingIfcMap, key)
} else {
existingIfcMap[key] = existingIfcsForKey[1:]
}

reusedIfcIDs[reusedIfc.ID] = struct{}{}
newdbIfcs = append(newdbIfcs, reusedIfc)

continue
}

input := cdbm.InterfaceCreateInput{
InstanceID: instance.ID,
SubnetID: dbifc.SubnetID,
Expand All @@ -3502,19 +3543,36 @@ func (uih UpdateInstanceHandler) Handle(c echo.Context) error {
ifc := *newDbifc
ifc.Vpc = dbifc.Vpc
ifc.VpcPrefix = dbifc.VpcPrefix // We created the interface in the DB based on the values in dbifc, so we can populate this as well.
ifc.Subnet = dbifc.Subnet
// Add the new Interface to the list of new Interfaces
newdbIfcs = append(newdbIfcs, ifc)
}

// Update status of existing Interfaces to Deleting
for i := range existingIfcs {
existingIfcs[i].Status = cdbm.InterfaceStatusDeleting
_, err := ifcDAO.Update(ctx, tx, cdbm.InterfaceUpdateInput{InterfaceID: existingIfcs[i].ID, Status: cutil.GetPtr(cdbm.InterfaceStatusDeleting)})
if err != nil {
logger.Error().Err(err).Msg("failed to update Interface record in DB")
return cutil.NewAPIError(http.StatusInternalServerError, "Failed to update Interface for Instance, DB error", nil)
unmatchedIfcs := make([]cdbm.Interface, 0, len(existingIfcs)-len(reusedIfcIDs))
for existingIfcIndex := range existingIfcs {
if _, reused := reusedIfcIDs[existingIfcs[existingIfcIndex].ID]; reused {
continue
}

if existingIfcs[existingIfcIndex].Status != cdbm.InterfaceStatusDeleting {
existingIfcs[existingIfcIndex].Status = cdbm.InterfaceStatusDeleting

// Deleting rows retain their associations and allocated addresses until Site cleanup releases them.
_, err := ifcDAO.Update(ctx, tx, cdbm.InterfaceUpdateInput{
InterfaceID: existingIfcs[existingIfcIndex].ID,
Status: cutil.GetPtr(cdbm.InterfaceStatusDeleting),
}) //nolint:exhaustruct // Only the lifecycle status changes; associations remain held.
if err != nil {
logger.Error().Err(err).Msg("failed to update Interface record in DB")

return cutil.NewAPIError(http.StatusInternalServerError, "Failed to update Interface for Instance, DB error", nil)
}
}

unmatchedIfcs = append(unmatchedIfcs, existingIfcs[existingIfcIndex])
}

existingIfcs = unmatchedIfcs
default:
newdbIfcs = existingIfcs
}
Expand Down
Loading
Loading