5
5
"net"
6
6
"strconv"
7
7
"strings"
8
+ "time"
8
9
9
10
"github.com/NinesStack/sidecar/catalog"
10
11
"github.com/NinesStack/sidecar/service"
@@ -26,8 +27,18 @@ import (
26
27
)
27
28
28
29
const (
29
- // ServiceNameSeparator is used to join service name and port. Must not occur in service names.
30
+
31
+ // ServiceNameSeparator is used to join service name and port. Must not
32
+ // occur in service names.
30
33
ServiceNameSeparator = ":"
34
+
35
+ // PortCollisionLoggingBackoff is how long we wait between logging about
36
+ // port collisions.
37
+ PortCollisionLoggingBackoff = 1 * time .Minute
38
+ )
39
+
40
+ var (
41
+ LastLoggedPortCollision time.Time
31
42
)
32
43
33
44
// EnvoyResources is a collection of Enovy API resource definitions
@@ -58,8 +69,8 @@ func SvcNameSplit(name string) (string, int64, error) {
58
69
return svcName , svcPort , nil
59
70
}
60
71
61
- // LookupHost does a vv slow lookup of the DNS host for a service. Totally
62
- // not optimized for high throughput. You should only do this in development
72
+ // LookupHost does a vv slow lookup of the DNS host for a service. Totally not
73
+ // optimized for high throughput. You should only do this in development
63
74
// scenarios.
64
75
func LookupHost (hostname string ) (string , error ) {
65
76
addrs , err := net .LookupHost (hostname )
@@ -70,17 +81,43 @@ func LookupHost(hostname string) (string, error) {
70
81
return addrs [0 ], nil
71
82
}
72
83
73
- // EnvoyResourcesFromState creates a set of Enovy API resource definitions from all
74
- // the ServicePorts in the Sidecar state. The Sidecar state needs to be locked by the
75
- // caller before calling this function.
84
+ // isPortCollision will make sure we don't tell Envoy about more than one
85
+ // service on the same port. This leads to it going completely apeshit both
86
+ // with CPU usage and logging.
87
+ func isPortCollision (portsMap map [int64 ]string , svc * service.Service , port service.Port ) bool {
88
+ registeredName , ok := portsMap [port .ServicePort ]
89
+ // See if we already know about this port
90
+ if ok {
91
+ // If it is the same service, then no collision
92
+ if registeredName == svc .Name {
93
+ return false
94
+ }
95
+
96
+ // Uh, oh, this is not the service assigned to this port
97
+ return true
98
+ }
99
+
100
+ // We don't know about it, so assign it.
101
+ portsMap [port .ServicePort ] = svc .Name
102
+ return false
103
+ }
104
+
105
+ // EnvoyResourcesFromState creates a set of Enovy API resource definitions from
106
+ // all the ServicePorts in the Sidecar state. The Sidecar state needs to be
107
+ // locked by the caller before calling this function.
76
108
func EnvoyResourcesFromState (state * catalog.ServicesState , bindIP string ,
77
109
useHostnames bool ) EnvoyResources {
78
110
79
111
endpointMap := make (map [string ]* api.ClusterLoadAssignment )
80
112
clusterMap := make (map [string ]* api.Cluster )
81
113
listenerMap := make (map [string ]cache_types.Resource )
82
114
83
- state .EachService (func (hostname * string , id * string , svc * service.Service ) {
115
+ // Used to make sure we don't map the same port to more than one service
116
+ portsMap := make (map [int64 ]string )
117
+
118
+ // We use the more expensive EachServiceSorted to make sure we make a stable
119
+ // port mapping allocation in the event of port collisions.
120
+ state .EachServiceSorted (func (hostname * string , id * string , svc * service.Service ) {
84
121
if svc == nil || ! svc .IsAlive () {
85
122
return
86
123
}
@@ -92,6 +129,19 @@ func EnvoyResourcesFromState(state *catalog.ServicesState, bindIP string,
92
129
continue
93
130
}
94
131
132
+ // Make sure we don't make Envoy go nuts by reporting the same port twice
133
+ if isPortCollision (portsMap , svc , port ) {
134
+ // This happens A LOT when it happens, so let's back off to once a minute-ish
135
+ if time .Now ().UTC ().Sub (LastLoggedPortCollision ) > PortCollisionLoggingBackoff {
136
+ log .Warnf (
137
+ "Port collision! %s is attempting to squat on port %d owned by %s" ,
138
+ svc .Name , port .ServicePort , portsMap [port .ServicePort ],
139
+ )
140
+ LastLoggedPortCollision = time .Now ().UTC ()
141
+ }
142
+ continue
143
+ }
144
+
95
145
envoyServiceName := SvcName (svc .Name , port .ServicePort )
96
146
97
147
if assignment , ok := endpointMap [envoyServiceName ]; ok {
0 commit comments