Skip to content

Commit 5fe0666

Browse files
Add default network checks and more IP validations
1 parent 8a6e4fe commit 5fe0666

File tree

1 file changed

+150
-13
lines changed

1 file changed

+150
-13
lines changed

pkg/cloud/instance.go

Lines changed: 150 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import (
3131
"github.com/pkg/errors"
3232
corev1 "k8s.io/api/core/v1"
3333
infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3"
34+
35+
netpkg "net"
3436
)
3537

3638
type VMIface interface {
@@ -310,38 +312,163 @@ func (c *client) CheckLimits(
310312
return nil
311313
}
312314

313-
func (c *client) resolveNetworkIDByName(name string) (string, error) {
315+
func (c *client) isIpAvailableInNetwork(ip, networkID string) (bool, error) {
316+
params := c.cs.Address.NewListPublicIpAddressesParams()
317+
params.SetNetworkid(networkID)
318+
params.SetIpaddress(ip)
319+
params.SetAllocatedonly(false)
320+
params.SetForvirtualnetwork(false)
321+
params.SetListall(true)
322+
323+
resp, err := c.cs.Address.ListPublicIpAddresses(params)
324+
if err != nil {
325+
return false, errors.Wrapf(err, "failed to list public IP addresses for network %q", networkID)
326+
}
327+
328+
for _, addr := range resp.PublicIpAddresses {
329+
if addr.State == "Free" {
330+
return true, nil
331+
}
332+
}
333+
334+
return false, nil
335+
}
336+
337+
func (c *client) hasFreeIPInNetwork(resolvedNet *cloudstack.Network) (bool, error) {
338+
params := c.cs.Address.NewListPublicIpAddressesParams()
339+
params.SetNetworkid(resolvedNet.Id)
340+
params.SetAllocatedonly(false)
341+
params.SetForvirtualnetwork(false)
342+
params.SetListall(true)
343+
344+
resp, err := c.cs.Address.ListPublicIpAddresses(params)
345+
if err != nil {
346+
return false, errors.Wrapf(err, "failed to check free IPs for network %q", resolvedNet.Id)
347+
}
348+
349+
for _, addr := range resp.PublicIpAddresses {
350+
if addr.State == "Free" {
351+
return true, nil
352+
}
353+
}
354+
355+
return false, nil
356+
}
357+
358+
func (c *client) buildStaticIPEntry(ip, networkID string, resolvedNet *cloudstack.Network) (map[string]string, error) {
359+
if resolvedNet.Type == "Shared" {
360+
if err := c.validateIPInCIDR(ip, resolvedNet, networkID); err != nil {
361+
return nil, err
362+
}
363+
364+
isAvailable, err := c.isIpAvailableInNetwork(ip, networkID)
365+
if err != nil {
366+
return nil, err
367+
}
368+
if !isAvailable {
369+
return nil, errors.Errorf("IP %q is already allocated in network %q or out of range", ip, networkID)
370+
}
371+
}
372+
373+
return map[string]string{
374+
"networkid": networkID,
375+
"ip": ip,
376+
}, nil
377+
}
378+
379+
func (c *client) buildDynamicIPEntry(resolvedNet *cloudstack.Network) (map[string]string, error) {
380+
if resolvedNet.Type == "Shared" {
381+
freeIPExists, err := c.hasFreeIPInNetwork(resolvedNet)
382+
if err != nil {
383+
return nil, err
384+
}
385+
if !freeIPExists {
386+
return nil, errors.Errorf("no free IPs available in network %q", resolvedNet.Id)
387+
}
388+
}
389+
390+
return map[string]string{
391+
"networkid": resolvedNet.Id,
392+
}, nil
393+
}
394+
395+
func (c *client) resolveNetworkByName(name string) (*cloudstack.Network, error) {
314396
net, count, err := c.cs.Network.GetNetworkByName(name, cloudstack.WithProject(c.user.Project.ID))
315397
if err != nil {
316-
return "", errors.Wrapf(err, "failed to look up network %q", name)
398+
return nil, errors.Wrapf(err, "failed to look up network %q", name)
317399
}
318400
if count != 1 {
319-
return "", errors.Errorf("expected 1 network named %q, but got %d", name, count)
401+
return nil, errors.Errorf("expected 1 network named %q, but got %d", name, count)
320402
}
321-
return net.Id, nil
403+
return net, nil
322404
}
323405

324406
func (c *client) buildIPToNetworkList(csMachine *infrav1.CloudStackMachine) ([]map[string]string, error) {
325-
ipToNetworkList := []map[string]string{}
407+
var ipToNetworkList []map[string]string
326408

327409
for _, net := range csMachine.Spec.Networks {
328-
networkID := net.ID
329-
if networkID == "" {
330-
var err error
331-
networkID, err = c.resolveNetworkIDByName(net.Name)
410+
networkID, resolvedNet, err := c.resolveNetworkReference(net)
411+
if err != nil {
412+
return nil, err
413+
}
414+
415+
var entry map[string]string
416+
if net.IP != "" {
417+
entry, err = c.buildStaticIPEntry(net.IP, networkID, resolvedNet)
418+
if err != nil {
419+
return nil, err
420+
}
421+
} else {
422+
entry, err = c.buildDynamicIPEntry(resolvedNet)
332423
if err != nil {
333424
return nil, err
334425
}
335426
}
336-
entry := map[string]string{"networkid": networkID}
337-
if net.IP != "" {
338-
entry["ip"] = net.IP
339-
}
427+
340428
ipToNetworkList = append(ipToNetworkList, entry)
341429
}
430+
342431
return ipToNetworkList, nil
343432
}
344433

434+
func (c *client) resolveNetworkReference(net infrav1.NetworkSpec) (string, *cloudstack.Network, error) {
435+
if net.ID == "" {
436+
resolvedNet, err := c.resolveNetworkByName(net.Name)
437+
if err != nil {
438+
return "", nil, err
439+
}
440+
return resolvedNet.Id, resolvedNet, nil
441+
}
442+
443+
resolvedNet, _, err := c.cs.Network.GetNetworkByID(net.ID, cloudstack.WithProject(c.user.Project.ID))
444+
if err != nil {
445+
return "", nil, errors.Wrapf(err, "failed to get network %q by ID", net.ID)
446+
}
447+
return net.ID, resolvedNet, nil
448+
}
449+
450+
func (c *client) validateIPInCIDR(ipStr string, net *cloudstack.Network, netID string) error {
451+
if net == nil {
452+
return errors.Errorf("network details not found for validation")
453+
}
454+
455+
ip := netpkg.ParseIP(ipStr)
456+
if ip == nil {
457+
return errors.Errorf("invalid IP address %q", ipStr)
458+
}
459+
460+
_, cidr, err := netpkg.ParseCIDR(net.Cidr)
461+
if err != nil {
462+
return errors.Wrapf(err, "invalid CIDR %q for network %q", net.Cidr, netID)
463+
}
464+
465+
if !cidr.Contains(ip) {
466+
return errors.Errorf("IP %q is not within network CIDR %q", ipStr, net.Cidr)
467+
}
468+
469+
return nil
470+
}
471+
345472
// DeployVM will create a VM instance,
346473
// and sets the infrastructure machine spec and status accordingly.
347474
func (c *client) DeployVM(
@@ -366,6 +493,16 @@ func (c *client) DeployVM(
366493
if len(csMachine.Spec.Networks) == 0 && fd.Spec.Zone.Network.ID != "" {
367494
p.SetNetworkids([]string{fd.Spec.Zone.Network.ID})
368495
} else {
496+
firstNetwork := csMachine.Spec.Networks[0]
497+
zoneNet := fd.Spec.Zone.Network
498+
499+
if zoneNet.ID != "" && firstNetwork.ID != "" && firstNetwork.ID != zoneNet.ID {
500+
return errors.Errorf("first network ID %q does not match zone network ID %q", firstNetwork.ID, zoneNet.ID)
501+
}
502+
if zoneNet.Name != "" && firstNetwork.Name != "" && firstNetwork.Name != zoneNet.Name {
503+
return errors.Errorf("first network name %q does not match zone network name %q", firstNetwork.Name, zoneNet.Name)
504+
}
505+
369506
ipToNetworkList, err := c.buildIPToNetworkList(csMachine)
370507
if err != nil {
371508
return err

0 commit comments

Comments
 (0)