Skip to content

Commit

Permalink
Add network attachment to gce instance (#8829)
Browse files Browse the repository at this point in the history
* first draft for new Network Attachment resource

* updated NetworkAttachment specification file

* added usage example for Network Attachment with GCE Instance

* verified attributes and descriptions

* enhanced network attachment examples

* adding network attachment field to compute instance network interface

* added network attachment specification to yaml file

* added integration tests

* fixing minor typo

* fixed typo

* fixed typo

* fixed tf config

* fixed typo

* fixed network attachment simple test

* updated instance resource documentation

* removed bad logic network attachment validation

* fixed tests and field specification for network attachment
  • Loading branch information
maxi-cit authored Sep 21, 2023
1 parent c68c0c0 commit a05ae4f
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 18 deletions.
8 changes: 8 additions & 0 deletions mmv1/products/compute/Instance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,14 @@ properties:
should be specified.
# networkInterfaces.kind is not necessary for convergence.
custom_expand: 'templates/terraform/custom_expand/resourceref_with_validation.go.erb'
- !ruby/object:Api::Type::ResourceRef
name: 'networkAttachment'
resource: 'networkAttachment'
min_version: beta
imports: 'selfLink'
description: |
The URL of the network attachment that this interface should connect to in the following format:
projects/{projectNumber}/regions/{region_name}/networkAttachments/{network_attachment_name}.
- !ruby/object:Api::Type::NestedObject
name: 'scheduling'
description: Sets the scheduling options for this instance.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,15 +440,6 @@ func expandNetworkInterfaces(d tpgresource.TerraformResourceData, config *transp
return nil, fmt.Errorf("exactly one of network, subnetwork, or network_attachment must be provided")
}


if networkAttachment != "" {
if network != "" {
return nil, fmt.Errorf("Cannot have a network provided with networkAttachment given that networkAttachment is associated with a network already")
}
if subnetwork != "" {
return nil, fmt.Errorf("Cannot have a subnetwork provided with networkAttachment given that networkAttachment is associated with a subnetwork already")
}
}
<% else -%>

network := data["network"].(string)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,17 @@ func ResourceComputeInstance() *schema.Resource {
Description: `The name or self_link of the subnetwork attached to this interface.`,
},

<% if version == "beta" -%>
"network_attachment": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName,
Description: `The URL of the network attachment that this interface should connect to in the following format: projects/{projectNumber}/regions/{region_name}/networkAttachments/{network_attachment_name}.`,
},
<% end %>

"subnetwork_project": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -1509,7 +1520,7 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
}
<% unless version == 'ga' -%>
// Add extra check on Scheduling to prevent STOP instance setting MaxRunDuration.
// When Instance being stopped, GCE will wipe out the MaxRunDuration field.
// When Instance being stopped, GCE will wipe out the MaxRunDuration field.
// And Terraform has no visiblity on this field after then. Given the infrastructure
// constraint, MaxRunDuration will only be supported with instance has
// DELETE InstanceTerminationAction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2626,6 +2626,70 @@ func testAccCheckComputeInstanceUpdateMachineType(t *testing.T, n string) resour
}
}

<% if version == "beta"%>
func TestAccComputeInstance_NetworkAttachment(t *testing.T) {
t.Parallel()
suffix := fmt.Sprintf("%s", acctest.RandString(t, 10))
var instance compute.Instance

testNetworkAttachmentName := fmt.Sprintf("tf-test-network-attachment-%s", suffix)

// Need to have the full network attachment name in the format project/{project_id}/regions/{region_id}/networkAttachments/{testNetworkAttachmentName}
fullFormNetworkAttachmentName := fmt.Sprintf("projects/%s/regions/%s/networkAttachments/%s", envvar.GetTestProjectFromEnv(), envvar.GetTestRegionFromEnv(), testNetworkAttachmentName)

context := map[string]interface{}{
"suffix": (acctest.RandString(t, 10)),
"network_attachment_name": testNetworkAttachmentName,
}

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
CheckDestroy: testAccCheckComputeInstanceDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccComputeInstance_networkAttachment(context),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeInstanceExists(
t, "google_compute_instance.foobar", &instance),
testAccCheckComputeInstanceHasNetworkAttachment(&instance, fmt.Sprintf("https://www.googleapis.com/compute/beta/%s", fullFormNetworkAttachmentName)),
),
},
},
})
}

