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

api: implement query parameters #814

Merged
merged 1 commit into from
Aug 27, 2014
Merged
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
26 changes: 23 additions & 3 deletions api/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,17 @@ func (sr *stateResource) list(rw http.ResponseWriter, req *http.Request) {
token = &def
}

page, err := getUnitStatePage(sr.cAPI, *token)
var machineID, unitName string
for _, val := range req.URL.Query()["machineID"] {
machineID = val
break
}
for _, val := range req.URL.Query()["unitName"] {
unitName = val
break
}

page, err := getUnitStatePage(sr.cAPI, machineID, unitName, *token)
if err != nil {
log.Errorf("Failed fetching page of UnitStates: %v", err)
sendError(rw, http.StatusInternalServerError, nil)
Expand All @@ -52,13 +62,23 @@ func (sr *stateResource) list(rw http.ResponseWriter, req *http.Request) {
sendResponse(rw, http.StatusOK, &page)
}

func getUnitStatePage(cAPI client.API, tok PageToken) (*schema.UnitStatePage, error) {
func getUnitStatePage(cAPI client.API, machineID, unitName string, tok PageToken) (*schema.UnitStatePage, error) {
states, err := cAPI.UnitStates()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out cAPI.UnitStates(name) doesn't exist as I thought it already did... Will come back and implement it later as an optimization

if err != nil {
return nil, err
}
var filtered []*schema.UnitState
for _, us := range states {
if machineID != "" && machineID != us.MachineID {
continue
}
if unitName != "" && unitName != us.Name {
continue
}
filtered = append(filtered, us)
}

items, next := extractUnitStatePageData(states, tok)
items, next := extractUnitStatePageData(filtered, tok)
page := schema.UnitStatePage{
States: items,
}
Expand Down
95 changes: 95 additions & 0 deletions api/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,101 @@ import (
)

func TestUnitStateList(t *testing.T) {
us1 := unit.UnitState{UnitName: "AAA", ActiveState: "active"}
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"}

for i, tt := range []struct {
url string
expected []*schema.UnitState
}{
{
// Standard query - return all results
"http://example.com/state",
[]*schema.UnitState{sus1, sus2, sus3, sus4},
},
{
// Query for specific unit name should be fine
"http://example.com/state?unitName=AAA",
[]*schema.UnitState{sus1},
},
{
// Query for a different specific unit name should be fine
"http://example.com/state?unitName=CCC",
[]*schema.UnitState{sus3, sus4},
},
{
// Query for nonexistent unit name should return nothing
"http://example.com/state?unitName=nope",
nil,
},
{
// Query for a specific machine ID should be fine
"http://example.com/state?machineID=XXX",
[]*schema.UnitState{sus2, sus3},
},
{
// Query for nonexistent machine ID should return nothing
"http://example.com/state?machineID=nope",
nil,
},
{
// Query for specific unit name and specific machine ID should filter by both
"http://example.com/state?unitName=CCC&machineID=XXX",
[]*schema.UnitState{sus3},
},
} {
fr := registry.NewFakeRegistry()
fr.SetUnitStates([]unit.UnitState{us1, us2, us3, us4})
fAPI := &client.RegistryClient{fr}
resource := &stateResource{fAPI, "/state"}
rw := httptest.NewRecorder()
req, err := http.NewRequest("GET", tt.url, nil)
if err != nil {
t.Fatalf("case %d: Failed creating http.Request: %v", i, err)
}

resource.list(rw, req)
if rw.Code != http.StatusOK {
t.Errorf("case %d: Expected 200, got %d", i, rw.Code)
}
ct := rw.HeaderMap["Content-Type"]
if len(ct) != 1 {
t.Errorf("case %d: Response has wrong number of Content-Type values: %v", i, ct)
} else if ct[0] != "application/json" {
t.Errorf("case %d: Expected application/json, got %s", i, ct)
}

if rw.Body == nil {
t.Errorf("case %d: Received nil response body", i)
continue
}

var page schema.UnitStatePage
if err := json.Unmarshal(rw.Body.Bytes(), &page); err != nil {
t.Fatalf("case %d: Received unparseable body: %v", i, err)
}

got := page.States
if !reflect.DeepEqual(got, tt.expected) {
t.Errorf("case %d: Unexpected UnitStates received.", i)
t.Logf("Got UnitStates:")
for _, us := range got {
t.Logf("%#v", us)
}
t.Logf("Expected UnitStates:")
for _, us := range tt.expected {
t.Logf("%#v", us)
}

}
}

fr := registry.NewFakeRegistry()
fr.SetUnitStates([]unit.UnitState{
unit.UnitState{UnitName: "XXX", ActiveState: "active"},
Expand Down
38 changes: 25 additions & 13 deletions registry/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
func NewFakeRegistry() *FakeRegistry {
return &FakeRegistry{
machines: []machine.MachineState{},
jobStates: map[string]*unit.UnitState{},
jobStates: map[string]map[string]*unit.UnitState{},
jobs: map[string]job.Job{},
units: []unit.UnitFile{},
version: nil,
Expand All @@ -31,7 +31,7 @@ type FakeRegistry struct {
sync.RWMutex

machines []machine.MachineState
jobStates map[string]*unit.UnitState
jobStates map[string]map[string]*unit.UnitState
jobs map[string]job.Job
units []unit.UnitFile
version *semver.Version
Expand All @@ -58,10 +58,14 @@ func (f *FakeRegistry) SetUnitStates(states []unit.UnitState) {
f.Lock()
defer f.Unlock()

f.jobStates = make(map[string]*unit.UnitState, len(states))
f.jobStates = make(map[string]map[string]*unit.UnitState, len(states))
for _, us := range states {
us := us
f.jobStates[us.UnitName] = &us
name := us.UnitName
if _, ok := f.jobStates[name]; !ok {
f.jobStates[name] = make(map[string]*unit.UnitState)
}
f.jobStates[name][us.MachineID] = &us
}
}

Expand Down Expand Up @@ -234,7 +238,7 @@ func (f *FakeRegistry) SaveUnitState(jobName string, unitState *unit.UnitState,
f.Lock()
defer f.Unlock()

f.jobStates[jobName] = unitState
f.jobStates[jobName][unitState.MachineID] = unitState
}

func (f *FakeRegistry) RemoveUnitState(jobName string) error {
Expand All @@ -246,16 +250,24 @@ func (f *FakeRegistry) UnitStates() ([]*unit.UnitState, error) {
f.Lock()
defer f.Unlock()

sortable := make([]string, 0)
for k := range f.jobStates {
sortable = append(sortable, k)
}
var states []*unit.UnitState

sort.Strings(sortable)
// Sort by unit name, then by machineID
sUnitNames := make([]string, 0)
for name := range f.jobStates {
sUnitNames = append(sUnitNames, name)
}
sort.Strings(sUnitNames)

states := make([]*unit.UnitState, len(f.jobStates))
for i, k := range sortable {
states[i] = f.jobStates[k]
for _, name := range sUnitNames {
sMIDs := make([]string, 0)
for machineID := range f.jobStates[name] {
sMIDs = append(sMIDs, machineID)
}
sort.Strings(sMIDs)
for _, mID := range sMIDs {
states = append(states, f.jobStates[name][mID])
}
}

return states, nil
Expand Down