Skip to content

Commit

Permalink
feat: Allow isolated network CIDR to be configured
Browse files Browse the repository at this point in the history
  • Loading branch information
hrak committed Aug 27, 2024
1 parent c9f806c commit cdf9f2a
Show file tree
Hide file tree
Showing 13 changed files with 96 additions and 29 deletions.
3 changes: 2 additions & 1 deletion api/v1beta1/zz_generated.conversion.go

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

3 changes: 2 additions & 1 deletion api/v1beta2/zz_generated.conversion.go

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

26 changes: 21 additions & 5 deletions api/v1beta3/cloudstackcluster_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package v1beta3

import (
"fmt"
"net"

"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -79,6 +80,12 @@ func (r *CloudStackCluster) ValidateCreate() (admission.Warnings, error) {
field.NewPath("spec", "failureDomains", "ACSEndpoint"),
"Name and Namespace are required"))
}
if fdSpec.Zone.Network.CIDR != "" {
if _, errMsg := ValidateCIDR(fdSpec.Zone.Network.CIDR); errMsg != nil {
errorList = append(errorList, field.Invalid(
field.NewPath("spec", "failureDomains", "Zone", "Network"), fdSpec.Zone.Network.CIDR, fmt.Sprintf("must be valid CIDR: %s", errMsg.Error())))
}
}
if fdSpec.Zone.Network.Domain != "" {
for _, errMsg := range validation.IsDNS1123Subdomain(fdSpec.Zone.Network.Domain) {
errorList = append(errorList, field.Invalid(
Expand Down Expand Up @@ -127,6 +134,13 @@ func (r *CloudStackCluster) ValidateUpdate(old runtime.Object) (admission.Warnin
return nil, webhookutil.AggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, errorList)
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *CloudStackCluster) ValidateDelete() (admission.Warnings, error) {
cloudstackclusterlog.V(1).Info("entered validate delete webhook", "api resource name", r.Name)
// No deletion validations. Deletion webhook not enabled.
return nil, nil
}

// ValidateFailureDomainUpdates verifies that at least one failure domain has not been deleted, and
// failure domains that are held over have not been modified.
func ValidateFailureDomainUpdates(oldFDs, newFDs []CloudStackFailureDomainSpec) *field.Error {
Expand Down Expand Up @@ -165,9 +179,11 @@ func FailureDomainsEqual(fd1, fd2 CloudStackFailureDomainSpec) bool {
fd1.Zone.Network.Domain == fd2.Zone.Network.Domain
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *CloudStackCluster) ValidateDelete() (admission.Warnings, error) {
cloudstackclusterlog.V(1).Info("entered validate delete webhook", "api resource name", r.Name)
// No deletion validations. Deletion webhook not enabled.
return nil, nil
// ValidateCIDR validates whether a CIDR matches the conventions expected by net.ParseCIDR
func ValidateCIDR(cidr string) (*net.IPNet, error) {
_, net, err := net.ParseCIDR(cidr)
if err != nil {
return nil, err
}
return net, nil
}
4 changes: 4 additions & 0 deletions api/v1beta3/cloudstackfailuredomain_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ type Network struct {
// Cloudstack Network Name the cluster is built in.
Name string `json:"name"`

// CIDR is the IP address range of the network.
//+optional
CIDR string `json:"cidr,omitempty"`

// Domain is the DNS domain name used for all instances in the network.
//+optional
Domain string `json:"domain,omitempty"`
Expand Down
7 changes: 4 additions & 3 deletions api/v1beta3/cloudstackisolatednetwork_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,17 @@ type CloudStackIsolatedNetworkSpec struct {
// FailureDomainName -- the FailureDomain the network is placed in.
FailureDomainName string `json:"failureDomainName"`

// CIDR is the IP range of the isolated network.
//+optional
CIDR string `json:"cidr,omitempty"`

// Domain is the DNS domain name used for all instances in the isolated network.
//+optional
Domain string `json:"domain,omitempty"`
}

// CloudStackIsolatedNetworkStatus defines the observed state of CloudStackIsolatedNetwork
type CloudStackIsolatedNetworkStatus struct {
// The CIDR of the assigned subnet.
CIDR string `json:"cidr,omitempty"`

// The outgoing IP of the isolated network.
PublicIPAddress string `json:"publicIPAddress,omitempty"`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,9 @@ spec:
network:
description: The network within the Zone to use.
properties:
cidr:
description: CIDR is the IP address range of the network.
type: string
domain:
description: Domain is the DNS domain name used for
all instances in the network.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ spec:
network:
description: The network within the Zone to use.
properties:
cidr:
description: CIDR is the IP address range of the network.
type: string
domain:
description: Domain is the DNS domain name used for all instances
in the network.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ spec:
description: CloudStackIsolatedNetworkSpec defines the desired state of
CloudStackIsolatedNetwork
properties:
cidr:
description: CIDR is the IP range of the isolated network.
type: string
controlPlaneEndpoint:
description: The kubernetes control plane endpoint.
properties:
Expand Down Expand Up @@ -240,9 +243,6 @@ spec:
- ipAddress
- ipAddressID
type: object
cidr:
description: The CIDR of the assigned subnet.
type: string
loadBalancerRuleID:
description: |-
Deprecated: The ID of the lb rule used to assign VMs to the lb.
Expand Down
5 changes: 2 additions & 3 deletions controllers/cloudstackfailuredomain_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,10 @@ func (r *CloudStackFailureDomainReconciliationRunner) Reconcile() (retRes ctrl.R
// CloudStackIsolatedNetwork to manage the many intricacies and wait until CloudStackIsolatedNetwork is ready.
if r.ReconciliationSubject.Spec.Zone.Network.ID == "" ||
r.ReconciliationSubject.Spec.Zone.Network.Type == infrav1.NetworkTypeIsolated {
netName := r.ReconciliationSubject.Spec.Zone.Network.Name
if res, err := r.GenerateIsolatedNetwork(
netName, r.ReconciliationSubject.Spec.Name, r.ReconciliationSubject.Spec.Zone.Network.Domain)(); r.ShouldReturn(res, err) {
r.ReconciliationSubject.Spec.Zone.Network, r.ReconciliationSubject.Spec.Name)(); r.ShouldReturn(res, err) {
return res, err
} else if res, err := r.GetObjectByName(r.IsoNetMetaName(netName), r.IsoNet)(); r.ShouldReturn(res, err) {
} else if res, err := r.GetObjectByName(r.IsoNetMetaName(r.ReconciliationSubject.Spec.Zone.Network.Name), r.IsoNet)(); r.ShouldReturn(res, err) {
return res, err
}
if r.IsoNet.Name == "" {
Expand Down
13 changes: 8 additions & 5 deletions controllers/utils/isolated_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,20 @@ func (r *ReconciliationRunner) IsoNetMetaName(name string) string {
return strings.TrimSuffix(str, "-")
}

// GenerateIsolatedNetwork of the passed name that's owned by the ReconciliationSubject.
func (r *ReconciliationRunner) GenerateIsolatedNetwork(name, fdName, domainName string) CloudStackReconcilerMethod {
// GenerateIsolatedNetwork creates a CloudStackIsolatedNetwork object that is owned by the ReconciliationSubject.
func (r *ReconciliationRunner) GenerateIsolatedNetwork(network infrav1.Network, fdName string) CloudStackReconcilerMethod {
return func() (ctrl.Result, error) {
lowerName := strings.ToLower(name)
lowerName := strings.ToLower(network.Name)
metaName := fmt.Sprintf("%s-%s", r.CSCluster.Name, lowerName)
csIsoNet := &infrav1.CloudStackIsolatedNetwork{}
csIsoNet.ObjectMeta = r.NewChildObjectMeta(metaName)
csIsoNet.Spec.Name = lowerName
csIsoNet.Spec.FailureDomainName = fdName
if domainName != "" {
csIsoNet.Spec.Domain = strings.ToLower(domainName)
if network.CIDR != "" {
csIsoNet.Spec.CIDR = network.CIDR
}
if network.Domain != "" {
csIsoNet.Spec.Domain = strings.ToLower(network.Domain)
}
csIsoNet.Spec.ControlPlaneEndpoint.Host = r.CSCluster.Spec.ControlPlaneEndpoint.Host
csIsoNet.Spec.ControlPlaneEndpoint.Port = r.CSCluster.Spec.ControlPlaneEndpoint.Port
Expand Down
49 changes: 42 additions & 7 deletions pkg/cloud/isolated_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package cloud

import (
"fmt"
"net"
"slices"
"strconv"
"strings"
Expand Down Expand Up @@ -123,13 +124,24 @@ func (c *client) CreateIsolatedNetwork(fd *infrav1.CloudStackFailureDomain, isoN
if isoNet.Spec.Domain != "" {
p.SetNetworkdomain(isoNet.Spec.Domain)
}
if isoNet.Spec.CIDR != "" {
m, err := parseCIDR(isoNet.Spec.CIDR)
if err != nil {
return errors.Wrap(err, "parsing CIDR")
}
// Set the needed IP subnet config
p.SetGateway(m["gateway"])
p.SetNetmask(m["netmask"])
p.SetStartip(m["startip"])
p.SetEndip(m["endip"])
}
resp, err := c.cs.Network.CreateNetwork(p)
if err != nil {
c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
return errors.Wrapf(err, "creating network with name %s", isoNet.Spec.Name)
}
isoNet.Spec.ID = resp.Id
isoNet.Status.CIDR = resp.Cidr
isoNet.Spec.CIDR = resp.Cidr

return c.AddCreatedByCAPCTag(ResourceTypeNetwork, isoNet.Spec.ID)
}
Expand Down Expand Up @@ -201,7 +213,7 @@ func (c *client) GetIsolatedNetwork(isoNet *infrav1.CloudStackIsolatedNetwork) (
"expected 1 Network with name %s, but got %d", isoNet.Name, count))
} else { // Got netID from the network's name.
isoNet.Spec.ID = netDetails.Id
isoNet.Status.CIDR = netDetails.Cidr
isoNet.Spec.CIDR = netDetails.Cidr
return nil
}

Expand All @@ -213,7 +225,7 @@ func (c *client) GetIsolatedNetwork(isoNet *infrav1.CloudStackIsolatedNetwork) (
return multierror.Append(retErr, errors.Errorf("expected 1 Network with UUID %s, but got %d", isoNet.Spec.ID, count))
}
isoNet.Spec.Name = netDetails.Name
isoNet.Status.CIDR = netDetails.Cidr
isoNet.Spec.CIDR = netDetails.Cidr
return nil
}

Expand Down Expand Up @@ -688,13 +700,15 @@ func (c *client) GetOrCreateIsolatedNetwork(
csCluster *infrav1.CloudStackCluster,
) error {
// Get or create the isolated network itself and resolve details into passed custom resources.
net := isoNet.Network()
if err := c.ResolveNetwork(net); err != nil { // Doesn't exist, create isolated network.
network := isoNet.Network()
if err := c.ResolveNetwork(network); err != nil { // Doesn't exist, create isolated network.
if err = c.CreateIsolatedNetwork(fd, isoNet); err != nil {
return errors.Wrap(err, "creating a new isolated network")
}
} else { // Network existed and was resolved. Set ID on isoNet CloudStackIsolatedNetwork in case it only had name set.
isoNet.Spec.ID = net.ID
} else {
// Network existed and was resolved. Set ID on isoNet CloudStackIsolatedNetwork in case it only had name set.
isoNet.Spec.ID = network.ID
isoNet.Spec.CIDR = network.CIDR
}

// Tag the created network.
Expand Down Expand Up @@ -910,3 +924,24 @@ func (c *client) DisassociatePublicIPAddress(ipAddressID string) error {

return err
}

// parseCIDR parses a CIDR-formatted string into the components required for CreateNetwork.
func parseCIDR(cidr string) (map[string]string, error) {
m := make(map[string]string, 4)

ip, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
return nil, fmt.Errorf("unable to parse cidr %s: %s", cidr, err)
}

msk := ipnet.Mask
sub := ip.Mask(msk)

m["netmask"] = fmt.Sprintf("%d.%d.%d.%d", msk[0], msk[1], msk[2], msk[3])
m["gateway"] = fmt.Sprintf("%d.%d.%d.%d", sub[0], sub[1], sub[2], sub[3]+1)
m["startip"] = fmt.Sprintf("%d.%d.%d.%d", sub[0], sub[1], sub[2], sub[3]+2)
m["endip"] = fmt.Sprintf("%d.%d.%d.%d",
sub[0]+(0xff-msk[0]), sub[1]+(0xff-msk[1]), sub[2]+(0xff-msk[2]), sub[3]+(0xff-msk[3]-1))

return m, nil
}
1 change: 0 additions & 1 deletion pkg/cloud/isolated_network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,6 @@ var _ = Describe("Network", func() {
dummies.CSISONet1.Status.PublicIPID = dummies.PublicIPID
dummies.CSISONet1.Status.PublicIPAddress = "10.11.12.13/32"
dummies.CSISONet1.Status.APIServerLoadBalancer.IPAddressID = dummies.LoadBalancerIPID
dummies.CSISONet1.Status.CIDR = "10.1.0.0/24"
fs.EXPECT().NewListFirewallRulesParams().Return(&csapi.ListFirewallRulesParams{})
fs.EXPECT().ListFirewallRules(gomock.Any()).Return(
&csapi.ListFirewallRulesResponse{FirewallRules: []*csapi.FirewallRule{}}, nil)
Expand Down
2 changes: 2 additions & 0 deletions pkg/cloud/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func (c *client) ResolveNetwork(net *infrav1.Network) (retErr error) {
} else { // Got netID from the network's name.
net.ID = netDetails.Id
net.Type = netDetails.Type
net.CIDR = netDetails.Cidr
net.Domain = netDetails.Networkdomain
return nil
}
Expand All @@ -67,6 +68,7 @@ func (c *client) ResolveNetwork(net *infrav1.Network) (retErr error) {
net.Name = netDetails.Name
net.ID = netDetails.Id
net.Type = netDetails.Type
net.CIDR = netDetails.Cidr
net.Domain = netDetails.Networkdomain
return nil
}
Expand Down

0 comments on commit cdf9f2a

Please sign in to comment.