Skip to content
Closed
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
167 changes: 126 additions & 41 deletions fleetctl/fleetctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,36 @@ func getChecker() *ssh.HostKeyChecker {
return ssh.NewHostKeyChecker(keyFile)
}

func getUnitFile(file string) (*unit.UnitFile, error) {
var uf *unit.UnitFile
name := unitNameMangle(file)

log.Debugf("Looking for Unit(%s) or its corresponding template", name)

// Assume that the file references a local unit file on disk and
// attempt to load it, if it exists
if _, err := os.Stat(file); !os.IsNotExist(err) {
uf, err = getUnitFromFile(file)
if err != nil {
return nil, fmt.Errorf("failed getting Unit(%s) from file: %v", file, err)
}
} else {
// Otherwise (if the unit file does not exist), check if the
// name appears to be an instance unit, and if so, check for
// a corresponding template unit in the Registry or disk.
// If we found a template unit, later we create a
// near-identical instance unit in the Registry - same
// unit file as the template, but different name
uf, err = getUnitFileFromTemplate(file)
if err != nil {
return nil, err
}
}

log.Debugf("Found Unit(%s)", name)
return uf, nil
}

// 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) {
Expand All @@ -496,6 +526,48 @@ func getUnitFromFile(file string) (*unit.UnitFile, error) {
return unit.NewUnitFile(string(out))
}

// getUnitFileFromTemplate checks if the name appears to be an instance unit
// and gets its corresponding template unit from the registry or local disk
// It returns the Unit or nil; and any error encountered
func getUnitFileFromTemplate(arg string) (*unit.UnitFile, error) {
var uf *unit.UnitFile
name := unitNameMangle(arg)

// Check if the name appears to be an instance unit, and if so,
// check for a corresponding template unit in the Registry
uni := unit.NewUnitNameInfo(name)
if uni == nil {
return nil, fmt.Errorf("error extracting information from unit name %s", name)
} else if !uni.IsInstance() {
return nil, fmt.Errorf("unable to find Unit(%s) in Registry or on filesystem", name)
}

tmpl, err := cAPI.Unit(uni.Template)
if err != nil {
return nil, fmt.Errorf("error retrieving template Unit(%s) from Registry: %v", uni.Template, err)
}

if tmpl != nil {
warnOnDifferentLocalUnit(arg, tmpl)
uf = schema.MapSchemaUnitOptionsToUnitFile(tmpl.Options)
log.Debugf("Template Unit(%s) found in registry", uni.Template)
} else {
// Finally, if we could not find a template unit in the Registry,
// check the local disk for one instead
file := path.Join(path.Dir(arg), uni.Template)
if _, err := os.Stat(file); os.IsNotExist(err) {
return nil, fmt.Errorf("unable to find Unit(%s) or template Unit(%s) in Registry or on filesystem", name, uni.Template)
}

uf, err = getUnitFromFile(file)
if err != nil {
return nil, fmt.Errorf("failed getting template Unit(%s) from file: %v", uni.Template, err)
}
}

return uf, nil
}

