1package main 2 3import ( 4 "flag" 5 "fmt" 6 "path/filepath" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 "gitlab.com/gitlab-org/gitaly/v14/client" 13 "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/service/setup" 14 "gitlab.com/gitlab-org/gitaly/v14/internal/praefect/config" 15 "gitlab.com/gitlab-org/gitaly/v14/internal/praefect/datastore" 16 "gitlab.com/gitlab-org/gitaly/v14/internal/praefect/datastore/glsql" 17 "gitlab.com/gitlab-org/gitaly/v14/internal/praefect/nodes" 18 "gitlab.com/gitlab-org/gitaly/v14/internal/praefect/protoregistry" 19 "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" 20 "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/promtest" 21 "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testcfg" 22 "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testserver" 23 "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb" 24) 25 26func TestAddRepository_FlagSet(t *testing.T) { 27 t.Parallel() 28 cmd := &trackRepository{} 29 fs := cmd.FlagSet() 30 require.NoError(t, fs.Parse([]string{"--virtual-storage", "vs", "--repository", "repo", "--authoritative-storage", "storage-0"})) 31 require.Equal(t, "vs", cmd.virtualStorage) 32 require.Equal(t, "repo", cmd.relativePath) 33 require.Equal(t, "storage-0", cmd.authoritativeStorage) 34} 35 36func TestAddRepository_Exec_invalidArgs(t *testing.T) { 37 t.Parallel() 38 t.Run("not all flag values processed", func(t *testing.T) { 39 cmd := trackRepository{} 40 flagSet := flag.NewFlagSet("cmd", flag.PanicOnError) 41 require.NoError(t, flagSet.Parse([]string{"stub"})) 42 err := cmd.Exec(flagSet, config.Config{}) 43 require.EqualError(t, err, "cmd doesn't accept positional arguments") 44 }) 45 46 t.Run("virtual-storage is not set", func(t *testing.T) { 47 cmd := trackRepository{} 48 err := cmd.Exec(flag.NewFlagSet("", flag.PanicOnError), config.Config{}) 49 require.EqualError(t, err, `"virtual-storage" is a required parameter`) 50 }) 51 52 t.Run("repository is not set", func(t *testing.T) { 53 cmd := trackRepository{virtualStorage: "stub"} 54 err := cmd.Exec(flag.NewFlagSet("", flag.PanicOnError), config.Config{}) 55 require.EqualError(t, err, `"repository" is a required parameter`) 56 }) 57 58 t.Run("authoritative-storage is not set", func(t *testing.T) { 59 cmd := trackRepository{virtualStorage: "stub", relativePath: "path/to/repo"} 60 err := cmd.Exec(flag.NewFlagSet("", flag.PanicOnError), config.Config{Failover: config.Failover{ElectionStrategy: config.ElectionStrategyPerRepository}}) 61 require.EqualError(t, err, `"authoritative-storage" is a required parameter`) 62 }) 63 64 t.Run("db connection error", func(t *testing.T) { 65 cmd := trackRepository{virtualStorage: "stub", relativePath: "stub", authoritativeStorage: "storage-0"} 66 cfg := config.Config{DB: config.DB{Host: "stub", SSLMode: "disable"}} 67 err := cmd.Exec(flag.NewFlagSet("", flag.PanicOnError), cfg) 68 require.Error(t, err) 69 require.Contains(t, err.Error(), "connect to database: dial tcp: lookup stub") 70 }) 71} 72 73func TestAddRepository_Exec(t *testing.T) { 74 t.Parallel() 75 g1Cfg := testcfg.Build(t, testcfg.WithStorages("gitaly-1")) 76 g2Cfg := testcfg.Build(t, testcfg.WithStorages("gitaly-2")) 77 78 g1Srv := testserver.StartGitalyServer(t, g1Cfg, nil, setup.RegisterAll, testserver.WithDisablePraefect()) 79 g2Srv := testserver.StartGitalyServer(t, g2Cfg, nil, setup.RegisterAll, testserver.WithDisablePraefect()) 80 defer g2Srv.Shutdown() 81 defer g1Srv.Shutdown() 82 83 g1Addr := g1Srv.Address() 84 85 db := glsql.NewDB(t) 86 var database string 87 require.NoError(t, db.QueryRow(`SELECT current_database()`).Scan(&database)) 88 dbConf := glsql.GetDBConfig(t, database) 89 90 virtualStorageName := "praefect" 91 conf := config.Config{ 92 AllowLegacyElectors: true, 93 SocketPath: testhelper.GetTemporaryGitalySocketFileName(t), 94 VirtualStorages: []*config.VirtualStorage{ 95 { 96 Name: virtualStorageName, 97 Nodes: []*config.Node{ 98 {Storage: g1Cfg.Storages[0].Name, Address: g1Addr}, 99 {Storage: g2Cfg.Storages[0].Name, Address: g2Srv.Address()}, 100 }, 101 DefaultReplicationFactor: 2, 102 }, 103 }, 104 DB: dbConf, 105 } 106 107 gitalyCC, err := client.Dial(g1Addr, nil) 108 require.NoError(t, err) 109 defer func() { require.NoError(t, gitalyCC.Close()) }() 110 ctx, cancel := testhelper.Context() 111 defer cancel() 112 113 gitaly1RepositoryClient := gitalypb.NewRepositoryServiceClient(gitalyCC) 114 115 createRepoThroughGitaly1 := func(relativePath string) error { 116 _, err := gitaly1RepositoryClient.CreateRepository( 117 ctx, 118 &gitalypb.CreateRepositoryRequest{ 119 Repository: &gitalypb.Repository{ 120 StorageName: g1Cfg.Storages[0].Name, 121 RelativePath: relativePath, 122 }, 123 }) 124 return err 125 } 126 127 testCases := map[string]struct { 128 failoverConfig config.Failover 129 authoritativeStorage string 130 }{ 131 "sql election": { 132 failoverConfig: config.Failover{ 133 Enabled: true, 134 ElectionStrategy: config.ElectionStrategySQL, 135 }, 136 authoritativeStorage: "", 137 }, 138 "per repository election": { 139 failoverConfig: config.Failover{ 140 Enabled: true, 141 ElectionStrategy: config.ElectionStrategyPerRepository, 142 }, 143 authoritativeStorage: g1Cfg.Storages[0].Name, 144 }, 145 } 146 147 logger := testhelper.NewTestLogger(t) 148 for tn, tc := range testCases { 149 t.Run(tn, func(t *testing.T) { 150 addCmdConf := conf 151 addCmdConf.Failover = tc.failoverConfig 152 153 t.Run("ok", func(t *testing.T) { 154 nodeMgr, err := nodes.NewManager(testhelper.DiscardTestEntry(t), addCmdConf, db.DB, nil, promtest.NewMockHistogramVec(), protoregistry.GitalyProtoPreregistered, nil, nil, nil) 155 require.NoError(t, err) 156 nodeMgr.Start(0, time.Hour) 157 defer nodeMgr.Stop() 158 159 relativePath := fmt.Sprintf("path/to/test/repo_%s", tn) 160 repoDS := datastore.NewPostgresRepositoryStore(db, conf.StorageNames()) 161 162 require.NoError(t, createRepoThroughGitaly1(relativePath)) 163 164 rmRepoCmd := &removeRepository{ 165 logger: logger, 166 virtualStorage: virtualStorageName, 167 relativePath: relativePath, 168 } 169 170 require.NoError(t, rmRepoCmd.Exec(flag.NewFlagSet("", flag.PanicOnError), conf)) 171 172 // create the repo on Gitaly without Praefect knowing 173 require.NoError(t, createRepoThroughGitaly1(relativePath)) 174 require.DirExists(t, filepath.Join(g1Cfg.Storages[0].Path, relativePath)) 175 require.NoDirExists(t, filepath.Join(g2Cfg.Storages[0].Path, relativePath)) 176 177 addRepoCmd := &trackRepository{ 178 logger: logger, 179 virtualStorage: virtualStorageName, 180 relativePath: relativePath, 181 authoritativeStorage: tc.authoritativeStorage, 182 } 183 184 require.NoError(t, addRepoCmd.Exec(flag.NewFlagSet("", flag.PanicOnError), addCmdConf)) 185 as := datastore.NewAssignmentStore(db, conf.StorageNames()) 186 187 assignments, err := as.GetHostAssignments(ctx, virtualStorageName, relativePath) 188 require.NoError(t, err) 189 require.Len(t, assignments, 2) 190 assert.Contains(t, assignments, g1Cfg.Storages[0].Name) 191 assert.Contains(t, assignments, g2Cfg.Storages[0].Name) 192 193 exists, err := repoDS.RepositoryExists(ctx, virtualStorageName, relativePath) 194 require.NoError(t, err) 195 assert.True(t, exists) 196 }) 197 198 t.Run("repository does not exist", func(t *testing.T) { 199 relativePath := fmt.Sprintf("path/to/test/repo_1_%s", tn) 200 201 cmd := &trackRepository{ 202 logger: testhelper.NewTestLogger(t), 203 virtualStorage: "praefect", 204 relativePath: relativePath, 205 authoritativeStorage: tc.authoritativeStorage, 206 } 207 208 assert.ErrorIs(t, cmd.Exec(flag.NewFlagSet("", flag.PanicOnError), addCmdConf), errAuthoritativeRepositoryNotExist) 209 }) 210 211 t.Run("records already exist", func(t *testing.T) { 212 relativePath := fmt.Sprintf("path/to/test/repo_2_%s", tn) 213 214 require.NoError(t, createRepoThroughGitaly1(relativePath)) 215 require.DirExists(t, filepath.Join(g1Cfg.Storages[0].Path, relativePath)) 216 require.NoDirExists(t, filepath.Join(g2Cfg.Storages[0].Path, relativePath)) 217 218 ds := datastore.NewPostgresRepositoryStore(db, conf.StorageNames()) 219 id, err := ds.ReserveRepositoryID(ctx, virtualStorageName, relativePath) 220 require.NoError(t, err) 221 require.NoError(t, ds.CreateRepository(ctx, id, virtualStorageName, relativePath, g1Cfg.Storages[0].Name, nil, nil, true, true)) 222 223 cmd := &trackRepository{ 224 logger: testhelper.NewTestLogger(t), 225 virtualStorage: virtualStorageName, 226 relativePath: relativePath, 227 authoritativeStorage: tc.authoritativeStorage, 228 } 229 230 assert.NoError(t, cmd.Exec(flag.NewFlagSet("", flag.PanicOnError), addCmdConf)) 231 }) 232 }) 233 } 234} 235