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  }