Skip to content

Commit 5b8d355

Browse files
authored
fix: add create loadb (#2334)
Signed-off-by: 张启航 <[email protected]>
1 parent edb13e1 commit 5b8d355

File tree

6 files changed

+323
-2
lines changed

6 files changed

+323
-2
lines changed

api/api/api_interface.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ type GatewayInterface interface {
313313
GatewayRouteInterface
314314
GatewayServiceInterface
315315
GatewayCertInterface
316+
GatewayLoadBalancerInterface
316317
}
317318

318319
// GatewayRouteInterface api gateway route interface
@@ -354,3 +355,10 @@ type GatewayCertInterface interface {
354355
UpdateCert(w http.ResponseWriter, r *http.Request)
355356
DeleteCert(w http.ResponseWriter, r *http.Request)
356357
}
358+
359+
// GatewayLoadBalancerInterface api gateway loadbalancer interface
360+
type GatewayLoadBalancerInterface interface {
361+
CreateLoadBalancer(w http.ResponseWriter, r *http.Request)
362+
GetLoadBalancer(w http.ResponseWriter, r *http.Request)
363+
DeleteLoadBalancer(w http.ResponseWriter, r *http.Request)
364+
}

api/api_routers/gateway/gateway.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ func Routes() chi.Router {
2626

2727
})
2828

