e04ed9c211
CF: Deploy Dev Docs / deploy (push) Has been cancelled
Sync Labels / build (push) Has been cancelled
tests / unit tests (macos-latest) (push) Has been cancelled
tests / unit tests (windows-latest) (push) Has been cancelled
tests / unit tests (ubuntu-latest) (push) Has been cancelled
166 lines
6.2 KiB
Go
166 lines
6.2 KiB
Go
// Copyright 2026 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 dataproc
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"time"
|
|
|
|
"cloud.google.com/go/dataproc/v2/apiv1/dataprocpb"
|
|
)
|
|
|
|
const (
|
|
logTimeBufferBefore = 1 * time.Minute
|
|
logTimeBufferAfter = 10 * time.Minute
|
|
)
|
|
|
|
// ClusterConsoleURLFromProto builds a URL to the Google Cloud Console linking to the cluster monitoring page.
|
|
func ClusterConsoleURLFromProto(clusterPb *dataprocpb.Cluster, region string) string {
|
|
return ClusterConsoleURL(clusterPb.ProjectId, region, clusterPb.ClusterName)
|
|
}
|
|
|
|
// ClusterLogsURLFromProto builds a URL to the Google Cloud Console showing Cloud Logging for the given cluster.
|
|
func ClusterLogsURLFromProto(clusterPb *dataprocpb.Cluster, region string) string {
|
|
var startTime, endTime time.Time
|
|
|
|
if clusterPb.Status != nil {
|
|
state := clusterPb.GetStatus().GetState()
|
|
stateStartTime := clusterPb.GetStatus().GetStateStartTime().AsTime()
|
|
|
|
// If the cluster is in a terminal or stopping state, we bound the logs
|
|
// to the hour preceding the state change to help with debugging.
|
|
switch state {
|
|
case dataprocpb.ClusterStatus_ERROR,
|
|
dataprocpb.ClusterStatus_DELETING,
|
|
dataprocpb.ClusterStatus_STOPPED,
|
|
dataprocpb.ClusterStatus_STOPPING:
|
|
if !stateStartTime.IsZero() {
|
|
endTime = stateStartTime
|
|
startTime = stateStartTime.Add(-1 * time.Hour)
|
|
}
|
|
}
|
|
}
|
|
|
|
return ClusterLogsURL(clusterPb.ProjectId, region, clusterPb.ClusterName, clusterPb.ClusterUuid, startTime, endTime)
|
|
}
|
|
|
|
// JobConsoleURLFromProto builds a URL to the Google Cloud Console linking to the job page.
|
|
func JobConsoleURLFromProto(jobPb *dataprocpb.Job, region string) string {
|
|
return JobConsoleURL(jobPb.Reference.ProjectId, region, jobPb.Reference.JobId)
|
|
}
|
|
|
|
// JobLogsURLFromProto builds a URL to the Google Cloud Console showing Cloud Logging for the given job.
|
|
func JobLogsURLFromProto(jobPb *dataprocpb.Job, region string) (string, error) {
|
|
if jobPb.Placement == nil {
|
|
return "", fmt.Errorf("job has no placement info")
|
|
}
|
|
clusterName := jobPb.Placement.ClusterName
|
|
projectID := jobPb.Reference.ProjectId
|
|
jobID := jobPb.Reference.JobId
|
|
|
|
var startTime, endTime time.Time
|
|
// Find min start time from history
|
|
for _, s := range jobPb.StatusHistory {
|
|
t := s.StateStartTime.AsTime()
|
|
if startTime.IsZero() || t.Before(startTime) {
|
|
startTime = t
|
|
}
|
|
}
|
|
// Also check current status
|
|
if jobPb.Status != nil {
|
|
t := jobPb.Status.StateStartTime.AsTime()
|
|
if startTime.IsZero() || t.Before(startTime) {
|
|
startTime = t
|
|
}
|
|
|
|
// Check for terminal state for end time
|
|
state := jobPb.Status.State
|
|
switch state {
|
|
case dataprocpb.JobStatus_DONE, dataprocpb.JobStatus_CANCELLED, dataprocpb.JobStatus_ERROR:
|
|
endTime = jobPb.Status.StateStartTime.AsTime()
|
|
}
|
|
}
|
|
|
|
return JobLogsURL(projectID, region, clusterName, jobID, startTime, endTime), nil
|
|
}
|
|
|
|
// ClusterConsoleURL builds a URL to the Google Cloud Console linking to the cluster monitoring page.
|
|
func ClusterConsoleURL(projectID, region, clusterName string) string {
|
|
return fmt.Sprintf("https://console.cloud.google.com/dataproc/clusters/%s/monitoring?region=%s&project=%s", clusterName, region, projectID)
|
|
}
|
|
|
|
// ClusterLogsURL builds a URL to the Google Cloud Console showing Cloud Logging for the given cluster and time range.
|
|
func ClusterLogsURL(projectID, region, clusterName, clusterUUID string, startTime, endTime time.Time) string {
|
|
advancedFilterTemplate := `resource.type="cloud_dataproc_cluster"
|
|
resource.labels.project_id=%s
|
|
resource.labels.region=%s
|
|
resource.labels.cluster_name=%s`
|
|
// Use %q to quote and escape the strings.
|
|
advancedFilter := fmt.Sprintf(advancedFilterTemplate, fmt.Sprintf("%q", projectID), fmt.Sprintf("%q", region), fmt.Sprintf("%q", clusterName))
|
|
|
|
if clusterUUID != "" {
|
|
advancedFilter += fmt.Sprintf("\nresource.labels.cluster_uuid=%q", clusterUUID)
|
|
}
|
|
|
|
if !startTime.IsZero() {
|
|
actualStart := startTime.Add(-1 * logTimeBufferBefore)
|
|
advancedFilter += fmt.Sprintf("\ntimestamp>=\"%s\"", actualStart.Format(time.RFC3339Nano))
|
|
}
|
|
if !endTime.IsZero() {
|
|
actualEnd := endTime.Add(logTimeBufferAfter)
|
|
advancedFilter += fmt.Sprintf("\ntimestamp<=\"%s\"", actualEnd.Format(time.RFC3339Nano))
|
|
}
|
|
|
|
v := url.Values{}
|
|
v.Add("resource", "cloud_dataproc_cluster/cluster_name/"+clusterName)
|
|
v.Add("advancedFilter", advancedFilter)
|
|
v.Add("project", projectID)
|
|
|
|
return "https://console.cloud.google.com/logs/viewer?" + v.Encode()
|
|
}
|
|
|
|
// JobConsoleURL builds a URL to the Google Cloud Console linking to the job page.
|
|
func JobConsoleURL(projectID, region, jobID string) string {
|
|
return fmt.Sprintf("https://console.cloud.google.com/dataproc/jobs/%s?region=%s&project=%s", jobID, region, projectID)
|
|
}
|
|
|
|
// JobLogsURL builds a URL to the Google Cloud Console showing Cloud Logging for the given job and time range.
|
|
func JobLogsURL(projectID, region, clusterName, jobID string, startTime, endTime time.Time) string {
|
|
advancedFilterTemplate := `resource.type="cloud_dataproc_cluster"
|
|
resource.labels.project_id=%s
|
|
resource.labels.region=%s
|
|
resource.labels.cluster_name=%s
|
|
labels.job_id=%s`
|
|
// Use %q to quote and escape the strings.
|
|
advancedFilter := fmt.Sprintf(advancedFilterTemplate, fmt.Sprintf("%q", projectID), fmt.Sprintf("%q", region), fmt.Sprintf("%q", clusterName), fmt.Sprintf("%q", jobID))
|
|
|
|
if !startTime.IsZero() {
|
|
actualStart := startTime.Add(-1 * logTimeBufferBefore)
|
|
advancedFilter += fmt.Sprintf("\ntimestamp>=\"%s\"", actualStart.Format(time.RFC3339Nano))
|
|
}
|
|
if !endTime.IsZero() {
|
|
actualEnd := endTime.Add(logTimeBufferAfter)
|
|
advancedFilter += fmt.Sprintf("\ntimestamp<=\"%s\"", actualEnd.Format(time.RFC3339Nano))
|
|
}
|
|
|
|
v := url.Values{}
|
|
v.Add("resource", "cloud_dataproc_cluster/cluster_name/"+clusterName)
|
|
v.Add("advancedFilter", advancedFilter)
|
|
v.Add("project", projectID)
|
|
|
|
return "https://console.cloud.google.com/logs/viewer?" + v.Encode()
|
|
}
|