func TestAccComputeInstance_NetworkAttachmentUpdate(t *testing.T) {
t.Parallel()
suffix := acctest.RandString(t, 10)
envRegion := envvar.GetTestRegionFromEnv()
instanceName := fmt.Sprintf("tf-test-compute-instance-%s", suffix)

networkAttachmentSelflink1 := "google_compute_network_attachment.test_network_attachment_1.self_link"
networkAttachmentSelflink2 := "google_compute_network_attachment.test_network_attachment_2.self_link"

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
CheckDestroy: testAccCheckComputeInstanceDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccComputeInstance_networkAttachmentUpdate(networkAttachmentSelflink1, envRegion, suffix),
},
computeInstanceImportStep("us-central1-a", instanceName, []string{"allow_stopping_for_update"}),
{
Config: testAccComputeInstance_networkAttachmentUpdate(networkAttachmentSelflink2, envRegion, suffix),
},
computeInstanceImportStep("us-central1-a", instanceName, []string{"allow_stopping_for_update"}),
{
Config: testAccComputeInstance_networkAttachmentUpdate(networkAttachmentSelflink1, envRegion, suffix),
},
computeInstanceImportStep("us-central1-a", instanceName, []string{"allow_stopping_for_update"}),
},
})
}
<% end %>

func testAccCheckComputeInstanceDestroyProducer(t *testing.T) func(s *terraform.State) error {
return func(s *terraform.State) error {
config := acctest.GoogleProviderConfig(t)
Expand Down Expand Up @@ -3204,6 +3268,19 @@ func testAccCheckComputeInstanceHasMinCpuPlatform(instance *compute.Instance, mi
}
}

<% if version == "beta"%>
func testAccCheckComputeInstanceHasNetworkAttachment(instance *compute.Instance, networkAttachmentName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
for _, networkInterface := range instance.NetworkInterfaces {
if networkInterface.NetworkAttachment != "" && networkInterface.NetworkAttachment == networkAttachmentName {
return nil
}
}
return fmt.Errorf("Network Attachment %s, was not found in the instance template", networkAttachmentName)
}
}
<% end %>

