@@ -19,6 +19,7 @@ package cloud
19
19
import (
20
20
"encoding/base64"
21
21
"fmt"
22
+ "net"
22
23
"strconv"
23
24
"strings"
24
25
@@ -44,7 +45,15 @@ func setMachineDataFromVMMetrics(vmResponse *cloudstack.VirtualMachinesMetric, c
44
45
csMachine .Spec .ProviderID = ptr .To (fmt .Sprintf ("cloudstack:///%s" , vmResponse .Id ))
45
46
// InstanceID is later used as required parameter to destroy VM.
46
47
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
+ }
48
57
newInstanceState := vmResponse .State
49
58
if newInstanceState != csMachine .Status .InstanceState || (newInstanceState != "" && csMachine .Status .InstanceStateLastUpdated .IsZero ()) {
50
59
csMachine .Status .InstanceState = newInstanceState
@@ -302,38 +311,151 @@ func (c *client) CheckLimits(
302
311
return nil
303
312
}
304
313
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 ) {
306
369
net , count , err := c .cs .Network .GetNetworkByName (name , cloudstack .WithProject (c .user .Project .ID ))
307
370
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 )
309
372
}
310
373
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 )
312
375
}
313
- return net . Id , nil
376
+ return net , nil
314
377
}
315
378
316
379
func (c * client ) buildIPToNetworkList (csMachine * infrav1.CloudStackMachine ) ([]map [string ]string , error ) {
317
- ipToNetworkList := []map [string ]string {}
380
+ var ipToNetworkList []map [string ]string
318
381
319
382
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
327
386
}
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
331
391
}
392
+
332
393
ipToNetworkList = append (ipToNetworkList , entry )
333
394
}
395
+
334
396
return ipToNetworkList , nil
335
397
}
336
398
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
+
337
459
// DeployVM will create a VM instance,
338
460
// and sets the infrastructure machine spec and status accordingly.
339
461
func (c * client ) DeployVM (
@@ -355,14 +477,8 @@ func (c *client) DeployVM(
355
477
356
478
p := c .cs .VirtualMachine .NewDeployVirtualMachineParams (offering .Id , templateID , fd .Spec .Zone .ID )
357
479
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
366
482
}
367
483
368
484
setIfNotEmpty (csMachine .Name , p .SetName )
0 commit comments