// Assert that jobs and their peers are properly indexed func TestGetJobsByPeer(t *testing.T) { state := NewState() u1 := unit.NewUnit(`[X-Fleet] X-ConditionMachineOf=b X-ConditionMachineOf=c `) j1 := job.NewJob("a", *u1) state.TrackJob(j1) u2 := unit.NewUnit(`[X-Fleet] X-ConditionMachineOf=c `) j2 := job.NewJob("d", *u2) state.TrackJob(j2) peers := state.GetJobsByPeer("b") if len(peers) != 1 || peers[0] != "a" { t.Fatalf("Unexpected index of job peers %v", peers) } peers = state.GetJobsByPeer("c") if len(peers) != 2 || peers[0] != "a" || peers[1] != "d" { t.Fatalf("Unexpected index of job peers %v", peers) } }
func TestSignJob(t *testing.T) { c, _ := initSign(t) u := unit.NewUnit("Echo") j := job.NewJob("echo.service", *u) data, err := marshal(u) if err != nil { t.Fatal("marshal error:", err) } expectedSig, err := c.keyring.Sign(testPublicKeys["rsa"], data) if err != nil { t.Fatal("sign error:", err) } s, err := c.SignJob(j) if err != nil { t.Fatal("sign payload error:", err) } if s.Tag != TagForJob("echo.service") { t.Fatal("sign tag error:", err) } if len(s.Signatures) != 1 { t.Fatal("expect 1 signature instead of", len(s.Signatures)) } if bytes.Compare(s.Signatures[0].Blob, expectedSig.Blob) != 0 { t.Fatal("wrong signature") } }
func mapUnitToJob(entity *schema.Unit, mm map[string]*machine.MachineState) (*job.Job, error) { contents, err := base64.StdEncoding.DecodeString(entity.FileContents) if err != nil { return nil, err } u, err := unit.NewUnit(string(contents)) if err != nil { return nil, err } js := job.JobState(entity.CurrentState) j := job.Job{ Name: entity.Name, State: &js, Unit: *u, } // populate a UnitState object only if the entity // is actually reporting relevant data if entity.Systemd != nil { j.UnitState = &unit.UnitState{ LoadState: entity.Systemd.LoadState, ActiveState: entity.Systemd.ActiveState, SubState: entity.Systemd.SubState, } if len(entity.Systemd.MachineID) > 0 { j.UnitState.MachineID = entity.Systemd.MachineID } } return &j, nil }
// getUnitByHash retrieves from the Registry the Unit associated with the given Hash func (r *EtcdRegistry) getUnitByHash(hash unit.Hash) *unit.Unit { req := etcd.Get{ Key: r.hashedUnitPath(hash), Recursive: true, } resp, err := r.etcd.Do(&req) if err != nil { if isKeyNotFound(err) { err = nil } return nil } var um unitModel if err := unmarshal(resp.Node.Value, &um); err != nil { log.Errorf("error unmarshaling Unit(%s): %v", hash, err) return nil } u, err := unit.NewUnit(um.Raw) if err != nil { log.Errorf("error parsing Unit(%s): %v", hash, err) return nil } return u }
func TestMarshaling(t *testing.T) { units := []string{ ``, `[Service] ExecStart=/bin/sleep 1`, `[Unit] Description=Foo [Service] ExecStart=echo "foo"`, `[Path] PathExists=/foo`, } for _, contents := range units { u := unit.NewUnit(contents) json, err := marshal(u) if err != nil { t.Error("Error marshaling unit:", err) } var um unit.Unit err = unmarshal(json, &um) if err != nil { t.Error("Error unmarshaling unit:", err) } if !reflect.DeepEqual(*u, um) { t.Errorf("Unmarshaled unit does not match original!\nOriginal:\n%s\nUnmarshaled:\n%s", *u, um) } } }
func newUnit(t *testing.T, str string) *unit.Unit { u, err := unit.NewUnit(str) if err != nil { t.Fatalf("Unexpected error creating unit from %q: %v", str, err) } return u }
func decodeUnitContents(c string) (*unit.Unit, error) { dec, err := base64.StdEncoding.DecodeString(c) if err != nil { return nil, err } return unit.NewUnit(string(dec)) }
func TestFakeRegistryJobLifecycle(t *testing.T) { reg := NewFakeRegistry() jobs, err := reg.Jobs() if err != nil { t.Fatalf("Received error while calling Jobs: %v", err) } if !reflect.DeepEqual([]job.Job{}, jobs) { t.Fatalf("Expected no jobs, got %v", jobs) } u, _ := unit.NewUnit("") j1 := job.NewJob("job1.service", *u) err = reg.CreateJob(j1) if err != nil { t.Fatalf("Received error while calling CreateJob: %v", err) } jobs, err = reg.Jobs() if err != nil { t.Fatalf("Received error while calling Jobs: %v", err) } if len(jobs) != 1 { t.Fatalf("Expected 1 Job, got %v", jobs) } if jobs[0].Name != "job1.service" { t.Fatalf("Expected Job with name \"job1.service\", got %q", jobs[0].Name) } err = reg.ScheduleJob("job1.service", "XXX") if err != nil { t.Fatalf("Received error while calling ScheduleJob: %v", err) } j, err := reg.Job("job1.service") if err != nil { t.Fatalf("Received error while calling JobTarget: %v", err) } if j.TargetMachineID != "XXX" { t.Fatalf("Job should be scheduled to XXX, got %v", j.TargetMachineID) } err = reg.DestroyJob("job1.service") if err != nil { t.Fatalf("Received error while calling DestroyJob: %v", err) } jobs, err = reg.Jobs() if err != nil { t.Fatalf("Received error while calling Jobs: %v", err) } if !reflect.DeepEqual([]job.Job{}, jobs) { t.Fatalf("Expected no jobs, got %v", jobs) } }
func newNamedTestJobFromUnitContents(t *testing.T, name, contents string) *job.Job { u, err := unit.NewUnit(contents) if err != nil { t.Fatalf("error creating Unit from %q: %v", contents, err) } j := job.NewJob(name, *u) if j == nil { t.Fatalf("error creating Job %q from %q", name, u) } return j }
// getUnitFromFile attempts to load a Job from a given filename // It returns the Job or nil, and any error encountered func getUnitFromFile(file string) (*unit.Unit, error) { out, err := ioutil.ReadFile(file) if err != nil { return nil, err } unitName := path.Base(file) log.V(1).Infof("Unit(%s) found in local filesystem", unitName) return unit.NewUnit(string(out)), nil }
func TestAbleToRunConditionMachineIDMatch(t *testing.T) { u := unit.NewUnit(`[X-Fleet] X-ConditionMachineID=XYZ `) job := job.NewJob("example.service", *u) mach := &machine.FakeMachine{machine.MachineState{ID: "XYZ"}} agent := Agent{Machine: mach, state: NewState()} if !agent.ableToRun(job) { t.Fatalf("Agent should be able to run job") } }
func TestAbleToRunConditionMachineIDMismatch(t *testing.T) { u := unit.NewUnit(`[X-Fleet] X-ConditionMachineID=XYZ `) job := job.NewJob("example.service", *u) mach := machine.New(machine.MachineState{ID: "123"}) agent := Agent{machine: mach, state: NewState()} if agent.AbleToRun(job) { t.Fatalf("Agent should not be able to run job") } }
func fleetUnit(t *testing.T, opts ...string) unit.Unit { contents := "[X-Fleet]" for _, v := range opts { contents = fmt.Sprintf("%s\n%s", contents, v) } u, err := unit.NewUnit(contents) if u == nil || err != nil { t.Fatalf("Failed creating test unit: unit=%v, err=%v", u, err) } return *u }
// Assert that no jobs are returned for unknown peers func TestGetJobsByPeerUnknown(t *testing.T) { u := unit.NewUnit(`[X-Fleet] X-ConditionMachineOf=b `) j := job.NewJob("a", *u) state := NewState() state.TrackJob(j) peers := state.GetJobsByPeer("c") if len(peers) != 0 { t.Fatalf("Unexpected index of job peers %v", peers) } }
// Assert that existing jobs and potential jobs that do not conflict do not // trigger a match func TestHasConflictNoMatch(t *testing.T) { state := NewState() u := unit.NewUnit(`[X-Fleet]`) j := job.NewJob("example.service", *u) state.TrackJob(j) state.SetTargetState(j.Name, job.JobStateLoaded) agent := Agent{state: state} matched, name := agent.HasConflict("other.service", []string{}) if matched { t.Errorf("Expected no match, but got conflict with %s", name) } }
func TestLegacyPayload(t *testing.T) { unitContents := ` [Service] ExecStart=/bin/sleep 30000 `[1:] legacyPayloadContents := `{"Name":"sleep.service","Unit":{"Contents":{"Service":{"ExecStart":"/bin/sleep 30000"}},"Raw":"[Service]\nExecStart=/bin/sleep 30000\n"}}` want := unit.NewUnit(unitContents) var ljp LegacyJobPayload err := unmarshal(legacyPayloadContents, &ljp) if err != nil { t.Error("Error unmarshaling legacy payload:", err) } got := ljp.Unit if !reflect.DeepEqual(*want, got) { t.Errorf("Unit from legacy payload does not match expected!\nwant:\n%s\ngot:\n%s", *want, got) } }
// Assert that a potential conflict is triggered against the existing job name func TestHasConflictPotentialMatch(t *testing.T) { state := NewState() u := unit.NewUnit(`[X-Fleet]`) j := job.NewJob("example.service", *u) state.TrackJob(j) state.SetTargetState(j.Name, job.JobStateLoaded) agent := Agent{state: state} matched, name := agent.HasConflict("other.service", []string{"example.service"}) if !matched { t.Errorf("Expected conflict with 'example.service', no conflict reported") } else if name != "example.service" { t.Errorf("Expected conflict with 'example.service', but conflict found with %s", name) } }
func TestHasConflictIgnoresBids(t *testing.T) { state := NewState() u := unit.NewUnit(`[X-Fleet] X-Conflicts=other.service `) j := job.NewJob("example.service", *u) state.TrackJob(j) state.TrackBid(j.Name) agent := Agent{state: state} matched, name := agent.HasConflict("other.service", []string{}) if matched { t.Errorf("Expected no conflict, but got conflict with %s", name) } }
func TestVerifyJob(t *testing.T) { c, v := initSign(t) u, err := unit.NewUnit("Echo") if err != nil { t.Fatalf("unexpected error creating new unit: %v", err) } j := job.NewJob("echo.service", *u) data, err := marshal(u) if err != nil { t.Fatal("marshal error:", err) } v.pubkeys = append(v.pubkeys, testPublicKeys["rsa"]) signature, err := c.keyring.Sign(testPublicKeys["rsa"], data) if err != nil { t.Fatal("sign error:", err) } ss := &SignatureSet{TagForJob("echo.service"), []*gossh.Signature{signature}} ok, err := v.VerifyJob(j, ss) if err != nil { t.Fatal("error verifying job:", err) } if !ok { t.Fatal("job verification failed") } ss.Tag = "" ok, err = v.VerifyJob(j, ss) if err == nil || ok == true { t.Fatal("should fail on job verification") } ok, err = v.VerifyJob(j, nil) if err == nil || ok == true { t.Fatal("should fail on job verification") } }
func (ljp *LegacyJobPayload) UnmarshalJSON(data []byte) error { var ljpm legacyJobPayloadModel err := json.Unmarshal(data, &ljpm) if err != nil { return fmt.Errorf("unable to JSON-deserialize object: %s", err) } var u *unit.Unit if len(ljpm.Unit.Raw) > 0 { u, err = unit.NewUnit(ljpm.Unit.Raw) } else { u, err = unit.NewUnitFromLegacyContents(ljpm.Unit.Contents) } if err != nil { return err } ljp.Unit = *u ljp.Name = ljpm.Name return nil }
func TestAgentLoadStartStopJob(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) } u, err := unit.NewUnit("") if err != nil { t.Fatalf("Failed creating Unit: %v", err) } j := job.NewJob("foo.service", *u) err = a.loadJob(j) if err != nil { t.Fatalf("Failed calling Agent.loadJob: %v", err) } a.startJob("foo.service") jobs, err := a.jobs() if err != nil { t.Fatalf("Failed calling Agent.jobs: %v", err) } jsLaunched := job.JobStateLaunched expectJobs := map[string]*job.Job{ "foo.service": &job.Job{ Name: "foo.service", UnitState: &unit.UnitState{ LoadState: "loaded", ActiveState: "active", SubState: "running", MachineID: "", }, State: &jsLaunched, 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.stopJob("foo.service") 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) } }
func TestSystemdUnitFlow(t *testing.T) { uDir, err := ioutil.TempDir("", "fleet-") if err != nil { t.Fatalf("Failed creating tempdir: %v", err) } defer os.RemoveAll(uDir) mgr, err := systemd.NewSystemdUnitManager(uDir) if err != nil { t.Fatalf("Failed initializing SystemdUnitManager: %v", err) } units, err := mgr.Units() if err != nil { t.Fatalf("Failed calling Units(): %v", err) } if len(units) > 0 { t.Fatalf("Expected no units to be returned, got %v", units) } contents := `[Service] ExecStart=/usr/bin/sleep 3000 ` name := fmt.Sprintf("fleet-unit-%d.service", rand.Int63()) uf, err := unit.NewUnit(contents) if err != nil { t.Fatalf("Invalid unit file: %v", err) } hash := uf.Hash().String() j := job.NewJob(name, *uf) if err := mgr.Load(j.Name, j.Unit); err != nil { t.Fatalf("Failed loading job: %v", err) } units, err = mgr.Units() if err != nil { t.Fatalf("Failed calling Units(): %v", err) } if !reflect.DeepEqual([]string{name}, units) { t.Fatalf("Expected [hello.service], got %v", units) } us, err := mgr.GetUnitState(name) if err == nil { expect := unit.UnitState{"loaded", "inactive", "dead", "", hash} if !reflect.DeepEqual(expect, *us) { t.Errorf("Expected UnitState %v, got %v", expect, *us) } } else { t.Errorf("Failed determining unit state: %v", err) } mgr.Start(name) us, err = mgr.GetUnitState(name) if err == nil { expect := unit.UnitState{"loaded", "active", "running", "", hash} if !reflect.DeepEqual(expect, *us) { t.Errorf("Expected UnitState %v, got %v", expect, *us) } } else { t.Errorf("Failed determining unit state: %v", err) } mgr.Stop(name) us, err = mgr.GetUnitState(name) if err == nil { expect := unit.UnitState{"loaded", "inactive", "dead", "", hash} if !reflect.DeepEqual(expect, *us) { t.Errorf("Expected UnitState %v, got %v", expect, *us) } } else { t.Errorf("Failed determining unit state: %v", err) } mgr.Unload(name) units, err = mgr.Units() if err != nil { t.Fatalf("Failed calling Units(): %v", err) } if len(units) > 0 { t.Fatalf("Expected no units to be returned, got %v", units) } }