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 }