func getTunnelFlag() string {
tun := globalFlags.Tunnel
if tun != "" && !strings.Contains(tun, ":") {
Expand Down Expand Up @@ -598,8 +670,6 @@ func lazyCreateUnits(args []string) error {
errchan := make(chan error)
var wg sync.WaitGroup
for _, arg := range args {
// TODO(jonboulle): this loop is getting too unwieldy; factor it out

arg = maybeAppendDefaultUnitType(arg)
name := unitNameMangle(arg)

Expand All @@ -614,45 +684,12 @@ func lazyCreateUnits(args []string) error {
continue
}

var uf *unit.UnitFile
// Failing that, assume the name references a local unit file on disk, and attempt to load that, if it exists
// TODO(mischief): consolidate these two near-identical codepaths
if _, err := os.Stat(arg); !os.IsNotExist(err) {
uf, err = getUnitFromFile(arg)
if err != nil {
return fmt.Errorf("failed getting Unit(%s) from file: %v", arg, err)
}
} else {
// Otherwise (if the unit file does not exist), check if the name appears to be an instance unit,
// and if so, check for a corresponding template unit in the Registry
uni := unit.NewUnitNameInfo(name)
if uni == nil {
return fmt.Errorf("error extracting information from unit name %s", name)
} else if !uni.IsInstance() {
return fmt.Errorf("unable to find Unit(%s) in Registry or on filesystem", name)
}
tmpl, err := cAPI.Unit(uni.Template)
if err != nil {
return fmt.Errorf("error retrieving template Unit(%s) from Registry: %v", uni.Template, err)
}

// Finally, if we could not find a template unit in the Registry, check the local disk for one instead
if tmpl == nil {
file := path.Join(path.Dir(arg), uni.Template)
if _, err := os.Stat(file); os.IsNotExist(err) {
return fmt.Errorf("unable to find Unit(%s) or template Unit(%s) in Registry or on filesystem", name, uni.Template)
}
uf, err = getUnitFromFile(file)
if err != nil {
return fmt.Errorf("failed getting template Unit(%s) from file: %v", uni.Template, err)
}
} else {
warnOnDifferentLocalUnit(arg, tmpl)
uf = schema.MapSchemaUnitOptionsToUnitFile(tmpl.Options)
}

// If we found a template unit, create a near-identical instance unit in
// the Registry - same unit file as the template, but different name
// 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 := getUnitFile(arg)
if err != nil {
return err
}

_, err = createUnit(name, uf)
Expand Down Expand Up @@ -745,6 +782,54 @@ func setTargetStateOfUnits(units []string, state job.JobState) ([]*schema.Unit,
return triggered, nil
}

// getBlockAttempts gets the correct value of how many attempts to try
// before giving up on an operation.
// It returns a negative value which means do not block, if zero is
// returned then it means try forever, and if a positive value is
// returned then try up to that value
func getBlockAttempts() int {
// By default we wait forever
var attempts int = 0

if sharedFlags.BlockAttempts > 0 {
attempts = sharedFlags.BlockAttempts
}

if sharedFlags.NoBlock {
attempts = -1
}

return attempts
}

// tryWaitForUnitStates tries to wait for units to reach the desired state.
// It takes 5 arguments, the units to wait for, the desired state, the
// desired JobState, how many attempts before timing out and a writer
// interface.
// tryWaitForUnitStates polls each of the indicated units until they
// reach the desired state. If maxAttempts is negative, then it will not
// wait, it will assume that all units reached their desired state.
// If maxAttempts is zero tryWaitForUnitStates will retry forever, and
// if it is greater than zero, it will retry up to the indicated value.
// It returns 0 on success or 1 on errors.
func tryWaitForUnitStates(units []string, state string, js job.JobState, maxAttempts int, out io.Writer) (ret int) {
// We do not wait just assume we reached the desired state
if maxAttempts <= -1 {
for _, name := range units {
stdout("Triggered unit %s %s", name, state)
}
return
}

errchan := waitForUnitStates(units, js, maxAttempts, out)
for err := range errchan {
stderr("Error waiting for units: %v", err)
ret = 1
}

return
}

// waitForUnitStates polls each of the indicated units until each of their
// states is equal to that which the caller indicates, or until the
// polling operation times out. waitForUnitStates will retry forever, or
Expand Down
45 changes: 45 additions & 0 deletions fleetctl/fleetctl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,51 @@ func TestUnitNameMangle(t *testing.T) {
}
}

func TestGetBlockAttempts(t *testing.T) {
oldNoBlock := sharedFlags.NoBlock
oldBlockAttempts := sharedFlags.BlockAttempts
sharedFlags.NoBlock = true
sharedFlags.BlockAttempts = 0

if n := getBlockAttempts(); n != -1 {
t.Errorf("got %d, want -1", n)
}

sharedFlags.BlockAttempts = -1
if n := getBlockAttempts(); n != -1 {
t.Errorf("got %d, want -1", n)
}

sharedFlags.BlockAttempts = 9999
if n := getBlockAttempts(); n != -1 {
t.Errorf("got %d, want -1", n)
}

sharedFlags.NoBlock = false
sharedFlags.BlockAttempts = 0
if n := getBlockAttempts(); n != 0 {
t.Errorf("got %d, want 0", n)
}

sharedFlags.BlockAttempts = -1
if n := getBlockAttempts(); n != 0 {
t.Errorf("got %d, want 0", n)
}

sharedFlags.BlockAttempts = 0
if n := getBlockAttempts(); n != 0 {
t.Errorf("got %d, want 0", n)
}

sharedFlags.BlockAttempts = 9999
if n := getBlockAttempts(); n != 9999 {
t.Errorf("got %d, want 9999", n)
}

sharedFlags.NoBlock = oldNoBlock
sharedFlags.BlockAttempts = oldBlockAttempts
}

func newUnitFile(t *testing.T, contents string) *unit.UnitFile {
uf, err := unit.NewUnitFile(contents)
if err != nil {
Expand Down
12 changes: 1 addition & 11 deletions fleetctl/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,7 @@ func runLoadUnits(args []string) (exit int) {
}
}

if !sharedFlags.NoBlock {
errchan := waitForUnitStates(loading, job.JobStateLoaded, sharedFlags.BlockAttempts, os.Stdout)
for err := range errchan {
stderr("Error waiting for units: %v", err)
exit = 1
}
} else {
for _, name := range loading {
stdout("Triggered unit %s load", name)
}
}
exit = tryWaitForUnitStates(loading, "load", job.JobStateLoaded, getBlockAttempts(), os.Stdout)

return
}
12 changes: 1 addition & 11 deletions fleetctl/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,7 @@ func runStartUnit(args []string) (exit int) {
}
}

if !sharedFlags.NoBlock {
errchan := waitForUnitStates(starting, job.JobStateLaunched, sharedFlags.BlockAttempts, os.Stdout)
for err := range errchan {
stderr("Error waiting for units: %v", err)
exit = 1
}
} else {
for _, name := range starting {
stdout("Triggered unit %s start", name)
}
}
exit = tryWaitForUnitStates(starting, "start", job.JobStateLaunched, getBlockAttempts(), os.Stdout)

return
}
12 changes: 1 addition & 11 deletions fleetctl/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,7 @@ func runStopUnit(args []string) (exit int) {
}
}

if !sharedFlags.NoBlock {
errchan := waitForUnitStates(stopping, job.JobStateLoaded, sharedFlags.BlockAttempts, os.Stdout)
for err := range errchan {
stderr("Error waiting for units: %v", err)
exit = 1
}
} else {
for _, name := range stopping {
stdout("Triggered unit %s stop", name)
}
}
exit = tryWaitForUnitStates(stopping, "stop", job.JobStateLoaded, getBlockAttempts(), os.Stdout)

return
}
12 changes: 1 addition & 11 deletions fleetctl/unload.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,7 @@ func runUnloadUnit(args []string) (exit int) {
}
}

if !sharedFlags.NoBlock {
errchan := waitForUnitStates(wait, job.JobStateInactive, sharedFlags.BlockAttempts, os.Stdout)
for err := range errchan {
stderr("Error waiting for units: %v", err)
exit = 1
}
} else {
for _, name := range wait {
stdout("Triggered unit %s unload", name)
}
}
exit = tryWaitForUnitStates(wait, "unload", job.JobStateInactive, getBlockAttempts(), os.Stdout)

return
}