Skip to content

Commit

Permalink
feat(compute): compute_ip_address_assign_static_existing_vm sample (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
irudykss committed Jun 17, 2024
1 parent 8947784 commit f80fd3c
Show file tree
Hide file tree
Showing 2 changed files with 304 additions and 0 deletions.
183 changes: 183 additions & 0 deletions compute/address/address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"testing"
"time"

"google.golang.org/protobuf/proto"

compute "cloud.google.com/go/compute/apiv1"
"google.golang.org/api/iterator"

Expand All @@ -31,6 +33,107 @@ import (
)

// helper functions

func createTestInstance(projectID, zone, instanceName string) error {
// projectID := "your_project_id"
// zone := "europe-central2-b"
// instanceName := "your_instance_name"

ctx := context.Background()
instancesClient, err := compute.NewInstancesRESTClient(ctx)
if err != nil {
return fmt.Errorf("NewInstancesRESTClient: %w", err)
}
defer instancesClient.Close()

imagesClient, err := compute.NewImagesRESTClient(ctx)
if err != nil {
return fmt.Errorf("NewImagesRESTClient: %w", err)
}
defer imagesClient.Close()

// List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details.
newestDebianReq := &computepb.GetFromFamilyImageRequest{
Project: "debian-cloud",
Family: "debian-12",
}
newestDebian, err := imagesClient.GetFromFamily(ctx, newestDebianReq)
if err != nil {
return fmt.Errorf("unable to get image from family: %w", err)
}

req := &computepb.InsertInstanceRequest{
Project: projectID,
Zone: zone,
InstanceResource: &computepb.Instance{
Name: proto.String(instanceName),
Disks: []*computepb.AttachedDisk{
{
InitializeParams: &computepb.AttachedDiskInitializeParams{
DiskSizeGb: proto.Int64(10),
SourceImage: newestDebian.SelfLink,
DiskType: proto.String(fmt.Sprintf("zones/%s/diskTypes/pd-standard", zone)),
},
AutoDelete: proto.Bool(true),
Boot: proto.Bool(true),
Type: proto.String(computepb.AttachedDisk_PERSISTENT.String()),
},
},
MachineType: proto.String(fmt.Sprintf("zones/%s/machineTypes/n1-standard-1", zone)),
NetworkInterfaces: []*computepb.NetworkInterface{
{
//Name: proto.String("global/networks/default"),
AccessConfigs: []*computepb.AccessConfig{
{
Type: proto.String(computepb.AccessConfig_ONE_TO_ONE_NAT.String()),
Name: proto.String("External NAT"),
NetworkTier: proto.String(computepb.AccessConfig_PREMIUM.String()),
},
},
},
},
},
}

op, err := instancesClient.Insert(ctx, req)
if err != nil {
return fmt.Errorf("unable to create instance: %w", err)
}

if err = op.Wait(ctx); err != nil {
return fmt.Errorf("unable to wait for the operation: %w", err)
}

return nil
}

func deleteInstance(projectID, zone, instanceName string) error {
ctx := context.Background()
client, err := compute.NewInstancesRESTClient(ctx)
if err != nil {
return fmt.Errorf("NewInstancesRESTClient: %w", err)
}
defer client.Close()

req := &computepb.DeleteInstanceRequest{
Project: projectID,
Zone: zone,
Instance: instanceName,
}

op, err := client.Delete(ctx, req)
if err != nil {
return fmt.Errorf("DeleteInstance: %w", err)
}

err = op.Wait(ctx)
if err != nil {
return fmt.Errorf("Wait for DeleteInstance operation: %w", err)
}

return nil
}

func listIPAddresses(ctx context.Context, projectID, region string) ([]string, error) {
var addresses []string
var err error
Expand Down Expand Up @@ -159,6 +262,8 @@ func _deleteRegionalIPAddress(ctx context.Context, projectID, region, addressNam
return nil
}

// end helper functions

func TestReserveNewRegionalExternal(t *testing.T) {
ctx := context.Background()
var seededRand = rand.New(
Expand Down Expand Up @@ -445,3 +550,81 @@ func TestGetRegionalExternal(t *testing.T) {
}

}

func TestAssignStaticAddressToExistingVM(t *testing.T) {
ctx := context.Background()
var seededRand = rand.New(
rand.NewSource(time.Now().UnixNano()))
tc := testutil.SystemTest(t)
instanceName := "test-instance-" + fmt.Sprint(seededRand.Int())
addressName := "test-address-" + fmt.Sprint(seededRand.Int())
zone := "us-central1-a"
region := "us-central1"
buf := &bytes.Buffer{}

// initiate instance
err := createTestInstance(tc.ProjectID, zone, instanceName)
if err != nil {
t.Fatalf("createTestInstance got err: %v", err)
return
}

defer func() {
if err := deleteInstance(tc.ProjectID, zone, instanceName); err != nil {
t.Errorf("deleteInstance got err: %v", err)
}

}()

// create and retrieve address
address, err := reserveNewRegionalExternal(buf, tc.ProjectID, region, addressName, true)
if err != nil {
t.Fatalf("reserveNewRegionalExternal got err: %v", err)
return
}

defer func() {

if err := deleteIPAddress(ctx, tc.ProjectID, region, addressName); err != nil {
t.Errorf("deleteIPAddress got err: %v", err)
}
}()

// assign address to test
if err := assignStaticAddressToExistingVM(buf, tc.ProjectID, zone, instanceName, address.GetAddress(), "nic0"); err != nil {
t.Errorf("assignStaticAddressToExistingVM got err: %v", err)
}

// verify output
expectedResult := fmt.Sprintf("Static address %s assigned to the instance %s", address.GetAddress(), instanceName)
if got := buf.String(); !strings.Contains(got, expectedResult) {
t.Errorf("assignStaticAddressToExistingVM got %q, want %q", got, expectedResult)
}

reqGet := &computepb.GetInstanceRequest{
Project: tc.ProjectID,
Zone: zone,
Instance: instanceName,
}

// verify address assign
instancesClient, err := compute.NewInstancesRESTClient(ctx)
instance, err := instancesClient.Get(ctx, reqGet)
if err != nil {
t.Errorf("instancesClient.Get got err: %v", err)
}

for _, ni := range instance.NetworkInterfaces {
if *ni.Name != "nic0" {
continue
}
for _, ac := range ni.AccessConfigs {

if ac.NatIP != nil && *ac.NatIP == address.GetAddress() {
return // address assign verified
}
}

}
t.Error("IP address did not assigned properly") // address assign not verified
}
121 changes: 121 additions & 0 deletions compute/address/assign_static_address_to_existing_vm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package snippets

// [START compute_ip_address_assign_static_existing_vm]
import (
"context"
"fmt"
"io"

"google.golang.org/protobuf/proto"

compute "cloud.google.com/go/compute/apiv1"
"cloud.google.com/go/compute/apiv1/computepb"
)

// assignStaticAddressToExistingVM assigns a static external IP address to an existing VM instance.
// Note: VM and assigned IP must be in the same region.
func assignStaticAddressToExistingVM(w io.Writer, projectID, zone, instanceName, IPAddress, networkInterfaceName string) error {
// projectID := "your_project_id"
// zone := "europe-central2-b"
// instanceName := "your_instance_name"
// IPAddress := "34.111.222.333"
// networkInterfaceName := "nic0"

ctx := context.Background()
instancesClient, err := compute.NewInstancesRESTClient(ctx)
if err != nil {
return fmt.Errorf("NewInstancesRESTClient: %w", err)
}
defer instancesClient.Close()

reqGet := &computepb.GetInstanceRequest{
Project: projectID,
Zone: zone,
Instance: instanceName,
}

instance, err := instancesClient.Get(ctx, reqGet)
if err != nil {
return fmt.Errorf("could not get instance: %w", err)
}

var networkInterface *computepb.NetworkInterface
for _, ni := range instance.NetworkInterfaces {
if *ni.Name == networkInterfaceName {
networkInterface = ni
break
}
}

if networkInterface == nil {
return fmt.Errorf("No network interface named '%s' found on instance %s", networkInterfaceName, instanceName)
}

var accessConfig *computepb.AccessConfig
for _, ac := range networkInterface.AccessConfigs {
if *ac.Type == computepb.AccessConfig_ONE_TO_ONE_NAT.String() {
accessConfig = ac
break
}
}

if accessConfig != nil {
// network interface is immutable - deletion stage is required in case of any assigned ip (static or ephemeral).
reqDelete := &computepb.DeleteAccessConfigInstanceRequest{
Project: projectID,
Zone: zone,
Instance: instanceName,
AccessConfig: *accessConfig.Name,
NetworkInterface: networkInterfaceName,
}

opDelete, err := instancesClient.DeleteAccessConfig(ctx, reqDelete)
if err != nil {
return fmt.Errorf("unable to delete access config: %w", err)
}

if err = opDelete.Wait(ctx); err != nil {
return fmt.Errorf("unable to wait for the operation: %w", err)
}
}

reqAdd := &computepb.AddAccessConfigInstanceRequest{
Project: projectID,
Zone: zone,
Instance: instanceName,
AccessConfigResource: &computepb.AccessConfig{
NatIP: &IPAddress,
Type: proto.String(computepb.AccessConfig_ONE_TO_ONE_NAT.String()),
},
NetworkInterface: networkInterfaceName,
}

opAdd, err := instancesClient.AddAccessConfig(ctx, reqAdd)
if err != nil {
return fmt.Errorf("unable to add access config: %w", err)
}

if err = opAdd.Wait(ctx); err != nil {
return fmt.Errorf("unable to wait for the operation: %w", err)
}

fmt.Fprintf(w, "Static address %s assigned to the instance %s\n", IPAddress, instanceName)

return nil
}

// [END compute_ip_address_assign_static_existing_vm]

0 comments on commit f80fd3c

Please sign in to comment.