From 46cd958744e1cfb57346b35b4e5e25b8866ed857 Mon Sep 17 00:00:00 2001
From: Aashiq-J <122446118+Aashiq-J@users.noreply.github.com>
Date: Mon, 11 Nov 2024 19:11:47 +0530
Subject: [PATCH] feat: add support for [Virtual Network
Interface](https://cloud.ibm.com/docs/vpc?topic=vpc-vni-about)
- The
module now creates VSI using the next gen virtual network interface by
default, these VNIs are created independent of the VSIs.
- *IMPORTANT*
When upgrading from a previous version, VSIs may be destroyed and recreated.
To prevent re-creation or to use the legacy network interface, set
`var.use_legacy_network_interface` to `true`. (#737)
---
README.md | 7 ++
examples/complete/main.tf | 44 ++++++----
main.tf | 179 ++++++++++++++++++++++++++++++++++----
variables.tf | 13 +++
4 files changed, 205 insertions(+), 38 deletions(-)
diff --git a/README.md b/README.md
index a8a91f0e..52219051 100644
--- a/README.md
+++ b/README.md
@@ -167,6 +167,7 @@ No modules.
|------|------|
| [ibm_iam_authorization_policy.block_storage_policy](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_authorization_policy) | resource |
| [ibm_is_floating_ip.secondary_fip](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_floating_ip) | resource |
+| [ibm_is_floating_ip.vni_secondary_fip](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_floating_ip) | resource |
| [ibm_is_floating_ip.vsi_fip](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_floating_ip) | resource |
| [ibm_is_instance.vsi](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_instance) | resource |
| [ibm_is_lb.lb](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_lb) | resource |
@@ -176,7 +177,11 @@ No modules.
| [ibm_is_lb_pool_member.nlb_pool_members](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_lb_pool_member) | resource |
| [ibm_is_security_group.security_group](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_security_group) | resource |
| [ibm_is_security_group_rule.security_group_rules](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_security_group_rule) | resource |
+| [ibm_is_subnet_reserved_ip.secondary_vni_ip](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_subnet_reserved_ip) | resource |
+| [ibm_is_subnet_reserved_ip.secondary_vsi_ip](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_subnet_reserved_ip) | resource |
| [ibm_is_subnet_reserved_ip.vsi_ip](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_subnet_reserved_ip) | resource |
+| [ibm_is_virtual_network_interface.primary_vni](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_virtual_network_interface) | resource |
+| [ibm_is_virtual_network_interface.secondary_vni](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_virtual_network_interface) | resource |
| [ibm_is_volume.volume](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_volume) | resource |
| [time_sleep.wait_for_authorization_policy](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource |
| [ibm_is_snapshot.snapshots_from_group](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/data-sources/is_snapshot) | data source |
@@ -202,6 +207,7 @@ No modules.
| [manage\_reserved\_ips](#input\_manage\_reserved\_ips) | Set to `true` if you want this terraform module to manage the reserved IP addresses that are assigned to VSI instances. If this option is enabled, when any VSI is recreated it should retain its original IP. | `bool` | `false` | no |
| [placement\_group\_id](#input\_placement\_group\_id) | Unique Identifier of the Placement Group for restricting the placement of the instance, default behaviour is placement on any host | `string` | `null` | no |
| [prefix](#input\_prefix) | The IBM Cloud platform API key needed to deploy IAM enabled resources | `string` | n/a | yes |
+| [primary\_vni\_additional\_ip\_count](#input\_primary\_vni\_additional\_ip\_count) | The number of secondary reversed IPs to attach to a Virtual Network Interface (VNI). Additional IPs are created only if `manage_reserved_ips` is set to true. | `number` | `0` | no |
| [resource\_group\_id](#input\_resource\_group\_id) | ID of resource group to create VSI and block storage volumes. If you wish to create the block storage volumes in a different resource group, you can optionally set that directly in the 'block\_storage\_volumes' variable. | `string` | n/a | yes |
| [secondary\_allow\_ip\_spoofing](#input\_secondary\_allow\_ip\_spoofing) | Allow IP spoofing on additional network interfaces | `bool` | `false` | no |
| [secondary\_floating\_ips](#input\_secondary\_floating\_ips) | List of secondary interfaces to add floating ips | `list(string)` | `[]` | no |
@@ -216,6 +222,7 @@ No modules.
| [subnets](#input\_subnets) | A list of subnet IDs where VSI will be deployed |
list(| n/a | yes | | [tags](#input\_tags) | List of tags to apply to resources created by this module. | `list(string)` | `[]` | no | | [use\_boot\_volume\_key\_as\_default](#input\_use\_boot\_volume\_key\_as\_default) | Set to true to use the key specified in the `boot_volume_encryption_key` input as default for all volumes, overriding any key value that may be specified in the `encryption_key` option of the `block_storage_volumes` input variable. If set to `false`, the value passed for the `encryption_key` option of the `block_storage_volumes` will be used instead. | `bool` | `false` | no | +| [use\_legacy\_network\_interface](#input\_use\_legacy\_network\_interface) | Set this to true to use legacy network interface for the created instances. | `bool` | `false` | no | | [use\_static\_boot\_volume\_name](#input\_use\_static\_boot\_volume\_name) | Sets the boot volume name for each VSI to a static name in the format `{hostname}_boot`, instead of a random name. Set this to `true` to have a consistent boot volume name even when VSIs are recreated. | `bool` | `false` | no | | [user\_data](#input\_user\_data) | User data to initialize VSI deployment | `string` | n/a | yes | | [vpc\_id](#input\_vpc\_id) | ID of VPC | `string` | n/a | yes | diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 3d126c83..6d99e273 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -160,25 +160,31 @@ locals { } module "slz_vsi" { - source = "../../" - resource_group_id = module.resource_group.resource_group_id - image_id = var.image_id - create_security_group = false - tags = var.resource_tags - access_tags = var.access_tags - subnets = module.slz_vpc.subnet_zone_list - vpc_id = module.slz_vpc.vpc_id - prefix = var.prefix - placement_group_id = ibm_is_placement_group.placement_group.id - machine_type = "cx2-2x4" - user_data = null - boot_volume_encryption_key = module.key_protect_all_inclusive.keys["slz-vsi.${var.prefix}-vsi"].crn - kms_encryption_enabled = true - existing_kms_instance_guid = module.key_protect_all_inclusive.kms_guid - vsi_per_subnet = 1 - ssh_key_ids = [local.ssh_key_id] - secondary_subnets = local.secondary_subnet_zone_list - secondary_security_groups = local.secondary_security_groups + source = "../../" + resource_group_id = module.resource_group.resource_group_id + image_id = var.image_id + create_security_group = false + tags = var.resource_tags + access_tags = var.access_tags + subnets = module.slz_vpc.subnet_zone_list + vpc_id = module.slz_vpc.vpc_id + prefix = var.prefix + placement_group_id = ibm_is_placement_group.placement_group.id + machine_type = "cx2-2x4" + user_data = null + boot_volume_encryption_key = module.key_protect_all_inclusive.keys["slz-vsi.${var.prefix}-vsi"].crn + kms_encryption_enabled = true + existing_kms_instance_guid = module.key_protect_all_inclusive.kms_guid + vsi_per_subnet = 1 + primary_vni_additional_ip_count = 2 + ssh_key_ids = [local.ssh_key_id] + secondary_subnets = local.secondary_subnet_zone_list + secondary_security_groups = local.secondary_security_groups + # Create a floating IPs for the additional VNI + secondary_floating_ips = [ + for subnet in local.secondary_subnet_zone_list : + subnet.name + ] # Create a floating IP for each virtual server created enable_floating_ip = true secondary_use_vsi_security_group = var.secondary_use_vsi_security_group diff --git a/main.tf b/main.tf index 98cc8849..c4176a74 100644 --- a/main.tf +++ b/main.tf @@ -42,7 +42,24 @@ locals { server.name => server } - secondary_fip_list = flatten([ + # List of additional private IP addresses to bind to the primary virtual network interface. + secondary_reserved_ips_list = flatten([ + for count in range(var.primary_vni_additional_ip_count) : [ + for vsi_key, vsi_value in local.vsi_map : + { + name = "${vsi_key}-${count}" + subnet_id = vsi_value.subnet_id + } + ] + ]) + + secondary_reserved_ips_map = { + for ip in local.secondary_reserved_ips_list : + ip.name => ip + } + + # Old approach to create floating IPs for the secondary network interface. + legacy_secondary_fip_list = var.use_legacy_network_interface ? flatten([ # For each interface in list of floating ips for interface in var.secondary_floating_ips : [ @@ -55,7 +72,24 @@ locals { target = instance.network_interfaces[index(var.secondary_subnets[*].name, interface)].id } ] - ]) + ]) : [] + + # List of secondary Virtual network interface for which floating IPs needs to be added. + secondary_fip_list = !var.use_legacy_network_interface && length(var.secondary_floating_ips) != 0 ? flatten([ + for instance in ibm_is_instance.vsi : [ + for network_attachment in instance.network_attachments : + network_attachment if contains([for subnet in var.secondary_floating_ips : subnet], network_attachment.name) + ] + ]) : [] + + secondary_fip_map = { + for vni in local.secondary_fip_list : + vni.name => { + vni_name = vni.virtual_network_interface[0].name + subnet_name = vni.name + vni_id = vni.virtual_network_interface[0].id + } + } # determine snapshot in following order: input variable -> from consistency group -> null (none) vsi_boot_volume_snapshot_id = try(coalesce(var.boot_volume_snapshot_id, local.consistency_group_boot_snapshot_id), null) @@ -76,6 +110,65 @@ data "ibm_is_vpc" "vpc" { identifier = var.vpc_id } +############################################################################## +# Create Virtual Network Interface +############################################################################## +resource "ibm_is_virtual_network_interface" "primary_vni" { + for_each = { for vsi_key, vsi_value in local.vsi_map : vsi_key => vsi_value if !var.use_legacy_network_interface } + name = "${each.key}-vni" + subnet = each.value.subnet_id + security_groups = flatten([ + (var.create_security_group ? [ibm_is_security_group.security_group[var.security_group.name].id] : []), + var.security_group_ids, + (var.create_security_group == false && length(var.security_group_ids) == 0 ? [data.ibm_is_vpc.vpc.default_security_group] : []), + ]) + allow_ip_spoofing = var.allow_ip_spoofing + auto_delete = false + enable_infrastructure_nat = true + dynamic "primary_ip" { + for_each = var.manage_reserved_ips ? [1] : [] + content { + reserved_ip = ibm_is_subnet_reserved_ip.vsi_ip[each.value.name].reserved_ip + } + } + dynamic "ips" { + for_each = var.primary_vni_additional_ip_count > 0 ? { for count in range(var.primary_vni_additional_ip_count) : count => count } : {} + content { + reserved_ip = ibm_is_subnet_reserved_ip.secondary_vsi_ip["${each.value.name}-${ips.key}"].reserved_ip + } + } +} + +resource "ibm_is_virtual_network_interface" "secondary_vni" { + for_each = { for k in var.secondary_subnets : k.zone => k if !var.use_legacy_network_interface } + name = each.value.name + subnet = each.value.id + # If security_groups is empty(list is len(0)) then default list to data.ibm_is_vpc.vpc.default_security_group. + # If list is empty it will fail on reapply as when vsi is passed an empty security group list it will attach the default security group. + allow_ip_spoofing = var.secondary_allow_ip_spoofing + security_groups = length(flatten([ + (var.create_security_group && var.secondary_use_vsi_security_group ? [ibm_is_security_group.security_group[var.security_group.name].id] : []), + [ + for group in var.secondary_security_groups : + group.security_group_id if group.interface_name == each.value.name + ] + ])) == 0 ? [data.ibm_is_vpc.vpc.default_security_group] : flatten([ + (var.create_security_group && var.secondary_use_vsi_security_group ? [ibm_is_security_group.security_group[var.security_group.name].id] : []), + [ + for group in var.secondary_security_groups : + group.security_group_id if group.interface_name == each.value.name + ] + ]) + auto_delete = false + enable_infrastructure_nat = true + dynamic "primary_ip" { + for_each = var.manage_reserved_ips ? [1] : [] + content { + reserved_ip = ibm_is_subnet_reserved_ip.secondary_vni_ip[each.key].reserved_ip + } + } +} + ############################################################################## # Create Virtual Servers ############################################################################## @@ -99,6 +192,20 @@ resource "ibm_is_subnet_reserved_ip" "vsi_ip" { auto_delete = false } +resource "ibm_is_subnet_reserved_ip" "secondary_vsi_ip" { + for_each = { for key, value in local.secondary_reserved_ips_map : key => value if var.primary_vni_additional_ip_count > 0 && !var.use_legacy_network_interface } + name = "${each.value.name}-ip" + subnet = each.value.subnet_id + auto_delete = false +} + +resource "ibm_is_subnet_reserved_ip" "secondary_vni_ip" { + for_each = { for k in var.secondary_subnets : k.zone => k if !var.use_legacy_network_interface && var.manage_reserved_ips } + name = "${each.value.name}-ip" + subnet = each.value.id + auto_delete = false +} + resource "ibm_is_instance" "vsi" { for_each = local.vsi_map name = each.value.vsi_name @@ -118,26 +225,52 @@ resource "ibm_is_instance" "vsi" { ] } - primary_network_interface { - subnet = each.value.subnet_id - security_groups = flatten([ - (var.create_security_group ? [ibm_is_security_group.security_group[var.security_group.name].id] : []), - var.security_group_ids, - (var.create_security_group == false && length(var.security_group_ids) == 0 ? [data.ibm_is_vpc.vpc.default_security_group] : []), - ]) - allow_ip_spoofing = var.allow_ip_spoofing - dynamic "primary_ip" { - for_each = var.manage_reserved_ips ? [1] : [] - content { - reserved_ip = ibm_is_subnet_reserved_ip.vsi_ip[each.value.name].reserved_ip + # Primary Virtual Network Interface + dynamic "primary_network_attachment" { + for_each = var.use_legacy_network_interface ? [] : [1] + content { + name = "${each.value.subnet_name}-eth0" + virtual_network_interface { + id = ibm_is_virtual_network_interface.primary_vni[each.key].id + } + } + } + + # Additional Virtual Network Interface + dynamic "network_attachments" { + for_each = { for key, value in ibm_is_virtual_network_interface.secondary_vni : key => value if key == each.value.zone && !var.use_legacy_network_interface } + content { + name = network_attachments.value.name + virtual_network_interface { + id = network_attachments.value.id } } } + # Legacy Network Interface + dynamic "primary_network_interface" { + for_each = var.use_legacy_network_interface ? [1] : [] + content { + subnet = each.value.subnet_id + security_groups = flatten([ + (var.create_security_group ? [ibm_is_security_group.security_group[var.security_group.name].id] : []), + var.security_group_ids, + (var.create_security_group == false && length(var.security_group_ids) == 0 ? [data.ibm_is_vpc.vpc.default_security_group] : []), + ]) + allow_ip_spoofing = var.allow_ip_spoofing + dynamic "primary_ip" { + for_each = var.manage_reserved_ips ? [1] : [] + content { + reserved_ip = ibm_is_subnet_reserved_ip.vsi_ip[each.value.name].reserved_ip + } + } + } + } + # Legacy additional Network Interface dynamic "network_interfaces" { for_each = { for k in var.secondary_subnets : k.zone => k - if k.zone == each.value.zone + if k.zone == each.value.zone && var.use_legacy_network_interface } content { subnet = network_interfaces.value.id @@ -181,17 +314,17 @@ resource "ibm_is_instance" "vsi" { resource "ibm_is_floating_ip" "vsi_fip" { for_each = var.enable_floating_ip ? ibm_is_instance.vsi : {} name = "${each.value.name}-fip" - target = each.value.primary_network_interface[0].id + target = var.use_legacy_network_interface ? each.value.primary_network_interface[0].id : each.value.primary_network_attachment[0].virtual_network_interface[0].id tags = var.tags access_tags = var.access_tags resource_group = var.resource_group_id } resource "ibm_is_floating_ip" "secondary_fip" { - for_each = length(var.secondary_floating_ips) == 0 ? {} : { - for interface in local.secondary_fip_list : + for_each = var.use_legacy_network_interface ? length(var.secondary_floating_ips) == 0 ? {} : { + for interface in local.legacy_secondary_fip_list : (interface.name) => interface - } + } : {} name = each.key target = each.value.target tags = var.tags @@ -199,4 +332,12 @@ resource "ibm_is_floating_ip" "secondary_fip" { resource_group = var.resource_group_id } +resource "ibm_is_floating_ip" "vni_secondary_fip" { + for_each = local.secondary_fip_map + name = each.key + target = each.value.vni_id + tags = var.tags + access_tags = var.access_tags + resource_group = var.resource_group_id +} ############################################################################## diff --git a/variables.tf b/variables.tf index 74233861..67857244 100644 --- a/variables.tf +++ b/variables.tf @@ -116,6 +116,13 @@ variable "manage_reserved_ips" { default = false } +variable "primary_vni_additional_ip_count" { + description = "The number of secondary reversed IPs to attach to a Virtual Network Interface (VNI). Additional IPs are created only if `manage_reserved_ips` is set to true." + type = number + nullable = false + default = 0 +} + variable "use_static_boot_volume_name" { description = "Sets the boot volume name for each VSI to a static name in the format `{hostname}_boot`, instead of a random name. Set this to `true` to have a consistent boot volume name even when VSIs are recreated." type = bool @@ -488,3 +495,9 @@ variable "snapshot_consistency_group_id" { } ############################################################################## + +variable "use_legacy_network_interface" { + description = "Set this to true to use legacy network interface for the created instances." + type = bool + default = false +}
object({
name = string
id = string
zone = string
cidr = optional(string)
})
)