Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting of first ip for network reservations #274

Merged
merged 1 commit into from
Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
FEATURES:

* **New Resource**: `opennebula_virtual_network_address_range` (#279)
* resources/opennebula_virtual_network: add attributes `reservation_first_ip` and `reservation_ar_id` (#274)

ENHANCEMENTS:

Expand Down
143 changes: 91 additions & 52 deletions opennebula/resource_opennebula_virtual_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ import (

var defaultVNetTimeout = time.Duration(5) * time.Minute

type Template string

const (
ReservationSize Template = "SIZE"
ReservationFirstIP Template = "IP"
ReservationAddressRangeID Template = "AR_ID"
)

func resourceOpennebulaVirtualNetwork() *schema.Resource {
return &schema.Resource{
CreateContext: resourceOpennebulaVirtualNetworkCreate,
Expand Down Expand Up @@ -98,21 +106,21 @@ func resourceOpennebulaVirtualNetwork() *schema.Resource {
Optional: true,
Computed: true,
Description: "Name of the bridge interface to which the vnet should be associated",
ConflictsWith: []string{"reservation_vnet", "reservation_size"},
ConflictsWith: []string{"reservation_vnet", "reservation_size", "reservation_ar_id", "reservation_first_ip"},
},
"physical_device": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "Name of the physical device to which the vnet should be associated",
ConflictsWith: []string{"reservation_vnet", "reservation_size"},
ConflictsWith: []string{"reservation_vnet", "reservation_size", "reservation_ar_id", "reservation_first_ip"},
},
"type": {
Type: schema.TypeString,
Optional: true,
Default: "bridge",
Description: "Type of the Virtual Network: dummy, bridge, fw, ebtables, 802.1Q, vxlan, ovswitch. Default is 'bridge'",
ConflictsWith: []string{"reservation_vnet", "reservation_size"},
ConflictsWith: []string{"reservation_vnet", "reservation_size", "reservation_ar_id", "reservation_first_ip"},
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
validtypes := []string{"dummy", "bridge", "fw", "ebtables", "802.1Q", "vxlan", "ovswitch"}
value := v.(string)
Expand All @@ -129,7 +137,7 @@ func resourceOpennebulaVirtualNetwork() *schema.Resource {
Optional: true,
Computed: true,
Description: "List of cluster IDs hosting the virtual Network, if not set it uses the default cluster",
ConflictsWith: []string{"reservation_vnet", "reservation_size"},
ConflictsWith: []string{"reservation_vnet", "reservation_size", "reservation_ar_id", "reservation_first_ip"},
Elem: &schema.Schema{
Type: schema.TypeInt,
},
Expand All @@ -139,42 +147,42 @@ func resourceOpennebulaVirtualNetwork() *schema.Resource {
Optional: true,
Computed: true,
Description: "VLAN ID. Only if 'Type' is : 802.1Q, vxlan or ovswich and if 'automatic_vlan_id' is not set",
ConflictsWith: []string{"reservation_vnet", "reservation_size", "automatic_vlan_id"},
ConflictsWith: []string{"reservation_vnet", "reservation_size", "reservation_ar_id", "reservation_first_ip", "automatic_vlan_id"},
},
"automatic_vlan_id": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
Description: "If set, let OpenNebula to attribute VLAN ID",
ConflictsWith: []string{"reservation_vnet", "reservation_size", "vlan_id"},
ConflictsWith: []string{"reservation_vnet", "reservation_size", "reservation_ar_id", "reservation_first_ip", "vlan_id"},
},
"mtu": {
Type: schema.TypeInt,
Optional: true,
Description: "MTU of the vnet (defaut: 1500)",
Default: 1500,
ConflictsWith: []string{"reservation_vnet", "reservation_size"},
ConflictsWith: []string{"reservation_vnet", "reservation_size", "reservation_ar_id", "reservation_first_ip"},
},
"guest_mtu": {
Type: schema.TypeInt,
Optional: true,
Description: "MTU of the Guest interface. Must be lower or equal to 'mtu' (defaut: 1500)",
Default: 1500,
ConflictsWith: []string{"reservation_vnet", "reservation_size"},
ConflictsWith: []string{"reservation_vnet", "reservation_size", "reservation_ar_id", "reservation_first_ip"},
},
"gateway": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "Gateway IP if necessary",
ConflictsWith: []string{"reservation_vnet", "reservation_size"},
ConflictsWith: []string{"reservation_vnet", "reservation_size", "reservation_ar_id", "reservation_first_ip"},
},
"network_mask": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "Network Mask",
ConflictsWith: []string{"reservation_vnet", "reservation_size"},
ConflictsWith: []string{"reservation_vnet", "reservation_size", "reservation_ar_id", "reservation_first_ip"},
},
"network_address": {
Type: schema.TypeString,
Expand All @@ -195,14 +203,14 @@ func resourceOpennebulaVirtualNetwork() *schema.Resource {
Optional: true,
Computed: true,
Description: "DNS IP if necessary",
ConflictsWith: []string{"reservation_vnet", "reservation_size"},
ConflictsWith: []string{"reservation_vnet", "reservation_size", "reservation_ar_id", "reservation_first_ip"},
},
"ar": {
Type: schema.TypeSet,
Optional: true,
MinItems: 1,
Description: "List of Address Ranges to be part of the Virtual Network",
ConflictsWith: []string{"reservation_vnet", "reservation_size"},
ConflictsWith: []string{"reservation_vnet", "reservation_size", "reservation_ar_id", "reservation_first_ip"},
Elem: &schema.Resource{
Schema: ARFields(),
},
Expand All @@ -212,7 +220,7 @@ func resourceOpennebulaVirtualNetwork() *schema.Resource {
Type: schema.TypeList,
Optional: true,
Description: "List of IPs to be held the VNET",
ConflictsWith: []string{"reservation_vnet", "reservation_size"},
ConflictsWith: []string{"reservation_vnet", "reservation_size", "reservation_ar_id", "reservation_first_ip"},
Elem: &schema.Schema{
Type: schema.TypeString,
},
Expand All @@ -221,17 +229,29 @@ func resourceOpennebulaVirtualNetwork() *schema.Resource {
"reservation_vnet": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
Description: "Create a reservation from this VNET ID",
ConflictsWith: []string{"bridge", "physical_device", "ar", "hold_ips", "type", "vlan_id", "automatic_vlan_id", "mtu", "clusters", "dns", "gateway", "network_mask", "network_address", "search_domain"},
Default: -1,
},
"reservation_size": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
Description: "Reserve this many IPs from reservation_vnet",
ConflictsWith: []string{"bridge", "physical_device", "ar", "hold_ips", "type", "vlan_id", "automatic_vlan_id", "mtu", "clusters", "dns", "gateway", "network_mask", "network_address", "search_domain"},
},
"reservation_first_ip": {
Type: schema.TypeString,
Optional: true,
Description: "First IP of the reservation",
ConflictsWith: []string{"bridge", "physical_device", "ar", "hold_ips", "type", "vlan_id", "automatic_vlan_id", "mtu", "clusters", "dns", "gateway", "network_mask"},
},
"reservation_ar_id": {
Type: schema.TypeInt,
Optional: true,
Default: -1,
Description: "Address Range ID to be used for the reservation",
ConflictsWith: []string{"bridge", "physical_device", "ar", "hold_ips", "type", "vlan_id", "automatic_vlan_id", "mtu", "clusters", "dns", "gateway", "network_mask"},
},
"security_groups": {
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -397,52 +417,55 @@ func resourceOpennebulaVirtualNetworkCreate(ctx context.Context, d *schema.Resou
var vnc *goca.VirtualNetworkController
var diags diag.Diagnostics

reservationVNet := d.Get("reservation_vnet").(int)

// VNET reservation
if rvnet, ok := d.GetOk("reservation_vnet"); ok {
reservation_vnet := rvnet.(int)
reservation_name := d.Get("name").(string)
reservation_size := d.Get("reservation_size").(int)
if reservationVNet > -1 {
reservationTemplate := dyn.NewTemplate()

if reservation_vnet < 0 {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Wrong ID for reservation VNET",
Detail: "Reservation VNET ID must be greater than 0",
})
} else if reservation_size <= 0 {
if reservationName, ok := d.GetOk("name"); ok {
reservationTemplate.AddPair("NAME", reservationName.(string))
}
if reservationFirstIP, ok := d.GetOk("reservation_first_ip"); ok {
reservationTemplate.AddPair("IP", reservationFirstIP.(string))
}
if reservationARID, ok := d.GetOk("reservation_ar_id"); ok && reservationARID != -1 {
reservationTemplate.AddPair("AR_ID", reservationARID.(int))
}
reservationSize, ok := d.GetOk("reservation_size")
if ok {
reservationTemplate.AddPair("SIZE", reservationSize.(int))
}

if reservationSize.(int) <= 0 {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Wrong size value",
Detail: "Reservation size must be strictly greater than 0",
})
}

if len(diags) > 0 {
return diags
}

//The API only takes ATTRIBUTE=VALUE for VNET reservations...
reservation_string := "SIZE=%d\nNAME=\"%s\""

// Get VNet Controller to reserve from
vnc = controller.VirtualNetwork(reservation_vnet)
vnc = controller.VirtualNetwork(reservationVNet)

// Call .Info to check if the Network exists
_, err := vnc.Info(false)
if err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Failed to retrieve informations",
Detail: fmt.Sprintf("Virtual network (ID: %d) reservation: %s", reservation_vnet, err),
Detail: fmt.Sprintf("Virtual network (ID: %d) reservation: %s", reservationVNet, err),
})
return diags
}

rID, err := vnc.Reserve(fmt.Sprintf(reservation_string, reservation_size, reservation_name))
rID, err := vnc.Reserve(reservationTemplate.String())
if err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Failed to reserve network addresses",
Detail: fmt.Sprintf("Virtual network (ID: %d) reservation: %s", reservation_vnet, err),
Detail: fmt.Sprintf("Virtual network (ID: %d) reservation: %s", reservationVNet, err),
})
return diags
}
Expand All @@ -452,6 +475,7 @@ func resourceOpennebulaVirtualNetworkCreate(ctx context.Context, d *schema.Resou
// TODO: fix it after 5.10 release
// Force the "decrypt" bool to false to keep ONE 5.8 behavior
vnet, err := vnc.Info(false)

if err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Expand Down Expand Up @@ -872,27 +896,14 @@ func resourceOpennebulaVirtualNetworkRead(ctx context.Context, d *schema.Resourc
d.Set("gname", vn.GName)
d.Set("bridge", vn.Bridge)
d.Set("physical_device", vn.PhyDev)

if vn.VlanID != "" {
d.Set("vlan_id", vn.VlanID)
}
if vn.VlanIDAutomatic == "1" {
d.Set("automatic_vlan_id", true)
}
d.Set("type", vn.VNMad)

if len(vn.ParentNetworkID) > 0 {
parentNetworkID, err := strconv.ParseInt(vn.ParentNetworkID, 10, 0)
if err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Failed to parse parent network ID",
Detail: fmt.Sprintf("virtual network (ID: %s): %s", d.Id(), err),
})
return diags
}
d.Set("reservation_vnet", parentNetworkID)
}

