fork of https://github.com/sourcegraph/zoekt
1package main
2
3import (
4 "bytes"
5 "errors"
6 "fmt"
7 "io/fs"
8 "log"
9 "os"
10 "time"
11)
12
13type ownerChangeError struct {
14 Path string
15 Owner, Current string
16}
17
18func (e *ownerChangeError) Error() string {
19 return fmt.Sprintf("detected a change of ownership of %s. In multiple replica setups this can lead to un-needed rebalancing or bugs if there are multiple writers: owner=%q current=%q", e.Path, e.Owner, e.Current)
20}
21
22// ownerChecker can write and check the owner file for a index directory. It
23// is used for detecting when multiple zoekts are writing to a directory, or
24// when the ownership is changing too often.
25//
26// The motivation for this is a person can misconfigure zoekt such that
27// multiple indexservers write to the same directory. This will lead to index
28// thrashing and hard to debug errors. Alternatively if the stable identity
29// (hostname) changes, this can lead to Sourcegraph's repo <-> owner hash
30// changing which means unnecessary rebalancing.
31type ownerChecker struct {
32 Path string
33 Hostname string
34}
35
36// Run will regularly init then regularly check if we are owner. If an error
37// is detected it will exit the current process with exit code 1. This method
38// blocks.
39func (o *ownerChecker) Run() {
40 if err := o.Init(); err != nil {
41 log.Fatal(err)
42 }
43 for {
44 time.Sleep(5 * time.Second)
45 if err := o.Check(); err != nil {
46 log.Fatal(err)
47 }
48 }
49}
50
51func (o *ownerChecker) Init() error {
52 var ownerErr *ownerChangeError
53 if err := o.Check(); errors.Is(err, fs.ErrNotExist) {
54 // do nothing, first run so we just write out the file
55 } else if errors.As(err, &ownerErr) {
56 debug.Printf("WARN: detected a change in ownership at startup. You can ignore this if you only have one zoekt replica: %s", err)
57 } else if err != nil {
58 return err
59 }
60
61 content := []byte(fmt.Sprintf(`DO NOT EDIT! generated by zoekt-sourcegraph-indexserver.
62This file records the identity of the owner of this zoekt index directory.
63If it changes, zoekt-sourcegraph-indexserver will exit with a non-zero exit code.
64This is to prevent multiple owners/writers.
65
66hostname=%s
67`, o.Hostname))
68
69 // Always write out since we may update the comment
70 if err := os.WriteFile(o.Path, content, 0o600); err != nil {
71 return fmt.Errorf("failed to write owner file %s: %w", o.Path, err)
72 }
73
74 return nil
75}
76
77func (o *ownerChecker) Check() error {
78 if b, err := os.ReadFile(o.Path); err != nil {
79 return fmt.Errorf("failed to read in owner file %s: %w", o.Path, err)
80 } else if owner := bestEffortParseOwner(b); o.Hostname != owner {
81 return &ownerChangeError{
82 Path: o.Path,
83 Owner: owner,
84 Current: o.Hostname,
85 }
86 }
87 return nil
88}
89
90func bestEffortParseOwner(b []byte) string {
91 prefix := []byte("hostname=")
92 from := bytes.Index(b, prefix)
93 if from < 0 {
94 return "UNKNOWN"
95 }
96
97 b = b[from+len(prefix):]
98 if to := bytes.IndexByte(b, '\n'); to > 0 {
99 b = b[:to]
100 }
101
102 return string(bytes.TrimSpace(b))
103}