func TestMachinesList(t *testing.T) { fr := registry.NewFakeRegistry() fr.SetMachines([]machine.MachineState{ {ID: "XXX", PublicIP: "", Metadata: nil}, {ID: "YYY", PublicIP: "1.2.3.4", Metadata: map[string]string{"ping": "pong"}}, }) resource := &machinesResource{fr} rw := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://example.com", nil) if err != nil { t.Fatalf("Failed creating http.Request: %v", err) } resource.ServeHTTP(rw, req) if rw.Code != http.StatusOK { t.Errorf("Expected 200, got %d", rw.Code) } ct := rw.HeaderMap["Content-Type"] if len(ct) != 1 { t.Errorf("Response has wrong number of Content-Type values: %v", ct) } else if ct[0] != "application/json" { t.Errorf("Expected application/json, got %s", ct) } if rw.Body == nil { t.Error("Received nil response body") } else { body := rw.Body.String() expected := `{"machines":[{"id":"XXX"},{"id":"YYY","metadata":{"ping":"pong"},"primaryIP":"1.2.3.4"}]}` if body != expected { t.Errorf("Expected body:\n%s\n\nReceived body:\n%s\n", expected, body) } } }
func newFakeRegistryForSsh() registry.Registry { machines := []machine.MachineState{ {"c31e44e1-f858-436e-933e-59c642517860", "1.2.3.4", map[string]string{"ping": "pong"}, "", resource.ResourceTuple{}}, {"595989bb-cbb7-49ce-8726-722d6e157b4e", "5.6.7.8", map[string]string{"foo": "bar"}, "", resource.ResourceTuple{}}, {"hello.service", "8.7.6.5", map[string]string{"foo": "bar"}, "", resource.ResourceTuple{}}, } jobs := []job.Job{ *job.NewJob("j1.service", unit.Unit{}), *job.NewJob("j2.service", unit.Unit{}), *job.NewJob("hello.service", unit.Unit{}), } states := map[string]*unit.UnitState{ "j1.service": unit.NewUnitState("loaded", "active", "listening", &machines[0]), "j2.service": unit.NewUnitState("loaded", "inactive", "dead", &machines[1]), "hello.service": unit.NewUnitState("loaded", "inactive", "dead", &machines[2]), } reg := registry.NewFakeRegistry() reg.SetMachines(machines) reg.SetUnitStates(states) reg.SetJobs(jobs) return reg }
func setupRegistryForStart(echoAttempts int) { m1 := machine.MachineState{ ID: "c31e44e1-f858-436e-933e-59c642517860", PublicIP: "1.2.3.4", Metadata: map[string]string{"ping": "pong"}, } m2 := machine.MachineState{ ID: "595989bb-cbb7-49ce-8726-722d6e157b4e", PublicIP: "5.6.7.8", Metadata: map[string]string{"foo": "bar"}, } m3 := machine.MachineState{ ID: "520983A8-FB9C-4A68-B49C-CED5BB2E9D08", Metadata: map[string]string{"foo": "bar"}, } js := unit.NewUnitState("loaded", "active", "listening", m1.ID) js2 := unit.NewUnitState("loaded", "inactive", "dead", m2.ID) js3 := unit.NewUnitState("loaded", "inactive", "dead", m2.ID) js4 := unit.NewUnitState("loaded", "inactive", "dead", m3.ID) states := map[string]*unit.UnitState{"pong.service": js, "hello.service": js2, "echo.service": js3, "private.service": js4} machines := []machine.MachineState{m1, m2, m3} reg := registry.NewFakeRegistry() reg.SetMachines(machines) reg.SetUnitStates(states) cAPI = &BlockedFakeRegistry{echoAttempts, *reg} }
func TestDefaultHandlers(t *testing.T) { tests := []struct { method string path string code int }{ {"GET", "/", http.StatusMethodNotAllowed}, {"GET", "/v1-alpha", http.StatusMethodNotAllowed}, {"GET", "/bogus", http.StatusNotFound}, } for i, tt := range tests { fr := registry.NewFakeRegistry() hdlr := NewServeMux(fr) rr := httptest.NewRecorder() req, err := http.NewRequest(tt.method, tt.path, nil) if err != nil { t.Errorf("case %d: failed setting up http.Request for test: %v", i, err) continue } hdlr.ServeHTTP(rr, req) err = assertErrorResponse(rr, tt.code) if err != nil { t.Errorf("case %d: %v", i, err) } } }
func newFakeRegistryForSsh() registry.Registry { // clear machineStates for every invocation machineStates = nil machines := []machine.MachineState{ newMachineState("c31e44e1-f858-436e-933e-59c642517860", "1.2.3.4", map[string]string{"ping": "pong"}), newMachineState("595989bb-cbb7-49ce-8726-722d6e157b4e", "5.6.7.8", map[string]string{"foo": "bar"}), newMachineState("hello.service", "8.7.6.5", map[string]string{"foo": "bar"}), } jobs := []job.Job{ *job.NewJob("j1.service", unit.Unit{}), *job.NewJob("j2.service", unit.Unit{}), *job.NewJob("hello.service", unit.Unit{}), } states := map[string]*unit.UnitState{ "j1.service": unit.NewUnitState("loaded", "active", "listening", machines[0].ID), "j2.service": unit.NewUnitState("loaded", "inactive", "dead", machines[1].ID), "hello.service": unit.NewUnitState("loaded", "inactive", "dead", machines[2].ID), } reg := registry.NewFakeRegistry() reg.SetMachines(machines) reg.SetUnitStates(states) reg.SetJobs(jobs) return reg }
func TestAgentLoadUnloadJob(t *testing.T) { uManager := unit.NewFakeUnitManager() usGenerator := unit.NewUnitStateGenerator(uManager) fReg := registry.NewFakeRegistry() mach := &machine.FakeMachine{machine.MachineState{ID: "XXX"}} a, err := New(uManager, usGenerator, fReg, mach, DefaultTTL) if err != nil { t.Fatalf("Failed creating Agent: %v", err) } j := newTestJobFromUnitContents(t, "foo.service", "") err = a.loadJob(j) if err != nil { t.Fatalf("Failed calling Agent.loadJob: %v", err) } jobs, err := a.jobs() if err != nil { t.Fatalf("Failed calling Agent.jobs: %v", err) } jsLoaded := job.JobStateLoaded expectJobs := map[string]*job.Job{ "foo.service": &job.Job{ Name: "foo.service", UnitState: &unit.UnitState{ LoadState: "loaded", ActiveState: "active", SubState: "running", MachineID: "", }, State: &jsLoaded, Unit: unit.Unit{}, TargetState: job.JobState(""), TargetMachineID: "", }, } if !reflect.DeepEqual(expectJobs, jobs) { t.Fatalf("Received unexpected collection of Jobs: %#v\nExpected: %#v", jobs, expectJobs) } a.unloadJob("foo.service") // This sucks, but we have to do it if Agent.unloadJob is going to spin // off the real work that matters in a goroutine time.Sleep(200) jobs, err = a.jobs() if err != nil { t.Fatalf("Failed calling Agent.jobs: %v", err) } expectJobs = map[string]*job.Job{} if !reflect.DeepEqual(expectJobs, jobs) { t.Fatalf("Received unexpected collection of Jobs: %#v\nExpected: %#v", jobs, expectJobs) } }
func TestAgentLoadStartStopUnit(t *testing.T) { uManager := unit.NewFakeUnitManager() usGenerator := unit.NewUnitStateGenerator(uManager) fReg := registry.NewFakeRegistry() mach := &machine.FakeMachine{MachineState: machine.MachineState{ID: "XXX"}} a := New(uManager, usGenerator, fReg, mach, time.Second) u := newTestUnitFromUnitContents(t, "foo.service", "") err := a.loadUnit(u) if err != nil { t.Fatalf("Failed calling Agent.loadUnit: %v", err) } err = a.startUnit("foo.service") if err != nil { t.Fatalf("Failed starting unit foo.service: %v", err) } units, err := a.units() if err != nil { t.Fatalf("Failed calling Agent.units: %v", err) } jsLaunched := job.JobStateLaunched expectUnits := unitStates{ "foo.service": unitState{ state: jsLaunched, }, } if !reflect.DeepEqual(expectUnits, units) { t.Fatalf("Received unexpected collection of Units: %#v\nExpected: %#v", units, expectUnits) } err = a.stopUnit("foo.service") if err != nil { t.Fatalf("Failed stopping unit foo.service: %v", err) } units, err = a.units() if err != nil { t.Fatalf("Failed calling Agent.units: %v", err) } jsLoaded := job.JobStateLoaded expectUnits = unitStates{ "foo.service": unitState{ state: jsLoaded, }, } if !reflect.DeepEqual(expectUnits, units) { t.Fatalf("Received unexpected collection of Units: %#v\nExpected: %#v", units, expectUnits) } }
func newFakeRegistryForCheckVersion(v string) registry.Registry { sv, err := semver.NewVersion(v) if err != nil { panic(err) } reg := registry.NewFakeRegistry() reg.SetLatestVersion(*sv) return reg }
func newFakeRegistryForCommands(unitPrefix string, unitCount int, template bool) client.API { // clear machineStates for every invocation machineStates = nil machines := []machine.MachineState{ newMachineState("c31e44e1-f858-436e-933e-59c642517860", "1.2.3.4", map[string]string{"ping": "pong"}), newMachineState("595989bb-cbb7-49ce-8726-722d6e157b4e", "5.6.7.8", map[string]string{"foo": "bar"}), } jobs := make([]job.Job, 0) appendJobsForTests(&jobs, machines[0], unitPrefix, unitCount, template) appendJobsForTests(&jobs, machines[1], unitPrefix, unitCount, template) states := make([]unit.UnitState, 0) if template { state := unit.UnitState{ UnitName: fmt.Sprintf("%[email protected]", unitPrefix), LoadState: "loaded", ActiveState: "inactive", SubState: "dead", MachineID: machines[0].ID, } states = append(states, state) state.MachineID = machines[1].ID states = append(states, state) } else { for i := 1; i <= unitCount; i++ { state := unit.UnitState{ UnitName: fmt.Sprintf("%s%d.service", unitPrefix, i), LoadState: "loaded", ActiveState: "active", SubState: "listening", MachineID: machines[0].ID, } states = append(states, state) } for i := 1; i <= unitCount; i++ { state := unit.UnitState{ UnitName: fmt.Sprintf("%s%d.service", unitPrefix, i), LoadState: "loaded", ActiveState: "inactive", SubState: "dead", MachineID: machines[1].ID, } states = append(states, state) } } reg := registry.NewFakeRegistry() reg.SetMachines(machines) reg.SetUnitStates(states) reg.SetJobs(jobs) return &client.RegistryClient{Registry: reg} }
func newTestRegistryForListMachines() registry.Registry { m := []machine.MachineState{ machine.MachineState{ID: "mnopqr"}, machine.MachineState{ID: "abcdef"}, machine.MachineState{ID: "ghijkl"}, } reg := registry.NewFakeRegistry() reg.SetMachines(m) return reg }
func fakeMachinesSetup() (*machinesResource, *httptest.ResponseRecorder) { fr := registry.NewFakeRegistry() fr.SetMachines([]machine.MachineState{ {ID: "XXX", PublicIP: "", Metadata: map[string]string{}}, {ID: "YYY", PublicIP: "1.2.3.4", Metadata: map[string]string{"ping": "pong"}}, }) fAPI := &client.RegistryClient{Registry: fr} resource := &machinesResource{cAPI: fAPI, tokenLimit: testTokenLimit} rw := httptest.NewRecorder() return resource, rw }
func newFakeRegistryForListUnits(jobs []job.Job) registry.Registry { j := []job.Job{*job.NewJob("pong.service", *unit.NewUnit("Echo"))} if jobs != nil { for _, job := range jobs { j = append(j, job) } } reg := registry.NewFakeRegistry() reg.SetJobs(j) return reg }
func setupRegistryForStart(echoAttempts int) { m1 := machine.MachineState{ ID: "c31e44e1-f858-436e-933e-59c642517860", PublicIP: "1.2.3.4", Metadata: map[string]string{"ping": "pong"}, } m2 := machine.MachineState{ ID: "595989bb-cbb7-49ce-8726-722d6e157b4e", PublicIP: "5.6.7.8", Metadata: map[string]string{"foo": "bar"}, } m3 := machine.MachineState{ ID: "520983A8-FB9C-4A68-B49C-CED5BB2E9D08", Metadata: map[string]string{"foo": "bar"}, } states := []unit.UnitState{ unit.UnitState{ UnitName: "pong.service", LoadState: "loaded", ActiveState: "active", SubState: "listening", MachineID: m1.ID, }, unit.UnitState{ UnitName: "hello.service", LoadState: "loaded", ActiveState: "inactive", SubState: "dead", MachineID: m2.ID, }, unit.UnitState{ UnitName: "echo.service", LoadState: "loaded", ActiveState: "inactive", SubState: "dead", MachineID: m2.ID, }, } machines := []machine.MachineState{m1, m2, m3} reg := registry.NewFakeRegistry() reg.SetMachines(machines) reg.SetUnitStates(states) cAPI = &client.RegistryClient{Registry: &BlockedFakeRegistry{EchoAttempts: echoAttempts, FakeRegistry: *reg}} }
func TestUnitsListBadNextPageToken(t *testing.T) { fr := registry.NewFakeRegistry() resource := &unitsResource{fr, "/units"} rw := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://example.com/units?nextPageToken=EwBMLg==", nil) if err != nil { t.Fatalf("Failed creating http.Request: %v", err) } resource.list(rw, req) err = assertErrorResponse(rw, http.StatusBadRequest) if err != nil { t.Error(err.Error()) } }
func newFakeRegistryForSsh() client.API { // clear machineStates for every invocation machineStates = nil machines := []machine.MachineState{ newMachineState("c31e44e1-f858-436e-933e-59c642517860", "1.2.3.4", map[string]string{"ping": "pong"}), newMachineState("595989bb-cbb7-49ce-8726-722d6e157b4e", "5.6.7.8", map[string]string{"foo": "bar"}), newMachineState("hello.service", "8.7.6.5", map[string]string{"foo": "bar"}), } jobs := []job.Job{ job.Job{Name: "j1.service", Unit: unit.UnitFile{}, TargetMachineID: machines[0].ID}, job.Job{Name: "j2.service", Unit: unit.UnitFile{}, TargetMachineID: machines[1].ID}, job.Job{Name: "hello.service", Unit: unit.UnitFile{}, TargetMachineID: machines[2].ID}, } states := []unit.UnitState{ unit.UnitState{ UnitName: "j1.service", LoadState: "loaded", ActiveState: "active", SubState: "listening", MachineID: machines[0].ID, }, unit.UnitState{ UnitName: "j2.service", LoadState: "loaded", ActiveState: "inactive", SubState: "dead", MachineID: machines[1].ID, }, unit.UnitState{ UnitName: "hello.service", LoadState: "loaded", ActiveState: "inactive", SubState: "dead", MachineID: machines[2].ID, }, } reg := registry.NewFakeRegistry() reg.SetMachines(machines) reg.SetUnitStates(states) reg.SetJobs(jobs) return &client.RegistryClient{Registry: reg} }
func TestUnitsSubResourceNotFound(t *testing.T) { fr := registry.NewFakeRegistry() ur := &unitsResource{fr, "/units"} rr := httptest.NewRecorder() req, err := http.NewRequest("GET", "/units/foo/bar", nil) if err != nil { t.Fatalf("Failed setting up http.Request for test: %v", err) } ur.ServeHTTP(rr, req) err = assertErrorResponse(rr, http.StatusNotFound) if err != nil { t.Error(err) } }
func TestAgentLoadUnloadJob(t *testing.T) { uManager := unit.NewFakeUnitManager() usGenerator := unit.NewUnitStateGenerator(uManager) fReg := registry.NewFakeRegistry() mach := &machine.FakeMachine{machine.MachineState{ID: "XXX"}} a := New(uManager, usGenerator, fReg, mach, time.Second) j := newTestJobFromUnitContents(t, "foo.service", "") err := a.loadJob(j) if err != nil { t.Fatalf("Failed calling Agent.loadJob: %v", err) } jobs, err := a.jobs() if err != nil { t.Fatalf("Failed calling Agent.jobs: %v", err) } jsLoaded := job.JobStateLoaded expectJobs := map[string]*job.Job{ "foo.service": &job.Job{ Name: "foo.service", State: &jsLoaded, Unit: unit.UnitFile{}, TargetState: job.JobState(""), TargetMachineID: "", }, } if !reflect.DeepEqual(expectJobs, jobs) { t.Fatalf("Received unexpected collection of Jobs: %#v\nExpected: %#v", jobs, expectJobs) } a.unloadJob("foo.service") jobs, err = a.jobs() if err != nil { t.Fatalf("Failed calling Agent.jobs: %v", err) } expectJobs = map[string]*job.Job{} if !reflect.DeepEqual(expectJobs, jobs) { t.Fatalf("Received unexpected collection of Jobs: %#v\nExpected: %#v", jobs, expectJobs) } }
func TestUnitStateListBadNextPageToken(t *testing.T) { fr := registry.NewFakeRegistry() fAPI := &client.RegistryClient{Registry: fr} resource := &stateResource{fAPI, "/state", testTokenLimit} rw := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://example.com/state?nextPageToken=EwBMLg==", nil) if err != nil { t.Fatalf("Failed creating http.Request: %v", err) } resource.list(rw, req) err = assertErrorResponse(rw, http.StatusBadRequest) if err != nil { t.Error(err.Error()) } }
func TestMachinesListBadNextPageToken(t *testing.T) { fr := registry.NewFakeRegistry() fAPI := &client.RegistryClient{Registry: fr} resource := &machinesResource{fAPI} rw := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://example.com/machines?nextPageToken=EwBMLg==", nil) if err != nil { t.Fatalf("Failed creating http.Request: %v", err) } resource.ServeHTTP(rw, req) err = assertErrorResponse(rw, http.StatusBadRequest) if err != nil { t.Error(err.Error()) } }
func TestPurge(t *testing.T) { cache := map[string]*unit.UnitState{ "foo.service": &unit.UnitState{ UnitName: "foo.service", ActiveState: "loaded", }, "bar.service": nil, } initStates := []unit.UnitState{ unit.UnitState{ UnitName: "foo.service", ActiveState: "active", }, unit.UnitState{ UnitName: "bar.service", ActiveState: "loaded", }, unit.UnitState{ UnitName: "baz.service", ActiveState: "inactive", }, } want := []*unit.UnitState{ &unit.UnitState{ UnitName: "baz.service", ActiveState: "inactive", }, } freg := registry.NewFakeRegistry() freg.SetUnitStates(initStates) usp := NewUnitStatePublisher(freg, &machine.FakeMachine{}, 0) usp.cache = cache usp.Purge() us, err := freg.UnitStates() if err != nil { t.Errorf("unexpected error retrieving unit states: %v", err) } else if !reflect.DeepEqual(us, want) { msg := fmt.Sprintln("received unexpected unit states") msg += fmt.Sprintf("got: %#v\n", us) msg += fmt.Sprintf("want: %#v\n", want) t.Error(msg) } }
func TestUnitsSetDesiredStateBadContentType(t *testing.T) { fr := registry.NewFakeRegistry() fAPI := &client.RegistryClient{Registry: fr} resource := &unitsResource{fAPI, "/units"} rr := httptest.NewRecorder() body := ioutil.NopCloser(bytes.NewBuffer([]byte(`{"foo":"bar"}`))) req, err := http.NewRequest("PUT", "http://example.com/units/foo.service", body) if err != nil { t.Fatalf("Failed creating http.Request: %v", err) } req.Header.Set("Content-Type", "application/xml") resource.set(rr, req, "foo.service") err = assertErrorResponse(rr, http.StatusUnsupportedMediaType) if err != nil { t.Fatal(err) } }
func TestUnitsList(t *testing.T) { fr := registry.NewFakeRegistry() fr.SetJobs([]job.Job{ {Name: "XXX.service"}, {Name: "YYY.service"}, }) fAPI := &client.RegistryClient{fr} resource := &unitsResource{fAPI, "/units"} rw := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://example.com/units", nil) if err != nil { t.Fatalf("Failed creating http.Request: %v", err) } resource.list(rw, req) if rw.Code != http.StatusOK { t.Errorf("Expected 200, got %d", rw.Code) } ct := rw.HeaderMap["Content-Type"] if len(ct) != 1 { t.Errorf("Response has wrong number of Content-Type values: %v", ct) } else if ct[0] != "application/json" { t.Errorf("Expected application/json, got %s", ct) } if rw.Body == nil { t.Error("Received nil response body") } else { var page schema.UnitPage err := json.Unmarshal(rw.Body.Bytes(), &page) if err != nil { t.Fatalf("Received unparseable body: %v", err) } if len(page.Units) != 2 || page.Units[0].Name != "XXX.service" || page.Units[1].Name != "YYY.service" { t.Errorf("Received incorrect UnitPage entity: %v", page) } } }
func TestUnitGet(t *testing.T) { tests := []struct { item string code int }{ {item: "XXX.service", code: http.StatusOK}, {item: "ZZZ", code: http.StatusNotFound}, } fr := registry.NewFakeRegistry() fr.SetJobs([]job.Job{ {Name: "XXX.service"}, {Name: "YYY.service"}, }) fAPI := &client.RegistryClient{fr} resource := &unitsResource{fAPI, "/units"} for i, tt := range tests { rw := httptest.NewRecorder() req, err := http.NewRequest("GET", fmt.Sprintf("http://example.com/units/%s", tt.item), nil) if err != nil { t.Errorf("case %d: failed creating http.Request: %v", i, err) continue } resource.get(rw, req, tt.item) if tt.code/100 == 2 { if tt.code != rw.Code { t.Errorf("case %d: expected %d, got %d", i, tt.code, rw.Code) } } else { err = assertErrorResponse(rw, tt.code) if err != nil { t.Errorf("case %d: %v", i, err) } } } }
func TestDefaultHandlers(t *testing.T) { tests := []struct { method string path string code int }{ {"GET", "/", http.StatusMethodNotAllowed}, {"GET", "/v1-alpha", http.StatusMethodNotAllowed}, {"GET", "/fleet/v1", http.StatusMethodNotAllowed}, {"GET", "/bogus", http.StatusNotFound}, } for i, tt := range tests { fr := registry.NewFakeRegistry() hdlr := NewServeMux(fr, testTokenLimit) rr := httptest.NewRecorder() req, err := http.NewRequest(tt.method, tt.path, nil) if err != nil { t.Errorf("case %d: failed setting up http.Request for test: %v", i, err) continue } hdlr.ServeHTTP(rr, req) err = assertErrorResponse(rr, tt.code) if err != nil { t.Errorf("case %d: %v", i, err) } wantServer := fmt.Sprintf("fleetd/%s", version.Version) gotServer := rr.HeaderMap["Server"][0] if wantServer != gotServer { t.Errorf("case %d: received incorrect Server header: want=%s, got=%s", i, wantServer, gotServer) } } }
func TestUnitsSetDesiredState(t *testing.T) { tests := []struct { // initial state of Registry initJobs []job.Job initStates map[string]job.JobState // item path (name) of the Unit item string // Unit to attempt to set arg schema.Unit // expected HTTP status code code int // expected state of registry after request finalStates map[string]job.JobState }{ // Modify the desired State of an existing Job { initJobs: []job.Job{job.Job{Name: "XXX.service", Unit: newUnit(t, "[Service]\nFoo=Bar")}}, initStates: map[string]job.JobState{"XXX.service": "inactive"}, item: "XXX.service", arg: schema.Unit{Name: "XXX.service", DesiredState: "launched"}, code: http.StatusNoContent, finalStates: map[string]job.JobState{"XXX.service": "launched"}, }, // Create a new Unit { initJobs: []job.Job{}, initStates: map[string]job.JobState{}, item: "YYY.service", arg: schema.Unit{ Name: "YYY.service", DesiredState: "loaded", Options: []*schema.UnitOption{ &schema.UnitOption{Section: "Service", Name: "Foo", Value: "Baz"}, }, }, code: http.StatusNoContent, finalStates: map[string]job.JobState{"YYY.service": "loaded"}, }, // Creating a new Unit without Options fails { initJobs: []job.Job{}, initStates: map[string]job.JobState{}, item: "YYY.service", arg: schema.Unit{ Name: "YYY.service", DesiredState: "loaded", Options: []*schema.UnitOption{}, }, code: http.StatusConflict, finalStates: map[string]job.JobState{}, }, // Referencing a Unit where the name is inconsistent with the path should fail { initJobs: []job.Job{ job.Job{Name: "XXX.service", Unit: newUnit(t, "[Service]\nFoo=Bar")}, job.Job{Name: "YYY.service", Unit: newUnit(t, "[Service]\nFoo=Baz")}, }, initStates: map[string]job.JobState{ "XXX.service": "inactive", "YYY.service": "inactive", }, item: "XXX.service", arg: schema.Unit{ Name: "YYY.service", DesiredState: "loaded", }, code: http.StatusBadRequest, finalStates: map[string]job.JobState{ "XXX.service": "inactive", "YYY.service": "inactive", }, }, // Referencing a Unit where the name is omitted should substitute the name from the path { initJobs: []job.Job{ job.Job{Name: "XXX.service", Unit: newUnit(t, "[Service]\nFoo=Bar")}, job.Job{Name: "YYY.service", Unit: newUnit(t, "[Service]\nFoo=Baz")}, }, initStates: map[string]job.JobState{ "XXX.service": "inactive", "YYY.service": "inactive", }, item: "XXX.service", arg: schema.Unit{ DesiredState: "loaded", }, code: http.StatusNoContent, finalStates: map[string]job.JobState{ "XXX.service": "loaded", "YYY.service": "inactive", }, }, } for i, tt := range tests { fr := registry.NewFakeRegistry() fr.SetJobs(tt.initJobs) for j, s := range tt.initStates { err := fr.SetUnitTargetState(j, s) if err != nil { t.Errorf("case %d: failed initializing unit's target state: %v", i, err) } } req, err := http.NewRequest("PUT", fmt.Sprintf("http://example.com/units/%s", tt.item), nil) if err != nil { t.Errorf("case %d: failed creating http.Request: %v", i, err) continue } enc, err := json.Marshal(tt.arg) if err != nil { t.Errorf("case %d: unable to JSON-encode request: %v", i, err) continue } req.Body = ioutil.NopCloser(bytes.NewBuffer(enc)) req.Header.Set("Content-Type", "application/json") fAPI := &client.RegistryClient{fr} resource := &unitsResource{fAPI, "/units"} rw := httptest.NewRecorder() resource.set(rw, req, tt.item) if tt.code/100 == 2 { if tt.code != rw.Code { t.Errorf("case %d: expected %d, got %d", i, tt.code, rw.Code) } } else { err = assertErrorResponse(rw, tt.code) if err != nil { t.Errorf("case %d: %v", i, err) } } for name, expect := range tt.finalStates { u, err := fr.Unit(name) if err != nil { t.Errorf("case %d: failed fetching Job: %v", i, err) } else if u == nil { t.Errorf("case %d: fetched nil Unit(%s), expected non-nil", i, name) continue } if u.TargetState != expect { t.Errorf("case %d: expect Unit(%s) target state %q, got %q", i, name, expect, u.TargetState) } } } }
func TestUnitsDestroy(t *testing.T) { tests := []struct { // initial state of registry init []job.Job // name of unit to delete arg string // expected HTTP status code code int // expected state of registry after deletion attempt remaining []string }{ // Deletion of an existing unit should succeed { init: []job.Job{job.Job{Name: "XXX.service", Unit: newUnit(t, "[Service]\nFoo=Bar")}}, arg: "XXX.service", code: http.StatusNoContent, remaining: []string{}, }, // Deletion of a nonexistent unit should fail { init: []job.Job{job.Job{Name: "XXX.service", Unit: newUnit(t, "[Service]\nFoo=Bar")}}, arg: "YYY.service", code: http.StatusNotFound, remaining: []string{"XXX.service"}, }, } for i, tt := range tests { fr := registry.NewFakeRegistry() fr.SetJobs(tt.init) req, err := http.NewRequest("DELETE", fmt.Sprintf("http://example.com/units/%s", tt.arg), nil) if err != nil { t.Errorf("case %d: failed creating http.Request: %v", i, err) continue } fAPI := &client.RegistryClient{fr} resource := &unitsResource{fAPI, "/units"} rw := httptest.NewRecorder() resource.destroy(rw, req, tt.arg) if tt.code/100 == 2 { if tt.code != rw.Code { t.Errorf("case %d: expected %d, got %d", i, tt.code, rw.Code) } } else { err = assertErrorResponse(rw, tt.code) if err != nil { t.Errorf("case %d: %v", i, err) } } units, err := fr.Units() if err != nil { t.Errorf("case %d: failed fetching Units after destruction: %v", i, err) continue } remaining := make([]string, len(units)) for i, u := range units { remaining[i] = u.Name } if !reflect.DeepEqual(tt.remaining, remaining) { t.Errorf("case %d: expected Units %v, got %v", i, tt.remaining, remaining) } } }
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{Registry: fr} resource := &stateResource{fAPI, "/state", testTokenLimit} 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"}, unit.UnitState{UnitName: "YYY", ActiveState: "inactive"}, }) fAPI := &client.RegistryClient{Registry: fr} resource := &stateResource{fAPI, "/state", testTokenLimit} rw := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://example.com/state", nil) if err != nil { t.Fatalf("Failed creating http.Request: %v", err) } resource.list(rw, req) if rw.Code != http.StatusOK { t.Errorf("Expected 200, got %d", rw.Code) } ct := rw.HeaderMap["Content-Type"] if len(ct) != 1 { t.Errorf("Response has wrong number of Content-Type values: %v", ct) } else if ct[0] != "application/json" { t.Errorf("Expected application/json, got %s", ct) } if rw.Body == nil { t.Error("Received nil response body") } else { var page schema.UnitStatePage err := json.Unmarshal(rw.Body.Bytes(), &page) if err != nil { t.Fatalf("Received unparseable body: %v", err) } if len(page.States) != 2 { t.Errorf("Expected 2 UnitState objects, got %d", len(page.States)) return } expect1 := &schema.UnitState{Name: "XXX", SystemdActiveState: "active"} 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"} if !reflect.DeepEqual(expect2, page.States[1]) { t.Errorf("expected first entity %#v, got %#v", expect2, page.States[1]) } } }
func TestExtractUnitPage(t *testing.T) { fr := registry.NewFakeRegistry() all := make([]job.Job, 103) for i := 0; i < 103; i++ { name := strconv.FormatInt(int64(i), 10) all[i] = job.Job{Name: name} } tests := []struct { token PageToken idxStart int idxEnd int next *PageToken }{ {PageToken{Page: 1, Limit: 60}, 0, 59, &PageToken{Page: 2, Limit: 60}}, {PageToken{Page: 2, Limit: 60}, 60, 102, nil}, } for i, tt := range tests { page, err := extractUnitPage(fr, all, tt.token) if err != nil { t.Errorf("case %d: call to extractUnitPage failed: %v", i, err) continue } expectCount := (tt.idxEnd - tt.idxStart + 1) if len(page.Units) != expectCount { t.Errorf("case %d: expected page of %d, got %d", i, expectCount, len(page.Units)) continue } first := page.Units[0].Name if first != strconv.FormatInt(int64(tt.idxStart), 10) { t.Errorf("case %d: irst element in first page should have ID %d, got %d", i, tt.idxStart, first) } last := page.Units[len(page.Units)-1].Name if last != strconv.FormatInt(int64(tt.idxEnd), 10) { t.Errorf("case %d: first element in first page should have ID %d, got %d", i, tt.idxEnd, last) } if tt.next == nil && page.NextPageToken != "" { t.Errorf("case %d: did not expect NextPageToken", i) continue } else if page.NextPageToken == "" { if tt.next != nil { t.Errorf("case %d: did not receive expected NextPageToken", i) } continue } next, err := decodePageToken(page.NextPageToken) if err != nil { t.Errorf("case %d: unable to parse NextPageToken: %v", i, err) continue } if !reflect.DeepEqual(next, tt.next) { t.Errorf("case %d: expected PageToken %v, got %v", i, tt.next, next) } } }
func TestUnitsDestroy(t *testing.T) { tests := []struct { // initial state of registry init []job.Job // which Job to attempt to delete arg schema.DeletableUnit // expected HTTP status code code int // expected state of registry after deletion attempt remaining []string }{ // Unsafe deletion of an existing unit should succeed { init: []job.Job{job.Job{Name: "XXX", Unit: unit.Unit{Raw: "FOO"}}}, arg: schema.DeletableUnit{Name: "XXX"}, code: http.StatusNoContent, remaining: []string{}, }, // Safe deletion of an existing unit should succeed { init: []job.Job{job.Job{Name: "XXX", Unit: unit.Unit{Raw: "FOO"}}}, arg: schema.DeletableUnit{Name: "XXX", FileContents: "Rk9P"}, code: http.StatusNoContent, remaining: []string{}, }, // Unsafe deletion of a nonexistent unit should fail { init: []job.Job{job.Job{Name: "XXX", Unit: unit.Unit{Raw: "FOO"}}}, arg: schema.DeletableUnit{Name: "YYY"}, code: http.StatusNotFound, remaining: []string{"XXX"}, }, // Safe deletion of a nonexistent unit should fail { init: []job.Job{}, arg: schema.DeletableUnit{Name: "XXX", FileContents: "Rk9P"}, code: http.StatusNotFound, remaining: []string{}, }, // Safe deletion of a unit with the wrong contents should fail { init: []job.Job{job.Job{Name: "XXX", Unit: unit.Unit{Raw: "FOO"}}}, arg: schema.DeletableUnit{Name: "XXX", FileContents: "QkFS"}, code: http.StatusConflict, remaining: []string{"XXX"}, }, // Safe deletion of a unit with the malformed contents should fail { init: []job.Job{job.Job{Name: "XXX", Unit: unit.Unit{Raw: "FOO"}}}, arg: schema.DeletableUnit{Name: "XXX", FileContents: "*"}, code: http.StatusBadRequest, remaining: []string{"XXX"}, }, } for i, tt := range tests { fr := registry.NewFakeRegistry() fr.SetJobs(tt.init) req, err := http.NewRequest("DELETE", fmt.Sprintf("http://example.com/units/%s", tt.arg.Name), nil) if err != nil { t.Errorf("case %d: failed creating http.Request: %v", i, err) continue } enc, err := json.Marshal(tt.arg) if err != nil { t.Errorf("case %d: unable to JSON-encode request: %v", i, err) continue } req.Body = ioutil.NopCloser(bytes.NewBuffer(enc)) req.Header.Set("Content-Type", "application/json") resource := &unitsResource{fr, "/units"} rw := httptest.NewRecorder() resource.destroy(rw, req, tt.arg.Name) if tt.code/100 == 2 { if tt.code != rw.Code { t.Errorf("case %d: expected %d, got %d", i, tt.code, rw.Code) } } else { err = assertErrorResponse(rw, tt.code) if err != nil { t.Errorf("case %d: %v", i, err) } } jobs, err := fr.Jobs() if err != nil { t.Errorf("case %d: failed fetching Jobs after destruction: %v", i, err) continue } remaining := make([]string, len(jobs)) for i, j := range jobs { remaining[i] = j.Name } if !reflect.DeepEqual(tt.remaining, remaining) { t.Errorf("case %d: expected Jobs %v, got %v", i, tt.remaining, remaining) } } }
func TestUnitsSetDesiredState(t *testing.T) { tests := []struct { // initial state of Registry initJobs []job.Job initStates map[string]job.JobState // which Job to attempt to delete arg schema.DesiredUnitState // expected HTTP status code code int // expected state of registry after request finalStates map[string]job.JobState }{ // Modify the DesiredState of an existing Job { initJobs: []job.Job{job.Job{Name: "XXX", Unit: unit.Unit{Raw: "FOO"}}}, initStates: map[string]job.JobState{"XXX": "inactive"}, arg: schema.DesiredUnitState{Name: "XXX", DesiredState: "launched"}, code: http.StatusNoContent, finalStates: map[string]job.JobState{"XXX": "launched"}, }, // Create a new Job { initJobs: []job.Job{}, initStates: map[string]job.JobState{}, arg: schema.DesiredUnitState{Name: "YYY", DesiredState: "loaded", FileContents: "cGVubnkNCg=="}, code: http.StatusNoContent, finalStates: map[string]job.JobState{"YYY": "loaded"}, }, { initJobs: []job.Job{}, initStates: map[string]job.JobState{}, arg: schema.DesiredUnitState{Name: "YYY", DesiredState: "loaded", FileContents: "*"}, code: http.StatusBadRequest, finalStates: map[string]job.JobState{}, }, // Modifying a Job with garbage fileContents should fail { initJobs: []job.Job{job.Job{Name: "XXX", Unit: unit.Unit{Raw: "FOO"}}}, initStates: map[string]job.JobState{"XXX": job.JobStateInactive}, arg: schema.DesiredUnitState{Name: "YYY", DesiredState: "loaded", FileContents: "*"}, code: http.StatusBadRequest, finalStates: map[string]job.JobState{"XXX": job.JobStateInactive}, }, // Modifying a nonexistent Job should fail { initJobs: []job.Job{}, initStates: map[string]job.JobState{}, arg: schema.DesiredUnitState{Name: "YYY", DesiredState: "loaded"}, code: http.StatusConflict, finalStates: map[string]job.JobState{}, }, // Modifying a Job with the incorrect fileContents should fail { initJobs: []job.Job{job.Job{Name: "XXX", Unit: unit.Unit{Raw: "FOO"}}}, initStates: map[string]job.JobState{"XXX": "inactive"}, arg: schema.DesiredUnitState{Name: "XXX", DesiredState: "loaded", FileContents: "ZWxyb3kNCg=="}, code: http.StatusConflict, finalStates: map[string]job.JobState{"XXX": "inactive"}, }, } for i, tt := range tests { fr := registry.NewFakeRegistry() fr.SetJobs(tt.initJobs) for j, s := range tt.initStates { err := fr.SetJobTargetState(j, s) if err != nil { t.Errorf("case %d: failed initializing Job target state: %v", i, err) } } req, err := http.NewRequest("PUT", fmt.Sprintf("http://example.com/units/%s", tt.arg.Name), nil) if err != nil { t.Errorf("case %d: failed creating http.Request: %v", i, err) continue } enc, err := json.Marshal(tt.arg) if err != nil { t.Errorf("case %d: unable to JSON-encode request: %v", i, err) continue } req.Body = ioutil.NopCloser(bytes.NewBuffer(enc)) req.Header.Set("Content-Type", "application/json") resource := &unitsResource{fr, "/units"} rw := httptest.NewRecorder() resource.set(rw, req, tt.arg.Name) if tt.code/100 == 2 { if tt.code != rw.Code { t.Errorf("case %d: expected %d, got %d", i, tt.code, rw.Code) } } else { err = assertErrorResponse(rw, tt.code) if err != nil { t.Errorf("case %d: %v", i, err) } } for name, expect := range tt.finalStates { j, err := fr.Job(name) if err != nil { t.Errorf("case %d: failed fetching Job: %v", i, err) } else if j == nil { t.Errorf("case %d: fetched nil Job(%s), expected non-nil", i, name) } if j.TargetState != expect { t.Errorf("case %d: expect Job(%s) target state %q, got %q", i, name, expect, j.TargetState) } } } }