@@ -3,51 +3,93 @@ package session
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ "strings"
6
7
7
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8
+ "github.com/ray-project/kuberay/kubectl-plugin/pkg/util"
9
+ "github.com/ray-project/kuberay/kubectl-plugin/pkg/util/client"
10
+ "github.com/spf13/cobra"
8
11
"k8s.io/cli-runtime/pkg/genericclioptions"
9
12
"k8s.io/cli-runtime/pkg/genericiooptions"
10
- "k8s.io/client-go/kubernetes"
11
13
"k8s.io/kubectl/pkg/cmd/portforward"
12
14
cmdutil "k8s.io/kubectl/pkg/cmd/util"
13
-
14
- "github.com/spf13/cobra"
15
+ "k8s.io/kubectl/pkg/util/templates"
15
16
)
16
17
17
- const (
18
- DASHBOARD_PORT = 8265
19
- CLIENT_PORT = 10001
20
- )
18
+ type appPort struct {
19
+ name string
20
+ port int
21
+ }
21
22
22
23
type SessionOptions struct {
23
- ioStreams * genericiooptions.IOStreams
24
24
configFlags * genericclioptions.ConfigFlags
25
+ ioStreams * genericiooptions.IOStreams
26
+ client client.Client
27
+ ResourceType util.ResourceType
25
28
ResourceName string
26
29
Namespace string
27
30
}
28
31
32
+ var (
33
+ dashboardPort = appPort {
34
+ name : "Ray Dashboard" ,
35
+ port : 8265 ,
36
+ }
37
+ clientPort = appPort {
38
+ name : "Ray Interactive Client" ,
39
+ port : 10001 ,
40
+ }
41
+ servePort = appPort {
42
+ name : "Ray Serve" ,
43
+ port : 8000 ,
44
+ }
45
+ )
46
+
47
+ var (
48
+ sessionLong = templates .LongDesc (`
49
+ Forward local ports to the Ray resources.
50
+
51
+ Forward different local ports depending on the resource type: RayCluster, RayJob, or RayService.
52
+ ` )
53
+
54
+ sessionExample = templates .Examples (`
55
+ # Without specifying the resource type, forward local ports to the RayCluster resource
56
+ kubectl ray session my-raycluster
57
+
58
+ # Forward local ports to the RayCluster resource
59
+ kubectl ray session raycluster/my-raycluster
60
+
61
+ # Forward local ports to the RayCluster used for the RayJob resource
62
+ kubectl ray session rayjob/my-rayjob
63
+
64
+ # Forward local ports to the RayCluster used for the RayService resource
65
+ kubectl ray session rayservice/my-rayservice
66
+ ` )
67
+ )
68
+
29
69
func NewSessionOptions (streams genericiooptions.IOStreams ) * SessionOptions {
70
+ configFlags := genericclioptions .NewConfigFlags (true )
30
71
return & SessionOptions {
31
72
ioStreams : & streams ,
32
- configFlags : genericclioptions . NewConfigFlags ( true ) ,
73
+ configFlags : configFlags ,
33
74
}
34
75
}
35
76
36
77
func NewSessionCommand (streams genericiooptions.IOStreams ) * cobra.Command {
37
78
options := NewSessionOptions (streams )
38
- factory := cmdutil .NewFactory (options .configFlags )
39
79
40
80
cmd := & cobra.Command {
41
- Use : "session NAME" ,
42
- Short : "Forward local ports to the Ray resources. Currently only supports RayCluster." ,
81
+ Use : "session (RAYCLUSTER | TYPE/NAME)" ,
82
+ Short : "Forward local ports to the Ray resources." ,
83
+ Long : sessionLong ,
84
+ Example : sessionExample ,
43
85
RunE : func (cmd * cobra.Command , args []string ) error {
44
86
if err := options .Complete (cmd , args ); err != nil {
45
87
return err
46
88
}
47
89
if err := options .Validate (); err != nil {
48
90
return err
49
91
}
50
- return options .Run (cmd .Context (), factory )
92
+ return options .Run (cmd .Context ())
51
93
},
52
94
}
53
95
options .configFlags .AddFlags (cmd .Flags ())
@@ -58,14 +100,43 @@ func (options *SessionOptions) Complete(cmd *cobra.Command, args []string) error
58
100
if len (args ) != 1 {
59
101
return cmdutil .UsageErrorf (cmd , "%s" , cmd .Use )
60
102
}
61
- options .ResourceName = args [0 ]
103
+
104
+ typeAndName := strings .Split (args [0 ], "/" )
105
+ if len (typeAndName ) == 1 {
106
+ options .ResourceType = util .RayCluster
107
+ options .ResourceName = typeAndName [0 ]
108
+ } else {
109
+ if len (typeAndName ) != 2 || typeAndName [1 ] == "" {
110
+ return cmdutil .UsageErrorf (cmd , "invalid resource type/name: %s" , args [0 ])
111
+ }
112
+
113
+ switch typeAndName [0 ] {
114
+ case string (util .RayCluster ):
115
+ options .ResourceType = util .RayCluster
116
+ case string (util .RayJob ):
117
+ options .ResourceType = util .RayJob
118
+ case string (util .RayService ):
119
+ options .ResourceType = util .RayService
120
+ default :
121
+ return cmdutil .UsageErrorf (cmd , "unsupported resource type: %s" , typeAndName [0 ])
122
+ }
123
+
124
+ options .ResourceName = typeAndName [1 ]
125
+ }
62
126
63
127
if * options .configFlags .Namespace == "" {
64
128
options .Namespace = "default"
65
129
} else {
66
130
options .Namespace = * options .configFlags .Namespace
67
131
}
68
132
133
+ factory := cmdutil .NewFactory (options .configFlags )
134
+ k8sClient , err := client .NewClient (factory )
135
+ if err != nil {
136
+ return fmt .Errorf ("failed to create client: %w" , err )
137
+ }
138
+ options .client = k8sClient
139
+
69
140
return nil
70
141
}
71
142
@@ -81,46 +152,42 @@ func (options *SessionOptions) Validate() error {
81
152
return nil
82
153
}
83
154
84
- func (options * SessionOptions ) Run (ctx context.Context , factory cmdutil.Factory ) error {
85
- kubeClientSet , err := factory .KubernetesClientSet ()
86
- if err != nil {
87
- return fmt .Errorf ("failed to initialize clientset: %w" , err )
88
- }
155
+ func (options * SessionOptions ) Run (ctx context.Context ) error {
156
+ factory := cmdutil .NewFactory (options .configFlags )
89
157
90
- svcName , err := findServiceName (ctx , kubeClientSet , options .Namespace , options .ResourceName )
158
+ svcName , err := options . client . GetRayHeadSvcName (ctx , options . Namespace , options .ResourceType , options .ResourceName )
91
159
if err != nil {
92
160
return err
93
161
}
162
+ fmt .Printf ("Forwarding ports to service %s\n " , svcName )
163
+
164
+ var appPorts []appPort
165
+ switch options .ResourceType {
166
+ case util .RayCluster :
167
+ appPorts = []appPort {dashboardPort , clientPort }
168
+ case util .RayJob :
169
+ appPorts = []appPort {dashboardPort }
170
+ case util .RayService :
171
+ appPorts = []appPort {dashboardPort , servePort }
172
+ default :
173
+ return fmt .Errorf ("unsupported resource type: %s" , options .ResourceType )
174
+ }
94
175
95
176
portForwardCmd := portforward .NewCmdPortForward (factory , * options .ioStreams )
96
- portForwardCmd .SetArgs ([]string {svcName , fmt .Sprintf ("%d:%d" , DASHBOARD_PORT , DASHBOARD_PORT ), fmt .Sprintf ("%d:%d" , CLIENT_PORT , CLIENT_PORT )})
177
+ args := []string {"service/" + svcName }
178
+ for _ , appPort := range appPorts {
179
+ args = append (args , fmt .Sprintf ("%d:%d" , appPort .port , appPort .port ))
180
+ }
181
+ portForwardCmd .SetArgs (args )
97
182
98
- fmt .Printf ("Ray Dashboard: http://localhost:%d\n Ray Interactive Client: http://localhost:%d\n \n " , DASHBOARD_PORT , CLIENT_PORT )
183
+ for _ , appPort := range appPorts {
184
+ fmt .Printf ("%s: http://localhost:%d\n " , appPort .name , appPort .port )
185
+ }
186
+ fmt .Println ()
99
187
100
188
if err := portForwardCmd .ExecuteContext (ctx ); err != nil {
101
189
return fmt .Errorf ("failed to port-forward: %w" , err )
102
190
}
103
191
104
192
return nil
105
193
}
106
-
107
- func findServiceName (ctx context.Context , kubeClientSet kubernetes.Interface , namespace , resourceName string ) (string , error ) {
108
- listopts := metav1.ListOptions {
109
- LabelSelector : fmt .Sprintf ("ray.io/cluster=%s, ray.io/node-type=head" , resourceName ),
110
- }
111
-
112
- rayHeadSvcs , err := kubeClientSet .CoreV1 ().Services (namespace ).List (ctx , listopts )
113
- if err != nil {
114
- return "" , fmt .Errorf ("unable to retrieve ray head services: %w" , err )
115
- }
116
-
117
- if len (rayHeadSvcs .Items ) == 0 {
118
- return "" , fmt .Errorf ("no ray head services found" )
119
- }
120
- if len (rayHeadSvcs .Items ) > 1 {
121
- return "" , fmt .Errorf ("more than one ray head service found" )
122
- }
123
-
124
- rayHeadSrc := rayHeadSvcs .Items [0 ]
125
- return "service/" + rayHeadSrc .Name , nil
126
- }
0 commit comments