d.Set("permissions", permissionsUnixString(*vn.Permissions))

err = flattenVnetTemplate(d, &vn.Template)
Expand All @@ -918,6 +929,33 @@ func resourceOpennebulaVirtualNetworkRead(ctx context.Context, d *schema.Resourc
}
}

if len(vn.ParentNetworkID) > 0 {
parentNetworkID, err := strconv.ParseInt(vn.ParentNetworkID, 10, 0)
if err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Failed to parse parent network ID",
Detail: fmt.Sprintf("virtual network (ID: %s): %s", d.Id(), err),
})
return diags
}
d.Set("reservation_vnet", parentNetworkID)
if len(vn.ARs) > 0 {
arID, err := strconv.ParseInt(vn.ARs[0].ID, 10, 0)
if err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Failed to parse address range ID",
Detail: fmt.Sprintf("virtual network (ID: %s): %s", d.Id(), err),
})
return diags
}
d.Set("reservation_ar_id", arID)
d.Set("reservation_size", vn.ARs[0].Size)
d.Set("reservation_first_ip", vn.ARs[0].IP)
}
}

if vn.Lock != nil {
d.Set("lock", LockLevelToString(vn.Lock.Locked))
}
Expand All @@ -929,7 +967,8 @@ func flattenVnetARs(d *schema.ResourceData, vn *vn.VirtualNetwork) error {

ARSet := make([]map[string]interface{}, 0, len(vn.ARs))
ARConfigs := d.Get("ar").(*schema.Set).List()

log.Printf("[INFO] ARs: %+v", vn.ARs)
log.Printf("[INFO] ARConfigs: %+v", ARConfigs)
for _, AR := range vn.ARs {

match := false
Expand Down Expand Up @@ -1261,7 +1300,7 @@ func resourceOpennebulaVirtualNetworkUpdate(ctx context.Context, d *schema.Resou
}

if d.HasChange("ar") {

log.Println("[DEBUG] AR changed")
old, new := d.GetChange("ar")
existingARsCfg := old.(*schema.Set).List()
newARsCfg := new.(*schema.Set).List()
Expand Down
9 changes: 6 additions & 3 deletions opennebula/resource_opennebula_virtual_network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ func TestAccVirtualNetwork(t *testing.T) {
ExpectNonEmptyPlan: true,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("opennebula_virtual_network.reservation", "name", "terravnetres"),
resource.TestCheckResourceAttr("opennebula_virtual_network.reservation", "reservation_size", "1"),
resource.TestCheckResourceAttr("opennebula_virtual_network.reservation", "reservation_size", "5"),
resource.TestCheckResourceAttr("opennebula_virtual_network.reservation", "reservation_first_ip", "172.16.100.115"),
resource.TestCheckResourceAttr("opennebula_virtual_network.reservation", "permissions", "660"),
resource.TestCheckResourceAttrSet("opennebula_virtual_network.reservation", "uid"),
resource.TestCheckResourceAttrSet("opennebula_virtual_network.reservation", "gid"),
Expand Down Expand Up @@ -447,8 +448,10 @@ resource "opennebula_virtual_network_address_range" "test4" {
resource "opennebula_virtual_network" "reservation" {
name = "terravnetres"
description = "my terraform vnet"
reservation_vnet = "${opennebula_virtual_network.test.id}"
reservation_size = 1
reservation_vnet = opennebula_virtual_network.test.id
reservation_size = 5
reservation_ar_id = opennebula_virtual_network_address_range.test.id
reservation_first_ip = "172.16.100.115"
security_groups = [0]
permissions = 660
}
Expand Down
8 changes: 6 additions & 2 deletions website/docs/r/virtual_network.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ resource "opennebula_virtual_network" "example" {
description = "Terraform vnet"
reservation_vnet = 394
reservation_size = 5
reservation_ar_id = 1
reservation_first_ip = "172.16.100.105"
security_groups = [0]
}
```
Expand Down Expand Up @@ -64,8 +66,10 @@ The following arguments are supported:
* `name` - (Required) The name of the virtual network.
* `description` - (Optional) Description of the virtual network.
* `permissions` - (Optional) Permissions applied on virtual network. Defaults to the UMASK in OpenNebula (in UNIX Format: owner-group-other => Use-Manage-Admin).
* `reservation_vnet` - (Optional) ID of the parent virtual network to reserve from. Conflicts with all parameters excepted `name`, `description`, `permissions`, `security_groups` and `group`.
* `reservation_size` - (Optional) Size (in address) reserved. Conflicts with all parameters excepted `name`, `description`, `permissions`, `security_groups` and `group`.
* `reservation_vnet` - (Optional) ID of the parent virtual network to reserve from. Conflicts with all parameters except `name`, `description`, `permissions`, `security_groups`, `group`, `reservation_ar_id`, `reservation_first_ip` and `reservation_size`.
* `reservation_size` - (Optional) Size (in address) reserved. Conflicts with all parameters except `name`, `description`, `permissions`, `security_groups`, `group`, `reservation_ar_id`, `reservation_first_ip` and `reservation_vnet`.
* `reservation_ar_id` - (Optional) ID of the address range from which to reserve the addresses. Conflicts with all parameters except `name`, `description`, `permissions`, `security_groups`, `group`, `reservation_size`, `reservation_first_ip` and `reservation_vnet`.
* `reservation_first_ip` - (Optional) The first IPv4 address to start the reservation range. Conflicts with all parameters except `name`, `description`, `permissions`, `security_groups`, `group`, `reservation_ar_id`, `reservation_size` and `reservation_vnet`.
* `security_groups` - (Optional) List of security group IDs to apply on the virtual network.
* `bridge` - (Optional) Name of the bridge interface to which the virtual network should be associated. Conflicts with `reservation_vnet` and `reservation_size`.
* `physical_device` - (Optional) Name of the physical device interface to which the virtual network should be associated. Conflicts with `reservation_vnet` and `reservation_size`.
Expand Down