29+
// 创建 LoadBalancer 接口
30+
r.Route("/loadbalancer", func(r chi.Router) {
31+
r.Post("/", controller.GetManager().CreateLoadBalancer)
32+
r.Get("/", controller.GetManager().GetLoadBalancer)
33+
r.Delete("/{name}", controller.GetManager().DeleteLoadBalancer)
34+
})
35+
2936
// 关于路由的接口
3037
r.Route("/routes/tcp", func(r chi.Router) {
3138
r.Get("/domains", controller.GetManager().GetTCPBindDomains)
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
// RAINBOND, Application Management Platform
2+
// Copyright (C) 2014-2017 Goodrain Co., Ltd.
3+
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version. For any non-GPL usage of Rainbond,
8+
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
9+
// must be obtained first.
10+
11+
// This program is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU General Public License for more details.
15+
16+
// You should have received a copy of the GNU General Public License
17+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
19+
package apigateway
20+
21+
import (
22+
"fmt"
23+
"net/http"
24+
"strings"
25+
"time"
26+
27+
"github.com/go-chi/chi"
28+
"github.com/goodrain/rainbond/api/model"
29+
"github.com/goodrain/rainbond/api/util/bcode"
30+
ctxutil "github.com/goodrain/rainbond/api/util/ctx"
31+
dbmodel "github.com/goodrain/rainbond/db/model"
32+
"github.com/goodrain/rainbond/pkg/component/k8s"
33+
httputil "github.com/goodrain/rainbond/util/http"
34+
"github.com/sirupsen/logrus"
35+
corev1 "k8s.io/api/core/v1"
36+
"k8s.io/apimachinery/pkg/api/errors"
37+
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
38+
"k8s.io/apimachinery/pkg/util/intstr"
39+
)
40+
41+
// CreateLoadBalancer 创建LoadBalancer服务
42+
func (g Struct) CreateLoadBalancer(w http.ResponseWriter, r *http.Request) {
43+
tenant := r.Context().Value(ctxutil.ContextKey("tenant")).(*dbmodel.Tenants)
44+
serviceID := r.URL.Query().Get("service_id")
45+
appID := r.URL.Query().Get("appID")
46+
k := k8s.Default().Clientset.CoreV1()
47+
48+
var createLBReq model.CreateLoadBalancerStruct
49+
if !httputil.ValidatorRequestStructAndErrorResponse(r, w, &createLBReq, nil) {
50+
return
51+
}
52+
53+
// 验证协议类型
54+
protocol := strings.ToUpper(createLBReq.Protocol)
55+
if protocol != "TCP" && protocol != "UDP" {
56+
httputil.ReturnBcodeError(r, w, bcode.NewBadRequest("protocol must be TCP or UDP"))
57+
return
58+
}
59+
60+
// 生成服务名称
61+
serviceName := fmt.Sprintf("%s-lb-%d", createLBReq.ServiceName, createLBReq.ServicePort)
62+
63+
// 检查服务是否已存在
64+
_, err := k.Services(tenant.Namespace).Get(r.Context(), serviceName, v1.GetOptions{})
65+
if err == nil {
66+
httputil.ReturnBcodeError(r, w, bcode.NewBadRequest("LoadBalancer service already exists"))
67+
return
68+
}
69+
if !errors.IsNotFound(err) {
70+
logrus.Errorf("check LoadBalancer service error %s", err.Error())
71+
httputil.ReturnBcodeError(r, w, bcode.ServerErr)
72+
return
73+
}
74+
75+
// 创建服务规格
76+
spec := corev1.ServiceSpec{
77+
Type: corev1.ServiceTypeLoadBalancer,
78+
Ports: []corev1.ServicePort{
79+
{
80+
Name: serviceName,
81+
Protocol: corev1.Protocol(protocol),
82+
Port: int32(createLBReq.ServicePort),
83+
TargetPort: intstr.FromInt(createLBReq.ServicePort),
84+
},
85+
},
86+
}
87+
88+
// 直接生成服务选择器
89+
spec.Selector = map[string]string{
90+
"service_alias": createLBReq.ServiceName,
91+
}
92+
93+
// 创建标签
94+
labels := make(map[string]string)
95+
labels["creator"] = "Rainbond"
96+
labels["loadbalancer"] = "true"
97+
if appID != "" {
98+
labels["app_id"] = appID
99+
}
100+
if serviceID != "" {
101+
labels["service_id"] = serviceID
102+
}
103+
labels["service_alias"] = createLBReq.ServiceName
104+
labels["port"] = fmt.Sprintf("%d", createLBReq.ServicePort)
105+
106+
// 创建注解
107+
annotations := make(map[string]string)
108+
if createLBReq.Annotations != nil {
109+
for k, v := range createLBReq.Annotations {
110+
annotations[k] = v
111+
}
112+
}
113+
114+
// 创建LoadBalancer服务
115+
service := &corev1.Service{
116+
ObjectMeta: v1.ObjectMeta{
117+
Name: serviceName,
118+
Labels: labels,
119+
Annotations: annotations,
120+
},
121+
Spec: spec,
122+
}
123+
124+
createdService, err := k.Services(tenant.Namespace).Create(r.Context(), service, v1.CreateOptions{})
125+
if err != nil {
126+
logrus.Errorf("create LoadBalancer service error %s", err.Error())
127+
httputil.ReturnBcodeError(r, w, fmt.Errorf("create LoadBalancer service error: %s", err.Error()))
128+
return
129+
}
130+
131+
// 构造响应
132+
response := &model.LoadBalancerResponse{
133+
Name: createdService.Name,
134+
Namespace: createdService.Namespace,
135+
ServiceName: createLBReq.ServiceName,
136+
ServicePort: createLBReq.ServicePort,
137+
Protocol: protocol,
138+
LoadBalancerIP: createdService.Spec.LoadBalancerIP,
139+
ExternalIPs: createdService.Spec.ExternalIPs,
140+
Annotations: createdService.Annotations,
141+
Status: "Creating",
142+
CreatedAt: createdService.CreationTimestamp.Format(time.RFC3339),
143+
}
144+
145+
// 如果LoadBalancer已分配IP,更新状态
146+
if len(createdService.Status.LoadBalancer.Ingress) > 0 {
147+
response.Status = "Ready"
148+
var externalIPs []string
149+
for _, ingress := range createdService.Status.LoadBalancer.Ingress {
150+
if ingress.IP != "" {
151+
externalIPs = append(externalIPs, ingress.IP)
152+
}
153+
if ingress.Hostname != "" {
154+
externalIPs = append(externalIPs, ingress.Hostname)
155+
}
156+
}
157+
response.ExternalIPs = externalIPs
158+
}
159+
160+
logrus.Infof("LoadBalancer service created successfully: %s", serviceName)
161+
httputil.ReturnSuccess(r, w, response)
162+
}
163+
164+
// GetLoadBalancer 获取LoadBalancer服务
165+
func (g Struct) GetLoadBalancer(w http.ResponseWriter, r *http.Request) {
166+
tenant := r.Context().Value(ctxutil.ContextKey("tenant")).(*dbmodel.Tenants)
167+
k := k8s.Default().Clientset.CoreV1()
168+
169+
// 获取查询参数
170+
appID := r.URL.Query().Get("appID")
171+
serviceName := r.URL.Query().Get("service_name")
172+
173+
// 构建标签选择器
174+
labelSelector := "loadbalancer=true"
175+
if appID != "" {
176+
labelSelector += ",app_id=" + appID
177+
}
178+
if serviceName != "" {
179+
labelSelector += ",service_alias=" + serviceName
180+
}
181+
182+
// 列出LoadBalancer服务
183+
list, err := k.Services(tenant.Namespace).List(r.Context(), v1.ListOptions{
184+
LabelSelector: labelSelector,
185+
})
186+
if err != nil {
187+
logrus.Errorf("get LoadBalancer services error %s", err.Error())
188+
httputil.ReturnBcodeError(r, w, bcode.ServerErr)
189+
return
190+
}
191+
192+
var responses []*model.LoadBalancerResponse
193+
for _, service := range list.Items {
194+
if service.Spec.Type != corev1.ServiceTypeLoadBalancer {
195+
continue
196+
}
197+
198+
response := &model.LoadBalancerResponse{
199+
Name: service.Name,
200+
Namespace: service.Namespace,
201+
ServiceName: service.Labels["service_alias"],
202+
ServicePort: int(service.Spec.Ports[0].Port),
203+
Protocol: string(service.Spec.Ports[0].Protocol),
204+
LoadBalancerIP: service.Spec.LoadBalancerIP,
205+
ExternalIPs: service.Spec.ExternalIPs,
206+
Annotations: service.Annotations,
207+
Status: "Creating",
208+
CreatedAt: service.CreationTimestamp.Format(time.RFC3339),
209+
}
210+
211+
// 检查LoadBalancer状态
212+
if len(service.Status.LoadBalancer.Ingress) > 0 {
213+
response.Status = "Ready"
214+
var externalIPs []string
215+
for _, ingress := range service.Status.LoadBalancer.Ingress {
216+
if ingress.IP != "" {
217+
externalIPs = append(externalIPs, ingress.IP)
218+
}
219+
if ingress.Hostname != "" {
220+
externalIPs = append(externalIPs, ingress.Hostname)
221+
}
222+
}
223+
if len(externalIPs) > 0 {
224+
response.ExternalIPs = externalIPs
225+
}
226+
}
227+
228+
responses = append(responses, response)
229+
}
230+
231+
httputil.ReturnSuccess(r, w, responses)
232+
}
233+
234+
// DeleteLoadBalancer 删除LoadBalancer服务
235+
func (g Struct) DeleteLoadBalancer(w http.ResponseWriter, r *http.Request) {
236+
tenant := r.Context().Value(ctxutil.ContextKey("tenant")).(*dbmodel.Tenants)
237+
serviceName := chi.URLParam(r, "name")
238+
if serviceName == "" {
239+
serviceName = r.URL.Query().Get("name")
240+
}
241+
242+
if serviceName == "" {
243+
httputil.ReturnBcodeError(r, w, bcode.NewBadRequest("service name is required"))
244+
return
245+
}
246+
247+
k := k8s.Default().Clientset.CoreV1()
248+
249+
// 检查服务是否存在且为LoadBalancer类型
250+
service, err := k.Services(tenant.Namespace).Get(r.Context(), serviceName, v1.GetOptions{})
251+
if err != nil {
252+
if errors.IsNotFound(err) {
253+
httputil.ReturnBcodeError(r, w, bcode.NotFound)
254+
return
255+
}
256+
logrus.Errorf("get LoadBalancer service error %s", err.Error())
257+
httputil.ReturnBcodeError(r, w, bcode.ServerErr)
258+
return
259+
}
260+
261+
// 验证是否为LoadBalancer服务
262+
if service.Spec.Type != corev1.ServiceTypeLoadBalancer {
263+
httputil.ReturnBcodeError(r, w, bcode.NewBadRequest("service is not a LoadBalancer type"))
264+
return
265+
}
266+
267+
// 验证是否为Rainbond创建的LoadBalancer
268+
if service.Labels["creator"] != "Rainbond" || service.Labels["loadbalancer"] != "true" {
269+
httputil.ReturnBcodeError(r, w, bcode.NewBadRequest("service is not a Rainbond LoadBalancer"))
270+
return
271+
}
272+
273+
// 删除服务
274+
err = k.Services(tenant.Namespace).Delete(r.Context(), serviceName, v1.DeleteOptions{})
275+
if err != nil && !errors.IsNotFound(err) {
276+
logrus.Errorf("delete LoadBalancer service error %s", err.Error())
277+
httputil.ReturnBcodeError(r, w, bcode.ServerErr)
278+
return
279+
}
280+
281+
logrus.Infof("LoadBalancer service deleted successfully: %s", serviceName)
282+
httputil.ReturnSuccess(r, w, map[string]string{
283+
"message": "LoadBalancer service deleted successfully",
284+
"name": serviceName,
285+
})
286+
}

api/model/gateway_model.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,3 +385,25 @@ type UpdCertificateReq struct {
385385
Certificate string `json:"certificate"`
386386
PrivateKey string `json:"private_key"`
387387
}
388+
389+
// CreateLoadBalancerStruct 创建LoadBalancer的请求结构体
390+
type CreateLoadBalancerStruct struct {
391+
ServiceName string `json:"service_name" validate:"required"` // 后端服务名称
392+
ServicePort int `json:"service_port" validate:"required"` // 后端服务端口
393+
Protocol string `json:"protocol" validate:"required"` // 协议类型 TCP/UDP
394+
Annotations map[string]string `json:"annotations,omitempty"` // 注解
395+
}
396+
397+
// LoadBalancerResponse LoadBalancer响应结构体
398+
type LoadBalancerResponse struct {
399+
Name string `json:"name"`
400+
Namespace string `json:"namespace"`
401+
ServiceName string `json:"service_name"`
402+
ServicePort int `json:"service_port"`
403+
Protocol string `json:"protocol"`
404+
LoadBalancerIP string `json:"load_balancer_ip,omitempty"`
405+
ExternalIPs []string `json:"external_ips,omitempty"`
406+
Annotations map[string]string `json:"annotations,omitempty"`
407+
Status string `json:"status"`
408+
CreatedAt string `json:"created_at"`
409+
}

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ require (
7474
github.com/stretchr/testify v1.10.0
7575
github.com/thejerf/suture v3.0.3+incompatible
7676
github.com/tidwall/gjson v1.9.3
77-
github.com/twinj/uuid v0.0.0-00010101000000-000000000000
7877
github.com/urfave/cli v1.22.4
7978
golang.org/x/crypto v0.37.0
8079
golang.org/x/net v0.39.0

go.sum

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -998,7 +998,6 @@ github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
998998
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
999999
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
10001000
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
1001-
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
10021001
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
10031002
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
10041003
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=

0 commit comments

Comments
 (0)