Skip to content

Commit 00df013

Browse files
committed
Add VPC Support
1 parent d892f42 commit 00df013

10 files changed

+245
-3
lines changed

api/v1beta3/cloudstackfailuredomain_types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,20 @@ type Network struct {
5353

5454
// Cloudstack Network Name the cluster is built in.
5555
Name string `json:"name"`
56+
57+
// Cloudstack VPC the network belongs to.
58+
// +optional
59+
VPC VPC `json:"vpc,omitempty"`
60+
}
61+
62+
type VPC struct {
63+
// Cloudstack VPC ID of the network.
64+
// +optional
65+
ID string `json:"id,omitempty"`
66+
67+
// Cloudstack VPC Name of the network.
68+
// +optional
69+
Name string `json:"name"`
5670
}
5771

5872
// CloudStackZoneSpec specifies a Zone's details.

api/v1beta3/cloudstackisolatednetwork_types.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ type CloudStackIsolatedNetworkSpec struct {
3939

4040
// FailureDomainName -- the FailureDomain the network is placed in.
4141
FailureDomainName string `json:"failureDomainName"`
42+
43+
// VPC the network belongs to.
44+
// +optional
45+
VPC VPC `json:"vpc,omitempty"`
4246
}
4347

4448
// CloudStackIsolatedNetworkStatus defines the observed state of CloudStackIsolatedNetwork
@@ -57,7 +61,8 @@ func (n *CloudStackIsolatedNetwork) Network() *Network {
5761
return &Network{
5862
Name: n.Spec.Name,
5963
Type: "IsolatedNetwork",
60-
ID: n.Spec.ID}
64+
ID: n.Spec.ID,
65+
VPC: n.Spec.VPC}
6166
}
6267

6368
//+kubebuilder:object:root=true

api/v1beta3/zz_generated.deepcopy.go

Lines changed: 17 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_cloudstackclusters.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,16 @@ spec:
403403
description: Cloudstack Network Type the cluster is
404404
built in.
405405
type: string
406+
vpc:
407+
description: Cloudstack VPC the network belongs to.
408+
properties:
409+
id:
410+
description: Cloudstack VPC ID of the network.
411+
type: string
412+
name:
413+
description: Cloudstack VPC Name of the network.
414+
type: string
415+
type: object
406416
required:
407417
- name
408418
type: object

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,16 @@ spec:
180180
description: Cloudstack Network Type the cluster is built
181181
in.
182182
type: string
183+
vpc:
184+
description: Cloudstack VPC the network belongs to.
185+
properties:
186+
id:
187+
description: Cloudstack VPC ID of the network.
188+
type: string
189+
name:
190+
description: Cloudstack VPC Name of the network.
191+
type: string
192+
type: object
183193
required:
184194
- name
185195
type: object

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,16 @@ spec:
198198
name:
199199
description: Name.
200200
type: string
201+
vpc:
202+
description: VPC the network belongs to.
203+
properties:
204+
id:
205+
description: Cloudstack VPC ID of the network.
206+
type: string
207+
name:
208+
description: Cloudstack VPC Name of the network.
209+
type: string
210+
type: object
201211
required:
202212
- controlPlaneEndpoint
203213
- failureDomainName

pkg/cloud/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type Client interface {
4545
ZoneIFace
4646
IsoNetworkIface
4747
UserCredIFace
48+
VPCIface
4849
NewClientInDomainAndAccount(string, string, string) (Client, error)
4950
}
5051

