// WaitService waits for service/s to reach a particular desired state within the designated timeout func (f *Facade) WaitService(ctx datastore.Context, dstate service.DesiredState, timeout time.Duration, serviceIDs ...string) error { glog.V(4).Infof("Facade.WaitService (%s)", dstate) // error out if the desired state is invalid if dstate.String() == "unknown" { return fmt.Errorf("desired state unknown") } // waitstatus is the return result for the awaiting service type waitstatus struct { ServiceID string Err error } cancel := make(chan interface{}) processing := make(map[string]struct{}) done := make(chan waitstatus) defer close(cancel) for _, serviceID := range serviceIDs { // spawn a goroutine to wait for each particular service svc, err := f.GetService(ctx, serviceID) if err != nil { glog.Errorf("Error while getting service %s: %s", serviceID, err) return err } processing[svc.ID] = struct{}{} go func(s *service.Service) { err := zkAPI(f).WaitService(s, dstate, cancel) // this blocks until we pass a waitstatus object into the channel or we get a signal to cancel select { case done <- waitstatus{s.ID, err}: case <-cancel: } glog.V(1).Infof("Finished waiting for %s (%s) to %s: %s", s.Name, s.ID, dstate, err) }(svc) } timeoutC := time.After(timeout) for len(processing) > 0 { // wait for all the services to return within the desired timeout select { case result := <-done: delete(processing, result.ServiceID) if result.Err != nil { glog.Errorf("Error while waiting for service %s to %s: %s", result.ServiceID, dstate, result.Err) return result.Err } case <-timeoutC: return fmt.Errorf("timeout") } } return nil }
// ScheduleService changes a service's desired state and returns the number of affected services func (f *Facade) ScheduleService(ctx datastore.Context, serviceID string, autoLaunch bool, desiredState service.DesiredState) (int, error) { glog.V(4).Infof("Facade.ScheduleService %s (%s)", serviceID, desiredState) if desiredState.String() == "unknown" { return 0, fmt.Errorf("desired state unknown") } else if desiredState != service.SVCStop { if err := f.validateService(ctx, serviceID, autoLaunch); err != nil { glog.Errorf("Facade.ScheduleService validate service result: %s", err) return 0, err } } affected := 0 visitor := func(svc *service.Service) error { if svc.ID != serviceID && svc.Launch == commons.MANUAL { return nil } else if svc.DesiredState == int(desiredState) { return nil } switch desiredState { case service.SVCRestart: // shutdown all service instances var states []servicestate.ServiceState if err := zkAPI(f).GetServiceStates(svc.PoolID, &states, svc.ID); err != nil { return err } for _, state := range states { if err := zkAPI(f).StopServiceInstance(svc.PoolID, state.HostID, state.ID); err != nil { return err } } svc.DesiredState = int(service.SVCRun) default: svc.DesiredState = int(desiredState) } if err := f.updateService(ctx, svc); err != nil { glog.Errorf("Facade.ScheduleService update service %s (%s): %s", svc.Name, svc.ID, err) return err } affected++ return nil } err := f.walkServices(ctx, serviceID, autoLaunch, visitor) return affected, err }