From 35ffb2f6fabba530fcea07321c579ee4fd64d19d Mon Sep 17 00:00:00 2001 From: Troy Chiu Date: Wed, 16 Apr 2025 23:56:41 -0700 Subject: [PATCH 1/6] wip Signed-off-by: Troy Chiu --- kubectl-plugin/go.mod | 10 ++- kubectl-plugin/go.sum | 25 +++++- kubectl-plugin/pkg/cmd/session/session.go | 2 +- .../pkg/cmd/session/session_kill_all.go | 79 +++++++++++++++++++ 4 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 kubectl-plugin/pkg/cmd/session/session_kill_all.go diff --git a/kubectl-plugin/go.mod b/kubectl-plugin/go.mod index ca2e5a2c0f5..6af4c351f8a 100644 --- a/kubectl-plugin/go.mod +++ b/kubectl-plugin/go.mod @@ -10,6 +10,7 @@ require ( github.com/onsi/ginkgo/v2 v2.22.1 github.com/onsi/gomega v1.36.2 github.com/ray-project/kuberay/ray-operator v0.0.0 + github.com/shirou/gopsutil/v4 v4.25.3 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.6 github.com/stretchr/testify v1.10.0 @@ -29,12 +30,14 @@ require ( github.com/blang/semver/v4 v4.0.0 // indirect github.com/chai2010/gettext-go v1.0.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/ebitengine/purego v0.8.2 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/camelcase v1.0.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-logr/logr v1.4.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect @@ -43,7 +46,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.6.9 // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect github.com/google/uuid v1.6.0 // indirect @@ -53,6 +56,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/moby/spdystream v0.5.0 // indirect @@ -65,9 +69,13 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.26.0 // indirect golang.org/x/sync v0.11.0 // indirect diff --git a/kubectl-plugin/go.sum b/kubectl-plugin/go.sum index aa426a14e3d..22db3db1a94 100644 --- a/kubectl-plugin/go.sum +++ b/kubectl-plugin/go.sum @@ -15,6 +15,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= @@ -27,6 +29,8 @@ github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8b github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= @@ -43,9 +47,10 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -75,6 +80,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -107,12 +114,16 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE= +github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -127,12 +138,18 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -155,8 +172,12 @@ golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= diff --git a/kubectl-plugin/pkg/cmd/session/session.go b/kubectl-plugin/pkg/cmd/session/session.go index 71d5a43b48c..f95f223f0c8 100644 --- a/kubectl-plugin/pkg/cmd/session/session.go +++ b/kubectl-plugin/pkg/cmd/session/session.go @@ -102,7 +102,7 @@ func NewSessionCommand(cmdFactory cmdutil.Factory, streams genericiooptions.IOSt } cmd.Flags().BoolVarP(&options.Verbose, "verbose", "v", false, "verbose output") - + cmd.AddCommand(NewKillAllSessionsCommand(cmdFactory, streams)) return cmd } diff --git a/kubectl-plugin/pkg/cmd/session/session_kill_all.go b/kubectl-plugin/pkg/cmd/session/session_kill_all.go new file mode 100644 index 00000000000..072371bfcc9 --- /dev/null +++ b/kubectl-plugin/pkg/cmd/session/session_kill_all.go @@ -0,0 +1,79 @@ +package session + +import ( + "context" + "fmt" + "github.com/ray-project/kuberay/kubectl-plugin/pkg/util" + "github.com/ray-project/kuberay/kubectl-plugin/pkg/util/completion" + "github.com/shirou/gopsutil/v4/process" + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericiooptions" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "strings" +) + +type KillAllSessionsOptions struct { + cmdFactory cmdutil.Factory + ioStreams *genericiooptions.IOStreams + currentContext string + ResourceType util.ResourceType + ResourceName string + namespace string + Verbose bool +} + +func NewKillAllSessionOptions(cmdFactory cmdutil.Factory, streams genericiooptions.IOStreams) *KillAllSessionsOptions { + return &KillAllSessionsOptions{ + cmdFactory: cmdFactory, + ioStreams: &streams, + } +} + +func NewKillAllSessionsCommand(cmdFactory cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { + options := NewKillAllSessionOptions(cmdFactory, streams) + + cmd := &cobra.Command{ + Use: "kill-all", + Short: "Forward local ports to the Ray resources.", + Long: sessionLong, + Example: sessionExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 0 { + return cmdutil.UsageErrorf(cmd, "accepts 0 arg, received %d\n%s", len(args), cmd.Use) + } + return nil + }, + ValidArgsFunction: completion.RayClusterResourceNameCompletionFunc(cmdFactory), + RunE: func(cmd *cobra.Command, args []string) error { + return options.KillAll(cmd.Context(), cmdFactory) + }, + } + + cmd.Flags().BoolVarP(&options.Verbose, "verbose", "v", false, "verbose output") + + return cmd +} + +func (options *KillAllSessionsOptions) KillAll(ctx context.Context, factory cmdutil.Factory) error { + procs, err := process.Processes() + if err != nil { + return fmt.Errorf("failed to get processes: %w", err) + } + + targetCmd := "kubectl-ray session" + selfCmd := "kubectl-ray session kill-all" + + for _, p := range procs { + cmdline, err := p.CmdlineWithContext(ctx) + if err == nil { + if strings.Contains(cmdline, targetCmd) && !strings.Contains(cmdline, selfCmd) { + fmt.Printf("Killing process %d: %s\n", p.Pid, cmdline) + if err := p.Kill(); err != nil { + return fmt.Errorf("failed to kill process %d: %w", p.Pid, err) + } + } + } + } + + return nil +} From 7a55f2d216e551964ebb2d0ae6a94168f232e0b8 Mon Sep 17 00:00:00 2001 From: Troy Chiu Date: Thu, 17 Apr 2025 09:29:21 -0700 Subject: [PATCH 2/6] wip Signed-off-by: Troy Chiu --- kubectl-plugin/pkg/cmd/session/session.go | 2 +- .../pkg/cmd/session/session_kill_all.go | 28 +++++-------------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/kubectl-plugin/pkg/cmd/session/session.go b/kubectl-plugin/pkg/cmd/session/session.go index f95f223f0c8..c55cdf5d0b5 100644 --- a/kubectl-plugin/pkg/cmd/session/session.go +++ b/kubectl-plugin/pkg/cmd/session/session.go @@ -102,7 +102,7 @@ func NewSessionCommand(cmdFactory cmdutil.Factory, streams genericiooptions.IOSt } cmd.Flags().BoolVarP(&options.Verbose, "verbose", "v", false, "verbose output") - cmd.AddCommand(NewKillAllSessionsCommand(cmdFactory, streams)) + cmd.AddCommand(NewKillAllSessionsCommand()) return cmd } diff --git a/kubectl-plugin/pkg/cmd/session/session_kill_all.go b/kubectl-plugin/pkg/cmd/session/session_kill_all.go index 072371bfcc9..6607e3d66ff 100644 --- a/kubectl-plugin/pkg/cmd/session/session_kill_all.go +++ b/kubectl-plugin/pkg/cmd/session/session_kill_all.go @@ -3,34 +3,22 @@ package session import ( "context" "fmt" - "github.com/ray-project/kuberay/kubectl-plugin/pkg/util" - "github.com/ray-project/kuberay/kubectl-plugin/pkg/util/completion" "github.com/shirou/gopsutil/v4/process" "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericiooptions" cmdutil "k8s.io/kubectl/pkg/cmd/util" "strings" ) type KillAllSessionsOptions struct { - cmdFactory cmdutil.Factory - ioStreams *genericiooptions.IOStreams - currentContext string - ResourceType util.ResourceType - ResourceName string - namespace string - Verbose bool + Verbose bool } -func NewKillAllSessionOptions(cmdFactory cmdutil.Factory, streams genericiooptions.IOStreams) *KillAllSessionsOptions { - return &KillAllSessionsOptions{ - cmdFactory: cmdFactory, - ioStreams: &streams, - } +func NewKillAllSessionOptions() *KillAllSessionsOptions { + return &KillAllSessionsOptions{} } -func NewKillAllSessionsCommand(cmdFactory cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { - options := NewKillAllSessionOptions(cmdFactory, streams) +func NewKillAllSessionsCommand() *cobra.Command { + options := NewKillAllSessionOptions() cmd := &cobra.Command{ Use: "kill-all", @@ -43,18 +31,16 @@ func NewKillAllSessionsCommand(cmdFactory cmdutil.Factory, streams genericioopti } return nil }, - ValidArgsFunction: completion.RayClusterResourceNameCompletionFunc(cmdFactory), RunE: func(cmd *cobra.Command, args []string) error { - return options.KillAll(cmd.Context(), cmdFactory) + return options.KillAll(cmd.Context()) }, } cmd.Flags().BoolVarP(&options.Verbose, "verbose", "v", false, "verbose output") - return cmd } -func (options *KillAllSessionsOptions) KillAll(ctx context.Context, factory cmdutil.Factory) error { +func (options *KillAllSessionsOptions) KillAll(ctx context.Context) error { procs, err := process.Processes() if err != nil { return fmt.Errorf("failed to get processes: %w", err) From 08748702fea5283f5cb60bdc3e882b867db066fe Mon Sep 17 00:00:00 2001 From: Troy Chiu Date: Thu, 17 Apr 2025 11:10:08 -0700 Subject: [PATCH 3/6] nit Signed-off-by: Troy Chiu --- kubectl-plugin/pkg/cmd/session/session_kill_all.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kubectl-plugin/pkg/cmd/session/session_kill_all.go b/kubectl-plugin/pkg/cmd/session/session_kill_all.go index 6607e3d66ff..b4bba5815f6 100644 --- a/kubectl-plugin/pkg/cmd/session/session_kill_all.go +++ b/kubectl-plugin/pkg/cmd/session/session_kill_all.go @@ -53,7 +53,7 @@ func (options *KillAllSessionsOptions) KillAll(ctx context.Context) error { cmdline, err := p.CmdlineWithContext(ctx) if err == nil { if strings.Contains(cmdline, targetCmd) && !strings.Contains(cmdline, selfCmd) { - fmt.Printf("Killing process %d: %s\n", p.Pid, cmdline) + fmt.Printf("Killing process with PID %d: %s\n", p.Pid, cmdline) if err := p.Kill(); err != nil { return fmt.Errorf("failed to kill process %d: %w", p.Pid, err) } From c3b6a751ef3d136317faeb77f4026e930219383d Mon Sep 17 00:00:00 2001 From: Troy Chiu Date: Thu, 17 Apr 2025 15:56:20 -0700 Subject: [PATCH 4/6] lint Signed-off-by: Troy Chiu --- .../pkg/cmd/session/session_kill_all.go | 44 +++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/kubectl-plugin/pkg/cmd/session/session_kill_all.go b/kubectl-plugin/pkg/cmd/session/session_kill_all.go index b4bba5815f6..5046a8ecb62 100644 --- a/kubectl-plugin/pkg/cmd/session/session_kill_all.go +++ b/kubectl-plugin/pkg/cmd/session/session_kill_all.go @@ -3,10 +3,27 @@ package session import ( "context" "fmt" + "strings" + "github.com/shirou/gopsutil/v4/process" "github.com/spf13/cobra" cmdutil "k8s.io/kubectl/pkg/cmd/util" - "strings" + "k8s.io/kubectl/pkg/util/templates" +) + +const ( + RaySessionCommand = "kubectl-ray session" + RaySessionKillAllCommand = "kubectl-ray session kill-all" +) + +var ( + sessionKillAllLong = templates.LongDesc(` + Kill all Ray session processes started by kubectl-ray session command. + `) + + sessionKillAllExample = templates.Examples(` + kubectl ray session kill-all + `) ) type KillAllSessionsOptions struct { @@ -22,16 +39,16 @@ func NewKillAllSessionsCommand() *cobra.Command { cmd := &cobra.Command{ Use: "kill-all", - Short: "Forward local ports to the Ray resources.", - Long: sessionLong, - Example: sessionExample, + Short: "Kill all Ray sessions", + Long: sessionKillAllLong, + Example: sessionKillAllExample, Args: func(cmd *cobra.Command, args []string) error { if len(args) != 0 { return cmdutil.UsageErrorf(cmd, "accepts 0 arg, received %d\n%s", len(args), cmd.Use) } return nil }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return options.KillAll(cmd.Context()) }, } @@ -46,17 +63,18 @@ func (options *KillAllSessionsOptions) KillAll(ctx context.Context) error { return fmt.Errorf("failed to get processes: %w", err) } - targetCmd := "kubectl-ray session" - selfCmd := "kubectl-ray session kill-all" - for _, p := range procs { cmdline, err := p.CmdlineWithContext(ctx) - if err == nil { - if strings.Contains(cmdline, targetCmd) && !strings.Contains(cmdline, selfCmd) { + if err != nil { + // Skip the process if we can't get its command line. It might be permission issue. + continue + } + if strings.Contains(cmdline, RaySessionCommand) && !strings.Contains(cmdline, RaySessionKillAllCommand) { + if options.Verbose { fmt.Printf("Killing process with PID %d: %s\n", p.Pid, cmdline) - if err := p.Kill(); err != nil { - return fmt.Errorf("failed to kill process %d: %w", p.Pid, err) - } + } + if err := p.Kill(); err != nil { + return fmt.Errorf("failed to kill process %d: %w", p.Pid, err) } } } From d95630cc19f4ffac586ca98dd44164a94479859a Mon Sep 17 00:00:00 2001 From: Troy Chiu Date: Thu, 17 Apr 2025 23:01:30 -0700 Subject: [PATCH 5/6] kill subprocess Signed-off-by: Troy Chiu --- .../pkg/cmd/session/session_kill_all.go | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/kubectl-plugin/pkg/cmd/session/session_kill_all.go b/kubectl-plugin/pkg/cmd/session/session_kill_all.go index 5046a8ecb62..14d3f6ec5e8 100644 --- a/kubectl-plugin/pkg/cmd/session/session_kill_all.go +++ b/kubectl-plugin/pkg/cmd/session/session_kill_all.go @@ -71,13 +71,30 @@ func (options *KillAllSessionsOptions) KillAll(ctx context.Context) error { } if strings.Contains(cmdline, RaySessionCommand) && !strings.Contains(cmdline, RaySessionKillAllCommand) { if options.Verbose { - fmt.Printf("Killing process with PID %d: %s\n", p.Pid, cmdline) + fmt.Printf("Found ray session: %s\n", cmdline) + } + // Since ray session spawn child processes to run the actual commands, + // we need to kill all child processes first. + children, err := p.ChildrenWithContext(ctx) + if err != nil { + return fmt.Errorf("failed to get children of process %d: %w", p.Pid, err) + } + for _, child := range children { + if options.Verbose { + fmt.Printf("Killing subprocess with PID %d\n", child.Pid) + } + if err := child.Kill(); err != nil { + return fmt.Errorf("failed to kill child process %d: %w", child.Pid, err) + } + } + // Then kill the parent process. + if options.Verbose { + fmt.Printf("Killing process with PID %d\n", p.Pid) } if err := p.Kill(); err != nil { return fmt.Errorf("failed to kill process %d: %w", p.Pid, err) } } } - return nil } From 0eb94f0f295ed92547c5b49a845a42000bb74d74 Mon Sep 17 00:00:00 2001 From: Troy Chiu Date: Fri, 18 Apr 2025 17:42:05 -0700 Subject: [PATCH 6/6] make killAll an option Signed-off-by: Troy Chiu --- kubectl-plugin/pkg/cmd/session/session.go | 87 +++++++++++++-- .../pkg/cmd/session/session_kill_all.go | 100 ------------------ 2 files changed, 80 insertions(+), 107 deletions(-) delete mode 100644 kubectl-plugin/pkg/cmd/session/session_kill_all.go diff --git a/kubectl-plugin/pkg/cmd/session/session.go b/kubectl-plugin/pkg/cmd/session/session.go index c55cdf5d0b5..dde5941eb19 100644 --- a/kubectl-plugin/pkg/cmd/session/session.go +++ b/kubectl-plugin/pkg/cmd/session/session.go @@ -3,18 +3,21 @@ package session import ( "context" "fmt" + "os" "os/exec" "strings" "sync" "time" - "github.com/ray-project/kuberay/kubectl-plugin/pkg/util" - "github.com/ray-project/kuberay/kubectl-plugin/pkg/util/client" - "github.com/ray-project/kuberay/kubectl-plugin/pkg/util/completion" + "github.com/shirou/gopsutil/v4/process" "github.com/spf13/cobra" "k8s.io/cli-runtime/pkg/genericiooptions" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util/templates" + + "github.com/ray-project/kuberay/kubectl-plugin/pkg/util" + "github.com/ray-project/kuberay/kubectl-plugin/pkg/util/client" + "github.com/ray-project/kuberay/kubectl-plugin/pkg/util/completion" ) type appPort struct { @@ -30,9 +33,13 @@ type SessionOptions struct { ResourceName string namespace string Verbose bool + killAll bool } -const reconnectDelay = 3 * time.Second +const ( + reconnectDelay = 3 * time.Second + raySessionCommand = "kubectl-ray session" +) var ( dashboardPort = appPort{ @@ -68,6 +75,9 @@ var ( # Forward local ports to the Ray cluster used for the RayService resource kubectl ray session rayservice/my-rayservice + + # Kill all existing Ray sessions started by kubectl-ray session command + kubectl ray session --kill-all `) ) @@ -87,8 +97,8 @@ func NewSessionCommand(cmdFactory cmdutil.Factory, streams genericiooptions.IOSt Long: sessionLong, Example: sessionExample, Args: func(cmd *cobra.Command, args []string) error { - if len(args) != 1 { - return cmdutil.UsageErrorf(cmd, "accepts 1 arg, received %d\n%s", len(args), cmd.Use) + if len(args) > 1 { + return cmdutil.UsageErrorf(cmd, "accepts at most 1 arg, received %d\n%s", len(args), cmd.Use) } return nil }, @@ -102,11 +112,22 @@ func NewSessionCommand(cmdFactory cmdutil.Factory, streams genericiooptions.IOSt } cmd.Flags().BoolVarP(&options.Verbose, "verbose", "v", false, "verbose output") - cmd.AddCommand(NewKillAllSessionsCommand()) + cmd.Flags().BoolVarP(&options.killAll, "kill-all", "", false, "kill all existing Ray sessions started by kubectl-ray session command") return cmd } func (options *SessionOptions) Complete(cmd *cobra.Command, args []string) error { + if options.killAll { + if len(args) > 0 { + return cmdutil.UsageErrorf(cmd, "accepts no args when killAll flag is set") + } + return nil + } + + if len(args) == 0 { + return cmdutil.UsageErrorf(cmd, "killAll flag is not set, but no args provided") + } + namespace, err := cmd.Flags().GetString("namespace") if err != nil { return fmt.Errorf("failed to get namespace: %w", err) @@ -153,6 +174,16 @@ func (options *SessionOptions) Complete(cmd *cobra.Command, args []string) error } func (options *SessionOptions) Run(ctx context.Context, factory cmdutil.Factory) error { + if options.killAll { + if options.Verbose { + fmt.Println("killAll flag is set, killing all existing Ray sessions...") + } + if err := killAllRaySessions(ctx, options.Verbose); err != nil { + return fmt.Errorf("failed to kill all ray sessions: %w", err) + } + return nil + } + k8sClient, err := client.NewClient(factory) if err != nil { return fmt.Errorf("failed to create client: %w", err) @@ -216,3 +247,45 @@ func (options *SessionOptions) Run(ctx context.Context, factory cmdutil.Factory) wg.Wait() return nil } + +func killAllRaySessions(ctx context.Context, verbose bool) error { + procs, err := process.Processes() + if err != nil { + return fmt.Errorf("failed to get processes: %w", err) + } + + for _, p := range procs { + cmdline, err := p.CmdlineWithContext(ctx) + if err != nil { + // Skip the process if we can't get its command line. It might be permission issue. + continue + } + if strings.Contains(cmdline, raySessionCommand) && int(p.Pid) != os.Getpid() { + if verbose { + fmt.Printf("Found ray session: %s\n", cmdline) + } + // Since ray session spawn child processes to run the actual commands, + // we need to kill all child processes first. + children, err := p.ChildrenWithContext(ctx) + if err != nil { + return fmt.Errorf("failed to get children of process %d: %w", p.Pid, err) + } + for _, child := range children { + if verbose { + fmt.Printf("Killing subprocess with PID %d\n", child.Pid) + } + if err := child.Kill(); err != nil { + return fmt.Errorf("failed to kill child process %d: %w", child.Pid, err) + } + } + // Then kill the parent process. + if verbose { + fmt.Printf("Killing process with PID %d\n", p.Pid) + } + if err := p.Kill(); err != nil { + return fmt.Errorf("failed to kill process %d: %w", p.Pid, err) + } + } + } + return nil +} diff --git a/kubectl-plugin/pkg/cmd/session/session_kill_all.go b/kubectl-plugin/pkg/cmd/session/session_kill_all.go deleted file mode 100644 index 14d3f6ec5e8..00000000000 --- a/kubectl-plugin/pkg/cmd/session/session_kill_all.go +++ /dev/null @@ -1,100 +0,0 @@ -package session - -import ( - "context" - "fmt" - "strings" - - "github.com/shirou/gopsutil/v4/process" - "github.com/spf13/cobra" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/templates" -) - -const ( - RaySessionCommand = "kubectl-ray session" - RaySessionKillAllCommand = "kubectl-ray session kill-all" -) - -var ( - sessionKillAllLong = templates.LongDesc(` - Kill all Ray session processes started by kubectl-ray session command. - `) - - sessionKillAllExample = templates.Examples(` - kubectl ray session kill-all - `) -) - -type KillAllSessionsOptions struct { - Verbose bool -} - -func NewKillAllSessionOptions() *KillAllSessionsOptions { - return &KillAllSessionsOptions{} -} - -func NewKillAllSessionsCommand() *cobra.Command { - options := NewKillAllSessionOptions() - - cmd := &cobra.Command{ - Use: "kill-all", - Short: "Kill all Ray sessions", - Long: sessionKillAllLong, - Example: sessionKillAllExample, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) != 0 { - return cmdutil.UsageErrorf(cmd, "accepts 0 arg, received %d\n%s", len(args), cmd.Use) - } - return nil - }, - RunE: func(cmd *cobra.Command, _ []string) error { - return options.KillAll(cmd.Context()) - }, - } - - cmd.Flags().BoolVarP(&options.Verbose, "verbose", "v", false, "verbose output") - return cmd -} - -func (options *KillAllSessionsOptions) KillAll(ctx context.Context) error { - procs, err := process.Processes() - if err != nil { - return fmt.Errorf("failed to get processes: %w", err) - } - - for _, p := range procs { - cmdline, err := p.CmdlineWithContext(ctx) - if err != nil { - // Skip the process if we can't get its command line. It might be permission issue. - continue - } - if strings.Contains(cmdline, RaySessionCommand) && !strings.Contains(cmdline, RaySessionKillAllCommand) { - if options.Verbose { - fmt.Printf("Found ray session: %s\n", cmdline) - } - // Since ray session spawn child processes to run the actual commands, - // we need to kill all child processes first. - children, err := p.ChildrenWithContext(ctx) - if err != nil { - return fmt.Errorf("failed to get children of process %d: %w", p.Pid, err) - } - for _, child := range children { - if options.Verbose { - fmt.Printf("Killing subprocess with PID %d\n", child.Pid) - } - if err := child.Kill(); err != nil { - return fmt.Errorf("failed to kill child process %d: %w", child.Pid, err) - } - } - // Then kill the parent process. - if options.Verbose { - fmt.Printf("Killing process with PID %d\n", p.Pid) - } - if err := p.Kill(); err != nil { - return fmt.Errorf("failed to kill process %d: %w", p.Pid, err) - } - } - } - return nil -}