pkg/cloud/isolated_network.go

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,18 @@ func (c *client) AssociatePublicIPAddress(
6767
csCluster.Spec.ControlPlaneEndpoint.Host = publicAddress.Ipaddress
6868
isoNet.Status.PublicIPID = publicAddress.Id
6969

70-
// Check if the address is already associated with the network.
71-
if publicAddress.Associatednetworkid == isoNet.Spec.ID {
70+
// Check if the address is already associated with the network or VPC.
71+
if publicAddress.Associatednetworkid == isoNet.Spec.ID || publicAddress.Vpcid == isoNet.Spec.VPC.ID {
7272
return nil
7373
}
7474

7575
// Public IP found, but not yet associated with network -- associate it.
7676
p := c.cs.Address.NewAssociateIpAddressParams()
7777
p.SetIpaddress(isoNet.Spec.ControlPlaneEndpoint.Host)
7878
p.SetNetworkid(isoNet.Spec.ID)
79+
if isoNet.Spec.VPC.ID != "" {
80+
p.SetVpcid(isoNet.Spec.VPC.ID)
81+
}
7982
setIfNotEmpty(c.user.Project.ID, p.SetProjectid)
8083
if _, err := c.cs.Address.AssociateIpAddress(p); err != nil {
8184
c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
@@ -100,10 +103,61 @@ func (c *client) CreateIsolatedNetwork(fd *infrav1.CloudStackFailureDomain, isoN
100103
return err
101104
}
102105

106+
// First, check if VPC is specified and handle it
107+
if isoNet.Spec.VPC.Name != "" || isoNet.Spec.VPC.ID != "" {
108+
// Try to resolve or create the VPC
109+
err := c.ResolveVPC(&isoNet.Spec.VPC)
110+
if err != nil {
111+
return errors.Wrap(err, "resolving VPC for isolated network")
112+
// }
113+
114+
// TODO: Handle VPC creation
115+
116+
// // VPC not found, need to create it
117+
// // First, get a VPC offering ID
118+
// vpcOfferingParams := c.cs.VPC.NewListVPCOfferingsParams()
119+
// vpcOfferingResp, err := c.cs.VPC.ListVPCOfferings(vpcOfferingParams)
120+
// if err != nil {
121+
// c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
122+
// return errors.Wrap(err, "listing VPC offerings")
123+
// }
124+
// if vpcOfferingResp.Count == 0 {
125+
// return errors.New("no VPC offerings available")
126+
// }
127+
128+
// // Use the first VPC offering
129+
// vpcOfferingID := vpcOfferingResp.VPCOfferings[0].Id
130+
131+
// // Create the VPC
132+
// vpcParams := c.cs.VPC.NewCreateVPCParams(isoNet.Spec.VPC.Name, vpcOfferingID, fd.Spec.Zone.ID)
133+
// vpcParams.SetDisplaytext(isoNet.Spec.VPC.Name)
134+
// setIfNotEmpty(c.user.Project.ID, vpcParams.SetProjectid)
135+
136+
// vpcResp, err := c.cs.VPC.CreateVPC(vpcParams)
137+
// if err != nil {
138+
// c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
139+
// return errors.Wrapf(err, "creating VPC with name %s", isoNet.Spec.VPC.Name)
140+
// }
141+
142+
// isoNet.Spec.VPC.ID = vpcResp.Id
143+
144+
// // Tag the VPC
145+
// if err := c.AddCreatedByCAPCTag(ResourceTypeVPC, isoNet.Spec.VPC.ID); err != nil {
146+
// return errors.Wrapf(err, "tagging VPC with ID %s", isoNet.Spec.VPC.ID)
147+
// }
148+
}
149+
}
150+
103151
// Do isolated network creation.
104152
p := c.cs.Network.NewCreateNetworkParams(isoNet.Spec.Name, offeringID, fd.Spec.Zone.ID)
105153
p.SetDisplaytext(isoNet.Spec.Name)
106154
setIfNotEmpty(c.user.Project.ID, p.SetProjectid)
155+
156+
// If VPC is specified, set the VPC ID for the network
157+
if isoNet.Spec.VPC.ID != "" {
158+
p.SetVpcid(isoNet.Spec.VPC.ID)
159+
}
160+
107161
resp, err := c.cs.Network.CreateNetwork(p)
108162
if err != nil {
109163
c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)

pkg/cloud/network.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ func (c *client) ResolveNetwork(net *infrav1.Network) (retErr error) {
6262
} else { // Got netID from the network's name.
6363
net.ID = netDetails.Id
6464
net.Type = netDetails.Type
65+
if netDetails.Vpcid != "" {
66+
net.VPC.ID = netDetails.Vpcid
67+
net.VPC.Name = netDetails.Vpcname
68+
}
6569
return nil
6670
}
6771

@@ -76,6 +80,10 @@ func (c *client) ResolveNetwork(net *infrav1.Network) (retErr error) {
7680
net.Name = netDetails.Name
7781
net.ID = netDetails.Id
7882
net.Type = netDetails.Type
83+
if netDetails.Vpcid != "" {
84+
net.VPC.ID = netDetails.Vpcid
85+
net.VPC.Name = netDetails.Vpcname
86+
}
7987
return nil
8088
}
8189

pkg/cloud/vpc.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cloud
18+
19+
import (
20+
"strings"
21+
22+
infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3"
23+
24+
"github.com/apache/cloudstack-go/v2/cloudstack"
25+
"github.com/pkg/errors"
26+
)
27+
28+
// ResourceTypeVPC is the type identifier for VPC resources.
29+
const ResourceTypeVPC = "VPC"
30+
31+
// VPCIface defines the interface for VPC operations.
32+
type VPCIface interface {
33+
ResolveVPC(*infrav1.VPC) error
34+
CreateVPC(*infrav1.VPC) error
35+
GetOrCreateVPC(*infrav1.VPC) error
36+
}
37+
38+
// ResolveVPC checks if the specified VPC exists by ID or name.
39+
// If it exists, it updates the VPC struct with the resolved ID or name.
40+
func (c *client) ResolveVPC(vpc *infrav1.VPC) error {
41+
if vpc == nil || (vpc.ID == "" && vpc.Name == "") {
42+
return nil
43+
}
44+
45+
// If VPC ID is provided, check if it exists
46+
if vpc.ID != "" {
47+
resp, count, err := c.cs.VPC.GetVPCByID(vpc.ID, cloudstack.WithProject(c.user.Project.ID))
48+
if err != nil {
49+
c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
50+
return errors.Wrapf(err, "failed to get VPC with ID %s", vpc.ID)
51+
}
52+
if count == 0 {
53+
return errors.Errorf("no VPC found with ID %s", vpc.ID)
54+
}
55+
vpc.Name = resp.Name
56+
return nil
57+
}
58+
59+
// If VPC name is provided, check if it exists
60+
resp, count, err := c.cs.VPC.GetVPCByName(vpc.Name, cloudstack.WithProject(c.user.Project.ID))
61+
if err != nil {
62+
c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
63+
return errors.Wrapf(err, "failed to get VPC with name %s", vpc.Name)
64+
}
65+
if count == 0 {
66+
return errors.Errorf("no VPC found with name %s", vpc.Name)
67+
}
68+
vpc.ID = resp.Id
69+
return nil
70+
}
71+
72+
// GetOrCreateVPC ensures a VPC exists for the given specification.
73+
// If the VPC doesn't exist, it creates a new one.
74+
func (c *client) GetOrCreateVPC(vpc *infrav1.VPC) error {
75+
if vpc == nil || (vpc.ID == "" && vpc.Name == "") {
76+
return nil
77+
}
78+
79+
// Try to resolve the VPC
80+
err := c.ResolveVPC(vpc)
81+
if err != nil {
82+
// If it's a "not found" error and we have a name, create the VPC
83+
if strings.Contains(err.Error(), "no VPC found") && vpc.Name != "" {
84+
return c.CreateVPC(vpc)
85+
}
86+
return err
87+
}
88+
89+
return nil
90+
}
91+
92+
// CreateVPC creates a new VPC in CloudStack.
93+
func (c *client) CreateVPC(vpc *infrav1.VPC) error {
94+
if vpc == nil || vpc.Name == "" {
95+
return errors.New("VPC name must be specified")
96+
}
97+
98+
// Get VPC offering ID
99+
p := c.cs.VPC.NewListVPCOfferingsParams()
100+
resp, err := c.cs.VPC.ListVPCOfferings(p)
101+
if err != nil {
102+
c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
103+
return errors.Wrap(err, "failed to list VPC offerings")
104+
}
105+
if resp.Count == 0 {
106+
return errors.New("no VPC offerings available")
107+
}
108+
109+
// Since the SDK's VPC creation API might have compatibility issues with different CloudStack versions,
110+
// we'll need to handle this in the implementation of the network creation rather than here.
111+
// For now, we'll just return a "not implemented" error, and handle VPC creation in the isolated network creation.
112+
return errors.New("creating VPC not directly implemented; handled in isolated network creation")
113+
}

0 commit comments

Comments
 (0)