Skip to content

Commit

Permalink
Add support for gRPC healthchecks (#3825) (#7038)
Browse files Browse the repository at this point in the history
Signed-off-by: Modular Magician <[email protected]>
  • Loading branch information
modular-magician authored Aug 17, 2020
1 parent 5d9c181 commit 8de5741
Show file tree
Hide file tree
Showing 7 changed files with 692 additions and 10 deletions.
6 changes: 6 additions & 0 deletions .changelog/3825.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
```release-note:enhancement
compute: added grpc_health_check block to compute_health_check
```
```release-note:enhancement
compute: added grpc_health_check block to compute_region_health_check
```
186 changes: 181 additions & 5 deletions google/resource_compute_health_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,63 @@ seconds.`,
Description: `An optional description of this resource. Provide this property when
you create the resource.`,
},
"grpc_health_check": {
Type: schema.TypeList,
Optional: true,
DiffSuppressFunc: portDiffSuppress,
Description: `A nested object resource`,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"grpc_service_name": {
Type: schema.TypeString,
Optional: true,
Description: `The gRPC service name for the health check.
The value of grpcServiceName has the following meanings by convention:
- Empty serviceName means the overall status of all services at the backend.
- Non-empty serviceName means the health of that gRPC service, as defined by the owner of the service.
The grpcServiceName can only be ASCII.`,
AtLeastOneOf: []string{"grpc_health_check.0.port", "grpc_health_check.0.port_name", "grpc_health_check.0.port_specification", "grpc_health_check.0.grpc_service_name"},
},
"port": {
Type: schema.TypeInt,
Optional: true,
Description: `The port number for the health check request.
Must be specified if portName and portSpecification are not set
or if port_specification is USE_FIXED_PORT. Valid values are 1 through 65535.`,
AtLeastOneOf: []string{"grpc_health_check.0.port", "grpc_health_check.0.port_name", "grpc_health_check.0.port_specification", "grpc_health_check.0.grpc_service_name"},
},
"port_name": {
Type: schema.TypeString,
Optional: true,
Description: `Port name as defined in InstanceGroup#NamedPort#name. If both port and
port_name are defined, port takes precedence.`,
AtLeastOneOf: []string{"grpc_health_check.0.port", "grpc_health_check.0.port_name", "grpc_health_check.0.port_specification", "grpc_health_check.0.grpc_service_name"},
},
"port_specification": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"USE_FIXED_PORT", "USE_NAMED_PORT", "USE_SERVING_PORT", ""}, false),
Description: `Specifies how port is selected for health checking, can be one of the
following values:
* 'USE_FIXED_PORT': The port number in 'port' is used for health checking.
* 'USE_NAMED_PORT': The 'portName' is used for health checking.
* 'USE_SERVING_PORT': For NetworkEndpointGroup, the port specified for each
network endpoint is used for health checking. For other backends, the
port or named port specified in the Backend Service is used for health
checking.
If not specified, gRPC health check follows behavior specified in 'port' and
'portName' fields. Possible values: ["USE_FIXED_PORT", "USE_NAMED_PORT", "USE_SERVING_PORT"]`,
AtLeastOneOf: []string{"grpc_health_check.0.port", "grpc_health_check.0.port_name", "grpc_health_check.0.port_specification", "grpc_health_check.0.grpc_service_name"},
},
},
},
ExactlyOneOf: []string{"http_health_check", "https_health_check", "http2_health_check", "tcp_health_check", "ssl_health_check", "grpc_health_check"},
},
"healthy_threshold": {
Type: schema.TypeInt,
Optional: true,
Expand Down Expand Up @@ -238,7 +295,7 @@ can only be ASCII.`,
},
},
},
ExactlyOneOf: []string{"http_health_check", "https_health_check", "http2_health_check", "tcp_health_check", "ssl_health_check"},
ExactlyOneOf: []string{"http_health_check", "https_health_check", "http2_health_check", "tcp_health_check", "ssl_health_check", "grpc_health_check"},
},
"http_health_check": {
Type: schema.TypeList,
Expand Down Expand Up @@ -317,7 +374,7 @@ can only be ASCII.`,
},
},
},
ExactlyOneOf: []string{"http_health_check", "https_health_check", "http2_health_check", "tcp_health_check", "ssl_health_check"},
ExactlyOneOf: []string{"http_health_check", "https_health_check", "http2_health_check", "tcp_health_check", "ssl_health_check", "grpc_health_check"},
},
"https_health_check": {
Type: schema.TypeList,
Expand Down Expand Up @@ -396,7 +453,7 @@ can only be ASCII.`,
},
},
},
ExactlyOneOf: []string{"http_health_check", "https_health_check", "http2_health_check", "tcp_health_check", "ssl_health_check"},
ExactlyOneOf: []string{"http_health_check", "https_health_check", "http2_health_check", "tcp_health_check", "ssl_health_check", "grpc_health_check"},
},
"ssl_health_check": {
Type: schema.TypeList,
Expand Down Expand Up @@ -468,7 +525,7 @@ can only be ASCII.`,
},
},
},
ExactlyOneOf: []string{"http_health_check", "https_health_check", "http2_health_check", "tcp_health_check", "ssl_health_check"},
ExactlyOneOf: []string{"http_health_check", "https_health_check", "http2_health_check", "tcp_health_check", "ssl_health_check", "grpc_health_check"},
},
"tcp_health_check": {
Type: schema.TypeList,
Expand Down Expand Up @@ -540,7 +597,7 @@ can only be ASCII.`,
},
},
},
ExactlyOneOf: []string{"http_health_check", "https_health_check", "http2_health_check", "tcp_health_check", "ssl_health_check"},
ExactlyOneOf: []string{"http_health_check", "https_health_check", "http2_health_check", "tcp_health_check", "ssl_health_check", "grpc_health_check"},
},
"timeout_sec": {
Type: schema.TypeInt,
Expand Down Expand Up @@ -651,6 +708,12 @@ func resourceComputeHealthCheckCreate(d *schema.ResourceData, meta interface{})
} else if v, ok := d.GetOkExists("http2_health_check"); !isEmptyValue(reflect.ValueOf(http2HealthCheckProp)) && (ok || !reflect.DeepEqual(v, http2HealthCheckProp)) {
obj["http2HealthCheck"] = http2HealthCheckProp
}
grpcHealthCheckProp, err := expandComputeHealthCheckGrpcHealthCheck(d.Get("grpc_health_check"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("grpc_health_check"); !isEmptyValue(reflect.ValueOf(grpcHealthCheckProp)) && (ok || !reflect.DeepEqual(v, grpcHealthCheckProp)) {
obj["grpcHealthCheck"] = grpcHealthCheckProp
}

obj, err = resourceComputeHealthCheckEncoder(d, meta, obj)
if err != nil {
Expand Down Expand Up @@ -754,6 +817,9 @@ func resourceComputeHealthCheckRead(d *schema.ResourceData, meta interface{}) er
if err := d.Set("http2_health_check", flattenComputeHealthCheckHttp2HealthCheck(res["http2HealthCheck"], d, config)); err != nil {
return fmt.Errorf("Error reading HealthCheck: %s", err)
}
if err := d.Set("grpc_health_check", flattenComputeHealthCheckGrpcHealthCheck(res["grpcHealthCheck"], d, config)); err != nil {
return fmt.Errorf("Error reading HealthCheck: %s", err)
}
if err := d.Set("self_link", ConvertSelfLinkToV1(res["selfLink"].(string))); err != nil {
return fmt.Errorf("Error reading HealthCheck: %s", err)
}
Expand Down Expand Up @@ -836,6 +902,12 @@ func resourceComputeHealthCheckUpdate(d *schema.ResourceData, meta interface{})
} else if v, ok := d.GetOkExists("http2_health_check"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, http2HealthCheckProp)) {
obj["http2HealthCheck"] = http2HealthCheckProp
}
grpcHealthCheckProp, err := expandComputeHealthCheckGrpcHealthCheck(d.Get("grpc_health_check"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("grpc_health_check"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, grpcHealthCheckProp)) {
obj["grpcHealthCheck"] = grpcHealthCheckProp
}

obj, err = resourceComputeHealthCheckEncoder(d, meta, obj)
if err != nil {
Expand Down Expand Up @@ -1322,6 +1394,54 @@ func flattenComputeHealthCheckHttp2HealthCheckPortSpecification(v interface{}, d
return v
}

func flattenComputeHealthCheckGrpcHealthCheck(v interface{}, d *schema.ResourceData, config *Config) interface{} {
if v == nil {
return nil
}
original := v.(map[string]interface{})
if len(original) == 0 {
return nil
}
transformed := make(map[string]interface{})
transformed["port"] =
flattenComputeHealthCheckGrpcHealthCheckPort(original["port"], d, config)
transformed["port_name"] =
flattenComputeHealthCheckGrpcHealthCheckPortName(original["portName"], d, config)
transformed["port_specification"] =
flattenComputeHealthCheckGrpcHealthCheckPortSpecification(original["portSpecification"], d, config)
transformed["grpc_service_name"] =
flattenComputeHealthCheckGrpcHealthCheckGrpcServiceName(original["grpcServiceName"], d, config)
return []interface{}{transformed}
}
func flattenComputeHealthCheckGrpcHealthCheckPort(v interface{}, d *schema.ResourceData, config *Config) interface{} {
// Handles the string fixed64 format
if strVal, ok := v.(string); ok {
if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil {
return intVal
}
}

// number values are represented as float64
if floatVal, ok := v.(float64); ok {
intVal := int(floatVal)
return intVal
}

return v // let terraform core handle it otherwise
}

func flattenComputeHealthCheckGrpcHealthCheckPortName(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func flattenComputeHealthCheckGrpcHealthCheckPortSpecification(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func flattenComputeHealthCheckGrpcHealthCheckGrpcServiceName(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func expandComputeHealthCheckCheckIntervalSec(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}
Expand Down Expand Up @@ -1769,6 +1889,62 @@ func expandComputeHealthCheckHttp2HealthCheckPortSpecification(v interface{}, d
return v, nil
}

func expandComputeHealthCheckGrpcHealthCheck(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
l := v.([]interface{})
if len(l) == 0 || l[0] == nil {
return nil, nil
}
raw := l[0]
original := raw.(map[string]interface{})
transformed := make(map[string]interface{})

transformedPort, err := expandComputeHealthCheckGrpcHealthCheckPort(original["port"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedPort); val.IsValid() && !isEmptyValue(val) {
transformed["port"] = transformedPort
}

transformedPortName, err := expandComputeHealthCheckGrpcHealthCheckPortName(original["port_name"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedPortName); val.IsValid() && !isEmptyValue(val) {
transformed["portName"] = transformedPortName
}

transformedPortSpecification, err := expandComputeHealthCheckGrpcHealthCheckPortSpecification(original["port_specification"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedPortSpecification); val.IsValid() && !isEmptyValue(val) {
transformed["portSpecification"] = transformedPortSpecification
}

transformedGrpcServiceName, err := expandComputeHealthCheckGrpcHealthCheckGrpcServiceName(original["grpc_service_name"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedGrpcServiceName); val.IsValid() && !isEmptyValue(val) {
transformed["grpcServiceName"] = transformedGrpcServiceName
}

return transformed, nil
}

func expandComputeHealthCheckGrpcHealthCheckPort(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandComputeHealthCheckGrpcHealthCheckPortName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandComputeHealthCheckGrpcHealthCheckPortSpecification(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandComputeHealthCheckGrpcHealthCheckGrpcServiceName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func resourceComputeHealthCheckEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) {

if _, ok := d.GetOk("http_health_check"); ok {
Expand Down
80 changes: 80 additions & 0 deletions google/resource_compute_health_check_generated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,86 @@ resource "google_compute_health_check" "http2-health-check" {
`, context)
}

func TestAccComputeHealthCheck_healthCheckGrpcExample(t *testing.T) {
t.Parallel()

context := map[string]interface{}{
"random_suffix": randString(t, 10),
}

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeHealthCheckDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccComputeHealthCheck_healthCheckGrpcExample(context),
},
{
ResourceName: "google_compute_health_check.grpc-health-check",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccComputeHealthCheck_healthCheckGrpcExample(context map[string]interface{}) string {
return Nprintf(`
resource "google_compute_health_check" "grpc-health-check" {
name = "tf-test-grpc-health-check%{random_suffix}"
timeout_sec = 1
check_interval_sec = 1
grpc_health_check {
port = "443"
}
}
`, context)
}

func TestAccComputeHealthCheck_healthCheckGrpcFullExample(t *testing.T) {
t.Parallel()

context := map[string]interface{}{
"random_suffix": randString(t, 10),
}

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeHealthCheckDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccComputeHealthCheck_healthCheckGrpcFullExample(context),
},
{
ResourceName: "google_compute_health_check.grpc-health-check",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccComputeHealthCheck_healthCheckGrpcFullExample(context map[string]interface{}) string {
return Nprintf(`
resource "google_compute_health_check" "grpc-health-check" {
name = "tf-test-grpc-health-check%{random_suffix}"
timeout_sec = 1
check_interval_sec = 1
grpc_health_check {
port_name = "health-check-port"
port_specification = "USE_NAMED_PORT"
grpc_service_name = "testservice"
}
}
`, context)
}

func testAccCheckComputeHealthCheckDestroyProducer(t *testing.T) func(s *terraform.State) error {
return func(s *terraform.State) error {
for name, rs := range s.RootModule().Resources {
Expand Down
Loading

0 comments on commit 8de5741

Please sign in to comment.