blob: 32a569197ed2362a86e7bb8e57e63294c2f7e1e2 [file] [log] [blame]
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package api
18
19import (
20 "context"
21 "crypto/rand"
22 "encoding/hex"
23 "fmt"
24 schema "git.monogon.dev/source/nexantic.git/core/generated/api"
25 "git.monogon.dev/source/nexantic.git/core/internal/common"
26
27 "errors"
28
29 "go.uber.org/zap"
30)
31
32var (
33 ErrAttestationFailed = errors.New("attestation_failed")
34)
35
36func (s *Server) AddNode(ctx context.Context, req *schema.AddNodeRequest) (*schema.AddNodeResponse, error) {
37 // Setup API client
38 c, err := common.NewSmalltownAPIClient(fmt.Sprintf("%s:%d", req.Host, req.ApiPort))
39 if err != nil {
40 return nil, err
41 }
42
43 // Check attestation
44 nonce := make([]byte, 20)
45 _, err = rand.Read(nonce)
46 if err != nil {
47 return nil, err
48 }
49 hexNonce := hex.EncodeToString(nonce)
50
51 aRes, err := c.Setup.Attest(ctx, &schema.AttestRequest{
52 Challenge: hexNonce,
53 })
54 if err != nil {
55 return nil, err
56 }
57
58 //TODO(hendrik): Verify response
59 if aRes.Response != hexNonce {
60 return nil, ErrAttestationFailed
61 }
62
63 // Provision cluster info locally
64 memberID, err := s.consensusService.AddMember(ctx, req.Name, fmt.Sprintf("http://%s:%d", req.Host, req.ConsensusPort))
65 if err != nil {
66 return nil, err
67 }
68
69 s.Logger.Info("Added new node to consensus cluster; provisioning external node now",
70 zap.String("host", req.Host), zap.Uint32("port", req.ApiPort),
71 zap.Uint32("consensus_port", req.ConsensusPort), zap.String("name", req.Name))
72
73 // Provision cluster info externally
74 _, err = c.Setup.ProvisionCluster(ctx, &schema.ProvisionClusterRequest{
75 InitialCluster: s.consensusService.GetInitialClusterString(),
76 ProvisioningToken: req.Token,
77 ExternalHost: req.Host,
78 NodeName: req.Name,
79 TrustBackend: req.TrustBackend,
80 })
81 if err != nil {
82 // Revert Consensus add member - might fail if consensus cannot be established
83 err2 := s.consensusService.RemoveMember(ctx, memberID)
84 if err2 != nil {
85 return nil, fmt.Errorf("Rollback failed after failed provisioning; err=%v; err_rb=%v", err, err2)
86 }
87 return nil, err
88 }
89 s.Logger.Info("Fully provisioned new node",
90 zap.String("host", req.Host), zap.Uint32("port", req.ApiPort),
91 zap.Uint32("consensus_port", req.ConsensusPort), zap.String("name", req.Name),
92 zap.Uint64("member_id", memberID))
93
94 return &schema.AddNodeResponse{}, nil
95}
96
97func (s *Server) RemoveNode(context.Context, *schema.RemoveNodeRequest) (*schema.RemoveNodeRequest, error) {
98 panic("implement me")
99}
100
101func (s *Server) GetNodes(context.Context, *schema.GetNodesRequest) (*schema.GetNodesResponse, error) {
102 nodes := s.consensusService.GetNodes()
103 resNodes := make([]*schema.Node, len(nodes))
104
105 for i, node := range nodes {
106 resNodes[i] = &schema.Node{
107 Id: node.ID,
108 Name: node.Name,
109 Address: node.Address,
110 Synced: node.Synced,
111 }
112 }
113
114 return &schema.GetNodesResponse{
115 Nodes: resNodes,
116 }, nil
117}