diff --git a/config/config.go b/config/config.go index b8fcc3c..498d9ad 100644 --- a/config/config.go +++ b/config/config.go @@ -1,25 +1,13 @@ package config import ( + "coredump-handler/types" "encoding/json" "io/ioutil" ) -type Coreconfig struct { - Core_pattern string - Core_limited string - Core__pipe_limit string - Socket_path string -} -type Pipeconfig struct { - Save_model int //0为文件保存 1为压缩保存 2为minidump保存 - File_base_path string - Total_file_mem_limit string - Containered_sock_path string -} - -func Init() (Coreconfig, error) { - var config Coreconfig +func Init() (types.Coreconfig, error) { + var config types.Coreconfig content, err := ioutil.ReadFile("./config.json") if err != nil { return config, err @@ -27,9 +15,9 @@ func Init() (Coreconfig, error) { err = json.Unmarshal(content, &config) return config, err } -func PipeInit() (Pipeconfig, error) { - var config Pipeconfig - content, err := ioutil.ReadFile("./pipe-config.json") +func PipeInit(config_path string) (types.Pipeconfig, error) { + var config types.Pipeconfig + content, err := ioutil.ReadFile(config_path) if err != nil { return config, err } diff --git a/coredump.go b/coredump.go index 5d671fa..2c8270f 100644 --- a/coredump.go +++ b/coredump.go @@ -1,14 +1,14 @@ package main import ( - "coredump-handler/config" "coredump-handler/logger" + "coredump-handler/types" "net" "os/exec" "syscall" ) -func coredumpInit(coreconfig config.Coreconfig) error { +func coredumpInit(coreconfig types.Coreconfig) error { cmd := exec.Command("sysctl", "-w", "kernel.core_pattern='"+coreconfig.Core_pattern+"'") err := cmd.Run() if err != nil { @@ -32,7 +32,7 @@ func coredumpInit(coreconfig config.Coreconfig) error { return nil } -func coredumpHandler(coreconfig config.Coreconfig) error { +func coredumpHandler(coreconfig types.Coreconfig) error { syscall.Unlink(coreconfig.Socket_path) laddr, err := net.ResolveUnixAddr("unix", coreconfig.Socket_path) if err != nil { diff --git a/corepipe/corepipe.go b/corepipe/corepipe.go index a27ca95..5859879 100644 --- a/corepipe/corepipe.go +++ b/corepipe/corepipe.go @@ -4,13 +4,17 @@ import ( "archive/zip" "context" "coredump-handler/config" + "coredump-handler/types" + "encoding/json" "errors" + "flag" "fmt" "io" "io/ioutil" "os" "regexp" "strconv" + "strings" "syscall" "github.com/containerd/containerd" @@ -19,8 +23,16 @@ import ( ) const chunkSize = 1024 * 1024 * 1024 // 1GB +var coredump_config types.Coredump_config -func isDiskSufficient(pipe_config config.Pipeconfig) (bool, error) { +func argsJudge() error { + if coredump_config.Initial_ns_pid == "" || coredump_config.Process_ns_pid == "" || coredump_config.Corepipe_config_path == "" || coredump_config.Timestap == 0 || coredump_config.Process_exe_path == "" { + err := fmt.Sprintf("Failed to initialize command line parameters. -P=%s -p=%s -E=%s -configpath=%s -t=%d", coredump_config.Initial_ns_pid, coredump_config.Process_ns_pid, coredump_config.Process_exe_path, coredump_config.Corepipe_config_path, coredump_config.Timestap) + return errors.New(err) + } + return nil +} +func isDiskSufficient(pipe_config types.Pipeconfig) (bool, error) { percent, err := strconv.ParseFloat(pipe_config.Total_file_mem_limit[:len(pipe_config.Total_file_mem_limit)-1], 64) if err != nil { return false, err @@ -43,8 +55,8 @@ func isDiskSufficient(pipe_config config.Pipeconfig) (bool, error) { } return true, nil } -func createCoreDumpDir(pipe_config *config.Pipeconfig, args []string) error { - pipe_config.File_base_path = fmt.Sprintf("%s/%s_%s_%s", pipe_config.File_base_path, args[1], args[2], args[3]) +func createCoreDumpDir(pipe_config *types.Pipeconfig, args types.Coredump_config) error { + pipe_config.File_base_path = fmt.Sprintf("%s/%s_%s_%d", pipe_config.File_base_path, args.Initial_ns_pid, args.Process_ns_pid, args.Timestap) dirName := pipe_config.File_base_path if _, err := os.Stat(dirName); os.IsNotExist(err) { // 目录不存在,创建目录 @@ -96,12 +108,16 @@ func getImageId(container_id string, sock_path string) (string, error) { } return imageRef.Name(), nil } -func writeCoreConfig(data string) error { - err := ioutil.WriteFile("coredump.config", []byte(data), 0666) +func writeCoreConfig(config types.Coredump_config) error { + file, err := os.Create("coredump.config") + if err != nil { + return err + } + encoder := json.NewEncoder(file) + err = encoder.Encode(&config) if err != nil { return err } - fmt.Printf("Wrote %d bytes to file\n", len(data)) return nil } func writeCoreDumpToFile() error { @@ -170,16 +186,22 @@ func compress() error { return nil } - func main() { - //切换到config文件路径 - err := changeDirectory("/root/pipe/corepipe") + flag.StringVar(&coredump_config.Initial_ns_pid, "P", "", "initial ns pid") + flag.StringVar(&coredump_config.Process_ns_pid, "p", "", "process ns pid") + flag.StringVar(&coredump_config.Process_exe_path, "E", "", "pathname of executable process") + flag.StringVar(&coredump_config.Corepipe_config_path, "configpath", "", "configfile's path") + flag.Int64Var(&coredump_config.Timestap, "t", 0, "the time of coredump") + flag.Parse() + coredump_config.Process_exe_path = strings.Replace(coredump_config.Process_exe_path, "!", "/", -1) + //判断参数读取是否正确 + err := argsJudge() if err != nil { journal.Print(journal.PriErr, err.Error()) return } //读取config文件并初始化 - pipe_config, err := config.PipeInit() + pipe_config, err := config.PipeInit(coredump_config.Corepipe_config_path) if err != nil { journal.Print(journal.PriErr, err.Error()) return @@ -190,13 +212,8 @@ func main() { journal.Print(journal.PriErr, err.Error()) return } - //判断参数传入是否符合要求 - if len(os.Args) < 4 { - journal.Print(journal.PriErr, "parameter passing exception") - return - } //创建存储coredump内容文件夹 - err = createCoreDumpDir(&pipe_config, os.Args) + err = createCoreDumpDir(&pipe_config, coredump_config) if err != nil { journal.Print(journal.PriErr, err.Error()) return @@ -208,17 +225,16 @@ func main() { return } //查找发生coredump进程对应的container id - container_id, err := getContainerId(os.Args[1]) + container_id, err := getContainerId(coredump_config.Initial_ns_pid) //根据查找到的container id查找对应的image name - var image_id string if err == nil && len(container_id) != 0 { - image_id, err = getImageId(container_id, pipe_config.Containered_sock_path) + coredump_config.Image_id, err = getImageId(container_id, pipe_config.Containered_sock_path) if err != nil { journal.Print(journal.PriInfo, err.Error()) } } //将image name写入coredump config - err = writeCoreConfig(image_id) + err = writeCoreConfig(coredump_config) if err != nil { journal.Print(journal.PriInfo, err.Error()) } diff --git a/corepipe/corepipe_test.go b/corepipe/corepipe_test.go index 755bd24..fa8f182 100644 --- a/corepipe/corepipe_test.go +++ b/corepipe/corepipe_test.go @@ -1,278 +1,189 @@ package main import ( + "bufio" "bytes" - "coredump-handler/config" + "coredump-handler/types" + "fmt" "io/ioutil" "os" - "strconv" - "strings" + "os/exec" "testing" + + "github.com/stretchr/testify/assert" ) -func TestIsMemorySufficient(t *testing.T) { - type input struct { - pipe_config config.Pipeconfig - } - tests := []struct { - name string - input input - want bool - wantErr bool - }{ - { - name: "memory is sufficient", - input: input{ - pipe_config: config.Pipeconfig{ - Total_file_mem_limit: "50%", - }, - }, - want: true, - wantErr: false, - }, - { - name: "memory is not sufficient", - input: input{ - pipe_config: config.Pipeconfig{ - Total_file_mem_limit: "0.001%", - }, - }, - want: false, - wantErr: false, - }, - { - name: "invalid memory limit", - input: input{ - pipe_config: config.Pipeconfig{ - Total_file_mem_limit: "invalid", - }, - }, - want: false, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := isDiskSufficient(tt.input.pipe_config) - if (err != nil) != tt.wantErr { - t.Errorf("isMemorySufficient() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("isMemorySufficient() = %v, want %v", got, tt.want) - } - }) +func TestArgsJudge(t *testing.T) { + var coredump_config types.Coredump_config + coredump_config.Initial_ns_pid = "123" + coredump_config.Process_ns_pid = "456" + coredump_config.Process_exe_path = "/path/to/executable" + coredump_config.Corepipe_config_path = "/path/to/config" + coredump_config.Timestap = 1618332579 + + err := argsJudge() + assert.Nil(t, err) + + // missing Initial_ns_pid + coredump_config.Initial_ns_pid = "" + err = argsJudge() + assert.NotNil(t, err) + coredump_config.Initial_ns_pid = "123" + + // missing Process_ns_pid + coredump_config.Process_ns_pid = "" + err = argsJudge() + assert.NotNil(t, err) + coredump_config.Process_ns_pid = "456" + + // missing Corepipe_config_path + coredump_config.Corepipe_config_path = "" + err = argsJudge() + assert.NotNil(t, err) + coredump_config.Corepipe_config_path = "/path/to/config" + + // missing Timestap + coredump_config.Timestap = 0 + err = argsJudge() + assert.NotNil(t, err) + coredump_config.Timestap = 1618332579 + + // missing Process_exe_path + coredump_config.Process_exe_path = "" + err = argsJudge() + assert.NotNil(t, err) + coredump_config.Process_exe_path = "/path/to/executable" +} + +func TestIsDiskSufficient(t *testing.T) { + pipe_config := types.Pipeconfig{ + Total_file_mem_limit: "50%", } + // create a temp file and set limit to 100% + file, err := ioutil.TempFile("", "") + assert.Nil(t, err) + defer os.Remove(file.Name()) + pipe_config.File_base_path = file.Name() + pipe_config.Total_file_mem_limit = "100%" + flag, err := isDiskSufficient(pipe_config) + assert.Nil(t, err) + assert.False(t, flag) + + // set limit to 0% + pipe_config.Total_file_mem_limit = "0%" + flag, err = isDiskSufficient(pipe_config) + assert.Nil(t, err) + assert.True(t, flag) } func TestCreateCoreDumpDir(t *testing.T) { - type input struct { - pipe_config *config.Pipeconfig - args []string + pipe_config := types.Pipeconfig{ + File_base_path: "/tmp", } - tests := []struct { - name string - input input - wantErr bool - }{ - { - name: "directory does not exist", - input: input{ - pipe_config: &config.Pipeconfig{ - File_base_path: "/tmp", - }, - args: []string{"", "pid", "arg2", "arg3"}, - }, - wantErr: false, - }, - { - name: "directory already exists", - input: input{ - pipe_config: &config.Pipeconfig{ - File_base_path: "/tmp", - }, - args: []string{"", "pid", "arg2", "arg3"}, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := createCoreDumpDir(tt.input.pipe_config, tt.input.args) - if (err != nil) != tt.wantErr { - t.Errorf("createCoreDumpDir() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} + err := createCoreDumpDir(&pipe_config, coredump_config) + assert.Nil(t, err) -func TestChangeDirectory(t *testing.T) { - type input struct { - dir string - } - tests := []struct { - name string - input input - wantErr bool - }{ - { - name: "change directory successfully", - input: input{ - dir: "/root", - }, - wantErr: false, - }, - { - name: "fail to change directory", - input: input{ - dir: "/invalid", - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := changeDirectory(tt.input.dir) - if (err != nil) != tt.wantErr { - t.Errorf("changeDirectory() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } + // try to create same directory again should return an error + err = createCoreDumpDir(&pipe_config, coredump_config) + assert.NotNil(t, err) } func TestGetContainerId(t *testing.T) { - type input struct { - pid string + pid := "1" + container_id, err := getContainerId(pid) + if err != nil { + t.Errorf("getContainerId() error = %v", err) } - tests := []struct { - name string - input input - want string - wantErr bool - }{ - { - name: "get container id successfully", - input: input{ - pid: strconv.Itoa(os.Getpid()), - }, - want: "", - wantErr: false, - }, - { - name: "fail to get container id", - input: input{ - pid: "invalid", - }, - want: "", - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := getContainerId(tt.input.pid) - if (err != nil) != tt.wantErr { - t.Errorf("getContainerId() error = %v, wantErr %v", err, tt.wantErr) - return - } - if strings.TrimSpace(got) == "" { - t.Errorf("getContainerId() got empty string") - } - }) + if len(container_id) == 0 { + t.Errorf("getContainerId() = %v, want not empty string", container_id) } } func TestGetImageId(t *testing.T) { - type input struct { - container_id string - sock_path string + container_id := "123456" + sock_path := "/var/run/containerd.sock" + image_id, err := getImageId(container_id, sock_path) + if err != nil { + t.Errorf("getImageId() error = %v", err) } - tests := []struct { - name string - input input - want string - wantErr bool - }{ - { - name: "get image id successfully", - input: input{ - container_id: "invalid", - sock_path: "/run/k3s/containerd/containerd.sock", - }, - want: "", - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := getImageId(tt.input.container_id, tt.input.sock_path) - if (err != nil) != tt.wantErr { - t.Errorf("getImageId() error = %v, wantErr %v", err, tt.wantErr) - return - } - if strings.TrimSpace(got) == "" { - t.Errorf("getImageId() got empty string") - } - }) + if len(image_id) == 0 { + t.Errorf("getImageId() = %v, want not empty string", image_id) } } func TestWriteCoreConfig(t *testing.T) { - type input struct { - data string + config := types.Coredump_config{ + Initial_ns_pid: "1", + Process_ns_pid: "2", + Process_exe_path: "/bin/bash", + Corepipe_config_path: "/tmp/config.yaml", + Timestap: 12345678, } - tests := []struct { - name string - input input - wantErr bool - }{ - { - name: "write core config successfully", - input: input{ - data: "test data", - }, - wantErr: false, - }, + err := writeCoreConfig(config) + if err != nil { + t.Errorf("writeCoreConfig() error = %v", err) } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := writeCoreConfig(tt.input.data) - if (err != nil) != tt.wantErr { - t.Errorf("writeCoreConfig() error = %v, wantErr %v", err, tt.wantErr) - } - content, err := ioutil.ReadFile("coredump.config") - if err != nil { - t.Error("failed to read file") - return - } - if !bytes.Equal(content, []byte(tt.input.data)) { - t.Errorf("writeCoreConfig() content = %v, want = %v", string(content), tt.input.data) - } - os.Remove("coredump.config") - }) + defer os.Remove("coredump.config") +} + +func TestWriteCoreDumpToFile(t *testing.T) { + cmd := exec.Command("echo", "test") + cmdReader, err := cmd.StdoutPipe() + if err != nil { + t.Errorf("WriteCoreDumpToFile() error = %v", err) } + scanner := bufio.NewScanner(cmdReader) + go func() { + for scanner.Scan() { + fmt.Printf("reading output: %s\n", scanner.Text()) + } + }() + err = cmd.Start() + if err != nil { + t.Errorf("WriteCoreDumpToFile() error = %v", err) + } + err = writeCoreDumpToFile() + if err != nil { + t.Errorf("WriteCoreDumpToFile() error = %v", err) + } + b, err := ioutil.ReadFile("coredump.info") + if err != nil { + t.Errorf("WriteCoreDumpToFile() error = %v", err) + } + expected := "test\n" + if string(b) != expected { + t.Errorf("WriteCoreDumpToFile() got = %v, want = %v", string(b), expected) + } + defer os.Remove("coredump.info") } func TestCompress(t *testing.T) { - tests := []struct { - name string - wantErr bool - }{ - { - name: "compress successfully", - }, + cmd := exec.Command("echo", "test") + cmdReader, err := cmd.StdoutPipe() + if err != nil { + t.Errorf("compress() error = %v", err) } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := compress() - if (err != nil) != tt.wantErr { - t.Errorf("compress() error = %v, wantErr %v", err, tt.wantErr) - } - // Check if the compressed file exists - if _, err := os.Stat("coredump.info.zip"); os.IsNotExist(err) { - t.Errorf("compress() failed to create compressed file") - } else { - os.Remove("coredump.info.zip") - } - }) + scanner := bufio.NewScanner(cmdReader) + go func() { + for scanner.Scan() { + fmt.Printf("reading output: %s\n", scanner.Text()) + } + }() + err = cmd.Start() + if err != nil { + t.Errorf("compress() error = %v", err) } + err = compress() + if err != nil { + t.Errorf("compress() error = %v", err) + } + b, err := ioutil.ReadFile("coredump.info.zip") + if err != nil { + t.Errorf("compress() error = %v", err) + } + expected := []byte{80, 75, 3, 4, 20, 0, 8, 8, 8, 0, 170, 114, 38, 99, 102, 0, 0, 0, 102, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 116, 101, 115, 116, 10, 0, 82, 105, 105, 90, 85, 50, 84, 110, 88, 70, 90, 65, 48, 108, 108, 81, 0, 116, 157, 80, 10, 128, 163, 239, 120, 53, 2, 190, 127, 7, 98, 131, 137, 51, 189, 206, 149, 14, 54, 126, 254, 152, 119, 177, 209, 155, 201, 23, 37, 4, 72, 113, 39, 46, 179, 144, 106, 184, 44, 251, 47, 88, 97, 2, 141, 11, 129, 71, 109, 187, 124, 32, 63, 22, 111, 181, 59, 30, 58, 184, 40, 203, 205, 3, 113, 165, 117, 232, 6, 228, 240, 132, 94, 137, 43, 95, 218, 221, 90, 203, 173, 43, 92, 216, 226, 65, 118, 222, 208, 59, 185, 250, 56, 70, 63, 135, 72, 44, 250, 215, 59, 36, 139, 74, 75, 112, 0, 0, 0} + if !bytes.Equal(b, expected) { + t.Errorf("compress() got = %v, want = %v", b, expected) + } + defer os.Remove("coredump.info.zip") } diff --git a/go.mod b/go.mod index 6b9e5f6..7f4ab65 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.20 require ( github.com/containerd/containerd v1.7.0 github.com/coreos/go-systemd/v22 v22.5.0 + github.com/stretchr/testify v1.8.2 ) require ( @@ -18,6 +19,7 @@ require ( github.com/containerd/ttrpc v1.2.1 // indirect github.com/containerd/typeurl/v2 v2.1.0 // indirect github.com/cyphar/filepath-securejoin v0.2.3 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -37,6 +39,7 @@ require ( github.com/opencontainers/runtime-spec v1.1.0-rc.1 // indirect github.com/opencontainers/selinux v1.11.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sirupsen/logrus v1.9.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel v1.14.0 // indirect @@ -50,4 +53,5 @@ require ( google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect google.golang.org/grpc v1.53.0 // indirect google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 711a287..87b33ae 100644 --- a/go.sum +++ b/go.sum @@ -134,6 +134,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= @@ -244,6 +245,7 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/types/types.go b/types/types.go new file mode 100644 index 0000000..2748b01 --- /dev/null +++ b/types/types.go @@ -0,0 +1,22 @@ +package types + +type Coreconfig struct { + Core_pattern string + Core_limited string + Core__pipe_limit string + Socket_path string +} +type Pipeconfig struct { + Save_model int //0为文件保存 1为压缩保存 2为minidump保存 + File_base_path string + Total_file_mem_limit string + Containered_sock_path string +} +type Coredump_config struct { + Initial_ns_pid string + Process_ns_pid string + Process_exe_path string + Timestap int64 + Corepipe_config_path string + Image_id string +}