mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-02 14:59:53 +08:00
* feat: debug service
* fix(service): lint and test assertion
* refactor: code review suggestions
* chore(service): update change log level log message
* fix(service): wrong error assertion
* fix(service): unable to set trace log level
* chore(utils): rename SupportedLogLevelsString
* refactor(teleport): change `seconds` default value
* fix(teleport): remove trailing line on profiles file
* chore(teleport): add profile examples
* refactor(service): use marshal/unmarshal log level functions
* fix(teleport): fix tarball file timestamps
* refactor: move debug service + constants to a separate package
* test(debug): move request to a helper
* chore(debug): remove leftover file
* chore(log): typo
* refactor: move package from lib/debug to lib/srv/debug
* refactor: code review suggestions
* fix(service): socket file consistency
* test(common): use one temporary directory per socket test
* chore(tool): update debug service connection error message
* refactor(debug): use enhanced serve mux
* chore(teleport): fix usage examples
* Apply suggestions from code review
Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com>
* refactor: code review suggestions
* refactor(debug): move marshal/unmarshal text log level to debug
* refactor: code review suggestions
* refactor: move debug service client to lib/client/debug/
* refactor: code review changes
* feat(debug): progress on collect profiles
* refactor: code review suggestions
* chore(gomod): update
* fix(debug): set log level validations
* refactor(debug): move client interface
* feat(debug): add limit reader for collect profiles
* refactor(debug): remove progress bar
* Revert "chore(gomod): update"
This reverts commit 8dbebbf69f.
* refactor: remove debug service endpoints constants
* test(service): fix wrong constant
* refactor(debug): remove collect profiles limit reader
* chore(debug): make client struct public
* refactor(debug): change collect time to 0
* test(service): deflake test
* test(service): add missing auth config
* test(debug): fix missing assertion
---------
Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com>
150 lines
4.3 KiB
Go
150 lines
4.3 KiB
Go
// Teleport
|
|
// Copyright (C) 2024 Gravitational, Inc.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package debug
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gravitational/trace"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/gravitational/teleport"
|
|
)
|
|
|
|
func TestSetLogLevel(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
t.Run("Success", func(t *testing.T) {
|
|
socketPath, _ := newSocketMockService(t, http.StatusOK, []byte{})
|
|
clt := NewClient(socketPath)
|
|
|
|
// The validation is done on the server side, here our server always
|
|
// return success.
|
|
_, err := clt.SetLogLevel(ctx, "INFO")
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("Failure", func(t *testing.T) {
|
|
socketPath, _ := newSocketMockService(t, http.StatusUnprocessableEntity, []byte{})
|
|
clt := NewClient(socketPath)
|
|
|
|
// The validation is done on the server side, here our server always
|
|
// return failure.
|
|
_, err := clt.SetLogLevel(ctx, "RANDOM")
|
|
require.Error(t, err)
|
|
require.True(t, trace.IsBadParameter(err))
|
|
})
|
|
}
|
|
|
|
func TestCollectProfile(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
for _, test := range []struct {
|
|
desc string
|
|
profile string
|
|
seconds int
|
|
expectErr bool
|
|
expectedArgs string
|
|
}{
|
|
{
|
|
desc: "profile",
|
|
profile: "goroutine",
|
|
},
|
|
{
|
|
desc: "profile with seconds flag",
|
|
profile: "block",
|
|
seconds: 10,
|
|
expectedArgs: "seconds=10",
|
|
},
|
|
{
|
|
desc: "invalid profile",
|
|
profile: "RANDOM",
|
|
expectErr: true,
|
|
},
|
|
} {
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
socketPath, closeFn := newSocketMockService(t, http.StatusOK, []byte("collected profile"))
|
|
defer closeFn()
|
|
clt := NewClient(socketPath)
|
|
|
|
_, err := clt.CollectProfile(ctx, test.profile, test.seconds)
|
|
if test.expectErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
requestedPaths := closeFn()
|
|
require.Len(t, requestedPaths, 1)
|
|
|
|
path, args, _ := strings.Cut(requestedPaths[0], "?")
|
|
require.True(t, strings.HasPrefix(path, "/debug/pprof/"), "expected PProf request but got %q", path)
|
|
require.Equal(t, test.expectedArgs, args)
|
|
require.Equal(t, test.profile, strings.TrimPrefix(path, "/debug/pprof/"))
|
|
})
|
|
}
|
|
}
|
|
|
|
// newSocketMockService creates a unix socket that access HTTP requests and
|
|
// always replies with success. Returns the path to the socket and `closeFn`,
|
|
// which when called closes the socket and returns the requested paths.
|
|
func newSocketMockService(t *testing.T, status int, contents []byte) (string, func() []string) {
|
|
t.Helper()
|
|
|
|
// We cannot simply use the `t.TempDir()` due to the size limit of UDS.
|
|
// Here, we place it inside the temporary directory, which will most likely
|
|
// give a smaller path.
|
|
// https://github.com/golang/go/issues/62614
|
|
socketDir, err := os.MkdirTemp("", "*")
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { os.RemoveAll(socketDir) })
|
|
|
|
socketPath := filepath.Join(socketDir, teleport.DebugServiceSocketName)
|
|
require.Greater(t, 100, len(socketPath), "expected socket name to be smaller (less than 100 characters)"+
|
|
" due to Unix domain socket size limitation but got %q (%d).", socketPath, len(socketPath))
|
|
|
|
l, err := net.Listen("unix", socketPath)
|
|
require.NoError(t, err)
|
|
|
|
var requests []string
|
|
srv := &http.Server{
|
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
requests = append(requests, r.URL.RequestURI())
|
|
w.WriteHeader(status)
|
|
w.Write(contents)
|
|
}),
|
|
}
|
|
|
|
go func() {
|
|
err := srv.Serve(l)
|
|
if err != nil && err != http.ErrServerClosed {
|
|
t.Log("Failed to start server", err)
|
|
}
|
|
}()
|
|
|
|
t.Cleanup(func() { srv.Shutdown(context.Background()) })
|
|
return socketPath, func() []string {
|
|
return requests
|
|
}
|
|
}
|