github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/apiserver/client/run.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package client
     5  
     6  import (
     7  	"fmt"
     8  	"path/filepath"
     9  	"sort"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/utils"
    15  	"github.com/juju/utils/set"
    16  
    17  	"github.com/juju/juju/agent"
    18  	"github.com/juju/juju/apiserver/common"
    19  	"github.com/juju/juju/apiserver/params"
    20  	"github.com/juju/juju/network"
    21  	"github.com/juju/juju/state"
    22  	"github.com/juju/juju/utils/ssh"
    23  )
    24  
    25  // remoteParamsForMachine returns a filled in RemoteExec instance
    26  // based on the machine, command and timeout params.  If the machine
    27  // does not have an internal address, the Host is empty. This is caught
    28  // by the function that actually tries to execute the command.
    29  func remoteParamsForMachine(machine *state.Machine, command string, timeout time.Duration) *RemoteExec {
    30  	// magic boolean parameters are bad :-(
    31  	address, ok := network.SelectInternalAddress(machine.Addresses(), false)
    32  	execParams := &RemoteExec{
    33  		ExecParams: ssh.ExecParams{
    34  			Command: command,
    35  			Timeout: timeout,
    36  		},
    37  		MachineId: machine.Id(),
    38  	}
    39  	if ok {
    40  		execParams.Host = fmt.Sprintf("ubuntu@%s", address.Value)
    41  	}
    42  	return execParams
    43  }
    44  
    45  // getAllUnitNames returns a sequence of valid Unit objects from state. If any
    46  // of the service names or unit names are not found, an error is returned.
    47  func getAllUnitNames(st *state.State, units, services []string) (result []*state.Unit, err error) {
    48  	unitsSet := set.NewStrings(units...)
    49  	for _, name := range services {
    50  		service, err := st.Service(name)
    51  		if err != nil {
    52  			return nil, err
    53  		}
    54  		units, err := service.AllUnits()
    55  		if err != nil {
    56  			return nil, err
    57  		}
    58  		for _, unit := range units {
    59  			unitsSet.Add(unit.Name())
    60  		}
    61  	}
    62  	for _, unitName := range unitsSet.Values() {
    63  		unit, err := st.Unit(unitName)
    64  		if err != nil {
    65  			return nil, err
    66  		}
    67  		// We only operate on units that have an assigned machine.
    68  		if _, err := unit.AssignedMachineId(); err != nil {
    69  			return nil, err
    70  		}
    71  		result = append(result, unit)
    72  	}
    73  	return result, nil
    74  }
    75  
    76  func (c *Client) getDataDir() string {
    77  	dataResource, ok := c.api.resources.Get("dataDir").(common.StringResource)
    78  	if !ok {
    79  		return ""
    80  	}
    81  	return dataResource.String()
    82  }
    83  
    84  // Run the commands specified on the machines identified through the
    85  // list of machines, units and services.
    86  func (c *Client) Run(run params.RunParams) (results params.RunResults, err error) {
    87  	if err := c.check.ChangeAllowed(); err != nil {
    88  		return params.RunResults{}, errors.Trace(err)
    89  	}
    90  	units, err := getAllUnitNames(c.api.state(), run.Units, run.Services)
    91  	if err != nil {
    92  		return results, err
    93  	}
    94  	// We want to create a RemoteExec for each unit and each machine.
    95  	// If we have both a unit and a machine request, we run it twice,
    96  	// once for the unit inside the exec context using juju-run, and
    97  	// the other outside the context just using bash.
    98  	var params []*RemoteExec
    99  	var quotedCommands = utils.ShQuote(run.Commands)
   100  	for _, unit := range units {
   101  		// We know that the unit is both a principal unit, and that it has an
   102  		// assigned machine.
   103  		machineId, _ := unit.AssignedMachineId()
   104  		machine, err := c.api.stateAccessor.Machine(machineId)
   105  		if err != nil {
   106  			return results, err
   107  		}
   108  		command := fmt.Sprintf("juju-run %s %s", unit.Name(), quotedCommands)
   109  		execParam := remoteParamsForMachine(machine, command, run.Timeout)
   110  		execParam.UnitId = unit.Name()
   111  		params = append(params, execParam)
   112  	}
   113  	for _, machineId := range run.Machines {
   114  		machine, err := c.api.stateAccessor.Machine(machineId)
   115  		if err != nil {
   116  			return results, err
   117  		}
   118  		command := fmt.Sprintf("juju-run --no-context %s", quotedCommands)
   119  		execParam := remoteParamsForMachine(machine, command, run.Timeout)
   120  		params = append(params, execParam)
   121  	}
   122  	return ParallelExecute(c.getDataDir(), params), nil
   123  }
   124  
   125  // RunOnAllMachines attempts to run the specified command on all the machines.
   126  func (c *Client) RunOnAllMachines(run params.RunParams) (params.RunResults, error) {
   127  	if err := c.check.ChangeAllowed(); err != nil {
   128  		return params.RunResults{}, errors.Trace(err)
   129  	}
   130  	machines, err := c.api.stateAccessor.AllMachines()
   131  	if err != nil {
   132  		return params.RunResults{}, err
   133  	}
   134  	var params []*RemoteExec
   135  	quotedCommands := utils.ShQuote(run.Commands)
   136  	command := fmt.Sprintf("juju-run --no-context %s", quotedCommands)
   137  	for _, machine := range machines {
   138  		params = append(params, remoteParamsForMachine(machine, command, run.Timeout))
   139  	}
   140  	return ParallelExecute(c.getDataDir(), params), nil
   141  }
   142  
   143  // RemoteExec extends the standard ssh.ExecParams by providing the machine and
   144  // perhaps the unit ids.  These are then returned in the params.RunResult return
   145  // values.
   146  type RemoteExec struct {
   147  	ssh.ExecParams
   148  	MachineId string
   149  	UnitId    string
   150  }
   151  
   152  // ParallelExecute executes all of the requests defined in the params,
   153  // using the system identity stored in the dataDir.
   154  func ParallelExecute(dataDir string, runParams []*RemoteExec) params.RunResults {
   155  	logger.Debugf("exec %#v", runParams)
   156  	var outstanding sync.WaitGroup
   157  	var lock sync.Mutex
   158  	var result []params.RunResult
   159  	identity := filepath.Join(dataDir, agent.SystemIdentity)
   160  	for _, param := range runParams {
   161  		outstanding.Add(1)
   162  		logger.Debugf("exec on %s: %#v", param.MachineId, *param)
   163  		param.IdentityFile = identity
   164  		go func(param *RemoteExec) {
   165  			response, err := ssh.ExecuteCommandOnMachine(param.ExecParams)
   166  			logger.Debugf("reponse from %s: %v (err:%v)", param.MachineId, response, err)
   167  			execResponse := params.RunResult{
   168  				ExecResponse: response,
   169  				MachineId:    param.MachineId,
   170  				UnitId:       param.UnitId,
   171  			}
   172  			if err != nil {
   173  				execResponse.Error = fmt.Sprint(err)
   174  			}
   175  
   176  			lock.Lock()
   177  			defer lock.Unlock()
   178  			result = append(result, execResponse)
   179  			outstanding.Done()
   180  		}(param)
   181  	}
   182  
   183  	outstanding.Wait()
   184  	sort.Sort(MachineOrder(result))
   185  	return params.RunResults{result}
   186  }
   187  
   188  // MachineOrder is used to provide the api to sort the results by the machine
   189  // id.
   190  type MachineOrder []params.RunResult
   191  
   192  func (a MachineOrder) Len() int           { return len(a) }
   193  func (a MachineOrder) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   194  func (a MachineOrder) Less(i, j int) bool { return a[i].MachineId < a[j].MachineId }