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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
|
// Copyright 2018 Envoyproxy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
import (
"errors"
"fmt"
"github.com/envoyproxy/go-control-plane/pkg/cache/types"
"github.com/envoyproxy/go-control-plane/pkg/resource/v3"
)
// Snapshot is an internally consistent snapshot of xDS resources.
// Consistency is important for the convergence as different resource types
// from the snapshot may be delivered to the proxy in arbitrary order.
type Snapshot struct {
Resources [types.UnknownType]Resources
// VersionMap holds the current hash map of all resources in the snapshot.
// This field should remain nil until it is used, at which point should be
// instantiated by calling ConstructVersionMap().
// VersionMap is only to be used with delta xDS.
VersionMap map[string]map[string]string
}
var _ ResourceSnapshot = &Snapshot{}
// NewSnapshot creates a snapshot from response types and a version.
// The resources map is keyed off the type URL of a resource, followed by the slice of resource objects.
func NewSnapshot(version string, resources map[resource.Type][]types.Resource) (*Snapshot, error) {
out := Snapshot{}
for typ, resource := range resources {
index := GetResponseType(typ)
if index == types.UnknownType {
return nil, errors.New("unknown resource type: " + typ)
}
out.Resources[index] = NewResources(version, resource)
}
return &out, nil
}
// NewSnapshotWithTTLs creates a snapshot of ResourceWithTTLs.
// The resources map is keyed off the type URL of a resource, followed by the slice of resource objects.
func NewSnapshotWithTTLs(version string, resources map[resource.Type][]types.ResourceWithTTL) (*Snapshot, error) {
out := Snapshot{}
for typ, resource := range resources {
index := GetResponseType(typ)
if index == types.UnknownType {
return nil, errors.New("unknown resource type: " + typ)
}
out.Resources[index] = NewResourcesWithTTL(version, resource)
}
return &out, nil
}
// Consistent check verifies that the dependent resources are exactly listed in the
// snapshot:
// - all EDS resources are listed by name in CDS resources
// - all SRDS/RDS resources are listed by name in LDS resources
// - all RDS resources are listed by name in SRDS resources
//
// Note that clusters and listeners are requested without name references, so
// Envoy will accept the snapshot list of clusters as-is even if it does not match
// all references found in xDS.
func (s *Snapshot) Consistent() error {
if s == nil {
return errors.New("nil snapshot")
}
referencedResources := GetAllResourceReferences(s.Resources)
// Loop through each referenced resource.
referencedResponseTypes := map[types.ResponseType]struct{}{
types.Endpoint: {},
types.Route: {},
}
for idx, items := range s.Resources {
// We only want to check resource types that are expected to be referenced by another resource type.
// Basically, if the consistency relationship is modeled as a DAG, we only want
// to check nodes that are expected to have edges pointing to it.
responseType := types.ResponseType(idx)
if _, ok := referencedResponseTypes[responseType]; ok {
typeURL, err := GetResponseTypeURL(responseType)
if err != nil {
return err
}
referenceSet := referencedResources[typeURL]
if len(referenceSet) != len(items.Items) {
return fmt.Errorf("mismatched %q reference and resource lengths: len(%v) != %d",
typeURL, referenceSet, len(items.Items))
}
// Check superset.
if err := superset(referenceSet, items.Items); err != nil {
return fmt.Errorf("inconsistent %q reference: %w", typeURL, err)
}
}
}
return nil
}
// GetResources selects snapshot resources by type, returning the map of resources.
func (s *Snapshot) GetResources(typeURL resource.Type) map[string]types.Resource {
resources := s.GetResourcesAndTTL(typeURL)
if resources == nil {
return nil
}
withoutTTL := make(map[string]types.Resource, len(resources))
for k, v := range resources {
withoutTTL[k] = v.Resource
}
return withoutTTL
}
// GetResourcesAndTTL selects snapshot resources by type, returning the map of resources and the associated TTL.
func (s *Snapshot) GetResourcesAndTTL(typeURL resource.Type) map[string]types.ResourceWithTTL {
if s == nil {
return nil
}
typ := GetResponseType(typeURL)
if typ == types.UnknownType {
return nil
}
return s.Resources[typ].Items
}
// GetVersion returns the version for a resource type.
func (s *Snapshot) GetVersion(typeURL resource.Type) string {
if s == nil {
return ""
}
typ := GetResponseType(typeURL)
if typ == types.UnknownType {
return ""
}
return s.Resources[typ].Version
}
// GetVersionMap will return the internal version map of the currently applied snapshot.
func (s *Snapshot) GetVersionMap(typeURL string) map[string]string {
return s.VersionMap[typeURL]
}
// ConstructVersionMap will construct a version map based on the current state of a snapshot
func (s *Snapshot) ConstructVersionMap() error {
if s == nil {
return fmt.Errorf("missing snapshot")
}
// The snapshot resources never change, so no need to ever rebuild.
if s.VersionMap != nil {
return nil
}
s.VersionMap = make(map[string]map[string]string)
for i, resources := range s.Resources {
typeURL, err := GetResponseTypeURL(types.ResponseType(i))
if err != nil {
return err
}
if _, ok := s.VersionMap[typeURL]; !ok {
s.VersionMap[typeURL] = make(map[string]string, len(resources.Items))
}
for _, r := range resources.Items {
// Hash our version in here and build the version map.
marshaledResource, err := MarshalResource(r.Resource)
if err != nil {
return err
}
v := HashResource(marshaledResource)
if v == "" {
return fmt.Errorf("failed to build resource version: %w", err)
}
s.VersionMap[typeURL][GetResourceName(r.Resource)] = v
}
}
return nil
}
|