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 }