Exemplo n.º 1
0
// NewUnit takes a component type and returns a Fleet unit
// that includes the relevant systemd service template
func NewUnit(component string, templatePaths []string, decorate bool) (uf *unit.UnitFile, err error) {
	template, err := readTemplate(component, templatePaths)
	if err != nil {
		return
	}
	if decorate {
		decorator, err := readDecorator(component)
		if err != nil {
			return nil, err
		}
		uf, err = unit.NewUnitFile(string(template) + "\n" + string(decorator))
	} else {
		uf, err = unit.NewUnitFile(string(template))
	}
	return
}
Exemplo n.º 2
0
func newUnitFile(t *testing.T, contents string) *unit.UnitFile {
	uf, err := unit.NewUnitFile(contents)
	if err != nil {
		t.Fatalf("error creating NewUnitFile from %s: %v", contents, err)
	}
	return uf
}
Exemplo n.º 3
0
// getUnitByHash retrieves from the Registry the Unit associated with the given Hash
func (r *EtcdRegistry) getUnitByHash(hash unit.Hash) *unit.UnitFile {
	key := r.hashedUnitPath(hash)
	opts := &etcd.GetOptions{
		Recursive: true,
	}
	resp, err := r.kAPI.Get(r.ctx(), key, opts)
	if err != nil {
		if isEtcdError(err, etcd.ErrorCodeKeyNotFound) {
			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.NewUnitFile(um.Raw)
	if err != nil {
		log.Errorf("error parsing Unit(%s): %v", hash, err)
		return nil
	}

	return u
}
Exemplo n.º 4
0
func newUF(t *testing.T, contents string) unit.UnitFile {
	uf, err := unit.NewUnitFile(contents)
	if err != nil {
		t.Fatalf("error creating new unit file from %v: %v", contents, err)
	}
	return *uf
}
Exemplo n.º 5
0
func newUnit(t *testing.T, str string) *unit.UnitFile {
	u, err := unit.NewUnitFile(str)
	if err != nil {
		t.Fatalf("Unexpected error creating unit from %q: %v", str, err)
	}
	return u
}
Exemplo n.º 6
0
// getUnitByHash retrieves from the Registry the Unit associated with the given Hash
func (r *EtcdRegistry) getUnitByHash(hash unit.Hash) *unit.UnitFile {
	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.NewUnitFile(um.Raw)
	if err != nil {
		log.Errorf("error parsing Unit(%s): %v", hash, err)
		return nil
	}

	return u
}
Exemplo n.º 7
0
func StartUnitsInDir(path string) {
	files, _ := ioutil.ReadDir(path)

	for _, f := range files {
		unitpath := fmt.Sprintf("v1-alpha/units/%s", f.Name())
		url := getFullAPIURL("10001", unitpath)
		filepath := fmt.Sprintf("%s/%s", path, f.Name())

		readfile, err := ioutil.ReadFile(filepath)
		checkForErrors(err)
		content := string(readfile)

		u, _ := unit.NewUnitFile(content)

		options_bytes, _ := json.Marshal(u.Options)
		options_str := lowerCasingOfUnitOptionsStr(string(options_bytes))

		json_str := fmt.Sprintf(
			`{"name": "%s", "desiredState":"launched", "options": %s}`,
			f.Name(),
			options_str)

		resp := httpPutRequest(url, []byte(json_str))

		if resp.StatusCode != 204 {
			body, err := ioutil.ReadAll(resp.Body)
			log.Printf("[Error] in HTTP Body: %s", body)
			checkForErrors(err)
		}
	}
}
Exemplo n.º 8
0
func TestRpcUnitToJobUnit(t *testing.T) {
	contents := `
[Unit]
Description = Foo
`
	unitFile, err := unit.NewUnitFile(contents)
	if err != nil {
		t.Fatalf("unexpected error parsing unit %q: %v", contents, err)
	}

	want := &pb.Unit{
		Name:         "foo",
		Unit:         unitFile.ToPB(),
		DesiredState: pb.TargetState_LOADED,
	}
	expect := &job.Unit{
		Name:        "foo",
		Unit:        *unitFile,
		TargetState: job.JobStateLoaded,
	}

	got := rpcUnitToJobUnit(want)
	if !reflect.DeepEqual(got, expect) {
		t.Fatalf("got %#v, expected %#v", got, expect)
	}
}
Exemplo n.º 9
0
func newUnitWithMetadata(t *testing.T, metadata string) unit.UnitFile {
	contents := fmt.Sprintf("[X-Fleet]\nMachineMetadata=%s", metadata)
	u, err := unit.NewUnitFile(contents)
	if err != nil {
		t.Fatalf("error creating unit from %q: %v", contents, err)
	}
	return *u
}
Exemplo n.º 10
0
// getUnitFromFile attempts to load a Unit from a given filename
// It returns the Unit or nil, and any error encountered
func getUnitFromFile(file string) (*unit.UnitFile, error) {
	out, err := ioutil.ReadFile(file)
	if err != nil {
		panic(err)
	}

	return unit.NewUnitFile(string(out))
}
Exemplo n.º 11
0
// getUnitFromStdin attempts to load a Unit from stdin
func getUnitFromStdin() (*unit.UnitFile, error) {
	bytes, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		panic(err)
	}

	return unit.NewUnitFile(string(bytes))
}
Exemplo n.º 12
0
func TestFakeRegistryUnitLifecycle(t *testing.T) {
	reg := NewFakeRegistry()

	units, err := reg.Units()
	if err != nil {
		t.Fatalf("Received error while calling Jobs: %v", err)
	}
	if !reflect.DeepEqual([]job.Unit{}, units) {
		t.Fatalf("Expected no units, got %v", units)
	}

	uf, _ := unit.NewUnitFile("")
	u1 := job.Unit{Name: "u1.service", Unit: *uf, TargetState: job.JobStateLoaded}
	err = reg.CreateUnit(&u1)
	if err != nil {
		t.Fatalf("Received error while calling CreateUnit: %v", err)
	}

	units, err = reg.Units()
	if err != nil {
		t.Fatalf("Received error while calling Units: %v", err)
	}
	if len(units) != 1 {
		t.Fatalf("Expected 1 Unit, got %v", units)
	}
	if !reflect.DeepEqual(u1, units[0]) {
		t.Fatalf("Expected unit %v, got %v", u1, units[0])
	}

	err = reg.ScheduleUnit("u1.service", "XXX")
	if err != nil {
		t.Fatalf("Received error while calling ScheduleUnit: %v", err)
	}

	su, err := reg.ScheduledUnit("u1.service")
	if err != nil {
		t.Fatalf("Received error while calling ScheduledUnit: %v", err)
	}
	if su.TargetMachineID != "XXX" {
		t.Fatalf("Unit should be scheduled to XXX, got %v", su.TargetMachineID)
	}

	err = reg.DestroyUnit("u1.service")
	if err != nil {
		t.Fatalf("Received error while calling DestroyUnit: %v", err)
	}

	units, err = reg.Units()
	if err != nil {
		t.Fatalf("Received error while calling Units: %v", err)
	}
	if !reflect.DeepEqual([]job.Unit{}, units) {
		t.Fatalf("Expected no units, got %v", units)
	}
}
Exemplo n.º 13
0
// NewUnit takes a component type and returns a Fleet unit
// that includes the relevant systemd service template
func NewUnit(component string) (uf *unit.UnitFile, err error) {
	template, err := readTemplate(component)
	if err != nil {
		return
	}
	uf, err = unit.NewUnitFile(string(template))
	if err != nil {
		return
	}
	return
}
Exemplo n.º 14
0
// getUnitFromFile attempts to load a Unit from a given filename
// It returns the Unit or nil, and any error encountered
func getUnitFromFile(file string) (*unit.UnitFile, error) {
	out, err := ioutil.ReadFile(file)
	if err != nil {
		return nil, err
	}

	unitName := path.Base(file)
	log.Debugf("Unit(%s) found in local filesystem", unitName)

	return unit.NewUnitFile(string(out))
}
Exemplo n.º 15
0
func (d *deployer) buildWantedUnits() (map[string]*schema.Unit, error) {
	units := make(map[string]*schema.Unit)
	servicesDefinition, err := d.serviceDefinitionClient.servicesDefinition()
	if err != nil {
		return nil, err
	}
	for _, srv := range servicesDefinition.Services {
		vars := make(map[string]interface{})
		serviceTemplate, err := d.serviceDefinitionClient.serviceFile(srv)
		if err != nil {
			log.Printf("%v", err)
			continue
		}
		vars["version"] = srv.Version
		serviceFile, err := renderedServiceFile(serviceTemplate, vars)
		if err != nil {
			log.Printf("%v", err)
			return nil, err
		}

		// fleet deploy
		uf, err := unit.NewUnitFile(serviceFile)
		if err != nil {
			//Broken service file, skip it and continue
			log.Printf("WARNING service file %s is incorrect: %v [SKIPPING]", srv.Name, err)
			continue
		}

		if srv.Count == 0 && !strings.Contains(srv.Name, "@") {
			u := &schema.Unit{
				Name:         srv.Name,
				Options:      schema.MapUnitFileToSchemaUnitOptions(uf),
				DesiredState: srv.DesiredState,
			}

			units[srv.Name] = u
		} else if srv.Count > 0 && strings.Contains(srv.Name, "@") {
			for i := 0; i < srv.Count; i++ {
				xName := strings.Replace(srv.Name, "@", fmt.Sprintf("@%d", i+1), -1)

				u := &schema.Unit{
					Name:         xName,
					Options:      schema.MapUnitFileToSchemaUnitOptions(uf),
					DesiredState: srv.DesiredState,
				}

				units[u.Name] = u
			}
		} else {
			log.Printf("WARNING skipping service: %s, incorrect service definition", srv.Name)
		}
	}
	return units, nil
}
Exemplo n.º 16
0
func newTestJobFromUnitContents(t *testing.T, name, contents string) *job.Job {
	u, err := unit.NewUnitFile(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
}
Exemplo n.º 17
0
func (u *Unit) GetUnitContentAsFleeted() (string, error) {
	unitFileContent, err := ioutil.ReadFile(u.unitPath)
	if err != nil {
		return "", err
	}

	fleetunit, err := unit.NewUnitFile(string(unitFileContent))
	if err != nil {
		return "", err
	}
	return convertMultilineUnitToString([]byte(fleetunit.String())), nil
}
Exemplo n.º 18
0
// NewDataUnit takes a component type and returns a Fleet unit
// that is hard-scheduled to a machine ID
func NewDataUnit(component string, machineID string) (uf *unit.UnitFile, err error) {
	template, err := readTemplate(component)
	if err != nil {
		return
	}
	// replace CHANGEME with random machineID
	replaced := strings.Replace(string(template), "CHANGEME", machineID, 1)
	uf, err = unit.NewUnitFile(replaced)
	if err != nil {
		return
	}
	return
}
Exemplo n.º 19
0
func hashUnitFile(loc string) (unit.Hash, error) {
	b, err := ioutil.ReadFile(loc)
	if err != nil {
		return unit.Hash{}, err
	}

	uf, err := unit.NewUnitFile(string(b))
	if err != nil {
		return unit.Hash{}, err
	}

	return uf.Hash(), nil
}
Exemplo n.º 20
0
func (srv *Service) unitToOptions(desiredUnit Unit) ([]*schema.UnitOption, error) {
	uf, err := unit.NewUnitFile(srv.UnitTemplate)
	if err != nil {
		return nil, mask(err)
	}
	options := schema.MapUnitFileToSchemaUnitOptions(uf)
	options = append(options, &schema.UnitOption{
		Section: "X-Fleet",
		Name:    "MachineID",
		Value:   desiredUnit.MachineID,
	})
	return options, nil
}
Exemplo n.º 21
0
func fleetUnit(t *testing.T, opts ...string) unit.UnitFile {
	contents := "[X-Fleet]"
	for _, v := range opts {
		contents = fmt.Sprintf("%s\n%s", contents, v)
	}

	u, err := unit.NewUnitFile(contents)
	if u == nil || err != nil {
		t.Fatalf("Failed creating test unit: unit=%v, err=%v", u, err)
	}

	return *u
}
Exemplo n.º 22
0
func startUnitFile(unitFile string) {
	filename := filepath.Base(unitFile)
	unitFilepath := fmt.Sprintf(
		"fleet/%s/units/%s", Conf.FleetAPIVersion, filename)
	url := getFullAPIURL(Conf.FleetAPIPort, unitFilepath)

	log.Printf("Starting unit file: %s", filename)

	statusCode := 0
	for statusCode != 204 {
		readfile, err := ioutil.ReadFile(unitFile)
		goutils.PrintErrors(
			goutils.ErrorParams{Err: err, CallerNum: 2, Fatal: false})

		content := string(readfile)
		u, _ := unit.NewUnitFile(content)

		options_bytes, _ := json.Marshal(u.Options)
		options_str := lowerCasingOfUnitOptionsStr(string(options_bytes))

		json_str := fmt.Sprintf(
			`{"name": "%s", "desiredState":"launched", "options": %s}`,
			filename,
			options_str)

		headers := map[string]string{
			"Content-Type": "application/json",
		}

		p := goutils.HttpRequestParams{
			HttpRequestType: "PUT",
			Url:             url,
			Data:            json_str,
			Headers:         headers,
		}
		statusCode, _, _ = goutils.HttpCreateRequest(p)

		time.Sleep(1 * time.Second)
		/*
			log.Printf(
				"curl -H \"Content-Type: application/json\" -X PUT "+
					"-d %q localhost:10001/v1-alpha/units/%s",
				json_str, filename)
			body, err := ioutil.ReadAll(resp.Body)
			log.Printf("Status Code: %s", statusCode)
			log.Printf("[Error] in HTTP Body: %s - %v", body, err)
			goutils.PrintErrors(
				goutils.ErrorParams{Err: err, CallerNum: 2, Fatal: false})
		*/
	}
}
Exemplo n.º 23
0
func TestInMemoryUnitState(t *testing.T) {
	inmemoryRegistry := newInmemoryRegistry()

	scheduleUnit := &pb.ScheduledUnit{
		Name:         "foo",
		CurrentState: pb.TargetState_INACTIVE,
		MachineID:    "machine1",
	}
	inmemoryRegistry.scheduledUnits[scheduleUnit.Name] = *scheduleUnit
	contents := `
[Unit]
Description = Foo
`
	unitFile, err := unit.NewUnitFile(contents)
	if err != nil {
		t.Fatalf("unexpected error parsing unit %q: %v", contents, err)
	}
	unit := &pb.Unit{
		Name:         "foo",
		Unit:         unitFile.ToPB(),
		DesiredState: pb.TargetState_LOADED,
	}
	machineID := "testMachine"
	ttl := 2 * time.Second
	inmemoryRegistry.CreateUnit(unit)
	inmemoryRegistry.ScheduleUnit(unit.Name, machineID)

	stateLoaded := &pb.UnitState{
		Name:        unit.Name,
		Hash:        "heh",
		LoadState:   "active",
		ActiveState: "loaded",
		SubState:    "active",
		MachineID:   machineID,
	}

	inmemoryRegistry.SaveUnitState(unit.Name, stateLoaded, ttl)
	if !inmemoryRegistry.isUnitLoaded(unit.Name, machineID) {
		u, ok := inmemoryRegistry.Unit(unit.Name)
		if !ok {
			t.Fatalf("unexpected error unit not found %s", unit.Name)
		}
		t.Fatalf("unexpected error unit expected to be loaded %v", u)
	}

	resUs := inmemoryRegistry.UnitState(unit.Name)
	if resUs == nil || len(resUs.Name) == 0 {
		t.Fatal("Invalid unit state in the in-memory registry")
	}
}
Exemplo n.º 24
0
func TestInMemoryScheduleUnit(t *testing.T) {
	inmemoryRegistry := newInmemoryRegistry()

	scheduleUnit := &pb.ScheduledUnit{
		Name:         "foo",
		CurrentState: pb.TargetState_INACTIVE,
		MachineID:    "machine1",
	}
	inmemoryRegistry.scheduledUnits[scheduleUnit.Name] = *scheduleUnit
	contents := `
[Unit]
Description = Foo
`
	unitFile, err := unit.NewUnitFile(contents)
	if err != nil {
		t.Fatalf("unexpected error parsing unit %q: %v", contents, err)
	}
	unit := &pb.Unit{
		Name:         "foo",
		Unit:         unitFile.ToPB(),
		DesiredState: pb.TargetState_LOADED,
	}
	machineID := "testMachine"
	inmemoryRegistry.CreateUnit(unit)

	inmemoryRegistry.ScheduleUnit(unit.Name, machineID)

	unitsLen := len(inmemoryRegistry.Units())
	if unitsLen == 0 {
		t.Fatalf("unexpected amount of units in the in-memory registry got %d expected 1", unitsLen)
	}

	if !inmemoryRegistry.isScheduled(unit.Name, machineID) {
		t.Fatalf("unexpected error unit should be scheduled %s %s", unit.Name, machineID)
	}

	inmemoryRegistry.UnscheduleUnit(unit.Name, machineID)
	if inmemoryRegistry.isScheduled("foo", "testMachine") {
		t.Fatalf("unexpected error unit should NOT be scheduled %s %s", unit.Name, machineID)
	}

	if !inmemoryRegistry.DestroyUnit(unit.Name) {
		t.Fatalf("unexpected error unit have to be destroy %s", unit.Name)
	}

	unitsLen = len(inmemoryRegistry.Units())
	if unitsLen > 0 {
		t.Fatalf("unexpected amount of units in the in-memory registry got %d expected 0", unitsLen)
	}
}
Exemplo n.º 25
0
Arquivo: unit.go Projeto: pulcy/j2
func (r *EtcdRegistry) unitFromEtcdNode(hash unit.Hash, etcdNode *etcd.Node) *unit.UnitFile {
	var um unitModel
	if err := unmarshal(etcdNode.Value, &um); err != nil {
		log.Errorf("error unmarshaling Unit(%s): %v", hash, err)
		return nil
	}

	u, err := unit.NewUnitFile(um.Raw)
	if err != nil {
		log.Errorf("error parsing Unit(%s): %v", hash, err)
		return nil
	}

	return u
}
Exemplo n.º 26
0
// lazyCreateUnits iterates over a set of unit names and, for each, attempts to
// ensure that a unit by that name exists in the Registry, by checking a number
// of conditions and acting on the first one that succeeds, in order of:
//  1. a unit by that name already existing in the Registry
//  2. a unit file by that name existing on disk
//  3. a corresponding unit template (if applicable) existing in the Registry
//  4. a corresponding unit template (if applicable) existing on disk
// Any error encountered during these steps is returned immediately (i.e.
// subsequent Jobs are not acted on). An error is also returned if none of the
// above conditions match a given Job.
func (f *FleetTunnel) lazyCreateUnits(units UnitDataList, events chan Event) error {
	errchan := make(chan error)
	blockAttempts := f.BlockAttempts
	var wg sync.WaitGroup
	for i := 0; i < units.Len(); i++ {
		u := units.Get(i)
		name := u.Name()
		create, err := f.checkUnitCreation(name)
		if err != nil {
			return err
		} else if !create {
			continue
		}

		// Assume that the name references a local unit file on
		// disk or if it is an instance unit and if so get its
		// corresponding unit
		uf, err := unit.NewUnitFile(u.Content())
		if err != nil {
			return err
		}

		events <- newEvent(name, "creating unit")
		_, err = f.createUnit(name, uf)
		if err != nil {
			return err
		}

		wg.Add(1)
		go f.checkUnitState(name, job.JobStateInactive, blockAttempts, events, &wg, errchan)
	}

	go func() {
		wg.Wait()
		close(errchan)
	}()

	var ae aerr.AggregateError
	for msg := range errchan {
		ae.Add(maskAny(fmt.Errorf("Error waiting on unit creation: %v\n", msg)))
	}

	if !ae.IsEmpty() {
		return maskAny(&ae)
	}

	return nil
}
Exemplo n.º 27
0
func (b *Builder) UseCustomUnitFileService(filePath string) error {
	filename, _ := filepath.Abs(filePath)
	unitFile, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Logger().Errorf("unable to read yaml file %v", err)
		return err
	}
	contents := string(unitFile)
	if strings.Contains(contents, "Global=true") {
		log.Logger().Warningf("Global fleet scheduling option 'Global=true' can cause undesired results")
	}

	b.unitFile, err = unit.NewUnitFile(contents)
	if err != nil {
		log.Logger().Errorf("error creating Unit from %q: %v", contents, err)
		return err
	}
	return nil
}
Exemplo n.º 28
0
func TestLegacyPayload(t *testing.T) {
	contents := `
[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, err := unit.NewUnitFile(contents)
	if err != nil {
		t.Fatalf("unexpected error creating unit from %q: %v", contents, err)
	}
	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)
	}
}
Exemplo n.º 29
0
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.UnitFile
	if len(ljpm.Unit.Raw) > 0 {
		u, err = unit.NewUnitFile(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
}
Exemplo n.º 30
0
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, false)
	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.NewUnitFile(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)
	}

	err = waitForUnitState(mgr, name, unit.UnitState{"loaded", "inactive", "dead", "", hash, ""})
	if err != nil {
		t.Error(err.Error())
	}

	mgr.TriggerStart(name)

	err = waitForUnitState(mgr, name, unit.UnitState{"loaded", "active", "running", "", hash, ""})
	if err != nil {
		t.Error(err.Error())
	}

	mgr.TriggerStop(name)

	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)
	}
}