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

Compute - Add update support for Network IP when changing network/subnetwork #4030

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
8fa2b40
Compute - Add update support for Network IP when changing network/sub…
ScottSuarez Sep 29, 2020
b81129f
merge with master
ScottSuarez Sep 29, 2020
c758f19
add field required by beta provider
ScottSuarez Sep 29, 2020
be26563
error check for d.ForceNew
ScottSuarez Sep 29, 2020
d43335b
refactor functions into own file. introduce network-interface-helper
ScottSuarez Sep 29, 2020
8de60c0
refresh instance after deleting alias config
ScottSuarez Sep 30, 2020
305fad0
merge with master
ScottSuarez Sep 30, 2020
d6a1176
correct bad merge
ScottSuarez Sep 30, 2020
465b8d5
spelling fix
ScottSuarez Sep 30, 2020
566c7ba
check error
ScottSuarez Sep 30, 2020
849d0c6
Add forcenew unit test
ScottSuarez Sep 30, 2020
cf00d31
resolve build issue
ScottSuarez Sep 30, 2020
0880d41
Update third_party/terraform/tests/resource_compute_instance_test.go.erb
ScottSuarez Sep 30, 2020
f6160ea
Update third_party/terraform/tests/resource_compute_instance_test.go.erb
ScottSuarez Sep 30, 2020
09c87d9
error couldn't convert to string
ScottSuarez Sep 30, 2020
55a87da
Merge branch 'compute_instance_network_ip' of https://github.com/Scot…
ScottSuarez Sep 30, 2020
f613684
to type string
ScottSuarez Sep 30, 2020
7eb0af6
Refactored some test code for cleaner style and condensed some if sta…
ScottSuarez Oct 1, 2020
9877285
merge with megan's changes
ScottSuarez Oct 1, 2020
f340254
resolve merge issue with megan's change
ScottSuarez Oct 1, 2020
6c0e819
update network interface helper to promote better go readability
ScottSuarez Oct 2, 2020
1f1a506
fixed instances where incorrect name was referenced
ScottSuarez Oct 2, 2020
903ffe6
Merge branch 'master' of https://github.com/GoogleCloudPlatform/magic…
ScottSuarez Oct 6, 2020
17a4d83
removed extraneous dependencies and extrapolated self mutating operat…
ScottSuarez Oct 7, 2020
9963199
Merge branch 'master' of https://github.com/GoogleCloudPlatform/magic…
ScottSuarez Oct 9, 2020
8cf7348
scrap refactor. only extrapolate functions when needed.
ScottSuarez Oct 9, 2020
32e332e
error check
ScottSuarez Oct 9, 2020
a2d114f
resolved some fomatting and comment concerns.
ScottSuarez Oct 9, 2020
a6adabc
find a comment a nice cozy new home on the hill side amongst all its …
ScottSuarez Oct 9, 2020
d4ac363
a comma boi
ScottSuarez Oct 9, 2020
29ee2ff
Merge branch 'master' of https://github.com/GoogleCloudPlatform/magic…
ScottSuarez Oct 12, 2020
8c800ac
another commma for the party
ScottSuarez Oct 12, 2020
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
186 changes: 141 additions & 45 deletions third_party/terraform/resources/resource_compute_instance.go.erb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,33 @@ var (
}
)

// network_interface.[d].network_ip can only changge when subnet/network
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
// is also changing. Validate that if network_ip is changing this scenario
// holds up to par.
func forceNewIfNetworkIPChangedAndNotAble(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error {
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
oldcount, newcount := d.GetChange("network_interface.#")
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
if oldcount.(int) != newcount.(int) {
return nil
}

for i := 0; i < newcount.(int); i++ {
prefix := fmt.Sprintf("network_interface.%d", i)
networkKey := prefix + ".network"
subnetworkKey := prefix + ".subnetwork"
subnetworkProjectKey := prefix + ".subnetwork_project"
networkIPKey := prefix + ".network_ip"
if d.HasChange(networkIPKey) {
if !d.HasChange(networkKey) && !d.HasChange(subnetworkKey) && !d.HasChange(subnetworkProjectKey) {
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
if err := d.ForceNew(networkIPKey); err != nil {
return err
}
}
}
}

return nil
}

func resourceComputeInstance() *schema.Resource {
return &schema.Resource{
Create: resourceComputeInstanceCreate,
Expand Down Expand Up @@ -256,7 +283,6 @@ func resourceComputeInstance() *schema.Resource {
"network_ip": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
Description: `The private IP address assigned to the instance.`,
},
Expand Down Expand Up @@ -717,6 +743,7 @@ func resourceComputeInstance() *schema.Resource {
suppressEmptyGuestAcceleratorDiff,
),
desiredStatusDiff,
forceNewIfNetworkIPChangedAndNotAble,
),
}
}
Expand Down Expand Up @@ -1346,7 +1373,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
return fmt.Errorf("Instance had unexpected number of network interfaces: %d", len(instance.NetworkInterfaces))
}

