GitOps for k8s
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

sync_test.go 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. package daemon
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "io/ioutil"
  7. "os"
  8. "path"
  9. "reflect"
  10. "sync"
  11. "testing"
  12. "time"
  13. "github.com/go-kit/kit/log"
  14. "github.com/fluxcd/flux/cluster"
  15. "github.com/fluxcd/flux/cluster/kubernetes"
  16. "github.com/fluxcd/flux/cluster/kubernetes/testfiles"
  17. "github.com/fluxcd/flux/cluster/mock"
  18. "github.com/fluxcd/flux/event"
  19. "github.com/fluxcd/flux/git"
  20. "github.com/fluxcd/flux/git/gittest"
  21. "github.com/fluxcd/flux/job"
  22. "github.com/fluxcd/flux/manifests"
  23. registryMock "github.com/fluxcd/flux/registry/mock"
  24. "github.com/fluxcd/flux/resource"
  25. fluxsync "github.com/fluxcd/flux/sync"
  26. )
  27. const (
  28. gitPath = ""
  29. gitNotesRef = "flux"
  30. gitUser = "Flux"
  31. gitEmail = "support@weave.works"
  32. )
  33. var (
  34. k8s *mock.Mock
  35. events *mockEventWriter
  36. )
  37. func daemon(t *testing.T) (*Daemon, func()) {
  38. repo, repoCleanup := gittest.Repo(t)
  39. k8s = &mock.Mock{}
  40. k8s.ExportFunc = func(ctx context.Context) ([]byte, error) { return nil, nil }
  41. events = &mockEventWriter{}
  42. wg := &sync.WaitGroup{}
  43. shutdown := make(chan struct{})
  44. if err := repo.Ready(context.Background()); err != nil {
  45. t.Fatal(err)
  46. }
  47. gitConfig := git.Config{
  48. Branch: "master",
  49. NotesRef: gitNotesRef,
  50. UserName: gitUser,
  51. UserEmail: gitEmail,
  52. }
  53. manifests := kubernetes.NewManifests(kubernetes.ConstNamespacer("default"), log.NewLogfmtLogger(os.Stdout))
  54. jobs := job.NewQueue(shutdown, wg)
  55. d := &Daemon{
  56. Cluster: k8s,
  57. Manifests: manifests,
  58. Registry: &registryMock.Registry{},
  59. Repo: repo,
  60. GitConfig: gitConfig,
  61. Jobs: jobs,
  62. JobStatusCache: &job.StatusCache{Size: 100},
  63. EventWriter: events,
  64. Logger: log.NewLogfmtLogger(os.Stdout),
  65. LoopVars: &LoopVars{GitTimeout: timeout},
  66. }
  67. return d, func() {
  68. close(shutdown)
  69. wg.Wait()
  70. repoCleanup()
  71. k8s = nil
  72. events = nil
  73. }
  74. }
  75. func TestPullAndSync_InitialSync(t *testing.T) {
  76. d, cleanup := daemon(t)
  77. defer cleanup()
  78. syncCalled := 0
  79. var syncDef *cluster.SyncSet
  80. expectedResourceIDs := resource.IDs{}
  81. for id, _ := range testfiles.ResourceMap {
  82. expectedResourceIDs = append(expectedResourceIDs, id)
  83. }
  84. expectedResourceIDs.Sort()
  85. k8s.SyncFunc = func(def cluster.SyncSet) error {
  86. syncCalled++
  87. syncDef = &def
  88. return nil
  89. }
  90. ctx := context.Background()
  91. head, err := d.Repo.BranchHead(ctx)
  92. if err != nil {
  93. t.Fatal(err)
  94. }
  95. syncTag := "sync"
  96. gitSync, _ := fluxsync.NewGitTagSyncProvider(d.Repo, syncTag, "", false, d.GitConfig)
  97. syncState := &lastKnownSyncState{logger: d.Logger, state: gitSync}
  98. if err := d.Sync(ctx, time.Now().UTC(), head, syncState); err != nil {
  99. t.Error(err)
  100. }
  101. // It applies everything
  102. if syncCalled != 1 {
  103. t.Errorf("Sync was not called once, was called %d times", syncCalled)
  104. } else if syncDef == nil {
  105. t.Errorf("Sync was called with a nil syncDef")
  106. }
  107. // The emitted event has all workload ids
  108. es, err := events.AllEvents(time.Time{}, -1, time.Time{})
  109. if err != nil {
  110. t.Error(err)
  111. } else if len(es) != 1 {
  112. t.Errorf("Unexpected events: %#v", es)
  113. } else if es[0].Type != event.EventSync {
  114. t.Errorf("Unexpected event type: %#v", es[0])
  115. } else {
  116. gotResourceIDs := es[0].ServiceIDs
  117. resource.IDs(gotResourceIDs).Sort()
  118. if !reflect.DeepEqual(gotResourceIDs, []resource.ID(expectedResourceIDs)) {
  119. t.Errorf("Unexpected event workload ids: %#v, expected: %#v", gotResourceIDs, expectedResourceIDs)
  120. }
  121. }
  122. // It creates the tag at HEAD
  123. if err := d.Repo.Refresh(context.Background()); err != nil {
  124. t.Errorf("pulling sync tag: %v", err)
  125. } else if revs, err := d.Repo.CommitsBefore(context.Background(), syncTag); err != nil {
  126. t.Errorf("finding revisions before sync tag: %v", err)
  127. } else if len(revs) <= 0 {
  128. t.Errorf("Found no revisions before the sync tag")
  129. }
  130. }
  131. func TestDoSync_NoNewCommits(t *testing.T) {
  132. d, cleanup := daemon(t)
  133. defer cleanup()
  134. var syncTag = "syncity"
  135. ctx := context.Background()
  136. err := d.WithWorkingClone(ctx, func(co *git.Checkout) error {
  137. ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
  138. defer cancel()
  139. tagAction := git.TagAction{
  140. Tag: syncTag,
  141. Revision: "master",
  142. Message: "Sync pointer",
  143. }
  144. return co.MoveTagAndPush(ctx, tagAction)
  145. })
  146. if err != nil {
  147. t.Fatal(err)
  148. }
  149. // NB this would usually trigger a sync in a running loop; but we
  150. // have not run the loop.
  151. if err = d.Repo.Refresh(ctx); err != nil {
  152. t.Error(err)
  153. }
  154. syncCalled := 0
  155. var syncDef *cluster.SyncSet
  156. expectedResourceIDs := resource.IDs{}
  157. for id, _ := range testfiles.ResourceMap {
  158. expectedResourceIDs = append(expectedResourceIDs, id)
  159. }
  160. expectedResourceIDs.Sort()
  161. k8s.SyncFunc = func(def cluster.SyncSet) error {
  162. syncCalled++
  163. syncDef = &def
  164. return nil
  165. }
  166. head, err := d.Repo.BranchHead(ctx)
  167. if err != nil {
  168. t.Fatal(err)
  169. }
  170. gitSync, _ := fluxsync.NewGitTagSyncProvider(d.Repo, syncTag, "", false, d.GitConfig)
  171. syncState := &lastKnownSyncState{logger: d.Logger, state: gitSync}
  172. if err := d.Sync(ctx, time.Now().UTC(), head, syncState); err != nil {
  173. t.Error(err)
  174. }
  175. // It applies everything
  176. if syncCalled != 1 {
  177. t.Errorf("Sync was not called once, was called %d times", syncCalled)
  178. } else if syncDef == nil {
  179. t.Errorf("Sync was called with a nil syncDef")
  180. }
  181. // The emitted event has no workload ids
  182. es, err := events.AllEvents(time.Time{}, -1, time.Time{})
  183. if err != nil {
  184. t.Error(err)
  185. } else if len(es) != 0 {
  186. t.Errorf("Unexpected events: %#v", es)
  187. }
  188. // It doesn't move the tag
  189. oldRevs, err := d.Repo.CommitsBefore(ctx, syncTag)
  190. if err != nil {
  191. t.Fatal(err)
  192. }
  193. if revs, err := d.Repo.CommitsBefore(ctx, syncTag); err != nil {
  194. t.Errorf("finding revisions before sync tag: %v", err)
  195. } else if !reflect.DeepEqual(revs, oldRevs) {
  196. t.Errorf("Should have kept the sync tag at HEAD")
  197. }
  198. }
  199. func TestDoSync_WithNewCommit(t *testing.T) {
  200. d, cleanup := daemon(t)
  201. defer cleanup()
  202. ctx := context.Background()
  203. var syncTag = "syncy-mcsyncface"
  204. // Set the sync tag to head
  205. var oldRevision, newRevision string
  206. err := d.WithWorkingClone(ctx, func(checkout *git.Checkout) error {
  207. ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
  208. defer cancel()
  209. var err error
  210. tagAction := git.TagAction{
  211. Tag: syncTag,
  212. Revision: "master",
  213. Message: "Sync pointer",
  214. }
  215. err = checkout.MoveTagAndPush(ctx, tagAction)
  216. if err != nil {
  217. return err
  218. }
  219. oldRevision, err = checkout.HeadRevision(ctx)
  220. if err != nil {
  221. return err
  222. }
  223. // Push some new changes
  224. cm := manifests.NewRawFiles(checkout.Dir(), checkout.AbsolutePaths(), d.Manifests)
  225. resourcesByID, err := cm.GetAllResourcesByID(context.TODO())
  226. if err != nil {
  227. return err
  228. }
  229. targetResource := "default:deployment/helloworld"
  230. res, ok := resourcesByID[targetResource]
  231. if !ok {
  232. return fmt.Errorf("resource not found: %q", targetResource)
  233. }
  234. absolutePath := path.Join(checkout.Dir(), res.Source())
  235. def, err := ioutil.ReadFile(absolutePath)
  236. if err != nil {
  237. return err
  238. }
  239. newDef := bytes.Replace(def, []byte("replicas: 5"), []byte("replicas: 4"), -1)
  240. if err := ioutil.WriteFile(absolutePath, newDef, 0600); err != nil {
  241. return err
  242. }
  243. commitAction := git.CommitAction{Author: "", Message: "test commit"}
  244. err = checkout.CommitAndPush(ctx, commitAction, nil, false)
  245. if err != nil {
  246. return err
  247. }
  248. newRevision, err = checkout.HeadRevision(ctx)
  249. return err
  250. })
  251. if err != nil {
  252. t.Fatal(err)
  253. }
  254. err = d.Repo.Refresh(ctx)
  255. if err != nil {
  256. t.Error(err)
  257. }
  258. syncCalled := 0
  259. var syncDef *cluster.SyncSet
  260. expectedResourceIDs := resource.IDs{}
  261. for id, _ := range testfiles.ResourceMap {
  262. expectedResourceIDs = append(expectedResourceIDs, id)
  263. }
  264. expectedResourceIDs.Sort()
  265. k8s.SyncFunc = func(def cluster.SyncSet) error {
  266. syncCalled++
  267. syncDef = &def
  268. return nil
  269. }
  270. head, err := d.Repo.BranchHead(ctx)
  271. if err != nil {
  272. t.Fatal(err)
  273. }
  274. gitSync, _ := fluxsync.NewGitTagSyncProvider(d.Repo, syncTag, "", false, d.GitConfig)
  275. syncState := &lastKnownSyncState{logger: d.Logger, state: gitSync}
  276. if err := d.Sync(ctx, time.Now().UTC(), head, syncState); err != nil {
  277. t.Error(err)
  278. }
  279. // It applies everything
  280. if syncCalled != 1 {
  281. t.Errorf("Sync was not called once, was called %d times", syncCalled)
  282. } else if syncDef == nil {
  283. t.Errorf("Sync was called with a nil syncDef")
  284. }
  285. // The emitted event has no workload ids
  286. es, err := events.AllEvents(time.Time{}, -1, time.Time{})
  287. if err != nil {
  288. t.Error(err)
  289. } else if len(es) != 1 {
  290. t.Errorf("Unexpected events: %#v", es)
  291. } else if es[0].Type != event.EventSync {
  292. t.Errorf("Unexpected event type: %#v", es[0])
  293. } else {
  294. gotResourceIDs := es[0].ServiceIDs
  295. resource.IDs(gotResourceIDs).Sort()
  296. // Event should only have changed workload ids
  297. if !reflect.DeepEqual(gotResourceIDs, []resource.ID{resource.MustParseID("default:deployment/helloworld")}) {
  298. t.Errorf("Unexpected event workload ids: %#v, expected: %#v", gotResourceIDs, []resource.ID{resource.MustParseID("default:deployment/helloworld")})
  299. }
  300. }
  301. // It moves the tag
  302. ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
  303. defer cancel()
  304. if err := d.Repo.Refresh(ctx); err != nil {
  305. t.Errorf("pulling sync tag: %v", err)
  306. } else if revs, err := d.Repo.CommitsBetween(ctx, oldRevision, syncTag); err != nil {
  307. t.Errorf("finding revisions before sync tag: %v", err)
  308. } else if len(revs) <= 0 {
  309. t.Errorf("Should have moved sync tag forward")
  310. } else if revs[len(revs)-1].Revision != newRevision {
  311. t.Errorf("Should have moved sync tag to HEAD (%s), but was moved to: %s", newRevision, revs[len(revs)-1].Revision)
  312. }
  313. }