From 10c370c0fb6fd7ba14b2c7237af648842980709d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 3 Mar 2018 08:43:19 -0800 Subject: [PATCH] agent/consul: set CreatedAt, UpdatedAt on intentions --- agent/consul/intention_endpoint.go | 11 +++++ agent/consul/intention_endpoint_test.go | 61 +++++++++++++++++++++++++ agent/consul/state/intention.go | 4 +- agent/consul/state/intention_test.go | 33 +++++++++++++ 4 files changed, 108 insertions(+), 1 deletion(-) diff --git a/agent/consul/intention_endpoint.go b/agent/consul/intention_endpoint.go index d130027227..70ad251830 100644 --- a/agent/consul/intention_endpoint.go +++ b/agent/consul/intention_endpoint.go @@ -33,6 +33,11 @@ func (s *Intention) Apply( defer metrics.MeasureSince([]string{"consul", "intention", "apply"}, time.Now()) defer metrics.MeasureSince([]string{"intention", "apply"}, time.Now()) + // Always set a non-nil intention to avoid nil-access below + if args.Intention == nil { + args.Intention = &structs.Intention{} + } + // If no ID is provided, generate a new ID. This must be done prior to // appending to the Raft log, because the ID is not deterministic. Once // the entry is in the log, the state update MUST be deterministic or @@ -60,6 +65,9 @@ func (s *Intention) Apply( break } } + + // Set the created at + args.Intention.CreatedAt = time.Now() } *reply = args.Intention.ID @@ -75,6 +83,9 @@ func (s *Intention) Apply( } } + // We always update the updatedat field. This has no effect for deletion. + args.Intention.UpdatedAt = time.Now() + // Commit resp, err := s.srv.raftApply(structs.IntentionRequestType, args) if err != nil { diff --git a/agent/consul/intention_endpoint_test.go b/agent/consul/intention_endpoint_test.go index 65170ff7ba..8ff584d754 100644 --- a/agent/consul/intention_endpoint_test.go +++ b/agent/consul/intention_endpoint_test.go @@ -5,6 +5,7 @@ import ( "reflect" "strings" "testing" + "time" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/testrpc" @@ -32,6 +33,9 @@ func TestIntentionApply_new(t *testing.T) { } var reply string + // Record now to check created at time + now := time.Now() + // Create if err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply); err != nil { t.Fatalf("err: %v", err) @@ -58,7 +62,26 @@ func TestIntentionApply_new(t *testing.T) { if resp.Index != actual.ModifyIndex { t.Fatalf("bad index: %d", resp.Index) } + + // Test CreatedAt + { + timeDiff := actual.CreatedAt.Sub(now) + if timeDiff < 0 || timeDiff > 5*time.Second { + t.Fatalf("should set created at: %s", actual.CreatedAt) + } + } + + // Test UpdatedAt + { + timeDiff := actual.UpdatedAt.Sub(now) + if timeDiff < 0 || timeDiff > 5*time.Second { + t.Fatalf("should set updated at: %s", actual.CreatedAt) + } + } + actual.CreateIndex, actual.ModifyIndex = 0, 0 + actual.CreatedAt = ixn.Intention.CreatedAt + actual.UpdatedAt = ixn.Intention.UpdatedAt if !reflect.DeepEqual(actual, ixn.Intention) { t.Fatalf("bad: %v", actual) } @@ -123,6 +146,28 @@ func TestIntentionApply_updateGood(t *testing.T) { t.Fatal("reply should be non-empty") } + // Read CreatedAt + var createdAt time.Time + ixn.Intention.ID = reply + { + req := &structs.IntentionQueryRequest{ + Datacenter: "dc1", + IntentionID: ixn.Intention.ID, + } + var resp structs.IndexedIntentions + if err := msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp); err != nil { + t.Fatalf("err: %v", err) + } + if len(resp.Intentions) != 1 { + t.Fatalf("bad: %v", resp) + } + actual := resp.Intentions[0] + createdAt = actual.CreatedAt + } + + // Sleep a bit so that the updated at will definitely be different, not much + time.Sleep(1 * time.Millisecond) + // Update ixn.Op = structs.IntentionOpUpdate ixn.Intention.ID = reply @@ -146,7 +191,23 @@ func TestIntentionApply_updateGood(t *testing.T) { t.Fatalf("bad: %v", resp) } actual := resp.Intentions[0] + + // Test CreatedAt + if !actual.CreatedAt.Equal(createdAt) { + t.Fatalf("should not modify created at: %s", actual.CreatedAt) + } + + // Test UpdatedAt + { + timeDiff := actual.UpdatedAt.Sub(createdAt) + if timeDiff <= 0 || timeDiff > 5*time.Second { + t.Fatalf("should set updated at: %s", actual.CreatedAt) + } + } + actual.CreateIndex, actual.ModifyIndex = 0, 0 + actual.CreatedAt = ixn.Intention.CreatedAt + actual.UpdatedAt = ixn.Intention.UpdatedAt if !reflect.DeepEqual(actual, ixn.Intention) { t.Fatalf("bad: %v", actual) } diff --git a/agent/consul/state/intention.go b/agent/consul/state/intention.go index 51f4e1e3b4..ba3871ea56 100644 --- a/agent/consul/state/intention.go +++ b/agent/consul/state/intention.go @@ -117,7 +117,9 @@ func (s *Store) intentionSetTxn(tx *memdb.Txn, idx uint64, ixn *structs.Intentio return fmt.Errorf("failed intention looup: %s", err) } if existing != nil { - ixn.CreateIndex = existing.(*structs.Intention).CreateIndex + oldIxn := existing.(*structs.Intention) + ixn.CreateIndex = oldIxn.CreateIndex + ixn.CreatedAt = oldIxn.CreatedAt } else { ixn.CreateIndex = idx } diff --git a/agent/consul/state/intention_test.go b/agent/consul/state/intention_test.go index 2f4fee26b8..1bfb6d248e 100644 --- a/agent/consul/state/intention_test.go +++ b/agent/consul/state/intention_test.go @@ -3,6 +3,7 @@ package state import ( "reflect" "testing" + "time" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/go-memdb" @@ -121,6 +122,38 @@ func TestStore_IntentionSet_emptyId(t *testing.T) { } } +func TestStore_IntentionSet_updateCreatedAt(t *testing.T) { + s := testStateStore(t) + + // Build a valid intention + now := time.Now() + ixn := structs.Intention{ + ID: testUUID(), + CreatedAt: now, + } + + // Insert + if err := s.IntentionSet(1, &ixn); err != nil { + t.Fatalf("err: %s", err) + } + + // Change a value and test updating + ixnUpdate := ixn + ixnUpdate.CreatedAt = now.Add(10 * time.Second) + if err := s.IntentionSet(2, &ixnUpdate); err != nil { + t.Fatalf("err: %s", err) + } + + // Read it back and verify + _, actual, err := s.IntentionGet(nil, ixn.ID) + if err != nil { + t.Fatalf("err: %s", err) + } + if !actual.CreatedAt.Equal(now) { + t.Fatalf("bad: %#v", actual) + } +} + func TestStore_IntentionDelete(t *testing.T) { s := testStateStore(t)