go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cipd/appengine/impl/testutil/metadata.go (about) 1 // Copyright 2018 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package testutil 16 17 import ( 18 "context" 19 "sort" 20 "strings" 21 "sync" 22 23 "google.golang.org/protobuf/proto" 24 25 api "go.chromium.org/luci/cipd/api/cipd/v1" 26 "go.chromium.org/luci/cipd/appengine/impl/metadata" 27 "go.chromium.org/luci/cipd/common" 28 ) 29 30 // MetadataStore implements metadata.Storage using memory, for tests. 31 // 32 // Not terribly efficient, shouldn't be used with a large number of entries. 33 type MetadataStore struct { 34 l sync.Mutex 35 metas map[string]*api.PrefixMetadata // e.g. "/a/b/c/" => metadata 36 } 37 38 // Populate adds a metadata entry to the storage. 39 // 40 // If populates Prefix and Fingerprint. Returns the added item. Panics if the 41 // prefix is bad or the given metadata is empty. 42 func (s *MetadataStore) Populate(prefix string, m *api.PrefixMetadata) *api.PrefixMetadata { 43 meta, err := s.UpdateMetadata(context.Background(), prefix, func(_ context.Context, e *api.PrefixMetadata) error { 44 proto.Reset(e) 45 proto.Merge(e, m) 46 return nil 47 }) 48 if err != nil { 49 panic(err) 50 } 51 if meta == nil { 52 panic("Populate should be used only with non-empty metadata") 53 } 54 return meta 55 } 56 57 // Purge removes metadata entry for some prefix. 58 // 59 // Panics if the prefix is bad. Purging missing metadata is noop. 60 func (s *MetadataStore) Purge(prefix string) { 61 prefix, err := normPrefix(prefix) 62 if err != nil { 63 panic(err) 64 } 65 66 s.l.Lock() 67 defer s.l.Unlock() 68 delete(s.metas, prefix) 69 } 70 71 // GetMetadata fetches metadata associated with the given prefix and all 72 // parent prefixes. 73 func (s *MetadataStore) GetMetadata(ctx context.Context, prefix string) ([]*api.PrefixMetadata, error) { 74 prefix, err := normPrefix(prefix) 75 if err != nil { 76 return nil, err 77 } 78 79 s.l.Lock() 80 defer s.l.Unlock() 81 82 var metas []*api.PrefixMetadata 83 for p := range s.metas { 84 if strings.HasPrefix(prefix, p) { 85 metas = append(metas, cloneMetadata(s.metas[p])) 86 } 87 } 88 89 sort.Slice(metas, func(i, j int) bool { 90 return metas[i].Prefix < metas[j].Prefix 91 }) 92 return metas, nil 93 } 94 95 // VisitMetadata performs depth-first enumeration of the metadata graph. 96 func (s *MetadataStore) VisitMetadata(ctx context.Context, prefix string, cb metadata.Visitor) error { 97 prefix, err := normPrefix(prefix) 98 if err != nil { 99 return err 100 } 101 return s.asGraph(prefix).traverse(func(n *node) (cont bool, err error) { 102 // If this node represents a path element without actual metadata attached 103 // to it, just recurse deeper until we find some metadata. The exception is 104 // the root prefix itself, since per VisitMetadata contract, we must visit 105 // it even if it has no metadata directly attached to it. 106 if !n.hasMeta && n.path != prefix { 107 return true, nil 108 } 109 110 // Convert the prefix back to the form expected by the public API. 111 clean := strings.Trim(n.path, "/") 112 113 // Grab full metadata for it (including inherited one). 114 md, err := s.GetMetadata(ctx, clean) 115 if err != nil { 116 return false, err 117 } 118 119 // And ask the callback whether we should proceed. 120 return cb(clean, md) 121 }) 122 } 123 124 // UpdateMetadata transactionally updates or creates metadata of some 125 // prefix. 126 func (s *MetadataStore) UpdateMetadata(ctx context.Context, prefix string, cb func(ctx context.Context, m *api.PrefixMetadata) error) (*api.PrefixMetadata, error) { 127 prefix, err := common.ValidatePackagePrefix(prefix) 128 if err != nil { 129 return nil, err 130 } 131 132 s.l.Lock() 133 defer s.l.Unlock() 134 135 key, _ := normPrefix(prefix) 136 before := s.metas[key] // the metadata before the callback 137 if before == nil { 138 before = &api.PrefixMetadata{Prefix: prefix} 139 } 140 141 // Don't let the callback modify or retain the internal data. 142 meta := cloneMetadata(before) 143 if err := cb(ctx, meta); err != nil { 144 return nil, err 145 } 146 147 // Don't let the callback mess with the prefix or the fingerprint. 148 meta.Prefix = before.Prefix 149 meta.Fingerprint = before.Fingerprint 150 151 // No changes at all? Return nil if the metadata didn't exist and wasn't 152 // created by the callback. Otherwise return the existing metadata. 153 if proto.Equal(before, meta) { 154 if before.Fingerprint == "" { 155 return nil, nil 156 } 157 return meta, nil 158 } 159 160 // Calculate the new fingerprint and put the metadata into the storage. 161 metadata.CalculateFingerprint(meta) 162 if s.metas == nil { 163 s.metas = make(map[string]*api.PrefixMetadata, 1) 164 } 165 s.metas[key] = cloneMetadata(meta) 166 return meta, nil 167 } 168 169 //////////////////////////////////////////////////////////////////////////////// 170 171 type node struct { 172 path string // full path from the metadata root, e.g. "/a/b/c/" 173 children map[string]*node // keys are elementary path components 174 hasMeta bool // true if this node has metadata attached to it 175 } 176 177 // child returns a child node, creating it if necessary. 178 func (n *node) child(name string) *node { 179 if c, ok := n.children[name]; ok { 180 return c 181 } 182 if n.children == nil { 183 n.children = make(map[string]*node, 1) 184 } 185 c := &node{path: n.path + name + "/"} 186 n.children[name] = c 187 return c 188 } 189 190 // traverse does depth-first traversal of the node's subtree starting from self. 191 // 192 // Children are visited in lexicographical order. 193 func (n *node) traverse(cb func(*node) (cont bool, err error)) error { 194 switch descend, err := cb(n); { 195 case err != nil: 196 return err 197 case !descend: 198 return nil 199 } 200 201 keys := make([]string, 0, len(n.children)) 202 for k := range n.children { 203 keys = append(keys, k) 204 } 205 sort.Strings(keys) 206 207 for _, k := range keys { 208 if err := n.children[k].traverse(cb); err != nil { 209 return err 210 } 211 } 212 return nil 213 } 214 215 // asGraph builds a graph representation of metadata subtree at the given 216 // prefix. 217 // 218 // The returned root node represents 'prefix'. 219 func (s *MetadataStore) asGraph(prefix string) *node { 220 s.l.Lock() 221 defer s.l.Unlock() 222 223 root := &node{path: prefix} 224 for pfx := range s.metas { 225 if !strings.HasPrefix(pfx, prefix) { 226 continue 227 } 228 // Convert "/<prefix>/a/b/c/" to "a/b/c". 229 rel := strings.TrimRight(strings.TrimPrefix(pfx, prefix), "/") 230 cur := root 231 if rel != "" { 232 for _, elem := range strings.Split(rel, "/") { 233 cur = cur.child(elem) 234 } 235 } 236 cur.hasMeta = true 237 } 238 return root 239 } 240 241 // normPrefix takes "a/b/c" and returns "/a/b/c/". 242 // 243 // For "" returns "/". 244 func normPrefix(p string) (string, error) { 245 p, err := common.ValidatePackagePrefix(p) 246 if err != nil { 247 return "", err 248 } 249 if p == "" { 250 return "/", nil 251 } 252 return "/" + p + "/", nil 253 } 254 255 // cloneMetadata makes a deep copy of 'm'. 256 func cloneMetadata(m *api.PrefixMetadata) *api.PrefixMetadata { 257 return proto.Clone(m).(*api.PrefixMetadata) 258 }