@@ -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
3638type 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
324406func (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.
347474func (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