var updatesToNIWhileStopped []func(...googleapi.CallOption) (*computeBeta.Operation, error)
var updatesToNIWhileStopped []func(*computeBeta.Instance) error
for i := 0; i < len(networkInterfaces); i++ {
prefix := fmt.Sprintf("network_interface.%d", i)
networkInterface := networkInterfaces[i]
Expand Down Expand Up @@ -1387,14 +1414,17 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
}
}

if d.HasChange(prefix + ".access_config") {

// TODO: This code deletes then recreates accessConfigs. This is bad because it may
// leave the machine inaccessible from either ip if the creation part fails (network
// timeout etc). However right now there is a GCE limit of 1 accessConfig so it is
// the only way to do it. In future this should be revised to only change what is
// necessary, and also add before removing.
readFingerPrint := func() error {
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
// re-read fingerprint
instance, err = config.clientComputeBeta.Instances.Get(project, zone, instance.Name).Do()
if err != nil {
return err
}
instNetworkInterface = instance.NetworkInterfaces[i]
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

deleteAccessConfigs := func() error {
// Delete any accessConfig that currently exists in instNetworkInterface
for _, ac := range instNetworkInterface.AccessConfigs {
op, err := config.clientCompute.Instances.DeleteAccessConfig(
Expand All @@ -1407,7 +1437,10 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
return opErr
}
}
return nil
}

createAccessConfigs := func() error {
// Create new ones
accessConfigsCount := d.Get(prefix + ".access_config.#").(int)
for j := 0; j < accessConfigsCount; j++ {
Expand All @@ -1432,22 +1465,10 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
return opErr
}
}

// re-read fingerprint
instance, err = config.clientComputeBeta.Instances.Get(project, zone, instance.Name).Do()
if err != nil {
return err
}
instNetworkInterface = instance.NetworkInterfaces[i]
return nil
}

// Setting NetworkIP to empty and AccessConfigs to nil.
// This will opt them out from being modified in the patch call.
networkInterface.NetworkIP = ""
networkInterface.AccessConfigs = nil
if !updateDuringStop && d.HasChange(prefix + ".alias_ip_range") {
// Alias IP ranges cannot be updated; they must be removed and then added
// unless you are changing subnetwork/network
deleteAliasIPRanges := func() error {
if len(instNetworkInterface.AliasIpRanges) > 0 {
ni := &computeBeta.NetworkInterface{
Fingerprint: instNetworkInterface.Fingerprint,
Expand All @@ -1461,28 +1482,99 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
if opErr != nil {
return opErr
}
// re-read fingerprint
instance, err = config.clientComputeBeta.Instances.Get(project, zone, instance.Name).Do()
}
return nil
}

if !updateDuringStop {
if d.HasChange(prefix + ".access_config") {
// Access configs cannot be updated, they must first be removed
if d.HasChange(prefix + ".access_config") {
err := deleteAccessConfigs()
if err != nil {
return err
}
err = createAccessConfigs()
if err != nil {
return err
}
err = readFingerPrint()
if err != nil {
return err
}
}
}
if d.HasChange(prefix + ".alias_ip_range") {
// Lets be explicit about what we are changing in the patch call
networkInterfacePatchObj := &computeBeta.NetworkInterface{}

// Alias IP ranges cannot be updated; they must be removed and then added
// unless you are changing subnetwork/network
if d.HasChange(prefix + ".alias_ip_range") {
err := deleteAliasIPRanges()
if err != nil {
return err
}
networkInterfacePatchObj.AliasIpRanges = networkInterface.AliasIpRanges
networkInterfacePatchObj.Fingerprint = instNetworkInterface.Fingerprint
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
}

patchCall := config.clientComputeBeta.Instances.UpdateNetworkInterface(project, zone, instance.Name, networkName, networkInterfacePatchObj).Do
op, err := patchCall()
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return errwrap.Wrapf("Error updating network interface: {{err}}", err)
}
opErr := computeOperationWaitTime(config, op, project, "network interface to update", userAgent, d.Timeout(schema.TimeoutUpdate))
if opErr != nil {
return opErr
}
}
} else {
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
// Lets be explicit about what we are changing in the patch call
networkInterfacePatchObj := &computeBeta.NetworkInterface{
Network: networkInterface.Network,
Subnetwork: networkInterface.Subnetwork,
AliasIpRanges: networkInterface.AliasIpRanges,
}

// network_ip can be inferred if not declared. Let's only patch if it's being changed by user
// otherwise this could fail if the network ip is not compatible with the new Subnetwork/Network.
if d.HasChange(prefix + ".network_ip") {
networkInterfacePatchObj.NetworkIP = networkInterface.NetworkIP
}

// Access config can run into some issues since we can't derive users original intent due to
// terraform limitation. Lets only update it if we need to.
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
if d.HasChange(prefix + ".access_config") {
err := deleteAccessConfigs()
if err != nil {
return err
}
instNetworkInterface = instance.NetworkInterfaces[i]
}

networkInterface.Fingerprint = instNetworkInterface.Fingerprint
updateCall := config.clientComputeBeta.Instances.UpdateNetworkInterface(project, zone, instance.Name, networkName, networkInterface).Do
op, err := updateCall()
if err != nil {
return errwrap.Wrapf("Error updating network interface: {{err}}", err)
}
opErr := computeOperationWaitTime(config, op, project, "network interface to update", userAgent, d.Timeout(schema.TimeoutUpdate))
if opErr != nil {
return opErr
// Access configs' ip changes when the instance stops invalidating our fingerprint
// expect caller to re-validate instance before calling patch
patchIndex := i
patch := func(instance *computeBeta.Instance) error {
networkInterfacePatchObj.Fingerprint = instance.NetworkInterfaces[patchIndex].Fingerprint
op, err := config.clientComputeBeta.Instances.UpdateNetworkInterface(project, zone, instance.Name, networkName, networkInterfacePatchObj).Do()
if err != nil {
return errwrap.Wrapf("Error updating network interface: {{err}}", err)
}
opErr := computeOperationWaitTime(config, op, project, "network interface to update", userAgent, d.Timeout(schema.TimeoutUpdate))
if opErr != nil {
return opErr
}
if d.HasChange(prefix + ".access_config") {
err := createAccessConfigs()
if err != nil {
return err
}
}
return nil
}
} else if updateDuringStop {
networkInterface.Fingerprint = instNetworkInterface.Fingerprint
updateCall := config.clientComputeBeta.Instances.UpdateNetworkInterface(project, zone, instance.Name, networkName, networkInterface).Do
updatesToNIWhileStopped = append(updatesToNIWhileStopped, updateCall)

updatesToNIWhileStopped = append(updatesToNIWhileStopped, patch)
}
}

Expand Down Expand Up @@ -1747,14 +1839,18 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
}
}

