infrastructure/alloydb-scale-function/go-sample/alloydbscale.go (186 lines of code) (raw):
// Copyright 2025 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
//
// http://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 p contains a Pub/Sub Cloud Function.
package p
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"golang.org/x/oauth2/google"
)
// PubSubMessage is the payload of a Pub/Sub event. Please refer to the docs for
// additional information regarding Pub/Sub events.
type PubSubMessage struct {
Data []byte `json:"data"`
}
// apiURL is the base URL for the AlloyDB Admin API.
var apiURL string = "https://alloydb.googleapis.com/v1beta"
// Labels represents the labels associated with an AlloyDB resource.
type Labels struct {
Cluster_id string `json:"cluster_id"`
Instance_id string `json:"instance_id"`
Location string `json:"location"`
Project_id string `json:"project_id"`
Resource_container string `json:"resource_container"`
}
// Resource represents an AlloyDB resource involved in an incident.
type Resource struct {
Policy_name string `json:"policy_name"`
Labels Labels `json:"labels"`
Type string `json:"type"`
}
// Incident represents an incident triggering a scaling operation.
type Incident struct {
Resource Resource `json:"resource"`
Policy_name string `json:"policy_name"`
}
// Alert represents an alert containing incident details.
type Alert struct {
Incident Incident `json:"incident"`
}
// MachineConfig represents the machine configuration of an AlloyDB instance.
type MachineConfig struct {
CpuCount int `json:"cpuCount"`
}
// Instance represents an AlloyDB instance.
type Instance struct {
Name string `json:"name"`
DisplayName string `json:"displayName"`
Uid string `json:"uid"`
CreateTime string `json:"createTime"`
UpdateTime string `json:"updateTime"`
DeleteTime string `json:"deleteTime"`
State string `json:"state"`
InstanceType string `json:"instanceType"`
MachineConfig MachineConfig `json:"machineConfig"`
Description string `json:"description"`
IpAddress string `json:"ipAddress"`
Reconciling bool `json:"reconciling"`
Etag string `json:"etag"`
}
// Instance_patch represents the fields to be patched in an Instance update.
type Instance_patch struct {
InstanceType string `json:"instanceType"`
MachineConfig MachineConfig `json:"machineConfig"`
}
// scaleUpInstance scales up the specified AlloyDB instance by doubling its CPU count.
func scaleUpInstance(ctx context.Context, project, location, cluster, instance string) error {
// Scale Up an instance
fmt.Println("Starting scaling up")
// Create a new authenticated HTTP client using the application default credentials.
client, err := google.DefaultClient(ctx, "https://www.googleapis.com/auth/cloud-platform")
if err != nil {
log.Fatal(err)
}
// Construct the URL to fetch the instance details
instancesURL := apiURL + "/" + "projects/" + project + "/locations/" + location + "/clusters/" + cluster + "/instances/" + instance
fmt.Println(instancesURL)
instancesReq, err := http.NewRequest("GET", instancesURL, nil)
if err != nil {
log.Fatal(err)
}
resp, err := client.Do(instancesReq)
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.Status)
instanceBody, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
var instance_json Instance
err = json.Unmarshal(instanceBody, &instance_json)
if err != nil {
log.Println(err)
}
fmt.Println(instance_json.MachineConfig.CpuCount)
// Double CPU count if it's less than 64
if instance_json.MachineConfig.CpuCount < 64 {
var instance_patch Instance_patch
instance_patch.MachineConfig.CpuCount = instance_json.MachineConfig.CpuCount * 2
instance_patch.InstanceType = instance_json.InstanceType
fmt.Println(instance_patch.MachineConfig.CpuCount)
instance_payload, err := json.Marshal(instance_patch)
if err != nil {
log.Fatal(err)
}
fmt.Println(instance_patch)
fmt.Println(string(instance_payload))
// Construct the URL for the PATCH request, including update mask.
instancesURL = apiURL + "/" + "projects/" + project + "/locations/" + location + "/clusters/" + cluster + "/instances/" + instance + "?updateMask=machineConfig.cpuCount"
fmt.Println(instancesURL)
// Create PATCH request to update the instance.
instancesReq, err = http.NewRequest("PATCH", instancesURL, bytes.NewBuffer(instance_payload))
if err != nil {
log.Fatal(err)
}
resp, err = client.Do(instancesReq)
if err != nil {
log.Fatal(err)
}
fmt.Println(resp)
}
return nil
}
// scaleDownInstance scales down the specified AlloyDB instance by halving its CPU count.
func scaleDownInstance(ctx context.Context, project, location, cluster, instance string) error {
// Scale down the instance
fmt.Println("Starting scaling down")
client, err := google.DefaultClient(ctx, "https://www.googleapis.com/auth/cloud-platform")
if err != nil {
log.Fatal(err)
}
instancesURL := apiURL + "/" + "projects/" + project + "/locations/" + location + "/clusters/" + cluster + "/instances/" + instance
fmt.Println(instancesURL)
instancesReq, err := http.NewRequest("GET", instancesURL, nil)
if err != nil {
log.Fatal(err)
}
resp, err := client.Do(instancesReq)
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.Status)
instanceBody, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
var instance_json Instance
err = json.Unmarshal(instanceBody, &instance_json)
if err != nil {
log.Println(err)
}
fmt.Println(instance_json.MachineConfig.CpuCount)
// Halve the CPU count if it's greater than 2.
if instance_json.MachineConfig.CpuCount > 2 {
var instance_patch Instance_patch
instance_patch.MachineConfig.CpuCount = instance_json.MachineConfig.CpuCount / 2
instance_patch.InstanceType = instance_json.InstanceType
fmt.Println(instance_patch.MachineConfig.CpuCount)
instance_payload, err := json.Marshal(instance_patch)
if err != nil {
log.Fatal(err)
}
fmt.Println(instance_patch)
fmt.Println(string(instance_payload))
instancesURL = apiURL + "/" + "projects/" + project + "/locations/" + location + "/clusters/" + cluster + "/instances/" + instance + "?updateMask=machineConfig.cpuCount"
fmt.Println(instancesURL)
instancesReq, err = http.NewRequest("PATCH", instancesURL, bytes.NewBuffer(instance_payload))
if err != nil {
log.Fatal(err)
}
resp, err = client.Do(instancesReq)
if err != nil {
log.Fatal(err)
}
fmt.Println(resp)
}
return nil
}
// ScaleAlloyDBInstance scales an AlloyDB instance based on the received Pub/Sub message.
func ScaleAlloyDBInstance(ctx context.Context, m PubSubMessage) error {
var alert Alert
err := json.Unmarshal(m.Data, &alert)
if err != nil {
log.Println(err)
}
fmt.Println(alert.Incident.Policy_name)
project := alert.Incident.Resource.Labels.Project_id
location := alert.Incident.Resource.Labels.Location
cluster := alert.Incident.Resource.Labels.Cluster_id
instance := alert.Incident.Resource.Labels.Instance_id
instanceoperation := alert.Incident.Policy_name
if instanceoperation == "alloydb-scale-up" {
//
err := scaleUpInstance(ctx, project, location, cluster, instance)
if err != nil {
log.Println(err)
}
} else if instanceoperation == "alloydb-scale-down" {
//
err := scaleDownInstance(ctx, project, location, cluster, instance)
if err != nil {
log.Println(err)
}
}
fmt.Println("Done ScaleAlloyDBInstance")
return nil
}