feat(backend): orchestrate federation runtime for shared middle and exit nodes

This commit is contained in:
sagit
2026-02-10 02:34:20 +00:00
parent 5f42c50689
commit 79f8aab600
8 changed files with 1406 additions and 30 deletions
@@ -30,6 +30,45 @@ type RemoteTunnelResponse struct {
TunnelID int64 `json:"tunnelId"`
}
type RuntimeReservePortRequest struct {
ResourceKey string `json:"resourceKey"`
Protocol string `json:"protocol"`
RequestedPort int `json:"requestedPort"`
}
type RuntimeReservePortResponse struct {
ReservationID string `json:"reservationId"`
BindingID string `json:"bindingId"`
AllocatedPort int `json:"allocatedPort"`
}
type RuntimeTarget struct {
Host string `json:"host"`
Port int `json:"port"`
Protocol string `json:"protocol"`
}
type RuntimeApplyRoleRequest struct {
ReservationID string `json:"reservationId"`
ResourceKey string `json:"resourceKey"`
Role string `json:"role"`
Protocol string `json:"protocol"`
Strategy string `json:"strategy"`
Targets []RuntimeTarget `json:"targets"`
}
type RuntimeApplyRoleResponse struct {
BindingID string `json:"bindingId"`
ReservationID string `json:"reservationId"`
AllocatedPort int `json:"allocatedPort"`
}
type RuntimeReleaseRoleRequest struct {
BindingID string `json:"bindingId"`
ReservationID string `json:"reservationId"`
ResourceKey string `json:"resourceKey"`
}
func NewFederationClient() *FederationClient {
return &FederationClient{
client: &http.Client{
@@ -119,3 +158,119 @@ func (c *FederationClient) CreateTunnel(url, token, localDomain, protocol string
return &res.Data, nil
}
func (c *FederationClient) ReservePort(url, token, localDomain string, reqData RuntimeReservePortRequest) (*RuntimeReservePortResponse, error) {
url = strings.TrimSuffix(url, "/")
bodyBytes, _ := json.Marshal(reqData)
req, err := http.NewRequest("POST", url+"/api/v1/federation/runtime/reserve-port", strings.NewReader(string(bodyBytes)))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+token)
if localDomain != "" {
req.Header.Set("X-Panel-Domain", localDomain)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("remote error %d: %s", resp.StatusCode, string(body))
}
var res struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data RuntimeReservePortResponse `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
return nil, err
}
if res.Code != 0 {
return nil, fmt.Errorf("remote api error: %s", res.Msg)
}
return &res.Data, nil
}
func (c *FederationClient) ApplyRole(url, token, localDomain string, reqData RuntimeApplyRoleRequest) (*RuntimeApplyRoleResponse, error) {
url = strings.TrimSuffix(url, "/")
bodyBytes, _ := json.Marshal(reqData)
req, err := http.NewRequest("POST", url+"/api/v1/federation/runtime/apply-role", strings.NewReader(string(bodyBytes)))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+token)
if localDomain != "" {
req.Header.Set("X-Panel-Domain", localDomain)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("remote error %d: %s", resp.StatusCode, string(body))
}
var res struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data RuntimeApplyRoleResponse `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
return nil, err
}
if res.Code != 0 {
return nil, fmt.Errorf("remote api error: %s", res.Msg)
}
return &res.Data, nil
}
func (c *FederationClient) ReleaseRole(url, token, localDomain string, reqData RuntimeReleaseRoleRequest) error {
url = strings.TrimSuffix(url, "/")
bodyBytes, _ := json.Marshal(reqData)
req, err := http.NewRequest("POST", url+"/api/v1/federation/runtime/release-role", strings.NewReader(string(bodyBytes)))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+token)
if localDomain != "" {
req.Header.Set("X-Panel-Domain", localDomain)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("remote error %d: %s", resp.StatusCode, string(body))
}
var res struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
return err
}
if res.Code != 0 {
return fmt.Errorf("remote api error: %s", res.Msg)
}
return nil
}