-
Notifications
You must be signed in to change notification settings - Fork 705
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
Fetch Argo workflow logs #1332
Fetch Argo workflow logs #1332
Changes from all commits
5bbcf71
a097910
280e8a6
2309cc5
e042093
117bc8b
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 |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// Copyright 2019 The SQLFlow Authors. All rights reserved. | ||
// 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 sql | ||
|
||
import ( | ||
"fmt" | ||
"os/exec" | ||
"regexp" | ||
"time" | ||
|
||
pb "sqlflow.org/sqlflow/pkg/proto" | ||
) | ||
|
||
// Reference: https://github.com/argoproj/argo/blob/723b3c15e55d2f8dceb86f1ac0a6dc7d1a58f10b/pkg/apis/workflow/v1alpha1/workflow_types.go#L30-L38 | ||
|
||
// NodePhase is a label for the condition of a node at the current time. | ||
type NodePhase string | ||
|
||
// Workflow and node statuses | ||
const ( | ||
NodePending NodePhase = "Pending" | ||
NodeRunning NodePhase = "Running" | ||
NodeSucceeded NodePhase = "Succeeded" | ||
NodeSkipped NodePhase = "Skipped" | ||
NodeFailed NodePhase = "Failed" | ||
NodeError NodePhase = "Error" | ||
) | ||
|
||
func isCompletedPhase(phase NodePhase) bool { | ||
return phase == NodeSucceeded || | ||
phase == NodeFailed || | ||
phase == NodeError || | ||
phase == NodeSkipped | ||
} | ||
|
||
func getWorkflowID(output string) (string, error) { | ||
reWorkflow := regexp.MustCompile(`.+/(.+) .+`) | ||
wf := reWorkflow.FindStringSubmatch(string(output)) | ||
if len(wf) != 2 { | ||
return "", fmt.Errorf("parse workflow ID error: %v", output) | ||
} | ||
|
||
return wf[1], nil | ||
} | ||
|
||
func getWorkflowStatusPhase(job pb.Job) (string, error) { | ||
cmd := exec.Command("kubectl", "get", "wf", job.Id, "-o", "jsonpath={.status.phase}") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we use https://github.com/kubernetes/client-go instead of calling the command line? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the advantage of using the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I created an issue: #1362 to discuss go-client or kubectl, maybe we can move to make some disccusion. |
||
output, err := cmd.CombinedOutput() | ||
if err != nil { | ||
return "", fmt.Errorf("getWorkflowStatusPhase error: %v\n%v", string(output), err) | ||
} | ||
|
||
return string(output), nil | ||
} | ||
|
||
func getWorkflowPodName(job pb.Job) (string, error) { | ||
cmd := exec.Command("kubectl", "get", "pods", | ||
fmt.Sprintf(`--selector=workflows.argoproj.io/workflow=%s`, job.Id), | ||
"-o", "jsonpath={.items[0].metadata.name}") | ||
output, err := cmd.CombinedOutput() | ||
if err != nil { | ||
return "", fmt.Errorf("getWorkflowPodName error: %v\n%v", string(output), err) | ||
} | ||
|
||
return string(output), nil | ||
} | ||
|
||
func getPodLogs(podName string) (string, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I will add this in the next PR. |
||
// NOTE(tony): A workflow pod usually contains two container: main and wait | ||
// I believe wait is used for management by Argo, so we only need to care about main. | ||
cmd := exec.Command("kubectl", "logs", podName, "main") | ||
output, err := cmd.CombinedOutput() | ||
if err != nil { | ||
return "", fmt.Errorf("getPodLogs error: %v\n%v", string(output), err) | ||
} | ||
return string(output), nil | ||
} | ||
|
||
func fetchWorkflowLog(job pb.Job) (string, error) { | ||
for { | ||
statusPhase, err := getWorkflowStatusPhase(job) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
// FIXME(tony): what if it is a long running job | ||
if isCompletedPhase(NodePhase(statusPhase)) { | ||
break | ||
} | ||
time.Sleep(time.Second) | ||
} | ||
|
||
// FIXME(tony): what if there are multiple pods | ||
podName, err := getWorkflowPodName(job) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return getPodLogs(podName) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// Copyright 2019 The SQLFlow Authors. All rights reserved. | ||
// 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 sql | ||
|
||
import ( | ||
"fmt" | ||
"github.com/stretchr/testify/assert" | ||
"io/ioutil" | ||
"os" | ||
"os/exec" | ||
pb "sqlflow.org/sqlflow/pkg/proto" | ||
"testing" | ||
) | ||
|
||
const ( | ||
argoYAML = `apiVersion: argoproj.io/v1alpha1 | ||
kind: Workflow # new type of k8s spec | ||
metadata: | ||
generateName: hello-world- # name of the workflow spec | ||
spec: | ||
entrypoint: whalesay # invoke the whalesay template | ||
templates: | ||
- name: whalesay # name of the template | ||
container: | ||
image: docker/whalesay | ||
command: [echo] | ||
args: ["hello world"] | ||
resources: # limit the resources | ||
limits: | ||
memory: 32Mi | ||
cpu: 100m | ||
` | ||
argoYAMLOutput = `hello world | ||
` | ||
) | ||
|
||
func createAndWriteTempFile(content string) (string, error) { | ||
tmpFile, err := ioutil.TempFile("/tmp", "sqlflow-") | ||
if err != nil { | ||
return "", nil | ||
} | ||
defer tmpFile.Close() | ||
|
||
if _, err = tmpFile.Write([]byte(content)); err != nil { | ||
return "", err | ||
} | ||
|
||
return tmpFile.Name(), nil | ||
} | ||
|
||
func kubectlCreateFromYAML(content string) (string, error) { | ||
fileName, err := createAndWriteTempFile(content) | ||
if err != nil { | ||
return "", err | ||
} | ||
defer os.Remove(fileName) | ||
|
||
cmd := exec.Command("kubectl", "create", "-f", fileName) | ||
output, err := cmd.CombinedOutput() | ||
if err != nil { | ||
return "", fmt.Errorf("submitYAML error: %v\n%v", string(output), err) | ||
} | ||
|
||
return getWorkflowID(string(output)) | ||
} | ||
|
||
func TestFetchWorkflowLog(t *testing.T) { | ||
if os.Getenv("SQLFLOW_TEST") != "workflow" { | ||
t.Skip("argo: skip workflow tests") | ||
} | ||
a := assert.New(t) | ||
|
||
workflowID, err := kubectlCreateFromYAML(argoYAML) | ||
a.NoError(err) | ||
logs, err := fetchWorkflowLog(pb.Job{Id: workflowID}) | ||
a.NoError(err) | ||
a.Equal(argoYAMLOutput, logs) | ||
} |
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.
Duplicated with
workflow.go
#getWorkflowID ?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.
Yes, I will move workflow related code to
package workflow
in the next PR.