@@ -31,6 +31,8 @@ import (
31
31
"github.com/pkg/errors"
32
32
corev1 "k8s.io/api/core/v1"
33
33
infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3"
34
+
35
+ netpkg "net"
34
36
)
35
37
36
38
type VMIface interface {
@@ -310,38 +312,163 @@ func (c *client) CheckLimits(
310
312
return nil
311
313
}
312
314
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 ) {
314
396
net , count , err := c .cs .Network .GetNetworkByName (name , cloudstack .WithProject (c .user .Project .ID ))
315
397
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 )
317
399
}
318
400
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 )
320
402
}
321
- return net . Id , nil
403
+ return net , nil
322
404
}
323
405
324
406
func (c * client ) buildIPToNetworkList (csMachine * infrav1.CloudStackMachine ) ([]map [string ]string , error ) {
325
- ipToNetworkList := []map [string ]string {}
407
+ var ipToNetworkList []map [string ]string
326
408
327
409
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 )
332
423
if err != nil {
333
424
return nil , err
334
425
}
335
426
}
336
- entry := map [string ]string {"networkid" : networkID }
337
- if net .IP != "" {
338
- entry ["ip" ] = net .IP
339
- }
427
+
340
428
ipToNetworkList = append (ipToNetworkList , entry )
341
429
}
430
+
342
431
return ipToNetworkList , nil
343
432
}
344
433
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
+
345
472
// DeployVM will create a VM instance,
346
473
// and sets the infrastructure machine spec and status accordingly.
347
474
func (c * client ) DeployVM (
@@ -366,6 +493,16 @@ func (c *client) DeployVM(
366
493
if len (csMachine .Spec .Networks ) == 0 && fd .Spec .Zone .Network .ID != "" {
367
494
p .SetNetworkids ([]string {fd .Spec .Zone .Network .ID })
368
495
} 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
+
369
506
ipToNetworkList , err := c .buildIPToNetworkList (csMachine )
370
507
if err != nil {
371
508
return err
0 commit comments