From 664c8d1b5d074be11c54c76031b79a47e74b1ba0 Mon Sep 17 00:00:00 2001 From: wuqixuan Date: Mon, 6 Jul 2015 15:59:09 +0800 Subject: [PATCH 1/4] fleetctl: Support service uptime as part of a list-units output We can get the uptime from systemctl, the resolve is like below: localhost # /home/wood/fleet/fleetctl list-units UNIT MACHINE ACTIVE SUB UPTIME world.service 06ecd4f7.../192.168.122.30 active running 2015-07-06 07:38:38 AM, Since 11m39s world2.service 1d3430ef.../192.168.122.31 active running 2015-07-06 07:48:24 AM, Since 1m54s world_glob.service 06ecd4f7.../192.168.122.30 active running 2015-07-06 07:48:00 AM, Since 2m18s world_glob.service 1d3430ef.../192.168.122.31 active running 2015-07-06 07:47:59 AM, Since 2m18s --- agent/unit_state_test.go | 2 +- fleetctl/list_units.go | 14 +++++- functional/systemd_test.go | 4 +- registry/unit_state.go | 31 ++++++------ registry/unit_state_test.go | 95 ++++++++++++++++++++----------------- schema/mapper.go | 26 +++++----- schema/v1-gen.go | 2 + systemd/manager.go | 11 +++++ unit/fake.go | 2 +- unit/fake_test.go | 2 +- unit/generator_test.go | 2 +- unit/unit.go | 24 +++++----- unit/unit_test.go | 13 ++--- 13 files changed, 134 insertions(+), 94 deletions(-) diff --git a/agent/unit_state_test.go b/agent/unit_state_test.go index 710571404..12c47eeb3 100644 --- a/agent/unit_state_test.go +++ b/agent/unit_state_test.go @@ -819,7 +819,7 @@ func TestMarshalJSON(t *testing.T) { if err != nil { t.Fatalf("unexpected error marshalling: %v", err) } - want = `{"Cache":{"bar.service":{"LoadState":"","ActiveState":"inactive","SubState":"","MachineID":"asdf","UnitHash":"","UnitName":"bar.service"},"foo.service":{"LoadState":"","ActiveState":"active","SubState":"","MachineID":"asdf","UnitHash":"","UnitName":"foo.service"}},"ToPublish":{"woof.service":{"LoadState":"","ActiveState":"active","SubState":"","MachineID":"asdf","UnitHash":"","UnitName":"woof.service"}}}` + want = `{"Cache":{"bar.service":{"LoadState":"","ActiveState":"inactive","SubState":"","MachineID":"asdf","UnitHash":"","UnitName":"bar.service","ActiveEnterTimestamp":0},"foo.service":{"LoadState":"","ActiveState":"active","SubState":"","MachineID":"asdf","UnitHash":"","UnitName":"foo.service","ActiveEnterTimestamp":0}},"ToPublish":{"woof.service":{"LoadState":"","ActiveState":"active","SubState":"","MachineID":"asdf","UnitHash":"","UnitName":"woof.service","ActiveEnterTimestamp":0}}}` if string(got) != want { t.Fatalf("Bad JSON representation: got\n%s\n\nwant\n%s", string(got), want) } diff --git a/fleetctl/list_units.go b/fleetctl/list_units.go index 2b362f5a4..39988540b 100644 --- a/fleetctl/list_units.go +++ b/fleetctl/list_units.go @@ -18,6 +18,7 @@ import ( "fmt" "sort" "strings" + "time" "github.com/spf13/cobra" @@ -26,7 +27,8 @@ import ( ) const ( - defaultListUnitsFields = "unit,machine,active,sub" + defaultListUnitsFields = "unit,machine,active,sub,uptime" + tmFormatString = "2006-01-02 03:04:05 PM MST" ) var ( @@ -75,6 +77,16 @@ var ( } return us.Hash }, + "uptime": func(us *schema.UnitState, full bool) string { + if us == nil || us.SystemdActiveState != "active" { + return "-" + } + // SystemdActiveEnterTimestamp is in microseconds, while time.Unix + // requires the 2nd parameter as value in nanoseconds. + tm := time.Unix(0, int64(us.SystemdActiveEnterTimestamp)*1000) + duration := time.Now().Sub(tm) + return fmt.Sprintf("%s, Since %ss", tm.Format(tmFormatString), strings.Split(duration.String(), ".")[0]) + }, } ) diff --git a/functional/systemd_test.go b/functional/systemd_test.go index d1a37448e..9089cf707 100644 --- a/functional/systemd_test.go +++ b/functional/systemd_test.go @@ -73,7 +73,7 @@ ExecStart=/usr/bin/sleep 3000 t.Fatalf("Expected [hello.service], got %v", units) } - err = waitForUnitState(mgr, name, unit.UnitState{"loaded", "inactive", "dead", "", hash, ""}) + err = waitForUnitState(mgr, name, unit.UnitState{"loaded", "inactive", "dead", "", hash, "", 0}) if err != nil { t.Error(err) } @@ -83,7 +83,7 @@ ExecStart=/usr/bin/sleep 3000 t.Error(err) } - err = waitForUnitState(mgr, name, unit.UnitState{"loaded", "active", "running", "", hash, ""}) + err = waitForUnitState(mgr, name, unit.UnitState{"loaded", "active", "running", "", hash, "", 0}) if err != nil { t.Error(err) } diff --git a/registry/unit_state.go b/registry/unit_state.go index 40d2f40a7..89922050a 100644 --- a/registry/unit_state.go +++ b/registry/unit_state.go @@ -239,11 +239,12 @@ func (r *EtcdRegistry) RemoveUnitState(jobName string) error { } type unitStateModel struct { - LoadState string `json:"loadState"` - ActiveState string `json:"activeState"` - SubState string `json:"subState"` - MachineState *machine.MachineState `json:"machineState"` - UnitHash string `json:"unitHash"` + LoadState string `json:"loadState"` + ActiveState string `json:"activeState"` + SubState string `json:"subState"` + MachineState *machine.MachineState `json:"machineState"` + UnitHash string `json:"unitHash"` + ActiveEnterTimestamp uint64 `json:"ActiveEnterTimestamp"` } func modelToUnitState(usm *unitStateModel, name string) *unit.UnitState { @@ -252,11 +253,12 @@ func modelToUnitState(usm *unitStateModel, name string) *unit.UnitState { } us := unit.UnitState{ - LoadState: usm.LoadState, - ActiveState: usm.ActiveState, - SubState: usm.SubState, - UnitHash: usm.UnitHash, - UnitName: name, + LoadState: usm.LoadState, + ActiveState: usm.ActiveState, + SubState: usm.SubState, + UnitHash: usm.UnitHash, + UnitName: name, + ActiveEnterTimestamp: usm.ActiveEnterTimestamp, } if usm.MachineState != nil { @@ -277,10 +279,11 @@ func unitStateToModel(us *unit.UnitState) *unitStateModel { } usm := unitStateModel{ - LoadState: us.LoadState, - ActiveState: us.ActiveState, - SubState: us.SubState, - UnitHash: us.UnitHash, + LoadState: us.LoadState, + ActiveState: us.ActiveState, + SubState: us.SubState, + UnitHash: us.UnitHash, + ActiveEnterTimestamp: us.ActiveEnterTimestamp, } if us.MachineID != "" { diff --git a/registry/unit_state_test.go b/registry/unit_state_test.go index 46972624b..438ee1ffd 100644 --- a/registry/unit_state_test.go +++ b/registry/unit_state_test.go @@ -101,7 +101,7 @@ func TestSaveUnitState(t *testing.T) { r := &EtcdRegistry{kAPI: e, keyPrefix: "/fleet/"} j := "foo.service" mID := "mymachine" - us := unit.NewUnitState("abc", "def", "ghi", mID) + us := unit.NewUnitState("abc", "def", "ghi", mID, 1234567890) // Saving nil unit state should fail r.SaveUnitState(j, nil, time.Second) @@ -122,7 +122,7 @@ func TestSaveUnitState(t *testing.T) { us.UnitHash = "quickbrownfox" r.SaveUnitState(j, us, time.Second) - json := `{"loadState":"abc","activeState":"def","subState":"ghi","machineState":{"ID":"mymachine","PublicIP":"","Metadata":null,"Capabilities":null,"Version":""},"unitHash":"quickbrownfox"}` + json := `{"loadState":"abc","activeState":"def","subState":"ghi","machineState":{"ID":"mymachine","PublicIP":"","Metadata":null,"Capabilities":null,"Version":""},"unitHash":"quickbrownfox","ActiveEnterTimestamp":1234567890}` p1 := "/fleet/state/foo.service" p2 := "/fleet/states/foo.service/mymachine" want := []action{ @@ -196,48 +196,53 @@ func TestUnitStateToModel(t *testing.T) { { // Unit state with no hash and no machineID is not OK in: &unit.UnitState{ - LoadState: "foo", - ActiveState: "bar", - SubState: "baz", - MachineID: "", - UnitHash: "", - UnitName: "name", + LoadState: "foo", + ActiveState: "bar", + SubState: "baz", + MachineID: "", + UnitHash: "", + UnitName: "name", + ActiveEnterTimestamp: 0, }, want: nil, }, { // Unit state with hash but no machineID is OK in: &unit.UnitState{ - LoadState: "foo", - ActiveState: "bar", - SubState: "baz", - MachineID: "", - UnitHash: "heh", - UnitName: "name", + LoadState: "foo", + ActiveState: "bar", + SubState: "baz", + MachineID: "", + UnitHash: "heh", + UnitName: "name", + ActiveEnterTimestamp: 1234567890, }, want: &unitStateModel{ - LoadState: "foo", - ActiveState: "bar", - SubState: "baz", - MachineState: nil, - UnitHash: "heh", + LoadState: "foo", + ActiveState: "bar", + SubState: "baz", + MachineState: nil, + UnitHash: "heh", + ActiveEnterTimestamp: 1234567890, }, }, { in: &unit.UnitState{ - LoadState: "foo", - ActiveState: "bar", - SubState: "baz", - MachineID: "woof", - UnitHash: "miaow", - UnitName: "name", + LoadState: "foo", + ActiveState: "bar", + SubState: "baz", + MachineID: "woof", + UnitHash: "miaow", + UnitName: "name", + ActiveEnterTimestamp: 54321, }, want: &unitStateModel{ - LoadState: "foo", - ActiveState: "bar", - SubState: "baz", - MachineState: &machine.MachineState{ID: "woof"}, - UnitHash: "miaow", + LoadState: "foo", + ActiveState: "bar", + SubState: "baz", + MachineState: &machine.MachineState{ID: "woof"}, + UnitHash: "miaow", + ActiveEnterTimestamp: 54321, }, }, } { @@ -258,25 +263,27 @@ func TestModelToUnitState(t *testing.T) { want: nil, }, { - in: &unitStateModel{"foo", "bar", "baz", nil, ""}, + in: &unitStateModel{"foo", "bar", "baz", nil, "", 1234567890}, want: &unit.UnitState{ - LoadState: "foo", - ActiveState: "bar", - SubState: "baz", - MachineID: "", - UnitHash: "", - UnitName: "name", + LoadState: "foo", + ActiveState: "bar", + SubState: "baz", + MachineID: "", + UnitHash: "", + UnitName: "name", + ActiveEnterTimestamp: 1234567890, }, }, { - in: &unitStateModel{"z", "x", "y", &machine.MachineState{ID: "abcd"}, ""}, + in: &unitStateModel{"z", "x", "y", &machine.MachineState{ID: "abcd"}, "", 987654321}, want: &unit.UnitState{ - LoadState: "z", - ActiveState: "x", - SubState: "y", - MachineID: "abcd", - UnitHash: "", - UnitName: "name", + LoadState: "z", + ActiveState: "x", + SubState: "y", + MachineID: "abcd", + UnitHash: "", + UnitName: "name", + ActiveEnterTimestamp: 987654321, }, }, } { diff --git a/schema/mapper.go b/schema/mapper.go index 463895e47..aa8949bbe 100644 --- a/schema/mapper.go +++ b/schema/mapper.go @@ -129,12 +129,13 @@ func MapUnitStatesToSchemaUnitStates(entities []*unit.UnitState) []*UnitState { func MapUnitStateToSchemaUnitState(entity *unit.UnitState) *UnitState { us := UnitState{ - Name: entity.UnitName, - Hash: entity.UnitHash, - MachineID: entity.MachineID, - SystemdLoadState: entity.LoadState, - SystemdActiveState: entity.ActiveState, - SystemdSubState: entity.SubState, + Name: entity.UnitName, + Hash: entity.UnitHash, + MachineID: entity.MachineID, + SystemdLoadState: entity.LoadState, + SystemdActiveState: entity.ActiveState, + SystemdSubState: entity.SubState, + SystemdActiveEnterTimestamp: entity.ActiveEnterTimestamp, } return &us @@ -144,12 +145,13 @@ func MapSchemaUnitStatesToUnitStates(entities []*UnitState) []*unit.UnitState { us := make([]*unit.UnitState, len(entities)) for i, e := range entities { us[i] = &unit.UnitState{ - UnitName: e.Name, - UnitHash: e.Hash, - MachineID: e.MachineID, - LoadState: e.SystemdLoadState, - ActiveState: e.SystemdActiveState, - SubState: e.SystemdSubState, + UnitName: e.Name, + UnitHash: e.Hash, + MachineID: e.MachineID, + LoadState: e.SystemdLoadState, + ActiveState: e.SystemdActiveState, + SubState: e.SystemdSubState, + ActiveEnterTimestamp: e.SystemdActiveEnterTimestamp, } } diff --git a/schema/v1-gen.go b/schema/v1-gen.go index 9eb3fa8b1..1fa1a7114 100644 --- a/schema/v1-gen.go +++ b/schema/v1-gen.go @@ -294,6 +294,8 @@ type UnitState struct { Name string `json:"name,omitempty"` + SystemdActiveEnterTimestamp string `json:"systemdActiveEnterTimestamp,omitempty"` + SystemdActiveState string `json:"systemdActiveState,omitempty"` SystemdLoadState string `json:"systemdLoadState,omitempty"` diff --git a/systemd/manager.go b/systemd/manager.go index cab4ed55c..446bd82c4 100644 --- a/systemd/manager.go +++ b/systemd/manager.go @@ -260,6 +260,17 @@ func (m *systemdUnitManager) GetUnitStates(filter pkg.Set) (map[string]*unit.Uni } } + // add Active enter time to UnitState + for name, us := range states { + prop, err := m.systemd.GetUnitProperty(name, "ActiveEnterTimestamp") + if err != nil { + return nil, err + } + + us.ActiveEnterTimestamp = prop.Value.Value().(uint64) + states[name] = us + } + return states, nil } diff --git a/unit/fake.go b/unit/fake.go index 34bcd5b17..9a6fb0674 100644 --- a/unit/fake.go +++ b/unit/fake.go @@ -84,7 +84,7 @@ func (fum *FakeUnitManager) GetUnitStates(filter pkg.Set) (map[string]*UnitState states := make(map[string]*UnitState) for _, name := range filter.Values() { if _, ok := fum.u[name]; ok { - states[name] = &UnitState{"loaded", "active", "running", "", "", name} + states[name] = &UnitState{"loaded", "active", "running", "", "", name, 0} } } diff --git a/unit/fake_test.go b/unit/fake_test.go index a0590c66d..ae78a10c5 100644 --- a/unit/fake_test.go +++ b/unit/fake_test.go @@ -60,7 +60,7 @@ func TestFakeUnitManagerLoadUnload(t *testing.T) { t.Fatalf("Expected non-nil UnitState") } - eus := NewUnitState("loaded", "active", "running", "") + eus := NewUnitState("loaded", "active", "running", "", 0) if !reflect.DeepEqual(*us, *eus) { t.Fatalf("Expected UnitState %v, got %v", eus, *us) } diff --git a/unit/generator_test.go b/unit/generator_test.go index 7e4f135f0..d01c0f0ef 100644 --- a/unit/generator_test.go +++ b/unit/generator_test.go @@ -49,7 +49,7 @@ func TestUnitStateGeneratorSubscribeLifecycle(t *testing.T) { // subscribed to foo.service so we should get a heartbeat expect := []UnitStateHeartbeat{ - UnitStateHeartbeat{Name: "foo.service", State: &UnitState{"loaded", "active", "running", "", "", "foo.service"}}, + UnitStateHeartbeat{Name: "foo.service", State: &UnitState{"loaded", "active", "running", "", "", "foo.service", 0}}, } assertGenerateUnitStateHeartbeats(t, um, gen, expect) diff --git a/unit/unit.go b/unit/unit.go index fc2b233b8..efe7d87f6 100644 --- a/unit/unit.go +++ b/unit/unit.go @@ -195,20 +195,22 @@ func HashFromHexString(key string) (Hash, error) { // UnitState encodes the current state of a unit loaded into a fleet agent type UnitState struct { - LoadState string - ActiveState string - SubState string - MachineID string - UnitHash string - UnitName string + LoadState string + ActiveState string + SubState string + MachineID string + UnitHash string + UnitName string + ActiveEnterTimestamp uint64 } -func NewUnitState(loadState, activeState, subState, mID string) *UnitState { +func NewUnitState(loadState, activeState, subState, mID string, activeEnterTimestamp uint64) *UnitState { return &UnitState{ - LoadState: loadState, - ActiveState: activeState, - SubState: subState, - MachineID: mID, + LoadState: loadState, + ActiveState: activeState, + SubState: subState, + MachineID: mID, + ActiveEnterTimestamp: activeEnterTimestamp, } } diff --git a/unit/unit_test.go b/unit/unit_test.go index 8bbb19729..4514b6c18 100644 --- a/unit/unit_test.go +++ b/unit/unit_test.go @@ -110,15 +110,16 @@ func TestDefaultUnitType(t *testing.T) { func TestNewUnitState(t *testing.T) { want := &UnitState{ - LoadState: "ls", - ActiveState: "as", - SubState: "ss", - MachineID: "id", + LoadState: "ls", + ActiveState: "as", + SubState: "ss", + MachineID: "id", + ActiveEnterTimestamp: 1234567890, } - got := NewUnitState("ls", "as", "ss", "id") + got := NewUnitState("ls", "as", "ss", "id", 1234567890) if !reflect.DeepEqual(got, want) { - t.Fatalf("NewUnitState did not create a correct UnitState: got %s, want %s", got, want) + t.Fatalf("NewUnitState did not create a correct UnitState: got %v, want %v", got, want) } } From 46c9557a8bd7b2bd026a44b44bcadbd6cef54536 Mon Sep 17 00:00:00 2001 From: Dongsu Park Date: Tue, 23 Aug 2016 16:41:41 +0200 Subject: [PATCH 2/4] systemd: avoid iteration into unit list using UnitState To avoid an additional iteration of unit states for each unit, set UnitState.ActiveEnterTimestamp accordingly, by calling m.systemd.GetUnitProperty(). Note that we need to retrieve the property not only in the normal loop for the current systemd (>=230), but also in the fallback handling for the older version of systemd (<=229). --- systemd/manager.go | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/systemd/manager.go b/systemd/manager.go index 446bd82c4..b09d68a20 100644 --- a/systemd/manager.go +++ b/systemd/manager.go @@ -174,9 +174,10 @@ func (m *systemdUnitManager) getUnitState(name string) (*unit.UnitState, error) return nil, err } us := unit.UnitState{ - LoadState: info["LoadState"].(string), - ActiveState: info["ActiveState"].(string), - SubState: info["SubState"].(string), + LoadState: info["LoadState"].(string), + ActiveState: info["ActiveState"].(string), + SubState: info["SubState"].(string), + ActiveEnterTimestamp: info["ActiveEnterTimestamp"].(uint64), } return &us, nil } @@ -238,6 +239,9 @@ func (m *systemdUnitManager) GetUnitStates(filter pkg.Set) (map[string]*unit.Uni if h, ok := m.hashes[dus.Name]; ok { us.UnitHash = h.String() } + + us.ActiveEnterTimestamp = m.getActiveEnterTimestamp(dus.Name) + states[dus.Name] = us } @@ -256,19 +260,11 @@ func (m *systemdUnitManager) GetUnitStates(filter pkg.Set) (map[string]*unit.Uni if h, ok := m.hashes[name]; ok { us.UnitHash = h.String() } - states[name] = us - } - } - // add Active enter time to UnitState - for name, us := range states { - prop, err := m.systemd.GetUnitProperty(name, "ActiveEnterTimestamp") - if err != nil { - return nil, err - } + us.ActiveEnterTimestamp = m.getActiveEnterTimestamp(name) - us.ActiveEnterTimestamp = prop.Value.Value().(uint64) - states[name] = us + states[name] = us + } } return states, nil @@ -328,6 +324,14 @@ func (m *systemdUnitManager) getUnitFilePath(name string) string { return path.Join(m.unitsDir, name) } +func (m *systemdUnitManager) getActiveEnterTimestamp(name string) (aTimestamp uint64) { + prop, err := m.systemd.GetUnitProperty(name, "ActiveEnterTimestamp") + if err != nil { + return 0 + } + return prop.Value.Value().(uint64) +} + func lsUnitsDir(dir string) ([]string, error) { filterFunc := func(name string) bool { if !unit.RecognizedUnitType(name) { From b654e92fcddd19b9fa6df3a0c42e59bca97045ea Mon Sep 17 00:00:00 2001 From: Dongsu Park Date: Tue, 23 Aug 2016 16:41:44 +0200 Subject: [PATCH 3/4] schema,etc: add more fields for systemdActiveEnterTimestamp * fix typo in registry/unit_state*.go, from ActiveEnterTimestamp to activeEnterTimestamp. * Add a new field systemdActiveEnterTimestamp to both v1-json.go and v1.json. --- api/state_test.go | 12 ++++++------ fleetctl/list_units.go | 4 +++- registry/unit_state.go | 2 +- registry/unit_state_test.go | 2 +- schema/mapper.go | 7 +++++-- schema/v1-json.go | 3 +++ schema/v1.json | 3 +++ 7 files changed, 22 insertions(+), 11 deletions(-) diff --git a/api/state_test.go b/api/state_test.go index 4107c31b1..898177594 100644 --- a/api/state_test.go +++ b/api/state_test.go @@ -33,10 +33,10 @@ func TestUnitStateList(t *testing.T) { us2 := unit.UnitState{UnitName: "BBB", ActiveState: "inactive", MachineID: "XXX"} us3 := unit.UnitState{UnitName: "CCC", ActiveState: "active", MachineID: "XXX"} us4 := unit.UnitState{UnitName: "CCC", ActiveState: "inactive", MachineID: "YYY"} - sus1 := &schema.UnitState{Name: "AAA", SystemdActiveState: "active"} - sus2 := &schema.UnitState{Name: "BBB", SystemdActiveState: "inactive", MachineID: "XXX"} - sus3 := &schema.UnitState{Name: "CCC", SystemdActiveState: "active", MachineID: "XXX"} - sus4 := &schema.UnitState{Name: "CCC", SystemdActiveState: "inactive", MachineID: "YYY"} + sus1 := &schema.UnitState{Name: "AAA", SystemdActiveState: "active", SystemdActiveEnterTimestamp: "0"} + sus2 := &schema.UnitState{Name: "BBB", SystemdActiveState: "inactive", MachineID: "XXX", SystemdActiveEnterTimestamp: "0"} + sus3 := &schema.UnitState{Name: "CCC", SystemdActiveState: "active", MachineID: "XXX", SystemdActiveEnterTimestamp: "0"} + sus4 := &schema.UnitState{Name: "CCC", SystemdActiveState: "inactive", MachineID: "YYY", SystemdActiveEnterTimestamp: "0"} for i, tt := range []struct { url string @@ -163,12 +163,12 @@ func TestUnitStateList(t *testing.T) { return } - expect1 := &schema.UnitState{Name: "XXX", SystemdActiveState: "active"} + expect1 := &schema.UnitState{Name: "XXX", SystemdActiveState: "active", SystemdActiveEnterTimestamp: "0"} if !reflect.DeepEqual(expect1, page.States[0]) { t.Errorf("expected first entity %#v, got %#v", expect1, page.States[0]) } - expect2 := &schema.UnitState{Name: "YYY", SystemdActiveState: "inactive"} + expect2 := &schema.UnitState{Name: "YYY", SystemdActiveState: "inactive", SystemdActiveEnterTimestamp: "0"} if !reflect.DeepEqual(expect2, page.States[1]) { t.Errorf("expected first entity %#v, got %#v", expect2, page.States[1]) } diff --git a/fleetctl/list_units.go b/fleetctl/list_units.go index 39988540b..9b32b60b4 100644 --- a/fleetctl/list_units.go +++ b/fleetctl/list_units.go @@ -17,6 +17,7 @@ package main import ( "fmt" "sort" + "strconv" "strings" "time" @@ -83,7 +84,8 @@ var ( } // SystemdActiveEnterTimestamp is in microseconds, while time.Unix // requires the 2nd parameter as value in nanoseconds. - tm := time.Unix(0, int64(us.SystemdActiveEnterTimestamp)*1000) + ts, _ := strconv.Atoi(us.SystemdActiveEnterTimestamp) + tm := time.Unix(0, int64(ts)*1000) duration := time.Now().Sub(tm) return fmt.Sprintf("%s, Since %ss", tm.Format(tmFormatString), strings.Split(duration.String(), ".")[0]) }, diff --git a/registry/unit_state.go b/registry/unit_state.go index 89922050a..f891a014c 100644 --- a/registry/unit_state.go +++ b/registry/unit_state.go @@ -244,7 +244,7 @@ type unitStateModel struct { SubState string `json:"subState"` MachineState *machine.MachineState `json:"machineState"` UnitHash string `json:"unitHash"` - ActiveEnterTimestamp uint64 `json:"ActiveEnterTimestamp"` + ActiveEnterTimestamp uint64 `json:"activeEnterTimestamp"` } func modelToUnitState(usm *unitStateModel, name string) *unit.UnitState { diff --git a/registry/unit_state_test.go b/registry/unit_state_test.go index 438ee1ffd..bdaeb1fd7 100644 --- a/registry/unit_state_test.go +++ b/registry/unit_state_test.go @@ -122,7 +122,7 @@ func TestSaveUnitState(t *testing.T) { us.UnitHash = "quickbrownfox" r.SaveUnitState(j, us, time.Second) - json := `{"loadState":"abc","activeState":"def","subState":"ghi","machineState":{"ID":"mymachine","PublicIP":"","Metadata":null,"Capabilities":null,"Version":""},"unitHash":"quickbrownfox","ActiveEnterTimestamp":1234567890}` + json := `{"loadState":"abc","activeState":"def","subState":"ghi","machineState":{"ID":"mymachine","PublicIP":"","Metadata":null,"Capabilities":null,"Version":""},"unitHash":"quickbrownfox","activeEnterTimestamp":1234567890}` p1 := "/fleet/state/foo.service" p2 := "/fleet/states/foo.service/mymachine" want := []action{ diff --git a/schema/mapper.go b/schema/mapper.go index aa8949bbe..c2b6e2d55 100644 --- a/schema/mapper.go +++ b/schema/mapper.go @@ -15,6 +15,8 @@ package schema import ( + "strconv" + gsunit "github.com/coreos/go-systemd/unit" "github.com/coreos/fleet/job" @@ -135,7 +137,7 @@ func MapUnitStateToSchemaUnitState(entity *unit.UnitState) *UnitState { SystemdLoadState: entity.LoadState, SystemdActiveState: entity.ActiveState, SystemdSubState: entity.SubState, - SystemdActiveEnterTimestamp: entity.ActiveEnterTimestamp, + SystemdActiveEnterTimestamp: strconv.Itoa(int(entity.ActiveEnterTimestamp)), } return &us @@ -144,6 +146,7 @@ func MapUnitStateToSchemaUnitState(entity *unit.UnitState) *UnitState { func MapSchemaUnitStatesToUnitStates(entities []*UnitState) []*unit.UnitState { us := make([]*unit.UnitState, len(entities)) for i, e := range entities { + ts, _ := strconv.Atoi(e.SystemdActiveEnterTimestamp) us[i] = &unit.UnitState{ UnitName: e.Name, UnitHash: e.Hash, @@ -151,7 +154,7 @@ func MapSchemaUnitStatesToUnitStates(entities []*UnitState) []*unit.UnitState { LoadState: e.SystemdLoadState, ActiveState: e.SystemdActiveState, SubState: e.SystemdSubState, - ActiveEnterTimestamp: e.SystemdActiveEnterTimestamp, + ActiveEnterTimestamp: uint64(ts), } } diff --git a/schema/v1-json.go b/schema/v1-json.go index 7d3520c98..e3bdfed2e 100644 --- a/schema/v1-json.go +++ b/schema/v1-json.go @@ -162,6 +162,9 @@ const DiscoveryJSON = `{ }, "systemdSubState": { "type": "string" + }, + "systemdActiveEnterTimestamp": { + "type": "string" } } }, diff --git a/schema/v1.json b/schema/v1.json index 189f15cfc..b1a007d20 100644 --- a/schema/v1.json +++ b/schema/v1.json @@ -141,6 +141,9 @@ }, "systemdSubState": { "type": "string" + }, + "systemdActiveEnterTimestamp": { + "type": "string" } } }, From d3c8fe89112fbaa9338ee52fff23eb47612769ce Mon Sep 17 00:00:00 2001 From: Dongsu Park Date: Tue, 23 Aug 2016 16:41:46 +0200 Subject: [PATCH 4/4] functional: exclude ActiveEnterTimestamp from unit comparison condition Actually it's not necessary to check that both units have the same value for ActiveEnterTimestamp, as it can vary at any time. So let's exclude ActiveEnterTimestamp from the condition for unit comparison. --- functional/systemd_test.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/functional/systemd_test.go b/functional/systemd_test.go index 9089cf707..cc884c1bd 100644 --- a/functional/systemd_test.go +++ b/functional/systemd_test.go @@ -119,8 +119,19 @@ func waitForUnitState(mgr unit.UnitManager, name string, want unit.UnitState) er return err } - if reflect.DeepEqual(want, *got) { + if isEqualUnitState(want, *got) { return nil } } } + +// isEqualUnitState checks if both units are the same, +// excluding ActiveEnterTimestamp field of each unit state. +func isEqualUnitState(src, dst unit.UnitState) bool { + return src.LoadState == dst.LoadState && + src.ActiveState == dst.ActiveState && + src.SubState == dst.SubState && + src.MachineID == dst.MachineID && + src.UnitHash == dst.UnitHash && + src.UnitName == dst.UnitName +}