for _, updateCall := range updatesToNIWhileStopped {
op, err := updateCall()
// If the instance stops it can invalidate the fingerprint for network interface.
// refresh the instance to get a new fingerprint
if len(updatesToNIWhileStopped) > 0 {
instance, err = config.clientComputeBeta.Instances.Get(project, zone, instance.Name).Do()
if err != nil {
return errwrap.Wrapf("Error updating network interface: {{err}}", err)
return err
}
opErr := computeOperationWaitTime(config, op, project, "network interface to update", userAgent, d.Timeout(schema.TimeoutUpdate))
if opErr != nil {
return opErr
}
for _, patch := range updatesToNIWhileStopped {
err := patch(instance)
if err != nil {
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
return err
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1887,6 +1887,10 @@ func TestAccComputeInstance_subnetworkUpdate(t *testing.T) {
Config: testAccComputeInstance_subnetworkUpdateTwo(suffix, instanceName),
},
computeInstanceImportStep("us-east1-d", instanceName, []string{"allow_stopping_for_update"}),
{
Config: testAccComputeInstance_subnetworkUpdate(suffix, instanceName),
},
computeInstanceImportStep("us-east1-d", instanceName, []string{"allow_stopping_for_update"}),
},
})
}
Expand Down Expand Up @@ -4896,6 +4900,11 @@ func testAccComputeInstance_subnetworkUpdateTwo(suffix, instance string) string
machine_type = "n1-standard-1"
zone = "us-east1-d"
allow_stopping_for_update = true
network_ip = "10.3.0.3"

access_config {
network_tier = "STANDARD"
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
}

boot_disk {
initialize_params {
Expand Down