/
server_mapper.go
155 lines (123 loc) · 3.61 KB
/
server_mapper.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package cloudscale_ccm
import (
"context"
"errors"
"fmt"
"github.com/cloudscale-ch/cloudscale-cloud-controller-manager/pkg/internal/limiter"
"github.com/cloudscale-ch/cloudscale-go-sdk/v4"
v1 "k8s.io/api/core/v1"
)
// serverMapper maps cloudscale servers to Kubernetes nodes.
type serverMapper struct {
client *cloudscale.Client
}
// findByNode finds a server by node name, or requests it by provider id.
func (s *serverMapper) findByNode(
ctx context.Context,
node *v1.Node,
) *limiter.Limiter[cloudscale.Server] {
if node == nil {
return limiter.New[cloudscale.Server](nil)
}
if node.Spec.ProviderID != "" {
providerID, err := parseCloudscaleProviderID(node.Spec.ProviderID)
// If there *is* a provider ID, but it is not valid, we return an
// error, as we can't say that the instance exist or not (it could
// be from another cloud provider and we have no knowledge about
// them).
//
// See also https://github.com/kubernetes/cloud-provider/issues/3
if err != nil {
return limiter.New[cloudscale.Server](fmt.Errorf(
"%s is not a valid cloudscale provider id: %w",
node.Spec.ProviderID,
err,
))
}
return s.getByProviderID(ctx, *providerID)
}
return s.findByName(ctx, node.Name)
}
// mapNodes returns a server for each given node. If a 1:1 mapping across all
// given nodes can be established, an error is returned.
func (s *serverMapper) mapNodes(
ctx context.Context,
nodes []*v1.Node,
) *limiter.Limiter[cloudscale.Server] {
servers := make([]cloudscale.Server, 0, len(nodes))
for _, node := range nodes {
server, err := s.findByNode(ctx, node).One()
if err != nil {
return limiter.New[cloudscale.Server](err)
}
servers = append(servers, *server)
}
return limiter.New[cloudscale.Server](nil, servers...)
}
// getByProviderID tries to access the server by provider ID (UUID)
func (s *serverMapper) getByProviderID(
ctx context.Context,
id cloudscaleProviderID,
) *limiter.Limiter[cloudscale.Server] {
server, err := s.client.Servers.Get(ctx, id.UUID().String())
if err != nil {
var response *cloudscale.ErrorResponse
if errors.As(err, &response) && response.StatusCode == 404 {
return limiter.New[cloudscale.Server](nil)
}
return limiter.New[cloudscale.Server](err)
}
return limiter.New[cloudscale.Server](nil, *server)
}
// findByName returns servers matching the given name (there may be multiple
// matches).
func (s *serverMapper) findByName(
ctx context.Context,
name string,
) *limiter.Limiter[cloudscale.Server] {
servers, err := s.client.Servers.List(ctx)
if err != nil {
return limiter.New[cloudscale.Server](err)
}
matches := []cloudscale.Server{}
for _, server := range servers {
srv := server
if srv.Name == name {
matches = append(matches, srv)
}
}
return limiter.New[cloudscale.Server](nil, matches...)
}
// nodeAddresses returns a v1.nodeAddresses slice for the metadata
func (s *serverMapper) nodeAddresses(
server *cloudscale.Server) []v1.NodeAddress {
if server == nil {
return []v1.NodeAddress{}
}
// We're likely going to have three entries (hostname, ipv4, ipv6), so
// use that as the initial capacity.
addrs := make([]v1.NodeAddress, 0, 3)
addrs = append(addrs, v1.NodeAddress{
Type: v1.NodeHostName,
Address: server.Name,
})
for _, i := range server.Interfaces {
for _, a := range i.Addresses {
var addr v1.NodeAddress
switch i.Type {
case "public":
addr = v1.NodeAddress{
Type: v1.NodeExternalIP,
Address: a.Address,
}
default:
addr = v1.NodeAddress{
Type: v1.NodeInternalIP,
Address: a.Address,
}
}
addrs = append(addrs, addr)
}
}
return addrs
}