func testAccCheckComputeInstanceHasMachineType(instance *compute.Instance, machineType string) resource.TestCheckFunc {
return func(s *terraform.State) error {
instanceMachineType := tpgresource.GetResourceNameFromSelfLink(instance.MachineType)
Expand Down Expand Up @@ -6925,3 +7002,140 @@ resource "google_compute_disk" "debian" {
}
`, instance, diskName, suffix, suffix, suffix)
}

<% if version =="beta"%>
func testAccComputeInstance_networkAttachment(context map[string]interface{}) string {
return acctest.Nprintf(`
data "google_compute_image" "my_image" {
family = "debian-11"
project = "debian-cloud"
}

resource "google_compute_network" "test-network"{
name = "tf-test-network-%{suffix}"
auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "test-subnetwork" {
name = "tf-test-compute-subnet-%{suffix}"
ip_cidr_range = "10.0.0.0/16"
region = "us-central1"
network = google_compute_network.test-network.id
}

resource "google_compute_network_attachment" "test_network_attachment" {
name = "%{network_attachment_name}"
region = "us-central1"
description = "network attachment description"
connection_preference = "ACCEPT_AUTOMATIC"

subnetworks = [
google_compute_subnetwork.test-subnetwork.self_link
]
}

resource "google_compute_instance" "foobar" {
name = "tf-test-instance-%{suffix}"
machine_type = "e2-medium"

boot_disk {
initialize_params {
image = data.google_compute_image.my_image.id
}
}

network_interface {
network = "default"
}

network_interface{
network_attachment = google_compute_network_attachment.test_network_attachment.self_link
}

metadata = {
foo = "bar"
}
}
`, context)
}

func testAccComputeInstance_networkAttachmentUpdate(networkAttachment, region, suffix string) string {
return fmt.Sprintf(`
data "google_compute_image" "my_image" {
family = "debian-11"
project = "debian-cloud"
}

resource "google_compute_network" "consumer_vpc_1" {
name = "tf-test-consumer-net-1-%s"
auto_create_subnetworks = false
}

resource "google_compute_network" "consumer_vpc_2" {
name = "tf-test-consumer-net-2-%s"
auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "consumer_subnet_1" {
name = "tf-test-consumer-subnet-1-%s"
ip_cidr_range = "10.0.0.0/16"
region = "%s"
network = google_compute_network.consumer_vpc_1.id
}

resource "google_compute_subnetwork" "consumer_subnet_2" {
name = "tf-test-consumer-subnet-2-%s"
ip_cidr_range = "10.3.0.0/16"
region = "%s"
network = google_compute_network.consumer_vpc_2.id
}

resource "google_compute_network_attachment" "test_network_attachment_1" {
name = "tf-test-network-attachment-1-%s"
region = "%s"
description = "network attachment 1 description"
connection_preference = "ACCEPT_AUTOMATIC"

subnetworks = [
google_compute_subnetwork.consumer_subnet_1.self_link
]
}

resource "google_compute_network_attachment" "test_network_attachment_2" {
name = "tf-test-network-attachment-2-%s"
region = "%s"
description = "network attachment 2 description"
connection_preference = "ACCEPT_AUTOMATIC"

subnetworks = [
google_compute_subnetwork.consumer_subnet_2.self_link
]
}

resource "google_compute_instance" "foobar" {
name = "tf-test-compute-instance-%s"
machine_type = "e2-medium"
zone = "%s-a"
allow_stopping_for_update = true

boot_disk {
initialize_params {
image = data.google_compute_image.my_image.self_link
}
}

network_interface {
network = "default"
}

network_interface{
network_attachment = %s
}

metadata = {
foo = "bar"
}
}
`, suffix, suffix, suffix, region, suffix, region, suffix, region, suffix, region, suffix, region, networkAttachment)
}
<% end %>
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ is desired, you will need to modify your state file manually using
For instance, the image `centos-6-v20180104` includes its family name `centos-6`.
These images can be referred by family name here.

* `labels` - (Optional) A set of key/value label pairs assigned to the disk. This
* `labels` - (Optional) A set of key/value label pairs assigned to the disk. This
field is only applicable for persistent disks.

* `resource_manager_tags` - (Optional) A tag is a key-value pair that can be attached to a Google Cloud resource. You can use tags to conditionally allow or deny policies based on whether a resource has a specific tag. This value is not returned by the API. In Terraform, this value cannot be updated and changing it will recreate the resource.
Expand Down Expand Up @@ -285,6 +285,7 @@ is desired, you will need to modify your state file manually using
network is in auto subnet mode, specifying the subnetwork is optional. If the network is
in custom subnet mode, specifying the subnetwork is required.


* `subnetwork_project` - (Optional) The project in which the subnetwork belongs.
If the `subnetwork` is a self_link, this field is ignored in favor of the project
defined in the subnetwork self_link. If the `subnetwork` is a name and this
Expand All @@ -306,6 +307,8 @@ is desired, you will need to modify your state file manually using

* `nic_type` - (Optional) The type of vNIC to be used on this interface. Possible values: GVNIC, VIRTIO_NET.

* `network_attachment` - (Optional) [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html) The URL of the network attachment that this interface should connect to in the following format: `projects/{projectNumber}/regions/{region_name}/networkAttachments/{network_attachment_name}`.

* `stack_type` - (Optional) The stack type for this network interface to identify whether the IPv6 feature is enabled or not. Values are IPV4_IPV6 or IPV4_ONLY. If not specified, IPV4_ONLY will be used.

* `ipv6_access_config` - (Optional) An array of IPv6 access configurations for this interface.
Expand All @@ -331,14 +334,14 @@ specified, then this instance will have no external IPv6 Internet access. Struct

<a name="nested_ipv6_access_config"></a>The `ipv6_access_config` block supports:

* `external_ipv6` - (Optional) The first IPv6 address of the external IPv6 range associated
with this instance, prefix length is stored in externalIpv6PrefixLength in ipv6AccessConfig.
To use a static external IP address, it must be unused and in the same region as the instance's zone.
* `external_ipv6` - (Optional) The first IPv6 address of the external IPv6 range associated
with this instance, prefix length is stored in externalIpv6PrefixLength in ipv6AccessConfig.
To use a static external IP address, it must be unused and in the same region as the instance's zone.
If not specified, Google Cloud will automatically assign an external IPv6 address from the instance's subnetwork.

* `external_ipv6_prefix_length` - (Optional) The prefix length of the external IPv6 range.

* `name` - (Optional) The name of this access configuration. In ipv6AccessConfigs, the recommended name
* `name` - (Optional) The name of this access configuration. In ipv6AccessConfigs, the recommended name
is "External IPv6".

* `network_tier` - (Optional) The service-level to be provided for IPv6 traffic when the
Expand Down Expand Up @@ -390,12 +393,12 @@ specified, then this instance will have no external IPv6 Internet access. Struct

* `min_node_cpus` - (Optional) The minimum number of virtual CPUs this instance will consume when running on a sole-tenant node.

* `provisioning_model` - (Optional) Describe the type of preemptible VM. This field accepts the value `STANDARD` or `SPOT`. If the value is `STANDARD`, there will be no discount. If this is set to `SPOT`,
* `provisioning_model` - (Optional) Describe the type of preemptible VM. This field accepts the value `STANDARD` or `SPOT`. If the value is `STANDARD`, there will be no discount. If this is set to `SPOT`,
`preemptible` should be `true` and `automatic_restart` should be
`false`. For more info about
`SPOT`, read [here](https://cloud.google.com/compute/docs/instances/spot)
* `instance_termination_action` - (Optional) Describe the type of termination action for VM. Can be `STOP` or `DELETE`. Read more on [here](https://cloud.google.com/compute/docs/instances/create-use-spot)

* `instance_termination_action` - (Optional) Describe the type of termination action for VM. Can be `STOP` or `DELETE`. Read more on [here](https://cloud.google.com/compute/docs/instances/create-use-spot)

* `max_run_duration` - (Optional) [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html) The duration of the instance. Instance will run and be terminated after then, the termination action could be defined in `instance_termination_action`. Only support `DELETE` `instance_termination_action` at this point. Structure is [documented below](#nested_max_run_duration).
<a name="nested_max_run_duration"></a>The `max_run_duration` block supports:
Expand Down

0 comments on commit a05ae4f

Please sign in to comment.