Adds a Nix cache proxy which can be used to send a Nix cache lookup to the first available cache that has the given NAR. We will use this for dynamically created builders. Relates to b/432. Change-Id: If970d2393e43ba032b5b7d653f2b92f6ac0eab63 Reviewed-on: https://cl.tvl.fyi/c/depot/+/12949 Tested-by: BuildkiteCI Autosubmit: tazjin <tazjin@tvl.su> Reviewed-by: sterni <sternenseemann@systemli.org>
		
			
				
	
	
		
			97 lines
		
	
	
	
		
			2.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			97 lines
		
	
	
	
		
			2.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Package discovery provides logic for discovering the current set of available
 | |
| // caches through Tailscale tags.
 | |
| package discovery
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"log/slog"
 | |
| 	"math/rand"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"tailscale.com/client/tailscale"
 | |
| 
 | |
| 	"tvl.fyi/ops/builderball/config"
 | |
| )
 | |
| 
 | |
| // GetCaches returns the currently known set of caches, updating it if required.
 | |
| //
 | |
| // If cached data is stale but an update fails, the stale data is returned
 | |
| // anyways. There is a fairly high chance that one or more of the known caches
 | |
| // are still alive in case of transient Tailscale issues.
 | |
| func GetCaches() []string {
 | |
| 	return caches.get()
 | |
| }
 | |
| 
 | |
| type cache struct {
 | |
| 	lock    sync.RWMutex
 | |
| 	caches  []string
 | |
| 	updated time.Time
 | |
| }
 | |
| 
 | |
| var caches *cache = new(cache)
 | |
| 
 | |
| func (c *cache) update() ([]string, error) {
 | |
| 	c.lock.Lock()
 | |
| 	defer c.lock.Unlock()
 | |
| 
 | |
| 	found := make([]string, len(config.Caches))
 | |
| 	copy(found, config.Caches)
 | |
| 
 | |
| 	if *config.Tailscale {
 | |
| 		client := tailscale.LocalClient{}
 | |
| 		status, err := client.Status(context.Background())
 | |
| 		if err != nil {
 | |
| 			slog.Error("failed to get tailscale status", "error", err.Error())
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		for _, peer := range status.Peer {
 | |
| 			if peer.Online && peer.Tags != nil && status.Self != peer && len(peer.TailscaleIPs) > 0 {
 | |
| 				for _, tag := range peer.Tags.All() {
 | |
| 					if tag == *config.TSTag {
 | |
| 						ip := peer.TailscaleIPs[0].String()
 | |
| 						slog.Debug("discovered cache on tailscale", "host", peer.HostName, "ip", ip)
 | |
| 						found = append(found, fmt.Sprintf("http://%s:%d", ip, *config.CachePort))
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// shuffle order of elements to avoid sending everything to the first
 | |
| 	// configured one for popular packages
 | |
| 	rand.Shuffle(len(found), func(i, j int) {
 | |
| 		found[i], found[j] = found[j], found[i]
 | |
| 	})
 | |
| 
 | |
| 	c.updated = time.Now()
 | |
| 	c.caches = make([]string, len(found))
 | |
| 	copy(c.caches, found)
 | |
| 	slog.Debug("updated discovered caches", "caches", found)
 | |
| 
 | |
| 	return found, nil
 | |
| }
 | |
| 
 | |
| func (c *cache) get() []string {
 | |
| 	c.lock.RLock()
 | |
| 	cached := make([]string, len(c.caches))
 | |
| 	copy(cached, c.caches)
 | |
| 	updated := c.updated
 | |
| 	c.lock.RUnlock()
 | |
| 
 | |
| 	if time.Since(updated) > 30*time.Second {
 | |
| 		slog.Debug("discovery cache is stale; updating")
 | |
| 		result, err := c.update()
 | |
| 		if err != nil {
 | |
| 			// return stale results; worth trying anyways
 | |
| 			slog.Debug("returning stale discovery cache results")
 | |
| 			return cached
 | |
| 		}
 | |
| 
 | |
| 		return result
 | |
| 	}
 | |
| 
 | |
| 	return cached
 | |
| }
 |