github.com/ssube/gitlab-ci-multi-runner@v1.2.1-0.20160607142738-b8d1285632e6/executors/docker/machine/provider.go (about) 1 package machine 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 "time" 8 9 "github.com/Sirupsen/logrus" 10 11 "gitlab.com/gitlab-org/gitlab-ci-multi-runner/common" 12 "gitlab.com/gitlab-org/gitlab-ci-multi-runner/helpers/docker" 13 ) 14 15 type machineProvider struct { 16 machine docker_helpers.Machine 17 details machinesDetails 18 lock sync.RWMutex 19 // provider stores a real executor that is used to start run the builds 20 provider common.ExecutorProvider 21 } 22 23 func (m *machineProvider) machineDetails(name string, acquire bool) *machineDetails { 24 m.lock.Lock() 25 defer m.lock.Unlock() 26 27 details, ok := m.details[name] 28 if !ok { 29 details = &machineDetails{ 30 Name: name, 31 Created: time.Now(), 32 Used: time.Now(), 33 State: machineStateIdle, 34 } 35 m.details[name] = details 36 } 37 38 if acquire { 39 if details.isUsed() { 40 return nil 41 } 42 details.State = machineStateAcquired 43 } 44 45 return details 46 } 47 48 func (m *machineProvider) create(config *common.RunnerConfig, state machineState) (details *machineDetails, errCh chan error) { 49 name := newMachineName(machineFilter(config)) 50 details = m.machineDetails(name, true) 51 details.State = machineStateCreating 52 errCh = make(chan error, 1) 53 54 // Create machine asynchronously 55 go func() { 56 started := time.Now() 57 err := m.machine.Create(config.Machine.MachineDriver, details.Name, config.Machine.MachineOptions...) 58 for i := 0; i < 3 && err != nil; i++ { 59 logrus.WithField("name", details.Name). 60 Warningln("Machine creation failed, trying to provision", err) 61 time.Sleep(provisionRetryInterval) 62 err = m.machine.Provision(details.Name) 63 } 64 65 if err != nil { 66 m.remove(details.Name, "Failed to create") 67 } else { 68 details.State = state 69 details.Used = time.Now() 70 logrus.WithField("time", time.Since(started)). 71 WithField("name", details.Name). 72 Infoln("Machine created") 73 } 74 errCh <- err 75 }() 76 return 77 } 78 79 func (m *machineProvider) findFreeMachine(machines ...string) (details *machineDetails) { 80 // Enumerate all machines 81 for _, name := range machines { 82 details := m.machineDetails(name, true) 83 if details == nil { 84 continue 85 } 86 87 // Check if node is running 88 canConnect := m.machine.CanConnect(name) 89 if !canConnect { 90 m.remove(name, "machine is unavailable") 91 continue 92 } 93 return details 94 } 95 96 return nil 97 } 98 99 func (m *machineProvider) useMachine(config *common.RunnerConfig) (details *machineDetails, err error) { 100 machines, err := m.loadMachines(config) 101 if err != nil { 102 return 103 } 104 details = m.findFreeMachine(machines...) 105 if details == nil { 106 var errCh chan error 107 details, errCh = m.create(config, machineStateAcquired) 108 err = <-errCh 109 } 110 return 111 } 112 113 func (m *machineProvider) retryUseMachine(config *common.RunnerConfig) (details *machineDetails, err error) { 114 // Try to find a machine 115 for i := 0; i < 3; i++ { 116 details, err = m.useMachine(config) 117 if err == nil { 118 break 119 } 120 time.Sleep(provisionRetryInterval) 121 } 122 return 123 } 124 125 func (m *machineProvider) finalizeRemoval(details *machineDetails) { 126 for { 127 if !m.machine.Exist(details.Name) { 128 logrus.WithField("name", details.Name). 129 WithField("created", time.Since(details.Created)). 130 WithField("used", time.Since(details.Used)). 131 WithField("reason", details.Reason). 132 Warningln("Skipping machine removal, because it doesn't exist") 133 break 134 } 135 136 err := m.machine.Remove(details.Name) 137 if err == nil { 138 break 139 } 140 time.Sleep(30 * time.Second) 141 logrus.WithField("name", details.Name). 142 WithField("created", time.Since(details.Created)). 143 WithField("used", time.Since(details.Used)). 144 WithField("reason", details.Reason). 145 Warningln("Retrying removal") 146 } 147 148 m.lock.Lock() 149 defer m.lock.Unlock() 150 delete(m.details, details.Name) 151 } 152 153 func (m *machineProvider) remove(machineName string, reason ...interface{}) { 154 m.lock.Lock() 155 defer m.lock.Unlock() 156 157 details, _ := m.details[machineName] 158 if details == nil { 159 return 160 } 161 162 details.Reason = fmt.Sprint(reason...) 163 details.State = machineStateRemoving 164 logrus.WithField("name", machineName). 165 WithField("created", time.Since(details.Created)). 166 WithField("used", time.Since(details.Used)). 167 WithField("reason", details.Reason). 168 Warningln("Removing machine") 169 details.Used = time.Now() 170 details.writeDebugInformation() 171 172 go m.finalizeRemoval(details) 173 } 174 175 func (m *machineProvider) updateMachine(config *common.RunnerConfig, data *machinesData, details *machineDetails) error { 176 if details.State != machineStateIdle { 177 return nil 178 } 179 180 if config.Machine.MaxBuilds > 0 && details.UsedCount >= config.Machine.MaxBuilds { 181 // Limit number of builds 182 return errors.New("Too many builds") 183 } 184 185 if data.Total() >= config.Limit && config.Limit > 0 { 186 // Limit maximum number of machines 187 return errors.New("Too many machines") 188 } 189 190 if time.Since(details.Used) > time.Second*time.Duration(config.Machine.IdleTime) { 191 if data.Idle >= config.Machine.IdleCount { 192 // Remove machine that are way over the idle time 193 return errors.New("Too many idle machines") 194 } 195 } 196 return nil 197 } 198 199 func (m *machineProvider) updateMachines(machines []string, config *common.RunnerConfig) (data machinesData) { 200 data.Runner = config.ShortDescription() 201 202 for _, name := range machines { 203 details := m.machineDetails(name, false) 204 err := m.updateMachine(config, &data, details) 205 if err != nil { 206 m.remove(details.Name, err) 207 } 208 209 data.Add(details.State) 210 } 211 return 212 } 213 214 func (m *machineProvider) createMachines(config *common.RunnerConfig, data *machinesData) { 215 // Create a new machines and mark them as Idle 216 for { 217 if data.Available() >= config.Machine.IdleCount { 218 // Limit maximum number of idle machines 219 break 220 } 221 if data.Total() >= config.Limit && config.Limit > 0 { 222 // Limit maximum number of machines 223 break 224 } 225 m.create(config, machineStateIdle) 226 data.Creating++ 227 } 228 } 229 230 func (m *machineProvider) loadMachines(config *common.RunnerConfig) ([]string, error) { 231 // Find a new machine 232 return m.machine.List(machineFilter(config)) 233 } 234 235 func (m *machineProvider) Acquire(config *common.RunnerConfig) (data common.ExecutorData, err error) { 236 if config.Machine == nil || config.Machine.MachineName == "" { 237 err = fmt.Errorf("Missing Machine options") 238 return 239 } 240 241 machines, err := m.loadMachines(config) 242 if err != nil { 243 return 244 } 245 246 // Update a list of currently configured machines 247 machinesData := m.updateMachines(machines, config) 248 249 // Pre-create machines 250 m.createMachines(config, &machinesData) 251 252 logrus.WithFields(machinesData.Fields()). 253 WithField("runner", config.ShortDescription()). 254 WithField("minIdleCount", config.Machine.IdleCount). 255 WithField("maxMachines", config.Limit). 256 WithField("time", time.Now()). 257 Debugln("Docker Machine Details") 258 machinesData.writeDebugInformation() 259 260 // Try to find a free machine 261 details := m.findFreeMachine(machines...) 262 if details != nil { 263 data = details 264 return 265 } 266 267 // If we have a free machines we can process a build 268 if config.Machine.IdleCount != 0 && machinesData.Idle == 0 { 269 err = errors.New("No free machines that can process builds") 270 } 271 return 272 } 273 274 func (m *machineProvider) Use(config *common.RunnerConfig, data common.ExecutorData) (newConfig common.RunnerConfig, newData common.ExecutorData, err error) { 275 // Find a new machine 276 details, _ := data.(*machineDetails) 277 if details == nil { 278 details, err = m.retryUseMachine(config) 279 if err != nil { 280 return 281 } 282 283 // Return details only if this is a new instance 284 newData = details 285 } 286 287 // Get machine credentials 288 dc, err := m.machine.Credentials(details.Name) 289 if err != nil { 290 if newData != nil { 291 m.Release(config, newData) 292 } 293 return 294 } 295 296 // Create shallow copy of config and store in it docker credentials 297 newConfig = *config 298 newConfig.Docker = &common.DockerConfig{} 299 if config.Docker != nil { 300 *newConfig.Docker = *config.Docker 301 } 302 newConfig.Docker.DockerCredentials = dc 303 304 // Mark machine as used 305 details.State = machineStateUsed 306 return 307 } 308 309 func (m *machineProvider) Release(config *common.RunnerConfig, data common.ExecutorData) error { 310 // Release machine 311 details, ok := data.(*machineDetails) 312 if ok { 313 // Mark last used time when is Used 314 if details.State == machineStateUsed { 315 details.Used = time.Now() 316 details.UsedCount++ 317 } 318 details.State = machineStateIdle 319 } 320 return nil 321 } 322 323 func (m *machineProvider) CanCreate() bool { 324 return m.provider.CanCreate() 325 } 326 327 func (m *machineProvider) GetFeatures(features *common.FeaturesInfo) { 328 m.provider.GetFeatures(features) 329 } 330 331 func (m *machineProvider) Create() common.Executor { 332 return &machineExecutor{ 333 provider: m, 334 } 335 } 336 337 func newMachineProvider(executor string) *machineProvider { 338 provider := common.GetExecutor(executor) 339 if provider == nil { 340 logrus.Panicln("Missing", executor) 341 } 342 343 return &machineProvider{ 344 details: make(machinesDetails), 345 machine: docker_helpers.NewMachineCommand(), 346 provider: provider, 347 } 348 }