Skip to content

Commit efa3793

Browse files
authored
Merge pull request #1606 from innodreamer/master
[NCP] Add features to find and apply available CIDR for LB type subnet
2 parents 1632ab0 + 9beeeeb commit efa3793

File tree

1 file changed

+194
-40
lines changed
  • cloud-control-manager/cloud-driver/drivers/ncp/resources

1 file changed

+194
-40
lines changed

cloud-control-manager/cloud-driver/drivers/ncp/resources/NLBHandler.go

Lines changed: 194 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
// This is a Cloud Driver Example for PoC Test.
88
//
99
// by ETRI, 2022.10.
10+
// Updated by ETRI, 2025.11.
1011

1112
package resources
1213

1314
import (
1415
"fmt"
16+
"net"
1517
"strconv"
1618
"strings"
1719
"time"
@@ -39,11 +41,11 @@ type NcpVpcNLBHandler struct {
3941

4042
const (
4143
// NCP VPC Cloud LB type code : 'APPLICATION' | 'NETWORK' | 'NETWORK_PROXY'
42-
NcpLbType string = "NETWORK"
44+
NcpNetworkLbType string = "NETWORK"
4345

4446
// NCP VPC Cloud NLB network type code : 'PUBLIC' | 'PRIVATE' (Default: 'PUBLIC')
45-
NcpPublicNlBType string = "PUBLIC"
46-
NcpInternalNlBType string = "PRIVATE"
47+
NcpPublicLbType string = "PUBLIC"
48+
NcpInternalLbType string = "PRIVATE"
4749

4850
// NCP LB performance(throughput) type code : 'SMALL' | 'MEDIUM' | 'LARGE' (Default: 'SMALL')
4951
// You can only select 'SMALL' if the LB type is 'NETWORK' and the LB network type is 'PRIVATE'.
@@ -54,7 +56,6 @@ const (
5456
DefaultHealthCheckerInterval int32 = 30 // Min: 5, Max: 300 (seconds). Default: 30 seconds
5557
DefaultHealthCheckerThreshold int32 = 2 // Min: 2, Max: 10. Default: 2
5658

57-
LbTypeSubnetDefaultCidr string = ".240/28"
5859
LbTypeSubnetDefaultName string = "ncp-subnet-for-nlb"
5960
)
6061

@@ -63,8 +64,7 @@ func init() {
6364
cblogger = cblog.GetLogger("NCP VPC NLBHandler")
6465
}
6566

66-
// Note : Cloud-Barista supports only this case => [ LB : Listener : VMGroup : Health Checker = 1 : 1 : 1 : 1 ]
67-
// ------ NLB Management
67+
// Note) Cloud-Barista supports only this case => [ LB : Listener : VMGroup : Health Checker = 1 : 1 : 1 : 1 ]
6868
func (nlbHandler *NcpVpcNLBHandler) CreateNLB(nlbReqInfo irs.NLBInfo) (createNLB irs.NLBInfo, newErr error) {
6969
cblogger.Info("NPC VPC Cloud Driver: called CreateNLB()")
7070
InitLog()
@@ -77,10 +77,10 @@ func (nlbHandler *NcpVpcNLBHandler) CreateNLB(nlbReqInfo irs.NLBInfo) (createNLB
7777
return irs.NLBInfo{}, newErr
7878
}
7979

80-
// Note!! : NCP VPC LB type code : 'APPLICATION' | 'NETWORK' | 'NETWORK_PROXY'
81-
lbType := NcpLbType
80+
// Note) NCP VPC LB type code : 'APPLICATION' | 'NETWORK' | 'NETWORK_PROXY'
81+
networkTypeLb := NcpNetworkLbType
8282

83-
// Note!! : Only valid if NcpLbType is Not a 'NETWORK' type.
83+
// Note) Only valid if LB Type is Not a 'NETWORK' type.
8484
// Min : 1, Max : 3600 sec(Dedicated LB : 1 ~ 480000). Default : 60 sec
8585
timeOut := int32(nlbReqInfo.HealthChecker.Timeout)
8686
if timeOut == -1 {
@@ -90,12 +90,12 @@ func (nlbHandler *NcpVpcNLBHandler) CreateNLB(nlbReqInfo irs.NLBInfo) (createNLB
9090
return irs.NLBInfo{}, fmt.Errorf("Invalid Timeout value. Must be a number between 1 and 3600.") // According to the NCP VPC API document.
9191
}
9292

93-
// Note!! : NCP VPC LB network type code : 'PUBLIC' | 'PRIVATE' (Default: 'PUBLIC')
93+
// Note) NCP VPC LB network type code : 'PUBLIC' | 'PRIVATE' (Default: 'PUBLIC')
9494
var lbNetType string
9595
if strings.EqualFold(nlbReqInfo.Type, "PUBLIC") || strings.EqualFold(nlbReqInfo.Type, "default") || strings.EqualFold(nlbReqInfo.Type, "") {
96-
lbNetType = NcpPublicNlBType
96+
lbNetType = NcpPublicLbType
9797
} else if strings.EqualFold(nlbReqInfo.Type, "INTERNAL") {
98-
lbNetType = NcpInternalNlBType
98+
lbNetType = NcpInternalLbType
9999
}
100100

101101
ncpVPCInfo, err := nlbHandler.getNcpVpcInfoWithName(nlbReqInfo.VpcIID.NameId)
@@ -106,7 +106,7 @@ func (nlbHandler *NcpVpcNLBHandler) CreateNLB(nlbReqInfo irs.NLBInfo) (createNLB
106106
return irs.NLBInfo{}, newErr
107107
}
108108

109-
// Caution!! : ### Need a Subnet for 'LB Only'('LB Type' Subnet)
109+
// Caution!! : ### Need a Subnet for 'LB Only'('LB Type' Subnet) to create a LB
110110
lbTypeSubnetId, err := nlbHandler.getSubnetIdForNlbOnly(*ncpVPCInfo.VpcNo)
111111
if err != nil {
112112
newErr := fmt.Errorf("Failed to Get the SubnetId of LB Type subnet : [%v]", err)
@@ -116,64 +116,68 @@ func (nlbHandler *NcpVpcNLBHandler) CreateNLB(nlbReqInfo irs.NLBInfo) (createNLB
116116
}
117117

118118
cblogger.Infof("### ncpVPCInfo.Ipv4CidrBlock : [%s]", *ncpVPCInfo.Ipv4CidrBlock)
119-
// VPC IP address ranges : /16~/28 in private IP range (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
120-
cidrBlock := strings.Split(*ncpVPCInfo.Ipv4CidrBlock, ".")
121-
cidrForNlbSubnet := cidrBlock[0] + "." + cidrBlock[1] + "." + cidrBlock[2] + LbTypeSubnetDefaultCidr
122-
// Ex) In case, VpcCIDR : "10.0.0.0/16",
123-
// Subnet for VM : "10.0.0.0/28"
124-
// LB Type Subnet : "10.0.0.240/28"
125-
126-
// ### In case, there is No Subnet for 'LB Only'('LB Type' subnet), Create the 'LB Type' subnet.
119+
// Note) VPC IP address ranges : /16~/28 in private IP range (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
120+
121+
// If LB Type subnet doesn't exist, create it
127122
if strings.EqualFold(lbTypeSubnetId, "") {
128-
cblogger.Info("# There is No Subnet for 'LB Only'('LB Type' Subnet), so it will be Created.")
129-
// NCP VPC Subnet Name Max length : 30
123+
cblogger.Info("LB Type subnet does not exist. Creating new subnet with conflict-free CIDR...")
124+
125+
// Find available CIDR for NLB subnet
126+
availableCIDR, err := nlbHandler.findAvailableCIDRForNLBSubnet(*ncpVPCInfo.VpcNo, *ncpVPCInfo.Ipv4CidrBlock)
127+
if err != nil {
128+
newErr := fmt.Errorf("Failed to find available CIDR for NLB subnet: [%v]", err)
129+
cblogger.Error(newErr.Error())
130+
LoggingError(callLogInfo, newErr)
131+
return irs.NLBInfo{}, newErr
132+
}
133+
134+
// Note) NCP VPC Subnet Name Max length : 30
130135
// LbTypeSubnetDefaultName : "ncp-subnet-for-nlb" => length : 21
131136
lbTypeSubnetName := LbTypeSubnetDefaultName + "-" + randSeq(8)
132-
cblogger.Infof("### Subnet Name for LB Type subnet : [%s]", lbTypeSubnetName)
137+
cblogger.Infof("### Creating LB Type subnet [%s] with CIDR: %s", lbTypeSubnetName, availableCIDR)
133138

134139
subnetReqInfo := irs.SubnetInfo{
135140
IId: irs.IID{
136141
NameId: lbTypeSubnetName,
137142
},
138-
IPv4_CIDR: cidrForNlbSubnet,
143+
IPv4_CIDR: availableCIDR,
139144
}
140-
// Note : Create a 'LOADB' type of subnet ('LB Type' subnet for LB Only)
145+
146+
// Create a 'LOADB' type of subnet ('LB Type' subnet for LB Only)
141147
ncpNlbSubnetInfo, err := nlbHandler.creatNcpSubnetForNlbOnly(irs.IID{SystemId: *ncpVPCInfo.VpcNo}, subnetReqInfo) // Waitting time Included
142148
if err != nil {
143149
newErr := fmt.Errorf("Failed to Create the 'LB Type' Subnet : [%v]", err)
144150
cblogger.Error(newErr.Error())
145151
LoggingError(callLogInfo, newErr)
146152
return irs.NLBInfo{}, newErr
147153
}
148-
cblogger.Infof("'LB type' Subnet ID : [%s]", *ncpNlbSubnetInfo.SubnetNo)
154+
cblogger.Infof("Successfully created LB Type subnet ID: [%s]", *ncpNlbSubnetInfo.SubnetNo)
149155
lbTypeSubnetId = *ncpNlbSubnetInfo.SubnetNo
150156
}
151157

152158
// To Get Subnet No list
153159
subnetNoList := []*string{ncloud.String(lbTypeSubnetId)}
154-
// cblogger.Infof("### ID list of 'LB Type' Subnet : ")
155-
// spew.Dump(subnetNoList)
156160

157-
// Note!! : SubnetNoList[] : Range constraints: Minimum range of 1. Maximum range of 2.
161+
// Note) SubnetNoList[] : Range constraints: Minimum range of 1. Maximum range of 2.
158162
if len(subnetNoList) < 1 || len(subnetNoList) > 2 {
159163
newErr := fmt.Errorf("SubnetNoList range constraints : Min. range of 1. Max. range of 2.")
160164
cblogger.Error(newErr.Error())
161165
LoggingError(callLogInfo, newErr)
162166
return irs.NLBInfo{}, newErr
163167
}
164168

165-
// LB performance(throughput) type code : 'SMALL' | 'MEDIUM' | 'LARGE' (Default: 'SMALL')
169+
// Note) LB performance(throughput) type code : 'SMALL' | 'MEDIUM' | 'LARGE' (Default: 'SMALL')
166170
// You can only select 'SMALL' if the LB type is 'NETWORK' and the LB network type is 'PRIVATE'.
167171
throughputType := DefaultThroughputType
168172
lbReq := vlb.CreateLoadBalancerInstanceRequest{
169173
RegionCode: &nlbHandler.RegionInfo.Region,
170174
IdleTimeout: &timeOut,
171175
LoadBalancerNetworkTypeCode: &lbNetType,
172-
LoadBalancerTypeCode: &lbType, // *** Required (Not Optional)
176+
LoadBalancerTypeCode: &networkTypeLb, // *** Required (Not Optional)
173177
LoadBalancerName: &nlbReqInfo.IId.NameId,
174178
ThroughputTypeCode: &throughputType,
175-
VpcNo: ncpVPCInfo.VpcNo, // *** Required (Not Optional)
176-
SubnetNoList: subnetNoList, // *** Required (Not Optional)
179+
VpcNo: ncpVPCInfo.VpcNo, // *** Required (Not Optional)
180+
SubnetNoList: subnetNoList, // *** Required (Not Optional)
177181
}
178182

179183
// ### LoadBalancerSubnetList > PublicIpInstanceNo
@@ -196,7 +200,7 @@ func (nlbHandler *NcpVpcNLBHandler) CreateNLB(nlbReqInfo irs.NLBInfo) (createNLB
196200
}
197201
LoggingInfo(callLogInfo, callLogStart)
198202

199-
if *result.TotalRows < 1 {
203+
if len(result.LoadBalancerInstanceList) < 1 {
200204
newErr := fmt.Errorf("Failed to Create New NLB. NLB does Not Exist!!")
201205
cblogger.Error(newErr.Error())
202206
LoggingError(callLogInfo, newErr)
@@ -378,7 +382,7 @@ func (nlbHandler *NcpVpcNLBHandler) DeleteNLB(nlbIID irs.IID) (bool, error) {
378382
func (nlbHandler *NcpVpcNLBHandler) AddVMs(nlbIID irs.IID, vmIIDs *[]irs.IID) (irs.VMGroupInfo, error) {
379383
cblogger.Info("NCP VPC Cloud Driver: called AddVMs()")
380384

381-
InitLog() // Caution!!
385+
InitLog()
382386
callLogInfo := GetCallLogScheme(nlbHandler.RegionInfo.Region, "NETWORKLOADBALANCE", nlbIID.SystemId, "AddVMs()")
383387

384388
if strings.EqualFold(nlbIID.SystemId, "") {
@@ -887,7 +891,7 @@ func (nlbHandler *NcpVpcNLBHandler) CreateListener(nlbId string, nlbReqInfo irs.
887891
func (nlbHandler *NcpVpcNLBHandler) GetListenerInfo(listenerId string, loadBalancerId string) (*irs.ListenerInfo, error) {
888892
cblogger.Info("NCP VPC Cloud Driver: called GetListenerInfo()")
889893

890-
InitLog() // Caution!!
894+
InitLog()
891895
callLogInfo := GetCallLogScheme(nlbHandler.RegionInfo.Region, "NETWORKLOADBALANCE", listenerId, "GetListenerInfo()")
892896

893897
if strings.EqualFold(listenerId, "") {
@@ -1020,7 +1024,7 @@ func (nlbHandler *NcpVpcNLBHandler) GetVMGroupInfo(nlb vlb.LoadBalancerInstance)
10201024
func (nlbHandler *NcpVpcNLBHandler) GetHealthCheckerInfo(nlb vlb.LoadBalancerInstance) (irs.HealthCheckerInfo, error) {
10211025
cblogger.Info("NCP VPC Cloud Driver: called GetHealthCheckerInfo()")
10221026

1023-
InitLog() // Caution!!
1027+
InitLog()
10241028
callLogInfo := GetCallLogScheme(nlbHandler.RegionInfo.Region, "NETWORKLOADBALANCE", *nlb.LoadBalancerInstanceNo, "GetHealthCheckerInfo()")
10251029

10261030
if strings.EqualFold(*nlb.VpcNo, "") {
@@ -1255,7 +1259,7 @@ func (nlbHandler *NcpVpcNLBHandler) waitForDelNlb(nlbIID irs.IID) (bool, error)
12551259
}
12561260
}
12571261

1258-
// NCP VPC LoadBalancerInstanceStatusName : Creating, Running, Changing, Terminating, Terminated, Repairing
1262+
// NCP VPC 'LoadBalancerInstanceStatusName' : Creating, Running, Changing, Terminating, Terminated, Repairing
12591263
func (nlbHandler *NcpVpcNLBHandler) getNcpNlbStatus(nlbIID irs.IID) (string, error) {
12601264
cblogger.Info("NCP VPC Cloud Driver: called getNcpNlbStatus()")
12611265
callLogInfo := GetCallLogScheme(nlbHandler.RegionInfo.Region, "NETWORKLOADBALANCE", nlbIID.SystemId, "getNcpNlbStatus()")
@@ -1315,7 +1319,7 @@ func (nlbHandler *NcpVpcNLBHandler) getNcpNlbInfo(nlbIID irs.IID) (*vlb.LoadBala
13151319
func (nlbHandler *NcpVpcNLBHandler) getNcpNlbListWithVpcId(vpcId *string) ([]*vlb.LoadBalancerInstance, error) {
13161320
cblogger.Info("NPC VPC Cloud Driver: called getNcpNlbListWithVpcId()")
13171321

1318-
InitLog() // Caution!!
1322+
InitLog()
13191323
callLogInfo := GetCallLogScheme(nlbHandler.RegionInfo.Region, "NETWORKLOADBALANCE", "getNcpNlbListWithVpcId()", "getNcpNlbListWithVpcId()")
13201324

13211325
if strings.EqualFold(*vpcId, "") {
@@ -1654,3 +1658,153 @@ func (nlbHandler *NcpVpcNLBHandler) ListIID() ([]*irs.IID, error) {
16541658
}
16551659
return iidList, nil
16561660
}
1661+
1662+
// Finds an available CIDR block for NLB subnet within the given VPC CIDR
1663+
// that doesn't conflict with existing subnets
1664+
func (nlbHandler *NcpVpcNLBHandler) findAvailableCIDRForNLBSubnet(vpcId string, vpcCIDR string) (string, error) {
1665+
cblogger.Info("NCP VPC Cloud Driver: called findAvailableCIDRForNLBSubnet()")
1666+
InitLog()
1667+
callLogInfo := GetCallLogScheme(nlbHandler.RegionInfo.Region, "NETWORKLOADBALANCE", vpcId, "findAvailableCIDRForNLBSubnet()")
1668+
1669+
if strings.EqualFold(vpcId, "") {
1670+
newErr := fmt.Errorf("Invalid VPC ID")
1671+
cblogger.Error(newErr.Error())
1672+
LoggingError(callLogInfo, newErr)
1673+
return "", newErr
1674+
}
1675+
1676+
// Get existing subnet list
1677+
vpcHandler := NcpVpcVPCHandler{
1678+
RegionInfo: nlbHandler.RegionInfo,
1679+
VPCClient: nlbHandler.VPCClient,
1680+
}
1681+
subnetInfoList, err := vpcHandler.ListSubnet(&vpcId)
1682+
if err != nil {
1683+
newErr := fmt.Errorf("Failed to Get Subnet List: %v", err)
1684+
cblogger.Error(newErr.Error())
1685+
LoggingError(callLogInfo, newErr)
1686+
return "", newErr
1687+
}
1688+
1689+
// Collect existing subnet CIDRs
1690+
existingCIDRs := make([]string, 0)
1691+
for _, subnet := range subnetInfoList {
1692+
existingCIDRs = append(existingCIDRs, subnet.IPv4_CIDR)
1693+
}
1694+
1695+
// Parse VPC CIDR
1696+
_, vpcNet, err := net.ParseCIDR(vpcCIDR)
1697+
if err != nil {
1698+
newErr := fmt.Errorf("Failed to Parse VPC CIDR [%s]: %v", vpcCIDR, err)
1699+
cblogger.Error(newErr.Error())
1700+
LoggingError(callLogInfo, newErr)
1701+
return "", newErr
1702+
}
1703+
1704+
// NLB subnet requires /28 (16 IP addresses)
1705+
nlbSubnetMask := 28
1706+
1707+
// Generate candidate CIDR blocks within the VPC range
1708+
candidateCIDRs, err := nlbHandler.generateCandidateCIDRs(vpcNet, nlbSubnetMask)
1709+
if err != nil {
1710+
newErr := fmt.Errorf("Failed to Generate Candidate CIDRs: %v", err)
1711+
cblogger.Error(newErr.Error())
1712+
LoggingError(callLogInfo, newErr)
1713+
return "", newErr
1714+
}
1715+
1716+
// Find first available CIDR that doesn't conflict with existing subnets
1717+
for _, candidateCIDR := range candidateCIDRs {
1718+
if !nlbHandler.isCIDRConflict(candidateCIDR, existingCIDRs) {
1719+
cblogger.Infof("Found available CIDR for NLB subnet: %s", candidateCIDR)
1720+
return candidateCIDR, nil
1721+
}
1722+
}
1723+
1724+
newErr := fmt.Errorf("No available CIDR block found for NLB subnet in VPC %s", vpcCIDR)
1725+
cblogger.Error(newErr.Error())
1726+
LoggingError(callLogInfo, newErr)
1727+
return "", newErr
1728+
}
1729+
1730+
// Generates candidate CIDR blocks within the VPC network
1731+
func (nlbHandler *NcpVpcNLBHandler) generateCandidateCIDRs(vpcNet *net.IPNet, subnetMask int) ([]string, error) {
1732+
cblogger.Info("NCP VPC Cloud Driver: called generateCandidateCIDRs()")
1733+
1734+
candidates := make([]string, 0)
1735+
1736+
// Get VPC network size
1737+
vpcOnes, vpcBits := vpcNet.Mask.Size()
1738+
if vpcOnes > subnetMask {
1739+
return nil, fmt.Errorf("VPC mask /%d is larger than required subnet mask /%d", vpcOnes, subnetMask)
1740+
}
1741+
1742+
// Calculate number of possible subnets
1743+
subnetCount := 1 << uint(subnetMask-vpcOnes)
1744+
1745+
// Calculate subnet size
1746+
subnetSize := 1 << uint(vpcBits-subnetMask)
1747+
1748+
// Generate candidate CIDRs
1749+
baseIP := vpcNet.IP
1750+
for i := 0; i < subnetCount; i++ {
1751+
// Calculate subnet IP
1752+
subnetIP := make(net.IP, len(baseIP))
1753+
copy(subnetIP, baseIP)
1754+
1755+
// Add offset
1756+
offset := i * subnetSize
1757+
addOffset(subnetIP, offset)
1758+
1759+
// Check if subnet IP is within VPC range
1760+
if vpcNet.Contains(subnetIP) {
1761+
candidateCIDR := fmt.Sprintf("%s/%d", subnetIP.String(), subnetMask)
1762+
candidates = append(candidates, candidateCIDR)
1763+
}
1764+
}
1765+
1766+
cblogger.Infof("Generated %d candidate CIDRs", len(candidates))
1767+
return candidates, nil
1768+
}
1769+
1770+
// Adds offset to IP address
1771+
func addOffset(ip net.IP, offset int) {
1772+
for i := len(ip) - 1; i >= 0 && offset > 0; i-- {
1773+
current := int(ip[i]) + offset
1774+
ip[i] = byte(current & 0xFF)
1775+
offset = current >> 8
1776+
}
1777+
}
1778+
1779+
// Checks if candidate CIDR conflicts with any existing CIDRs
1780+
func (nlbHandler *NcpVpcNLBHandler) isCIDRConflict(candidateCIDR string, existingCIDRs []string) bool {
1781+
cblogger.Debug("Checking CIDR conflict for:", candidateCIDR)
1782+
1783+
_, candidateNet, err := net.ParseCIDR(candidateCIDR)
1784+
if err != nil {
1785+
cblogger.Error("Failed to parse candidate CIDR:", err)
1786+
return true
1787+
}
1788+
1789+
for _, existingCIDR := range existingCIDRs {
1790+
_, existingNet, err := net.ParseCIDR(existingCIDR)
1791+
if err != nil {
1792+
cblogger.Warn("Failed to parse existing CIDR:", existingCIDR, err)
1793+
continue
1794+
}
1795+
1796+
// Check if networks overlap
1797+
if nlbHandler.networksOverlap(candidateNet, existingNet) {
1798+
cblogger.Debugf("CIDR conflict detected: %s overlaps with %s", candidateCIDR, existingCIDR)
1799+
return true
1800+
}
1801+
}
1802+
1803+
return false
1804+
}
1805+
1806+
// Checks if two networks overlap
1807+
func (nlbHandler *NcpVpcNLBHandler) networksOverlap(net1, net2 *net.IPNet) bool {
1808+
// Check if net1 contains net2's network address or vice versa
1809+
return net1.Contains(net2.IP) || net2.Contains(net1.IP)
1810+
}

0 commit comments

Comments
 (0)