github.com/Cloud-Foundations/Dominator@v0.3.4/imagebuilder/builder/build.go (about)

     1  package builder
     2  
     3  import (
     4  	"bytes"
     5  	stderrors "errors"
     6  	"fmt"
     7  	"io"
     8  	"time"
     9  
    10  	buildclient "github.com/Cloud-Foundations/Dominator/imagebuilder/client"
    11  	"github.com/Cloud-Foundations/Dominator/imagebuilder/logarchiver"
    12  	imgclient "github.com/Cloud-Foundations/Dominator/imageserver/client"
    13  	"github.com/Cloud-Foundations/Dominator/lib/backoffdelay"
    14  	"github.com/Cloud-Foundations/Dominator/lib/errors"
    15  	"github.com/Cloud-Foundations/Dominator/lib/format"
    16  	"github.com/Cloud-Foundations/Dominator/lib/image"
    17  	"github.com/Cloud-Foundations/Dominator/lib/retry"
    18  	"github.com/Cloud-Foundations/Dominator/lib/srpc"
    19  	"github.com/Cloud-Foundations/Dominator/lib/srpc/retryclient"
    20  	"github.com/Cloud-Foundations/Dominator/lib/url/urlutil"
    21  	proto "github.com/Cloud-Foundations/Dominator/proto/imaginator"
    22  )
    23  
    24  type dualBuildLogger struct {
    25  	buffer *bytes.Buffer
    26  	writer io.Writer
    27  }
    28  
    29  type buildErrorType struct {
    30  	error                     string
    31  	needSourceImage           bool
    32  	sourceImage               string
    33  	sourceImageBuildVariables map[string]string
    34  	sourceImageGitCommitId    string
    35  }
    36  
    37  func copyClientLogs(clientAddress string, keepSlave bool, buildError error,
    38  	buildLog io.Writer) {
    39  	fmt.Fprintln(buildLog,
    40  		"*********************************************************************")
    41  	fmt.Fprintf(buildLog, "Logs for slave: %s, keepSlave: %v, err: %v\n",
    42  		clientAddress, keepSlave, buildError)
    43  	readCloser, err := urlutil.Open(
    44  		fmt.Sprintf("http://%s/logs/dump?name=latest", clientAddress))
    45  	if err != nil {
    46  		fmt.Fprintf(buildLog, "Error opening logs: %s\n", err)
    47  		return
    48  	}
    49  	defer readCloser.Close()
    50  	io.Copy(buildLog, readCloser)
    51  	fmt.Fprintln(buildLog, "End logs for slave")
    52  	fmt.Fprintln(buildLog,
    53  		"*********************************************************************")
    54  }
    55  
    56  func dialServer(address string, retryTimeout time.Duration) (
    57  	srpc.ClientI, error) {
    58  	return retryclient.DialHTTP(retryclient.Params{
    59  		Address:         address,
    60  		KeepAlive:       true,
    61  		KeepAlivePeriod: time.Minute,
    62  		Network:         "tcp",
    63  		Params: retry.Params{
    64  			RetryTimeout: retryTimeout,
    65  			Sleeper:      backoffdelay.NewExponential(0, 0, 1),
    66  		},
    67  	})
    68  }
    69  
    70  // checkToAutoExtend will return true if auto building images should have their
    71  // lifetimes extended rather than built. Some conditions where it returns true:
    72  // - Auto building has been disabled.
    73  // - A slave driver is configured but failes to provide a slave in time
    74  func (b *Builder) checkToAutoExtend() bool {
    75  	b.disableLock.RLock()
    76  	disableUntil := b.disableAutoBuildsUntil
    77  	b.disableLock.RUnlock()
    78  	if duration := time.Until(disableUntil); duration > 0 {
    79  		b.logger.Printf("Auto rebuilds disabled until %s (for %s), will extend image lifetimes\n",
    80  			disableUntil.Format(format.TimeFormatSeconds),
    81  			format.Duration(duration))
    82  		return true
    83  	}
    84  	if b.slaveDriver == nil {
    85  		return false
    86  	}
    87  	slave, err := b.slaveDriver.GetSlaveWithTimeout(b.createSlaveTimeout)
    88  	if err == nil {
    89  		slave.Release()
    90  		return false
    91  	}
    92  	b.logger.Printf("Error getting slave: %s, will extend image lifetimes\n",
    93  		err)
    94  	return true
    95  }
    96  
    97  func (b *Builder) build(client srpc.ClientI, request proto.BuildImageRequest,
    98  	authInfo *srpc.AuthInformation,
    99  	logWriter io.Writer) (*image.Image, string, error) {
   100  	startTime := time.Now()
   101  	builder := b.getImageBuilderWithReload(request.StreamName)
   102  	if builder == nil {
   103  		return nil, "", errors.New("unknown stream: " + request.StreamName)
   104  	}
   105  	if err := b.checkPermission(builder, request, authInfo); err != nil {
   106  		return nil, "", err
   107  	}
   108  	buildLogBuffer := &bytes.Buffer{}
   109  	buildInfo := &currentBuildInfo{
   110  		buffer:    buildLogBuffer,
   111  		startedAt: time.Now(),
   112  	}
   113  	b.buildResultsLock.Lock()
   114  	b.currentBuildInfos[request.StreamName] = buildInfo
   115  	b.buildResultsLock.Unlock()
   116  	var buildLog buildLogger
   117  	if logWriter == nil {
   118  		buildLog = buildLogBuffer
   119  	} else {
   120  		buildLog = &dualBuildLogger{
   121  			buffer: buildLogBuffer,
   122  			writer: io.MultiWriter(buildLogBuffer, logWriter),
   123  		}
   124  	}
   125  	img, name, err := b.buildWithLogger(builder, client, request, authInfo,
   126  		startTime, &buildInfo.slaveAddress, buildLog)
   127  	finishTime := time.Now()
   128  	b.buildResultsLock.Lock()
   129  	defer b.buildResultsLock.Unlock()
   130  	delete(b.currentBuildInfos, request.StreamName)
   131  	b.lastBuildResults[request.StreamName] = buildResultType{
   132  		name, startTime, finishTime, buildLog.Bytes(), err}
   133  	buildLogInfo := logarchiver.BuildInfo{
   134  		Duration: finishTime.Sub(startTime),
   135  		Error:    errors.ErrorToString(err),
   136  	}
   137  	buildLogImageName := name
   138  	if buildLogImageName == "" {
   139  		buildLogImageName = makeImageName(request.StreamName)
   140  	}
   141  	if authInfo != nil {
   142  		buildLogInfo.RequestorUsername = authInfo.Username
   143  	}
   144  	archiveError := b.buildLogArchiver.AddBuildLog(buildLogImageName,
   145  		buildLogInfo, buildLog.Bytes())
   146  	if archiveError != nil {
   147  		b.logger.Printf("Error archiving build log: %s\n", archiveError)
   148  	}
   149  	if err == nil {
   150  		b.logger.Printf("Built image for stream: %s in %s\n",
   151  			request.StreamName, format.Duration(finishTime.Sub(startTime)))
   152  	}
   153  	return img, name, err
   154  }
   155  
   156  func (b *Builder) buildImage(request proto.BuildImageRequest,
   157  	authInfo *srpc.AuthInformation,
   158  	logWriter io.Writer) (*image.Image, string, error) {
   159  	b.disableLock.RLock()
   160  	disableUntil := b.disableBuildRequestsUntil
   161  	b.disableLock.RUnlock()
   162  	if duration := time.Until(disableUntil); duration > 0 {
   163  		return nil, "", fmt.Errorf("builds disabled until %s (for %s)",
   164  			disableUntil.Format(format.TimeFormatSeconds),
   165  			format.Duration(duration))
   166  	}
   167  	if request.ExpiresIn < b.minimumExpiration {
   168  		return nil, "", fmt.Errorf("minimum expiration duration is %s",
   169  			format.Duration(b.minimumExpiration))
   170  	}
   171  	if err := b.WaitForStreamsLoaded(time.Minute); err != nil {
   172  		return nil, "", err
   173  	}
   174  	client, err := dialServer(b.imageServerAddress, time.Minute)
   175  	if err != nil {
   176  		return nil, "", err
   177  	}
   178  	defer client.Close()
   179  	img, name, err := b.build(client, request, authInfo, logWriter)
   180  	if request.ReturnImage {
   181  		return img, "", err
   182  	}
   183  	return nil, name, err
   184  }
   185  
   186  func (b *Builder) buildLocal(builder imageBuilder, client srpc.ClientI,
   187  	request proto.BuildImageRequest, authInfo *srpc.AuthInformation,
   188  	buildLog buildLogger) (*image.Image, error) {
   189  	// Check the namespace to make sure it hasn't changed. This is to catch
   190  	// golang bugs.
   191  	currentNamespace, err := getNamespace()
   192  	if err != nil {
   193  		fmt.Fprintln(buildLog, err)
   194  		return nil, err
   195  	}
   196  	if currentNamespace != b.initialNamespace {
   197  		err := fmt.Errorf("namespace changed from: %s to: %s",
   198  			b.initialNamespace, currentNamespace)
   199  		fmt.Fprintln(buildLog, err)
   200  		return nil, err
   201  	}
   202  	if authInfo == nil {
   203  		b.logger.Printf("Auto building image for stream: %s\n",
   204  			request.StreamName)
   205  	} else {
   206  		b.logger.Printf("%s requested building image for stream: %s\n",
   207  			authInfo.Username, request.StreamName)
   208  	}
   209  	img, err := builder.build(b, client, request, buildLog)
   210  	if err != nil {
   211  		fmt.Fprintf(buildLog, "Error building image: %s\n", err)
   212  		return nil, err
   213  	}
   214  	return img, nil
   215  }
   216  
   217  func (b *Builder) buildOnSlave(client srpc.ClientI,
   218  	request proto.BuildImageRequest, authInfo *srpc.AuthInformation,
   219  	slaveAddress *string, buildLog buildLogger) (*image.Image, error) {
   220  	request.DisableRecursiveBuild = true
   221  	request.ReturnImage = true
   222  	request.StreamBuildLog = true
   223  	if len(request.Variables) < 1 {
   224  		request.Variables = b.variables
   225  	} else if len(b.variables) > 0 {
   226  		variables := make(map[string]string,
   227  			len(b.variables)+len(request.Variables))
   228  		for key, value := range b.variables {
   229  			variables[key] = value
   230  		}
   231  		for key, value := range request.Variables {
   232  			variables[key] = value
   233  		}
   234  		request.Variables = variables
   235  	}
   236  	slave, err := b.slaveDriver.GetSlaveWithTimeout(b.createSlaveTimeout)
   237  	if err != nil {
   238  		return nil, fmt.Errorf("error getting slave: %s", err)
   239  	}
   240  	*slaveAddress = slave.GetClientAddress()
   241  	keepSlave := false
   242  	defer func() {
   243  		if keepSlave {
   244  			slave.Release()
   245  		} else {
   246  			slave.Destroy()
   247  		}
   248  	}()
   249  	if authInfo == nil {
   250  		b.logger.Printf("Auto building image on %s for stream: %s\n",
   251  			slave, request.StreamName)
   252  		fmt.Fprintf(buildLog, "Auto building image on %s for stream: %s\n",
   253  			slave, request.StreamName)
   254  	} else {
   255  		b.logger.Printf("%s requested building image on %s for stream: %s\n",
   256  			authInfo.Username, slave, request.StreamName)
   257  		fmt.Fprintf(buildLog,
   258  			"%s requested building image on %s for stream: %s\n",
   259  			authInfo.Username, slave, request.StreamName)
   260  	}
   261  	var reply proto.BuildImageResponse
   262  	err = buildclient.BuildImage(slave.GetClient(), request, &reply, buildLog)
   263  	copyClientLogs(slave.GetClientAddress(), keepSlave, err, buildLog)
   264  	if err != nil {
   265  		if reply.NeedSourceImage {
   266  			return nil, &buildErrorType{
   267  				error:                     err.Error(),
   268  				needSourceImage:           true,
   269  				sourceImage:               reply.SourceImage,
   270  				sourceImageBuildVariables: reply.SourceImageBuildVariables,
   271  				sourceImageGitCommitId:    reply.SourceImageGitCommitId,
   272  			}
   273  		}
   274  		return nil, err
   275  	}
   276  	return reply.Image, nil
   277  }
   278  
   279  func (b *Builder) buildSomewhere(builder imageBuilder, client srpc.ClientI,
   280  	request proto.BuildImageRequest, authInfo *srpc.AuthInformation,
   281  	slaveAddress *string, buildLog buildLogger) (*image.Image, error) {
   282  	if b.slaveDriver == nil {
   283  		return b.buildLocal(builder, client, request, authInfo, buildLog)
   284  	} else {
   285  		return b.buildOnSlave(client, request, authInfo, slaveAddress, buildLog)
   286  	}
   287  }
   288  
   289  func (b *Builder) buildWithLogger(builder imageBuilder, client srpc.ClientI,
   290  	request proto.BuildImageRequest, authInfo *srpc.AuthInformation,
   291  	startTime time.Time, slaveAddress *string,
   292  	buildLog buildLogger) (*image.Image, string, error) {
   293  	img, err := b.buildSomewhere(builder, client, request, authInfo,
   294  		slaveAddress, buildLog)
   295  	if err != nil {
   296  		var buildError *buildErrorType
   297  		if stderrors.As(err, &buildError) && buildError.needSourceImage {
   298  			if request.DisableRecursiveBuild {
   299  				return nil, "", err
   300  			}
   301  			// Try to build source image.
   302  			expiresIn := time.Hour
   303  			if request.ExpiresIn > 0 {
   304  				expiresIn = request.ExpiresIn
   305  			}
   306  			variables := variablesGetter(buildError.sourceImageBuildVariables)
   307  			variables.merge(request.Variables)
   308  			sourceReq := proto.BuildImageRequest{
   309  				ExpiresIn:    expiresIn,
   310  				GitBranch:    buildError.sourceImageGitCommitId,
   311  				MaxSourceAge: request.MaxSourceAge,
   312  				StreamName:   buildError.sourceImage,
   313  				Variables:    variables,
   314  			}
   315  			if _, _, e := b.build(client, sourceReq, nil, buildLog); e != nil {
   316  				return nil, "", e
   317  			}
   318  			img, err = b.buildSomewhere(builder, client, request, authInfo,
   319  				slaveAddress, buildLog)
   320  		}
   321  	}
   322  	if err != nil {
   323  		return nil, "", err
   324  	}
   325  	if request.ReturnImage {
   326  		return img, "", nil
   327  	}
   328  	if authInfo != nil {
   329  		img.CreatedFor = authInfo.Username
   330  	}
   331  	uploadStartTime := time.Now()
   332  	if name, err := addImage(client, request, img); err != nil {
   333  		fmt.Fprintln(buildLog, err)
   334  		return nil, "", err
   335  	} else {
   336  		finishTime := time.Now()
   337  		fmt.Fprintf(buildLog,
   338  			"Uploaded %s in %s, total build duration: %s\n",
   339  			name, format.Duration(finishTime.Sub(uploadStartTime)),
   340  			format.Duration(finishTime.Sub(startTime)))
   341  		return img, name, nil
   342  	}
   343  }
   344  
   345  func (b *Builder) checkPermission(builder imageBuilder,
   346  	request proto.BuildImageRequest, authInfo *srpc.AuthInformation) error {
   347  	if authInfo == nil || authInfo.HaveMethodAccess {
   348  		if request.ExpiresIn > b.maximumExpirationPrivileged {
   349  			return fmt.Errorf("maximum expiration time is %s for you",
   350  				format.Duration(b.maximumExpirationPrivileged))
   351  		}
   352  		return nil
   353  	}
   354  	if request.ExpiresIn > b.maximumExpiration {
   355  		return fmt.Errorf("maximum expiration time is %s",
   356  			format.Duration(b.maximumExpiration))
   357  	}
   358  	if builder, ok := builder.(*imageStreamType); ok {
   359  		if _, ok := builder.builderUsers[authInfo.Username]; ok {
   360  			return nil
   361  		}
   362  		for _, group := range builder.BuilderGroups {
   363  			if _, ok := authInfo.GroupList[group]; ok {
   364  				return nil
   365  			}
   366  		}
   367  	}
   368  	return errors.New("no permission to build: " + request.StreamName)
   369  }
   370  
   371  func (b *Builder) disableAutoBuilds(disableFor time.Duration) (
   372  	time.Time, error) {
   373  	if b.imageRebuildInterval > 0 {
   374  		if disableFor > b.imageRebuildInterval<<2 {
   375  			return time.Time{},
   376  				fmt.Errorf("cannot disable auto building for more than: %s",
   377  					format.Duration(b.imageRebuildInterval<<2))
   378  		}
   379  	}
   380  	b.disableLock.Lock()
   381  	defer b.disableLock.Unlock()
   382  	b.disableAutoBuildsUntil = time.Now().Add(disableFor)
   383  	return b.disableAutoBuildsUntil, nil
   384  }
   385  
   386  func (b *Builder) disableBuildRequests(disableFor time.Duration) (
   387  	time.Time, error) {
   388  	if disableFor > 24*time.Hour {
   389  		return time.Time{},
   390  			fmt.Errorf("cannot disable build requests for more than: 24h")
   391  	}
   392  	b.disableLock.Lock()
   393  	defer b.disableLock.Unlock()
   394  	b.disableBuildRequestsUntil = time.Now().Add(disableFor)
   395  	return b.disableBuildRequestsUntil, nil
   396  }
   397  
   398  // extendAutoRebuildImages will return true if image lifetimes were extended and
   399  // thus auto rebuilding should be skipped.
   400  func (b *Builder) extendAutoRebuildImages(client srpc.ClientI,
   401  	expiresIn time.Duration) bool {
   402  	if !b.checkToAutoExtend() {
   403  		return false
   404  	}
   405  	for _, streamName := range b.listStreamsToAutoRebuild() {
   406  		imageName, err := imgclient.FindLatestImage(client, streamName, false)
   407  		if err != nil {
   408  			b.logger.Printf("Error finding latest image for stream: %s: %s\n",
   409  				streamName, err)
   410  			continue
   411  		}
   412  		if imageName == "" {
   413  			continue
   414  		}
   415  		err = imgclient.ChangeImageExpiration(client, imageName,
   416  			time.Now().Add(expiresIn))
   417  		if err != nil {
   418  			b.logger.Printf("Error extending expiration for image: %s: %s\n",
   419  				imageName, err)
   420  		}
   421  	}
   422  	return true
   423  }
   424  
   425  func (b *Builder) getCurrentBuildLog(streamName string) ([]byte, error) {
   426  	b.buildResultsLock.RLock()
   427  	defer b.buildResultsLock.RUnlock()
   428  	if result, ok := b.currentBuildInfos[streamName]; !ok {
   429  		return nil, errors.New("unknown image: " + streamName)
   430  	} else {
   431  		log := make([]byte, result.buffer.Len())
   432  		copy(log, result.buffer.Bytes())
   433  		return log, nil
   434  	}
   435  }
   436  
   437  func (b *Builder) getImageBuilder(streamName string) imageBuilder {
   438  	if stream := b.getBootstrapStream(streamName); stream != nil {
   439  		return stream
   440  	}
   441  	if stream := b.getNormalStream(streamName); stream != nil {
   442  		return stream
   443  	}
   444  	// Ensure a nil interface is returned, not a stream with value == nil.
   445  	return nil
   446  }
   447  
   448  func (b *Builder) getImageBuilderWithReload(streamName string) imageBuilder {
   449  	if stream := b.getImageBuilder(streamName); stream != nil {
   450  		return stream
   451  	}
   452  	if err := b.reloadNormalStreamsConfiguration(); err != nil {
   453  		b.logger.Printf("Error reloading configuration: %s\n", err)
   454  		return nil
   455  	}
   456  	return b.getImageBuilder(streamName)
   457  }
   458  
   459  func (b *Builder) getLatestBuildLog(streamName string) ([]byte, error) {
   460  	b.buildResultsLock.RLock()
   461  	defer b.buildResultsLock.RUnlock()
   462  	if result, ok := b.lastBuildResults[streamName]; !ok {
   463  		return nil, errors.New("unknown image: " + streamName)
   464  	} else {
   465  		log := make([]byte, len(result.buildLog))
   466  		copy(log, result.buildLog)
   467  		return log, nil
   468  	}
   469  }
   470  
   471  func (b *Builder) rebuildImage(client srpc.ClientI, streamName string,
   472  	expiresIn time.Duration) {
   473  	_, _, err := b.build(client, proto.BuildImageRequest{
   474  		StreamName: streamName,
   475  		ExpiresIn:  expiresIn,
   476  	},
   477  		nil, nil)
   478  	if err == nil {
   479  		return
   480  	}
   481  	imageName, e := imgclient.FindLatestImage(client, streamName, false)
   482  	if e != nil {
   483  		b.logger.Printf("Error finding latest image: %s: %s\n", streamName, e)
   484  	} else if imageName != "" {
   485  		e := imgclient.ChangeImageExpiration(client, imageName,
   486  			time.Now().Add(expiresIn))
   487  		if e == nil {
   488  			b.logger.Printf("Error building image: %s: %s, extended: %s\n",
   489  				streamName, err, imageName)
   490  			return
   491  		}
   492  		b.logger.Printf(
   493  			"Error building image: %s: %s, failed to extend: %s: %s\n",
   494  			streamName, err, imageName, e)
   495  		return
   496  	}
   497  	b.logger.Printf("Error building image: %s: %s\n", streamName, err)
   498  }
   499  
   500  func (b *Builder) rebuildImages(minInterval time.Duration) {
   501  	if minInterval < 1 {
   502  		return
   503  	}
   504  	client, _ := dialServer(b.imageServerAddress, 0)
   505  	var sleepUntil time.Time
   506  	for ; ; time.Sleep(time.Until(sleepUntil)) {
   507  		b.logger.Println("Starting automatic image build cycle")
   508  		startTime := time.Now()
   509  		sleepUntil = startTime.Add(minInterval)
   510  		if b.extendAutoRebuildImages(client, minInterval*2) {
   511  			continue
   512  		}
   513  		for _, streamName := range b.listStreamsToAutoRebuild() {
   514  			b.rebuildImage(client, streamName, minInterval*2)
   515  		}
   516  		b.logger.Printf("Completed automatic image build cycle in %s\n",
   517  			format.Duration(time.Since(startTime)))
   518  	}
   519  }
   520  
   521  func (err *buildErrorType) Error() string {
   522  	return err.error
   523  }
   524  
   525  func (bl *dualBuildLogger) Bytes() []byte {
   526  	return bl.buffer.Bytes()
   527  }
   528  
   529  func (bl *dualBuildLogger) Write(p []byte) (int, error) {
   530  	return bl.writer.Write(p)
   531  }