-
Notifications
You must be signed in to change notification settings - Fork 71
feat(builder/googlecompute): Add reservation affinity support #316
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
base: main
Are you sure you want to change the base?
Changes from all commits
ee732c9
6993bb2
2d2bb97
3f5aad3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |||
| package googlecompute | ||||
|
|
||||
| import ( | ||||
| "context" | ||||
| "crypto/rand" | ||||
| "crypto/rsa" | ||||
| "crypto/x509" | ||||
|
|
@@ -18,6 +19,7 @@ import ( | |||
|
|
||||
| "github.com/hashicorp/packer-plugin-googlecompute/lib/common" | ||||
| "github.com/hashicorp/packer-plugin-sdk/acctest" | ||||
| "google.golang.org/api/compute/v1" | ||||
| ) | ||||
|
|
||||
| //go:embed testdata | ||||
|
|
@@ -586,3 +588,110 @@ func TestAccBuilder_CustomEndpointsAndUniverse(t *testing.T) { | |||
| }) | ||||
| } | ||||
| } | ||||
|
|
||||
| func TestAccBuilder_WithReservation(t *testing.T) { | ||||
| t.Parallel() | ||||
|
|
||||
| // Use a timestamp to generate a unique name. | ||||
| uniqueID := fmt.Sprintf("packer-test-%d", time.Now().UnixNano()) | ||||
| reservationName := uniqueID | ||||
| imageName := uniqueID | ||||
|
|
||||
| tmpl, err := testDataFs.ReadFile("testdata/reservation.pkr.hcl") | ||||
| if err != nil { | ||||
| t.Fatalf("failed to read testdata file: %s", err) | ||||
| } | ||||
|
|
||||
| testCase := &acctest.PluginTestCase{ | ||||
| Name: "googlecompute-packer-with-reservation", | ||||
| Template: string(tmpl), | ||||
| BuildExtraArgs: []string{ | ||||
| "-var", fmt.Sprintf("project_id=%s", os.Getenv("GOOGLE_PROJECT_ID")), | ||||
| "-var", fmt.Sprintf("image_name=%s", imageName), | ||||
| "-var", fmt.Sprintf("reservation_name=%s", reservationName), | ||||
| }, | ||||
| Setup: func() error { | ||||
| projectID := os.Getenv("GOOGLE_PROJECT_ID") | ||||
| if projectID == "" { | ||||
| return fmt.Errorf("GOOGLE_PROJECT_ID environment variable not set") | ||||
| } | ||||
| // The firewall rule is required for Packer to be able to SSH into the instance. | ||||
| ctx := context.Background() | ||||
| computeService, err := compute.NewService(ctx) | ||||
| if err != nil { | ||||
| return fmt.Errorf("failed to create compute service: %s", err) | ||||
| } | ||||
|
|
||||
| firewall := &compute.Firewall{ | ||||
| Name: "packer-test", | ||||
| Allowed: []*compute.FirewallAllowed{ | ||||
| { | ||||
| IPProtocol: "tcp", | ||||
| Ports: []string{"22"}, | ||||
| }, | ||||
| }, | ||||
| Network: "global/networks/default", | ||||
| TargetTags: []string{"packer-test"}, | ||||
| } | ||||
| _, err = computeService.Firewalls.Insert(projectID, firewall).Context(ctx).Do() | ||||
| if err != nil { | ||||
| return fmt.Errorf("failed to create firewall rule: %s", err) | ||||
| } | ||||
| reservation := &compute.Reservation{ | ||||
| Name: reservationName, | ||||
| SpecificReservation: &compute.AllocationSpecificSKUReservation{ | ||||
| Count: 1, | ||||
| InstanceProperties: &compute.AllocationSpecificSKUAllocationReservedInstanceProperties{ | ||||
| MachineType: "n1-standard-1", | ||||
| }, | ||||
| }, | ||||
| SpecificReservationRequired: true, | ||||
| Zone: "us-central1-a", | ||||
| } | ||||
| _, err = computeService.Reservations.Insert(projectID, "us-central1-a", reservation).Context(ctx).Do() | ||||
| if err != nil { | ||||
| return fmt.Errorf("failed to create reservation: %s", err) | ||||
| } | ||||
| return nil | ||||
| }, Teardown: func() error { | ||||
| projectID := os.Getenv("GOOGLE_PROJECT_ID") | ||||
| if projectID == "" { | ||||
| return fmt.Errorf("GOOGLE_PROJECT_ID environment variable not set") | ||||
| } | ||||
| defer os.Unsetenv("GOOGLE_PROJECT_ID") | ||||
|
||||
| defer os.Unsetenv("GOOGLE_PROJECT_ID") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please look into this @lornaluo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once the build completes, is there a way to verify if the reservation was actually utilized? Currently we seem to only be checking for build success.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets use t.Logf instead to ensure output is associated with the test
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -241,6 +241,10 @@ type Config struct { | |||||||||||||||
| // | ||||||||||||||||
| // Refer to the [Node Affinity](#node-affinities) for more information on affinities. | ||||||||||||||||
| NodeAffinities []common.NodeAffinity `mapstructure:"node_affinity" required:"false"` | ||||||||||||||||
| // ReservationAffinity: Specifies the reservations that this instance can consume from. | ||||||||||||||||
| ReservationAffinity *common.ReservationAffinity `mapstructure:"reservation_affinity" required:"false"` | ||||||||||||||||
| // If you are using a reservation, you must set this to true. | ||||||||||||||||
|
||||||||||||||||
| // If you are using a reservation, you must set this to true. | |
| // SpecificReservationRequired: When using ReservationAffinity with type | |
| // SPECIFIC_RESERVATION, this field must be set to true to require that the | |
| // instance consume capacity only from the specified reservation. This works | |
| // together with the `reservation_affinity` configuration; if | |
| // `reservation_affinity.type` is SPECIFIC_RESERVATION and this flag is false, | |
| // the instance may not be constrained to that specific reservation. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| variable "image_name" { | ||
| type = string | ||
| } | ||
|
|
||
| variable "reservation_name" { | ||
| type = string | ||
| } | ||
|
|
||
| variable "project_id" { | ||
| type = string | ||
| } | ||
|
|
||
| source "googlecompute" "reservation-test" { | ||
| project_id = var.project_id | ||
| source_image_family = "ubuntu-2204-lts" | ||
| source_image_project_id = ["ubuntu-os-cloud"] | ||
| zone = "us-central1-a" | ||
| tags = ["packer-test"] | ||
| network = "default" | ||
| ssh_username = "packer" | ||
| machine_type = "n1-standard-1" | ||
| reservation_affinity { | ||
| consume_reservation_type = "SPECIFIC_RESERVATION" | ||
| key = "compute.googleapis.com/reservation-name" | ||
| values = [var.reservation_name] | ||
| } | ||
| specific_reservation_required = true | ||
| } | ||
|
|
||
| build { | ||
| sources = ["source.googlecompute.reservation-test"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <!-- Code generated from the comments of the ReservationAffinity struct in lib/common/reservation_affinity.go; DO NOT EDIT MANUALLY --> | ||
|
|
||
| - `consume_reservation_type` (string) - ConsumeReservationType: Specifies the type of reservation from which this | ||
| instance can consume resources. | ||
| See https://cloud.google.com/compute/docs/instances/reservations-overview for examples. | ||
|
|
||
| - `key` (string) - Key: Corresponds to the label key of a reservation resource. To target a | ||
| SPECIFIC_RESERVATION by name, specify `compute.googleapis.com/reservation-name` | ||
| as the key. | ||
|
|
||
| - `values` ([]string) - Values: Corresponds to the label values of a reservation resource. | ||
|
|
||
| <!-- End of code generated from the comments of the ReservationAffinity struct in lib/common/reservation_affinity.go; --> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <!-- Code generated from the comments of the ReservationAffinity struct in lib/common/reservation_affinity.go; DO NOT EDIT MANUALLY --> | ||
|
|
||
| ReservationAffinity is the configuration structure for instance reservation | ||
| affinity. It allows you to consume a specific reservation. | ||
|
|
||
| <!-- End of code generated from the comments of the ReservationAffinity struct in lib/common/reservation_affinity.go; --> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // Copyright (c) HashiCorp, Inc. | ||
| // SPDX-License-Identifier: MPL-2.0 | ||
|
|
||
| //go:generate packer-sdc struct-markdown | ||
| //go:generate packer-sdc mapstructure-to-hcl2 -type ReservationAffinity | ||
|
|
||
| package common | ||
|
|
||
| import compute "google.golang.org/api/compute/v1" | ||
|
|
||
| // ReservationAffinity is the configuration structure for instance reservation | ||
| // affinity. It allows you to consume a specific reservation. | ||
| type ReservationAffinity struct { | ||
| // ConsumeReservationType: Specifies the type of reservation from which this | ||
| // instance can consume resources. | ||
| // See https://cloud.google.com/compute/docs/instances/reservations-overview for examples. | ||
| ConsumeReservationType string `mapstructure:"consume_reservation_type"` | ||
|
|
||
| // Key: Corresponds to the label key of a reservation resource. To target a | ||
| // SPECIFIC_RESERVATION by name, specify `compute.googleapis.com/reservation-name` | ||
| // as the key. | ||
| Key string `mapstructure:"key"` | ||
|
|
||
| // Values: Corresponds to the label values of a reservation resource. | ||
| Values []string `mapstructure:"values"` | ||
| } | ||
|
|
||
| // ComputeType converts the Packer-specific ReservationAffinity struct to the | ||
| // type required by the Google Cloud API client library. | ||
| func (r *ReservationAffinity) ComputeType() *compute.ReservationAffinity { | ||
| if r == nil { | ||
| return nil | ||
| } | ||
| return &compute.ReservationAffinity{ | ||
| ConsumeReservationType: r.ConsumeReservationType, | ||
| Key: r.Key, | ||
| Values: r.Values, | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep this name similar to that of the test name.