Skip to content
This repository has been archived by the owner on Jan 30, 2020. It is now read-only.

fleetctl: service uptime for list-units #1669

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agent/unit_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
12 changes: 6 additions & 6 deletions api/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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])
}
Expand Down
16 changes: 15 additions & 1 deletion fleetctl/list_units.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ package main
import (
"fmt"
"sort"
"strconv"
"strings"
"time"

"github.com/spf13/cobra"

Expand All @@ -26,7 +28,8 @@ import (
)

const (
defaultListUnitsFields = "unit,machine,active,sub"
defaultListUnitsFields = "unit,machine,active,sub,uptime"
tmFormatString = "2006-01-02 03:04:05 PM MST"
)

var (
Expand Down Expand Up @@ -75,6 +78,17 @@ 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.
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])
},
}
)

Expand Down
17 changes: 14 additions & 3 deletions functional/systemd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
}
31 changes: 17 additions & 14 deletions registry/unit_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 != "" {
Expand Down
95 changes: 51 additions & 44 deletions registry/unit_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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{
Expand Down Expand Up @@ -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,
},
},
} {
Expand All @@ -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,
},
},
} {
Expand Down
29 changes: 17 additions & 12 deletions schema/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package schema

import (
"strconv"

gsunit "github.com/coreos/go-systemd/unit"

"github.com/coreos/fleet/job"
Expand Down Expand Up @@ -129,12 +131,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: strconv.Itoa(int(entity.ActiveEnterTimestamp)),
}

return &us
Expand All @@ -143,13 +146,15 @@ 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,
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: uint64(ts),
}
}

Expand Down
2 changes: 2 additions & 0 deletions schema/v1-gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
3 changes: 3 additions & 0 deletions schema/v1-json.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ const DiscoveryJSON = `{
},
"systemdSubState": {
"type": "string"
},
"systemdActiveEnterTimestamp": {
"type": "string"
}
}
},
Expand Down
3 changes: 3 additions & 0 deletions schema/v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@
},
"systemdSubState": {
"type": "string"
},
"systemdActiveEnterTimestamp": {
"type": "string"
}
}
},
Expand Down
Loading