github.com/asifdxtreme/cli@v6.1.3-0.20150123051144-9ead8700b4ae+incompatible/cf/commands/application/start.go (about) 1 package application 2 3 import ( 4 "fmt" 5 "os" 6 "regexp" 7 "strconv" 8 "strings" 9 "time" 10 11 . "github.com/cloudfoundry/cli/cf/i18n" 12 13 "github.com/cloudfoundry/cli/cf" 14 "github.com/cloudfoundry/cli/cf/api" 15 "github.com/cloudfoundry/cli/cf/api/app_instances" 16 "github.com/cloudfoundry/cli/cf/api/applications" 17 "github.com/cloudfoundry/cli/cf/command_metadata" 18 "github.com/cloudfoundry/cli/cf/configuration/core_config" 19 "github.com/cloudfoundry/cli/cf/errors" 20 "github.com/cloudfoundry/cli/cf/models" 21 "github.com/cloudfoundry/cli/cf/requirements" 22 "github.com/cloudfoundry/cli/cf/terminal" 23 "github.com/cloudfoundry/loggregatorlib/logmessage" 24 "github.com/codegangsta/cli" 25 ) 26 27 const ( 28 DefaultStagingTimeout = 15 * time.Minute 29 DefaultStartupTimeout = 5 * time.Minute 30 DefaultPingerThrottle = 5 * time.Second 31 ) 32 33 const LogMessageTypeStaging = "STG" 34 35 type Start struct { 36 ui terminal.UI 37 config core_config.Reader 38 appDisplayer ApplicationDisplayer 39 appReq requirements.ApplicationRequirement 40 appRepo applications.ApplicationRepository 41 appInstancesRepo app_instances.AppInstancesRepository 42 logRepo api.LogsRepository 43 44 StartupTimeout time.Duration 45 StagingTimeout time.Duration 46 PingerThrottle time.Duration 47 } 48 49 type ApplicationStarter interface { 50 SetStartTimeoutInSeconds(timeout int) 51 ApplicationStart(app models.Application, orgName string, spaceName string) (updatedApp models.Application, err error) 52 } 53 54 type ApplicationStagingWatcher interface { 55 ApplicationWatchStaging(app models.Application, orgName string, spaceName string, startCommand func(app models.Application) (models.Application, error)) (updatedApp models.Application, err error) 56 } 57 58 func NewStart(ui terminal.UI, config core_config.Reader, appDisplayer ApplicationDisplayer, appRepo applications.ApplicationRepository, appInstancesRepo app_instances.AppInstancesRepository, logRepo api.LogsRepository) (cmd *Start) { 59 cmd = new(Start) 60 cmd.ui = ui 61 cmd.config = config 62 cmd.appDisplayer = appDisplayer 63 cmd.appRepo = appRepo 64 cmd.appInstancesRepo = appInstancesRepo 65 cmd.logRepo = logRepo 66 67 cmd.PingerThrottle = DefaultPingerThrottle 68 69 if os.Getenv("CF_STAGING_TIMEOUT") != "" { 70 duration, err := strconv.ParseInt(os.Getenv("CF_STAGING_TIMEOUT"), 10, 64) 71 if err != nil { 72 cmd.ui.Failed(T("invalid value for env var CF_STAGING_TIMEOUT\n{{.Err}}", 73 map[string]interface{}{"Err": err})) 74 } 75 cmd.StagingTimeout = time.Duration(duration) * time.Minute 76 } else { 77 cmd.StagingTimeout = DefaultStagingTimeout 78 } 79 80 if os.Getenv("CF_STARTUP_TIMEOUT") != "" { 81 duration, err := strconv.ParseInt(os.Getenv("CF_STARTUP_TIMEOUT"), 10, 64) 82 if err != nil { 83 cmd.ui.Failed(T("invalid value for env var CF_STARTUP_TIMEOUT\n{{.Err}}", 84 map[string]interface{}{"Err": err})) 85 } 86 cmd.StartupTimeout = time.Duration(duration) * time.Minute 87 } else { 88 cmd.StartupTimeout = DefaultStartupTimeout 89 } 90 91 return 92 } 93 94 func (cmd *Start) Metadata() command_metadata.CommandMetadata { 95 return command_metadata.CommandMetadata{ 96 Name: "start", 97 ShortName: "st", 98 Description: T("Start an app"), 99 Usage: T("CF_NAME start APP_NAME"), 100 } 101 } 102 103 func (cmd *Start) GetRequirements(requirementsFactory requirements.Factory, c *cli.Context) (reqs []requirements.Requirement, err error) { 104 if len(c.Args()) != 1 { 105 cmd.ui.FailWithUsage(c) 106 } 107 108 cmd.appReq = requirementsFactory.NewApplicationRequirement(c.Args()[0]) 109 110 reqs = []requirements.Requirement{requirementsFactory.NewLoginRequirement(), cmd.appReq} 111 return 112 } 113 114 func (cmd *Start) Run(c *cli.Context) { 115 cmd.ApplicationStart(cmd.appReq.GetApplication(), cmd.config.OrganizationFields().Name, cmd.config.SpaceFields().Name) 116 } 117 118 func (cmd *Start) ApplicationStart(app models.Application, orgName, spaceName string) (updatedApp models.Application, err error) { 119 if app.State == "started" { 120 cmd.ui.Say(terminal.WarningColor(T("App ") + app.Name + T(" is already started"))) 121 return 122 } 123 124 return cmd.ApplicationWatchStaging(app, orgName, spaceName, func(app models.Application) (models.Application, error) { 125 cmd.ui.Say(T("Starting app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.CurrentUser}}...", 126 map[string]interface{}{ 127 "AppName": terminal.EntityNameColor(app.Name), 128 "OrgName": terminal.EntityNameColor(orgName), 129 "SpaceName": terminal.EntityNameColor(spaceName), 130 "CurrentUser": terminal.EntityNameColor(cmd.config.Username())})) 131 132 state := "STARTED" 133 return cmd.appRepo.Update(app.Guid, models.AppParams{State: &state}) 134 }) 135 } 136 137 func (cmd *Start) ApplicationWatchStaging(app models.Application, orgName, spaceName string, start func(app models.Application) (models.Application, error)) (updatedApp models.Application, err error) { 138 stopLoggingChan := make(chan bool, 1) 139 loggingStartedChan := make(chan bool) 140 doneLoggingChan := make(chan bool) 141 142 go cmd.tailStagingLogs(app, loggingStartedChan, doneLoggingChan) 143 go func() { 144 <-stopLoggingChan 145 cmd.logRepo.Close() 146 }() 147 <-loggingStartedChan // block until we have established connection to Loggregator 148 149 updatedApp, apiErr := start(app) 150 if apiErr != nil { 151 cmd.ui.Failed(apiErr.Error()) 152 return 153 } 154 155 isStaged := cmd.waitForInstancesToStage(updatedApp) 156 stopLoggingChan <- true 157 <-doneLoggingChan 158 159 cmd.ui.Say("") 160 161 if !isStaged { 162 cmd.ui.Failed(fmt.Sprintf("%s failed to stage within %f minutes", app.Name, cmd.StagingTimeout.Minutes())) 163 } 164 165 cmd.waitForOneRunningInstance(updatedApp) 166 cmd.ui.Say(terminal.HeaderColor(T("\nApp started\n"))) 167 cmd.ui.Say("") 168 cmd.ui.Ok() 169 170 //detectedstartcommand on first push is not present until starting completes 171 startedApp, apiErr := cmd.appRepo.Read(updatedApp.Name) 172 if err != nil { 173 cmd.ui.Failed(apiErr.Error()) 174 return 175 } 176 177 var appStartCommand string 178 if app.Command == "" { 179 appStartCommand = startedApp.DetectedStartCommand 180 } else { 181 appStartCommand = startedApp.Command 182 } 183 184 cmd.ui.Say(T("\nApp {{.AppName}} was started using this command `{{.Command}}`\n", 185 map[string]interface{}{ 186 "AppName": terminal.EntityNameColor(startedApp.Name), 187 "Command": appStartCommand, 188 })) 189 190 cmd.appDisplayer.ShowApp(startedApp, orgName, spaceName) 191 return 192 } 193 194 func (cmd *Start) SetStartTimeoutInSeconds(timeout int) { 195 cmd.StartupTimeout = time.Duration(timeout) * time.Second 196 } 197 198 func simpleLogMessageOutput(logMsg *logmessage.LogMessage) (msgText string) { 199 msgText = string(logMsg.GetMessage()) 200 reg, err := regexp.Compile("[\n\r]+$") 201 if err != nil { 202 return 203 } 204 msgText = reg.ReplaceAllString(msgText, "") 205 return 206 } 207 208 func (cmd Start) tailStagingLogs(app models.Application, startChan, doneChan chan bool) { 209 onConnect := func() { 210 startChan <- true 211 } 212 213 err := cmd.logRepo.TailLogsFor(app.Guid, onConnect, func(msg *logmessage.LogMessage) { 214 if msg.GetSourceName() == LogMessageTypeStaging { 215 cmd.ui.Say(simpleLogMessageOutput(msg)) 216 } 217 }) 218 219 if err != nil { 220 cmd.ui.Warn(T("Warning: error tailing logs")) 221 cmd.ui.Say("%s", err) 222 startChan <- true 223 } 224 225 close(doneChan) 226 } 227 228 func isStagingError(err error) bool { 229 httpError, ok := err.(errors.HttpError) 230 return ok && httpError.ErrorCode() == errors.APP_NOT_STAGED 231 } 232 233 func (cmd Start) waitForInstancesToStage(app models.Application) bool { 234 stagingStartTime := time.Now() 235 _, err := cmd.appInstancesRepo.GetInstances(app.Guid) 236 237 for isStagingError(err) && time.Since(stagingStartTime) < cmd.StagingTimeout { 238 cmd.ui.Wait(cmd.PingerThrottle) 239 _, err = cmd.appInstancesRepo.GetInstances(app.Guid) 240 } 241 242 if err != nil && !isStagingError(err) { 243 cmd.ui.Say("") 244 cmd.ui.Failed(T("{{.Err}}\n\nTIP: use '{{.Command}}' for more information", 245 map[string]interface{}{ 246 "Err": err.Error(), 247 "Command": terminal.CommandColor(fmt.Sprintf("%s logs %s --recent", cf.Name(), app.Name))})) 248 } 249 250 if time.Since(stagingStartTime) >= cmd.StagingTimeout { 251 return false 252 } 253 254 return true 255 } 256 257 func (cmd Start) waitForOneRunningInstance(app models.Application) { 258 startupStartTime := time.Now() 259 260 for { 261 if time.Since(startupStartTime) > cmd.StartupTimeout { 262 cmd.ui.Failed(fmt.Sprintf(T("Start app timeout\n\nTIP: use '{{.Command}}' for more information", 263 map[string]interface{}{ 264 "Command": terminal.CommandColor(fmt.Sprintf("%s logs %s --recent", cf.Name(), app.Name))}))) 265 return 266 } 267 268 count, err := cmd.fetchInstanceCount(app.Guid) 269 if err != nil { 270 cmd.ui.Wait(cmd.PingerThrottle) 271 continue 272 } 273 274 cmd.ui.Say(instancesDetails(count)) 275 276 if count.running > 0 { 277 return 278 } 279 280 if count.flapping > 0 { 281 cmd.ui.Failed(fmt.Sprintf(T("Start unsuccessful\n\nTIP: use '{{.Command}}' for more information", 282 map[string]interface{}{"Command": terminal.CommandColor(fmt.Sprintf("%s logs %s --recent", cf.Name(), app.Name))}))) 283 return 284 } 285 286 cmd.ui.Wait(cmd.PingerThrottle) 287 } 288 } 289 290 type instanceCount struct { 291 running int 292 starting int 293 flapping int 294 down int 295 total int 296 } 297 298 func (cmd Start) fetchInstanceCount(appGuid string) (instanceCount, error) { 299 count := instanceCount{} 300 301 instances, apiErr := cmd.appInstancesRepo.GetInstances(appGuid) 302 if apiErr != nil { 303 return instanceCount{}, apiErr 304 } 305 306 count.total = len(instances) 307 308 for _, inst := range instances { 309 switch inst.State { 310 case models.InstanceRunning: 311 count.running++ 312 case models.InstanceStarting: 313 count.starting++ 314 case models.InstanceFlapping: 315 count.flapping++ 316 case models.InstanceDown: 317 count.down++ 318 } 319 } 320 321 return count, nil 322 } 323 324 func instancesDetails(count instanceCount) string { 325 details := []string{fmt.Sprintf(T("{{.RunningCount}} of {{.TotalCount}} instances running", 326 map[string]interface{}{"RunningCount": count.running, "TotalCount": count.total}))} 327 328 if count.starting > 0 { 329 details = append(details, fmt.Sprintf(T("{{.StartingCount}} starting", 330 map[string]interface{}{"StartingCount": count.starting}))) 331 } 332 333 if count.down > 0 { 334 details = append(details, fmt.Sprintf(T("{{.DownCount}} down", 335 map[string]interface{}{"DownCount": count.down}))) 336 } 337 338 if count.flapping > 0 { 339 details = append(details, fmt.Sprintf(T("{{.FlappingCount}} failing", 340 map[string]interface{}{"FlappingCount": count.flapping}))) 341 } 342 343 return strings.Join(details, ", ") 344 }