github.com/cloudfoundry-attic/ltc@v0.0.0-20151123212628-098adc7919fc/droplet_runner/droplet_runner.go (about)

     1  package droplet_runner
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/cloudfoundry-incubator/bbs/models"
    13  	"github.com/cloudfoundry-incubator/buildpack_app_lifecycle"
    14  	"github.com/cloudfoundry-incubator/ltc/app_examiner"
    15  	"github.com/cloudfoundry-incubator/ltc/app_runner"
    16  	"github.com/cloudfoundry-incubator/ltc/blob_store"
    17  	"github.com/cloudfoundry-incubator/ltc/blob_store/blob"
    18  	"github.com/cloudfoundry-incubator/ltc/config"
    19  	"github.com/cloudfoundry-incubator/ltc/task_runner"
    20  )
    21  
    22  const (
    23  	DropletStack  = "cflinuxfs2"
    24  	DropletRootFS = "preloaded:" + DropletStack
    25  )
    26  
    27  //go:generate counterfeiter -o fake_droplet_runner/fake_droplet_runner.go . DropletRunner
    28  type DropletRunner interface {
    29  	UploadBits(dropletName, uploadPath string) error
    30  	BuildDroplet(taskName, dropletName, buildpackUrl string, environment map[string]string, memoryMB, cpuWeight, diskMB int) error
    31  	LaunchDroplet(appName, dropletName, startCommand string, startArgs []string, appEnvironmentParams app_runner.AppEnvironmentParams) error
    32  	ListDroplets() ([]Droplet, error)
    33  	RemoveDroplet(dropletName string) error
    34  	ExportDroplet(dropletName string) (io.ReadCloser, error)
    35  	ImportDroplet(dropletName, dropletPath string) error
    36  }
    37  
    38  type Droplet struct {
    39  	Name    string
    40  	Created time.Time
    41  	Size    int64
    42  }
    43  
    44  type dropletRunner struct {
    45  	appRunner       app_runner.AppRunner
    46  	taskRunner      task_runner.TaskRunner
    47  	config          *config.Config
    48  	blobStore       BlobStore
    49  	appExaminer     app_examiner.AppExaminer
    50  	proxyConfReader ProxyConfReader
    51  }
    52  
    53  //go:generate counterfeiter -o fake_blob_store/fake_blob_store.go . BlobStore
    54  type BlobStore interface {
    55  	List() ([]blob.Blob, error)
    56  	Delete(path string) error
    57  	Upload(path string, contents io.ReadSeeker) error
    58  	Download(path string) (io.ReadCloser, error)
    59  
    60  	blob_store.DropletStore
    61  }
    62  
    63  type annotation struct {
    64  	DropletSource struct {
    65  		DropletName string `json:"droplet_name"`
    66  	} `json:"droplet_source"`
    67  }
    68  
    69  //go:generate counterfeiter -o fake_proxyconf_reader/fake_proxyconf_reader.go . ProxyConfReader
    70  type ProxyConfReader interface {
    71  	ProxyConf() (ProxyConf, error)
    72  }
    73  
    74  type ProxyConf struct {
    75  	HTTPProxy  string `json:"http_proxy"`
    76  	HTTPSProxy string `json:"https_proxy"`
    77  	NoProxy    string `json:"no_proxy"`
    78  }
    79  
    80  func New(appRunner app_runner.AppRunner, taskRunner task_runner.TaskRunner, config *config.Config, blobStore BlobStore, appExaminer app_examiner.AppExaminer, proxyConfReader ProxyConfReader) DropletRunner {
    81  	return &dropletRunner{
    82  		appRunner:       appRunner,
    83  		taskRunner:      taskRunner,
    84  		config:          config,
    85  		blobStore:       blobStore,
    86  		appExaminer:     appExaminer,
    87  		proxyConfReader: proxyConfReader,
    88  	}
    89  }
    90  
    91  func (dr *dropletRunner) ListDroplets() ([]Droplet, error) {
    92  	blobs, err := dr.blobStore.List()
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	droplets := []Droplet{}
    98  	for _, blob := range blobs {
    99  		if strings.HasSuffix(blob.Path, "-droplet.tgz") {
   100  			dropletName := strings.Replace(blob.Path, "-droplet.tgz", "", 1)
   101  			droplets = append(droplets, Droplet{Name: dropletName, Size: blob.Size, Created: blob.Created})
   102  		}
   103  	}
   104  
   105  	return droplets, nil
   106  }
   107  
   108  func (dr *dropletRunner) UploadBits(dropletName, uploadPath string) error {
   109  	uploadFile, err := os.Open(uploadPath)
   110  	if err != nil {
   111  		return err
   112  	}
   113  	defer uploadFile.Close()
   114  
   115  	return dr.blobStore.Upload(dropletName+"-bits.zip", uploadFile)
   116  }
   117  
   118  func (dr *dropletRunner) BuildDroplet(taskName, dropletName, buildpackUrl string, environment map[string]string, memoryMB, cpuWeight, diskMB int) error {
   119  	builderConfig := buildpack_app_lifecycle.NewLifecycleBuilderConfig([]string{buildpackUrl}, true, false)
   120  
   121  	action := models.WrapAction(&models.SerialAction{
   122  		Actions: []*models.Action{
   123  			models.WrapAction(&models.DownloadAction{
   124  				From: "http://file-server.service.cf.internal:8080/v1/static/cell-helpers/cell-helpers.tgz",
   125  				To:   "/tmp",
   126  				User: "vcap",
   127  			}),
   128  			models.WrapAction(&models.DownloadAction{
   129  				From: "http://file-server.service.cf.internal:8080/v1/static/buildpack_app_lifecycle/buildpack_app_lifecycle.tgz",
   130  				To:   "/tmp",
   131  				User: "vcap",
   132  			}),
   133  			dr.blobStore.DownloadAppBitsAction(dropletName),
   134  			dr.blobStore.DeleteAppBitsAction(dropletName),
   135  			models.WrapAction(&models.RunAction{
   136  				Path: "/bin/chmod",
   137  				Dir:  "/tmp/app",
   138  				Args: []string{"-R", "a+X", "."},
   139  				User: "vcap",
   140  			}),
   141  			models.WrapAction(&models.RunAction{
   142  				Path: "/tmp/builder",
   143  				Dir:  "/",
   144  				Args: builderConfig.Args(),
   145  				User: "vcap",
   146  			}),
   147  			dr.blobStore.UploadDropletAction(dropletName),
   148  		},
   149  	})
   150  
   151  	environment["CF_STACK"] = DropletStack
   152  	environment["MEMORY_LIMIT"] = fmt.Sprintf("%dM", memoryMB)
   153  
   154  	proxyConf, err := dr.proxyConfReader.ProxyConf()
   155  	if err != nil {
   156  		return err
   157  	}
   158  	environment["http_proxy"] = proxyConf.HTTPProxy
   159  	environment["https_proxy"] = proxyConf.HTTPSProxy
   160  	environment["no_proxy"] = proxyConf.NoProxy
   161  
   162  	createTaskParams := task_runner.NewCreateTaskParams(
   163  		action,
   164  		taskName,
   165  		DropletRootFS,
   166  		"lattice",
   167  		"BUILD",
   168  		environment,
   169  		[]*models.SecurityGroupRule{},
   170  		memoryMB,
   171  		cpuWeight,
   172  		diskMB,
   173  	)
   174  
   175  	return dr.taskRunner.CreateTask(createTaskParams)
   176  }
   177  
   178  func (dr *dropletRunner) LaunchDroplet(appName, dropletName string, startCommand string, startArgs []string, appEnvironmentParams app_runner.AppEnvironmentParams) error {
   179  	dropletAnnotation := annotation{}
   180  	dropletAnnotation.DropletSource.DropletName = dropletName
   181  
   182  	annotationBytes, err := json.Marshal(dropletAnnotation)
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	if appEnvironmentParams.EnvironmentVariables == nil {
   188  		appEnvironmentParams.EnvironmentVariables = map[string]string{}
   189  	}
   190  
   191  	appEnvironmentParams.EnvironmentVariables["PWD"] = "/home/vcap"
   192  	appEnvironmentParams.EnvironmentVariables["TMPDIR"] = "/home/vcap/tmp"
   193  	appEnvironmentParams.WorkingDir = "/home/vcap"
   194  
   195  	proxyConf, err := dr.proxyConfReader.ProxyConf()
   196  	if err != nil {
   197  		return err
   198  	}
   199  	appEnvironmentParams.EnvironmentVariables["http_proxy"] = proxyConf.HTTPProxy
   200  	appEnvironmentParams.EnvironmentVariables["https_proxy"] = proxyConf.HTTPSProxy
   201  	appEnvironmentParams.EnvironmentVariables["no_proxy"] = proxyConf.NoProxy
   202  
   203  	appParams := app_runner.CreateAppParams{
   204  		AppEnvironmentParams: appEnvironmentParams,
   205  
   206  		Name:         appName,
   207  		RootFS:       DropletRootFS,
   208  		StartCommand: "/tmp/launcher",
   209  		AppArgs: []string{
   210  			"/home/vcap/app",
   211  			strings.Join(append([]string{startCommand}, startArgs...), " "),
   212  			"{}",
   213  		},
   214  
   215  		Annotation: string(annotationBytes),
   216  
   217  		Setup: models.WrapAction(&models.SerialAction{
   218  			LogSource: appName,
   219  			Actions: []*models.Action{
   220  				models.WrapAction(&models.DownloadAction{
   221  					From: "http://file-server.service.cf.internal:8080/v1/static/cell-helpers/cell-helpers.tgz",
   222  					To:   "/tmp",
   223  					User: "vcap",
   224  				}),
   225  				dr.blobStore.DownloadDropletAction(dropletName),
   226  			},
   227  		}),
   228  	}
   229  
   230  	return dr.appRunner.CreateApp(appParams)
   231  }
   232  
   233  func dropletMatchesAnnotation(dropletName string, a annotation) bool {
   234  	return a.DropletSource.DropletName == dropletName
   235  }
   236  
   237  func (dr *dropletRunner) RemoveDroplet(dropletName string) error {
   238  	apps, err := dr.appExaminer.ListApps()
   239  	if err != nil {
   240  		return err
   241  	}
   242  	for _, app := range apps {
   243  		dropletAnnotation := annotation{}
   244  		if err := json.Unmarshal([]byte(app.Annotation), &dropletAnnotation); err != nil {
   245  			continue
   246  		}
   247  
   248  		if dropletMatchesAnnotation(dropletName, dropletAnnotation) {
   249  			return fmt.Errorf("app %s was launched from droplet", app.ProcessGuid)
   250  		}
   251  	}
   252  
   253  	blobs, err := dr.blobStore.List()
   254  	if err != nil {
   255  		return err
   256  	}
   257  
   258  	found := false
   259  	for _, blob := range blobs {
   260  		if strings.HasPrefix(blob.Path, dropletName+"-") {
   261  			if err := dr.blobStore.Delete(blob.Path); err != nil {
   262  				return err
   263  			} else {
   264  				found = true
   265  			}
   266  		}
   267  	}
   268  
   269  	if !found {
   270  		return errors.New("droplet not found")
   271  	}
   272  
   273  	return nil
   274  }
   275  
   276  func (dr *dropletRunner) ExportDroplet(dropletName string) (io.ReadCloser, error) {
   277  	dropletReader, err := dr.blobStore.Download(dropletName + "-droplet.tgz")
   278  	if err != nil {
   279  		return nil, fmt.Errorf("droplet not found: %s", err)
   280  	}
   281  
   282  	return dropletReader, err
   283  }
   284  
   285  func (dr *dropletRunner) ImportDroplet(dropletName, dropletPath string) error {
   286  	dropletFile, err := os.Open(dropletPath)
   287  	if err != nil {
   288  		return err
   289  	}
   290  	defer dropletFile.Close()
   291  
   292  	if err := dr.blobStore.Upload(dropletName+"-droplet.tgz", dropletFile); err != nil {
   293  		return err
   294  	}
   295  
   296  	return nil
   297  }