-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Unify endpoints for clustering nodes (#109)
Previously, a client needed to call different API endpoints to generate control plane or worker token and joining those different node types to the k8s cluster. This commit adds a unified endpoint for creating a token (`POST /k8sd/tokens`) and joining a node (`POST /k8sd/cluster/<node>`).
- Loading branch information
1 parent
4c2b06f
commit 469aefc
Showing
10 changed files
with
220 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package v1 | ||
|
||
// JoinNodeRequest is used to request to add a node to the cluster. | ||
type JoinNodeRequest struct { | ||
Name string `json:"name"` | ||
Address string `json:"address"` | ||
Token string `json:"token"` | ||
} | ||
|
||
// JoinNodeResponse is the response from "POST 1.0/k8sd/cluster/{node}" | ||
type JoinNodeResponse struct{} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package v1 | ||
|
||
// TokenRequest is used to request a token for joining a node to the cluster. | ||
type TokenRequest struct { | ||
// If true, a token for joining a worker node is created. | ||
// If false, a token for joining a control plane node is created. | ||
Worker bool `json:"worker"` | ||
// Name of the node that should join. | ||
// Only required for control plane nodes as all workers share the same token. | ||
Name string `json:"name"` | ||
} | ||
|
||
// TokensResponse is used to return a token for joining nodes in the cluster. | ||
type TokensResponse struct { | ||
// We want to be able to quickly find the tokens in the code, but have the same | ||
// JSON response for control-plane and worker nodes, thus the discrepancy in naming. | ||
EncodedToken string `json:"token"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package client | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
apiv1 "github.com/canonical/k8s/api/v1" | ||
"github.com/canonical/lxd/shared/api" | ||
) | ||
|
||
func (c *Client) CreateJoinToken(ctx context.Context, name string, worker bool) (string, error) { | ||
request := apiv1.TokenRequest{ | ||
Name: name, | ||
Worker: worker, | ||
} | ||
response := apiv1.TokensResponse{} | ||
|
||
err := c.mc.Query(ctx, "POST", api.NewURL().Path("k8sd", "cluster", "tokens"), request, &response) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to query endpoint POST /k8sd/cluster/tokens: %w", err) | ||
} | ||
return response.EncodedToken, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package api | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"time" | ||
|
||
apiv1 "github.com/canonical/k8s/api/v1" | ||
"github.com/canonical/k8s/pkg/k8sd/types" | ||
"github.com/canonical/lxd/lxd/response" | ||
"github.com/canonical/microcluster/microcluster" | ||
"github.com/canonical/microcluster/state" | ||
) | ||
|
||
func postClusterNode(s *state.State, r *http.Request) response.Response { | ||
req := apiv1.JoinNodeRequest{} | ||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { | ||
return response.BadRequest(fmt.Errorf("failed to parse request: %w", err)) | ||
} | ||
|
||
// differentiate between control plane and worker node tokens | ||
info := &types.InternalWorkerNodeToken{} | ||
if info.Decode(req.Token) == nil { | ||
// valid worker node token | ||
if err := joinWorkerNode(s, r, req.Name, req.Address, req.Token); err != nil { | ||
return response.SmartError(fmt.Errorf("failed to join k8sd cluster as worker: %w", err)) | ||
} | ||
} else { | ||
if err := joinControlPlaneNode(s, r, req.Name, req.Address, req.Token); err != nil { | ||
return response.SmartError(fmt.Errorf("failed to join k8sd cluster as control plane: %w", err)) | ||
} | ||
} | ||
|
||
return response.SyncResponse(true, &apiv1.JoinNodeResponse{}) | ||
} | ||
|
||
func joinWorkerNode(s *state.State, r *http.Request, name, address, token string) error { | ||
m, err := microcluster.App(r.Context(), microcluster.Args{ | ||
StateDir: s.OS.StateDir, | ||
}) | ||
if err != nil { | ||
return fmt.Errorf("failed to get microcluster app: %w", err) | ||
} | ||
return m.NewCluster(name, address, map[string]string{"workerToken": token}, time.Second*180) | ||
} | ||
|
||
func joinControlPlaneNode(s *state.State, r *http.Request, name, address, token string) error { | ||
m, err := microcluster.App(r.Context(), microcluster.Args{ | ||
StateDir: s.OS.StateDir, | ||
}) | ||
if err != nil { | ||
return fmt.Errorf("failed to get microcluster app: %w", err) | ||
} | ||
return m.JoinCluster(name, address, token, nil, time.Second*180) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package api | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
|
||
apiv1 "github.com/canonical/k8s/api/v1" | ||
"github.com/canonical/k8s/pkg/k8sd/database" | ||
"github.com/canonical/k8s/pkg/k8sd/types" | ||
"github.com/canonical/lxd/lxd/response" | ||
"github.com/canonical/microcluster/microcluster" | ||
"github.com/canonical/microcluster/state" | ||
) | ||
|
||
func postTokens(s *state.State, r *http.Request) response.Response { | ||
req := apiv1.TokenRequest{} | ||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { | ||
return response.BadRequest(fmt.Errorf("failed to parse request: %w", err)) | ||
} | ||
|
||
var token string | ||
var err error | ||
if req.Worker { | ||
token, err = createWorkerToken(s, r) | ||
} else { | ||
token, err = createControlPlaneToken(s, r, req.Name) | ||
} | ||
|
||
if err != nil { | ||
return response.SmartError(fmt.Errorf("failed to create token: %w", err)) | ||
} | ||
|
||
return response.SyncResponse(true, &apiv1.TokensResponse{EncodedToken: token}) | ||
|
||
} | ||
|
||
func createWorkerToken(s *state.State, r *http.Request) (string, error) { | ||
var token string | ||
if err := s.Database.Transaction(s.Context, func(ctx context.Context, tx *sql.Tx) error { | ||
var err error | ||
token, err = database.GetOrCreateWorkerNodeToken(ctx, tx) | ||
if err != nil { | ||
return fmt.Errorf("failed to create worker node token: %w", err) | ||
} | ||
return err | ||
}); err != nil { | ||
return "", fmt.Errorf("database transaction failed: %w", err) | ||
} | ||
|
||
remoteAddresses := s.Remotes().Addresses() | ||
addresses := make([]string, 0, len(remoteAddresses)) | ||
for _, addrPort := range remoteAddresses { | ||
addresses = append(addresses, addrPort.String()) | ||
} | ||
|
||
info := &types.InternalWorkerNodeToken{ | ||
Token: token, | ||
JoinAddresses: addresses, | ||
} | ||
token, err := info.Encode() | ||
if err != nil { | ||
return "", fmt.Errorf("failed to encode join token: %w", err) | ||
} | ||
|
||
return token, nil | ||
} | ||
|
||
func createControlPlaneToken(s *state.State, r *http.Request, name string) (string, error) { | ||
m, err := microcluster.App(r.Context(), microcluster.Args{ | ||
StateDir: s.OS.StateDir, | ||
}) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to get microcluster app: %w", err) | ||
} | ||
|
||
c, err := m.LocalClient() | ||
if err != nil { | ||
return "", fmt.Errorf("failed to get local microcluster client: %w", err) | ||
} | ||
|
||
return c.RequestToken(r.Context(), name) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters