@@ -19,6 +19,7 @@ package cloud
1919import (
2020 "encoding/base64"
2121 "fmt"
22+ "net"
2223 "strconv"
2324 "strings"
2425
@@ -44,7 +45,15 @@ func setMachineDataFromVMMetrics(vmResponse *cloudstack.VirtualMachinesMetric, c
4445 csMachine .Spec .ProviderID = ptr .To (fmt .Sprintf ("cloudstack:///%s" , vmResponse .Id ))
4546 // InstanceID is later used as required parameter to destroy VM.
4647 csMachine .Spec .InstanceID = ptr .To (vmResponse .Id )
47- csMachine .Status .Addresses = []corev1.NodeAddress {{Type : corev1 .NodeInternalIP , Address : vmResponse .Ipaddress }}
48+ csMachine .Status .Addresses = []corev1.NodeAddress {}
49+ for _ , nic := range vmResponse .Nic {
50+ if nic .Ipaddress != "" {
51+ csMachine .Status .Addresses = append (csMachine .Status .Addresses , corev1.NodeAddress {
52+ Type : corev1 .NodeInternalIP ,
53+ Address : nic .Ipaddress ,
54+ })
55+ }
56+ }
4857 newInstanceState := vmResponse .State
4958 if newInstanceState != csMachine .Status .InstanceState || (newInstanceState != "" && csMachine .Status .InstanceStateLastUpdated .IsZero ()) {
5059 csMachine .Status .InstanceState = newInstanceState
@@ -302,38 +311,151 @@ func (c *client) CheckLimits(
302311 return nil
303312}
304313
305- func (c * client ) resolveNetworkIDByName (name string ) (string , error ) {
314+ func (c * client ) isFreeIPAvailable (networkID , ip string ) (bool , error ) {
315+ params := c .cs .Address .NewListPublicIpAddressesParams ()
316+ params .SetNetworkid (networkID )
317+ params .SetAllocatedonly (false )
318+ params .SetForvirtualnetwork (false )
319+ params .SetListall (true )
320+
321+ if ip != "" {
322+ params .SetIpaddress (ip )
323+ }
324+
325+ resp , err := c .cs .Address .ListPublicIpAddresses (params )
326+ if err != nil {
327+ return false , errors .Wrapf (err , "failed to list public IP addresses for network %q" , networkID )
328+ }
329+
330+ for _ , addr := range resp .PublicIpAddresses {
331+ if addr .State == "Free" {
332+ return true , nil
333+ }
334+ }
335+
336+ return false , nil
337+ }
338+
339+ func (c * client ) buildIPEntry (resolvedNet * cloudstack.Network , ip string ) (map [string ]string , error ) {
340+ if ip != "" {
341+ if err := validateIPInCIDR (ip , resolvedNet .Cidr ); err != nil {
342+ return nil , err
343+ }
344+ }
345+
346+ if resolvedNet .Type == NetworkTypeShared {
347+ isAvailable , err := c .isFreeIPAvailable (resolvedNet .Id , ip )
348+ if err != nil {
349+ return nil , err
350+ }
351+ if ! isAvailable {
352+ if ip != "" {
353+ return nil , errors .Errorf ("IP %q is already allocated in network %q or out of range" , ip , resolvedNet .Id )
354+ }
355+ return nil , errors .Errorf ("no free IPs available in network %q" , resolvedNet .Id )
356+ }
357+ }
358+
359+ entry := map [string ]string {
360+ "networkid" : resolvedNet .Id ,
361+ }
362+ if ip != "" {
363+ entry ["ip" ] = ip
364+ }
365+ return entry , nil
366+ }
367+
368+ func (c * client ) resolveNetworkByName (name string ) (* cloudstack.Network , error ) {
306369 net , count , err := c .cs .Network .GetNetworkByName (name , cloudstack .WithProject (c .user .Project .ID ))
307370 if err != nil {
308- return "" , errors .Wrapf (err , "failed to look up network %q" , name )
371+ return nil , errors .Wrapf (err , "failed to look up network %q" , name )
309372 }
310373 if count != 1 {
311- return "" , errors .Errorf ("expected 1 network named %q, but got %d" , name , count )
374+ return nil , errors .Errorf ("expected 1 network named %q, but got %d" , name , count )
312375 }
313- return net . Id , nil
376+ return net , nil
314377}
315378
316379func (c * client ) buildIPToNetworkList (csMachine * infrav1.CloudStackMachine ) ([]map [string ]string , error ) {
317- ipToNetworkList := []map [string ]string {}
380+ var ipToNetworkList []map [string ]string
318381
319382 for _ , net := range csMachine .Spec .Networks {
320- networkID := net .ID
321- if networkID == "" {
322- var err error
323- networkID , err = c .resolveNetworkIDByName (net .Name )
324- if err != nil {
325- return nil , err
326- }
383+ resolvedNet , err := c .resolveNetwork (net )
384+ if err != nil {
385+ return nil , err
327386 }
328- entry := map [string ]string {"networkid" : networkID }
329- if net .IP != "" {
330- entry ["ip" ] = net .IP
387+
388+ entry , err := c .buildIPEntry (resolvedNet , net .IP )
389+ if err != nil {
390+ return nil , err
331391 }
392+
332393 ipToNetworkList = append (ipToNetworkList , entry )
333394 }
395+
334396 return ipToNetworkList , nil
335397}
336398
399+ func (c * client ) resolveNetwork (net infrav1.NetworkSpec ) (* cloudstack.Network , error ) {
400+ if net .ID == "" {
401+ return c .resolveNetworkByName (net .Name )
402+ }
403+
404+ resolvedNet , _ , err := c .cs .Network .GetNetworkByID (net .ID , cloudstack .WithProject (c .user .Project .ID ))
405+ if err != nil {
406+ return nil , errors .Wrapf (err , "failed to get network %q by ID" , net .ID )
407+ }
408+ return resolvedNet , nil
409+ }
410+
411+ func validateIPInCIDR (ipStr , cidrStr string ) error {
412+ ip := net .ParseIP (ipStr )
413+ if ip == nil {
414+ return errors .Errorf ("invalid IP address %q" , ipStr )
415+ }
416+
417+ _ , cidr , err := net .ParseCIDR (cidrStr )
418+ if err != nil {
419+ return errors .Wrapf (err , "invalid CIDR %q" , cidrStr )
420+ }
421+
422+ if ! cidr .Contains (ip ) {
423+ return errors .Errorf ("IP %q is not within network CIDR %q" , ipStr , cidrStr )
424+ }
425+
426+ return nil
427+ }
428+
429+ func (c * client ) configureNetworkParams (
430+ p * cloudstack.DeployVirtualMachineParams ,
431+ csMachine * infrav1.CloudStackMachine ,
432+ fd * infrav1.CloudStackFailureDomain ,
433+ ) error {
434+ if len (csMachine .Spec .Networks ) == 0 {
435+ if fd .Spec .Zone .Network .ID != "" {
436+ p .SetNetworkids ([]string {fd .Spec .Zone .Network .ID })
437+ }
438+ } else {
439+ firstNetwork := csMachine .Spec .Networks [0 ]
440+ zoneNet := fd .Spec .Zone .Network
441+
442+ if zoneNet .ID != "" && firstNetwork .ID != "" && firstNetwork .ID != zoneNet .ID {
443+ return errors .Errorf ("first network ID %q does not match zone network ID %q" , firstNetwork .ID , zoneNet .ID )
444+ }
445+ if zoneNet .Name != "" && firstNetwork .Name != "" && firstNetwork .Name != zoneNet .Name {
446+ return errors .Errorf ("first network name %q does not match zone network name %q" , firstNetwork .Name , zoneNet .Name )
447+ }
448+
449+ ipToNetworkList , err := c .buildIPToNetworkList (csMachine )
450+ if err != nil {
451+ return err
452+ }
453+ p .SetIptonetworklist (ipToNetworkList )
454+ }
455+
456+ return nil
457+ }
458+
337459// DeployVM will create a VM instance,
338460// and sets the infrastructure machine spec and status accordingly.
339461func (c * client ) DeployVM (
@@ -355,14 +477,8 @@ func (c *client) DeployVM(
355477
356478 p := c .cs .VirtualMachine .NewDeployVirtualMachineParams (offering .Id , templateID , fd .Spec .Zone .ID )
357479
358- if len (csMachine .Spec .Networks ) == 0 && fd .Spec .Zone .Network .ID != "" {
359- p .SetNetworkids ([]string {fd .Spec .Zone .Network .ID })
360- } else {
361- ipToNetworkList , err := c .buildIPToNetworkList (csMachine )
362- if err != nil {
363- return err
364- }
365- p .SetIptonetworklist (ipToNetworkList )
480+ if err := c .configureNetworkParams (p , csMachine , fd ); err != nil {
481+ return err
366482 }
367483
368484 setIfNotEmpty (csMachine .Name , p .SetName )
0 commit comments