func (s *HTTPService) Start(ctx *core.Context, servicePort string) error { server := &http.Server{ Handler: s, // ReadTimeout: 60 * time.Second, // WriteTimeout: 60 * time.Second, MaxHeaderBytes: 1 << 20, ConnState: func(c net.Conn, state http.ConnState) { s.connStates.Inc(state.String()) loc := c.LocalAddr() rem := c.RemoteAddr() core.Log(core.INFO, ctx, "service.Server", "conn", c, "state", state, "local", loc, "remote", rem) }, } core.Log(core.INFO, ctx, "service.HTTPService", "port", servicePort) if !BeGraceful { return server.ListenAndServe() } else { l, err := NewListener(ctx, s, servicePort, false) if err != nil { return err } s.Service.Stopper = func(ctx *core.Context, d time.Duration) error { return l.Stop(d) } server.Serve(l) n := l.Drain(5 * time.Second) if n == 0 { return nil } return fmt.Errorf("killing %d pending requests", n) } }
func (c *Cron) schedule(ctx *core.Context, job *CronJob, checkLimit bool) error { core.Log(core.INFO|CRON, ctx, "Cron.schedule", "job", *job, "name", c.Name) if job.Expression != nil { job.Next = job.Expression.Next(time.Now().UTC()) } c.Lock() //remove existing job with the same id if _, err := c.rem(ctx, job.Id); nil != err { c.Unlock() core.Log(core.WARN|CRON, ctx, "Cron.schedule", "error", err) return err } var err error if checkLimit { count := len(c.Timeline) limit := c.Limit if limit <= count { err = fmt.Errorf("Cron %p %s capacity limit (%d) hit", c, c.Name, limit) core.Log(core.WARN|CRON, ctx, "Cron.schedule", "limit", limit, "error", err, "name", c.Name) } } if err == nil { c.insert(ctx, job) } c.Unlock() return err }
func (l *Listener) Accept() (c net.Conn, err error) { select { case op := <-l.ctl: core.Log(core.INFO, l.ctx, "service.Listener", "op", op) l.mode = op default: } if !l.admin { maxed, n := l.service.Maxed() switch l.mode { case "stop": err := fmt.Errorf("service stopping (%d)", n) core.Log(core.INFO, l.ctx, "service.Listener", "stopping", n) return nil, err case "stopped": err := fmt.Errorf("service stopped") core.Log(core.INFO, l.ctx, "service.Listener", "stopped", n) return nil, err case "": default: core.Log(core.WARN, l.ctx, "service.Listener", "mode", l.mode) } if maxed { err := fmt.Errorf("too many connections: %d", n) core.Log(core.WARN, l.ctx, "service.Listener", "error", err) tooMany(c) return nil, TooManyConnections } } return l.l.Accept() }
func (l *Listener) Stop(d time.Duration) error { core.Log(core.INFO, l.ctx, "service.Listener.Stop") l.ctl <- "stop" n := l.Drain(d) core.Log(core.INFO, l.ctx, "service.Listener.Stop", "pending", n) l.mode = "stopped" return nil }
// Start starts up a goroutine that operates the cron system. // // This method returns after the goroutine is started. // // You need to call this method for the cron system to run any jobs. func (c *Cron) Start(ctx *core.Context) { core.Log(core.INFO|CRON, ctx, "Cron.Start", "name", c.Name) go func() { err := c.start(ctx) // Make sure we log any trouble. if err != nil { core.Log(core.CRIT|CRON, ctx, "Cron.Start", "error", err, "name", c.Name) } }() // ToDo: Try to make sure we are in the select loop before returning here. c.Resume(ctx) }
func (s *Service) HealthDeeper(ctx *core.Context) error { core.Log(core.INFO, ctx, "HealthDeeper") err := s.HealthDeep(ctx) if err == nil { core.Log(core.INFO, ctx, "HealthDeeper", "looking", "storage") storage, err := s.System.PeekStorage(ctx) if err == nil && storage != nil { core.Log(core.INFO, ctx, "HealthDeeper", "checking", "storage") err = storage.Health(ctx) } } core.Log(core.INFO, ctx, "HealthDeeper", "err", err) return err }
func (c *InternalCron) ScheduleEvent(ctx *core.Context, se *ScheduledEvent) error { sched, _, err := ParseSchedule(se.Schedule) if err != nil { return err } var event core.Map if err = json.Unmarshal([]byte(se.Event), &event); err != nil { return err } fn := func(t time.Time) error { loc := ctx.Location() if loc == nil { return errors.New("no location in ctx") } fr, err := loc.ProcessEvent(ctx, event) if err != nil { return err } core.Log(core.DEBUG|CRON, ctx, "InternalCron.ScheduleEvent", "findrules", *fr) return nil } return c.Cron.Add(ctx, se.Id, sched, fn) }
func (s *Service) HealthDeep(ctx *core.Context) error { core.Log(core.INFO, ctx, "HealthDeep") // Just try some Javascript. code := "'go' + 'od'" x, err := core.RunJavascript(ctx, nil, nil, code) if err == nil { var js []byte js, err = json.Marshal(&x) if err == nil { if string(js) != `"good"` { err = fmt.Errorf("Unexpected %s", js) } } } core.Log(core.INFO, ctx, "HealthDeep", "err", err) return err }
func (s *Service) Shutdown(ctx *core.Context) error { core.Log(core.WARN, ctx, "Service.Shutdown") storage, err := s.System.PeekStorage(ctx) if err != nil { return err } if storage != nil { core.Log(core.WARN, ctx, "Service.Shutdown", "closing", "storage") err = storage.Close(ctx) if err != nil { return err } } rc := 0 if err != nil { rc = 1 } os.Exit(rc) return nil }
// control is the low-level method for controlling the Cron instance. // // Gets the lock and sends the given command to the Cron's control channel. func (c *Cron) command(ctx *core.Context, command string) error { core.Log(core.INFO|CRON, ctx, "Cron.command", "command", command, "name", c.Name) c.Lock() if c.control == nil { c.Unlock() return fmt.Errorf("Not started") } c.control <- command c.Unlock() return nil }
func (l *Listener) Drain(d time.Duration) int { pause := 1 * time.Second waited := time.Duration(0) var n int32 for i := 0; true; i++ { _, n = l.service.Maxed() core.Log(core.INFO, l.ctx, "service.Listener.Stop", "loop", i, "pending", n, "waited", waited.String()) if n <= 0 { break } time.Sleep(pause) waited += pause if d <= waited { break } } core.Log(core.INFO, l.ctx, "service.Listener.Stop", "pending", n, "waited", waited.String()) return int(n) }
func (c *InternalCron) Schedule(ctx *core.Context, sw *ScheduledWork) error { sched, _, err := ParseSchedule(sw.Schedule) if err != nil { return err } fn := func(t time.Time) error { buf := strings.NewReader(sw.Body) req, err := http.NewRequest(sw.Method, sw.URL, buf) id := sw.Id if err != nil { core.Log(core.WARN|CRON, ctx, "InternalCron.Schedule", "id", id, "error", err) return err } for header, val := range sw.Headers { req.Header.Set(header, val) } // ToDo: Don't use stock http.DefaultClient. resp, err := http.DefaultClient.Do(req) if err != nil { core.Log(core.WARN|CRON, ctx, "InternalCron.Schedule", "id", id, "error", err) return err } _, err = ioutil.ReadAll(resp.Body) // ToDo: At least log what we got. if err != nil { core.Log(core.WARN|CRON, ctx, "ExternalCron.Schedule", "id", id, "error", err) return err } return nil } return c.Cron.Add(ctx, sw.Id, sched, fn) }
func (c *Cron) run(ctx *core.Context, job *CronJob) { core.Log(core.INFO|CRON, ctx, "Cron.run", "job", *job, "name", c.Name) once := job.Once() err := job.Fn(time.Now()) if err != nil { job.Err = err } if once { } else { // ToDo: Consider an error here. c.schedule(ctx, job, false) } }
func (c *Cron) insert(ctx *core.Context, job *CronJob) int { core.Log(core.INFO|CRON, ctx, "Cron.insert", "job", *job, "name", c.Name) // Assumes we have the lock. at := c.Timeline.Search(job.Next) if at == len(c.Timeline) { c.Timeline = append(c.Timeline, job) } else { c.Timeline = append(c.Timeline, nil) copy(c.Timeline[at+1:], c.Timeline[at:]) c.Timeline[at] = job } c.resetTimer() return at }
// Add creates a new cron job. // // Use the ID to Rem() that job later if you want. F is the work to // be performed. The first argument is the scheduled time for that // invocation, and the second argument is true if the job is a // one-shot job. // // The schedule syntax can have three forms: // // 1. A cron schedule string (supposedly in syntax at // https://en.wikipedia.org/wiki/Cron). // // 2. "!TIME", where TIME is according to RFC3339. // // 3. "+DURATION", where DURATION is a Go Duration // (http://golang.org/pkg/time/#ParseDuration). Examples: "5s" means // "5 seconds", "2m" means "2 minutes", and "1h" means "1 hour". func (c *Cron) Add(ctx *core.Context, id string, schedule string, f func(t time.Time) error) error { core.Log(core.INFO|CRON, ctx, "Cron.Add", "id", id, "schedule", schedule, "name", c.Name) job := CronJob{} job.Id = id job.Schedule = schedule job.Fn = f if core.OneShotSchedule(schedule) { switch schedule[0:1] { case "!": t, err := time.Parse(time.RFC3339, schedule[1:]) if err != nil { return err } job.Next = t case "+": d, err := time.ParseDuration(schedule[1:]) if err != nil { return err } job.Next = time.Now().Add(d) default: return fmt.Errorf("bad one-shot schedule '%s'", schedule) } } else { expr, err := cronexpr.Parse(schedule) if err != nil { return err } job.Expression = expr } future := job.Next.Sub(time.Now()) core.Log(core.DEBUG|CRON, ctx, "Cron.Add", "id", id, "in", future) return c.schedule(ctx, &job, true) }
func (c *Cron) rem(ctx *core.Context, id string) (bool, error) { core.Log(core.INFO|CRON, ctx, "Cron.rem", "id", id, "name", c.Name) found := false for at, job := range c.Timeline { if job.Id == id { copy(c.Timeline[at:], c.Timeline[at+1:]) c.Timeline = c.Timeline[0 : len(c.Timeline)-1] found = true break } } if !found { // log.Printf("Cron.Rem %p %s job %s not found", c, c.Name, id) } return found, nil }
func DWIMURI(ctx *core.Context, uri string) string { // http://en.wikipedia.org/wiki/DWIM // Clean up URI to help with dispatch. // We have "/api" in front of all our APIs can their calls to // make it easier to search/replace those names in code and // docs. given := uri dropParams, _ := regexp.Compile("[?].*") uri = dropParams.ReplaceAllString(uri, "") // Strip any pesky version, which we can ignore at the momemnt. dropVersion, _ := regexp.Compile("^/v?[.0-9]+") uri = dropVersion.ReplaceAllString(uri, "") if !strings.HasPrefix(uri, "/api") { uri = "/api" + uri } core.Log(core.DEBUG, ctx, "service.DWIMURI", "from", given, "to", uri) return uri }
func GetHTTPRequest(ctx *core.Context, r *http.Request) (map[string]interface{}, error) { uri := DWIMURI(ctx, r.URL.String()) core.Log(core.INFO, ctx, "service.GetHTTPRequest", "method", r.Method, "uri", uri) m := make(map[string]interface{}) parseQuery := func(q string) error { qvs, err := url.ParseQuery(q) if err != nil { return err } for p, vs := range qvs { if len(vs) != 1 { return fmt.Errorf("need exactly one value (not %d) for %s", len(vs), p) } var v interface{} if v, err = parseParameter(p, vs[0]); err != nil { return err } m[p] = v } return nil } if err := parseQuery(r.URL.RawQuery); err != nil { return nil, err } var err error switch uri { case "/api/json", "/api/yaml": var err error switch r.Method { case "POST": var js []byte if js, err = ioutil.ReadAll(r.Body); err != nil { return nil, err } switch uri { case "/api/json": if err = json.Unmarshal(js, &m); err != nil { return nil, err } case "/api/yaml": if err = UnmarshalYAML(js, &m); err != nil { return nil, err } } given, have := m["uri"] if !have { return nil, fmt.Errorf("no uri given by %s", js) } var ok bool uri, ok = given.(string) if !ok { return nil, fmt.Errorf("need a string uri, not a %T (%s)", given, given) } default: return nil, errors.New("no uri given") } default: m["uri"] = r.URL.Path switch r.Method { case "POST": var js []byte if js, err = ioutil.ReadAll(r.Body); err != nil { return nil, err } if js[0] == '{' { // If the body looks like JSON, treat it as JSON. if err = json.Unmarshal(js, &m); err != nil { return nil, err } } else if MaybeYAML(js) { if err = UnmarshalYAML(js, &m); err != nil { return nil, err } } else { // Try to parse a form. if err := parseQuery(string(js)); err != nil { return nil, err } } } } io.Copy(ioutil.Discard, r.Body) core.Log(core.INFO, ctx, "service.GetHTTPRequest", "m", m) return m, nil }
func (s *Service) ProcessRequest(ctx *core.Context, m map[string]interface{}, out io.Writer) (map[string]interface{}, error) { core.Log(core.INFO, ctx, "service.ProcessRequest", "m", m) timer := core.NewTimer(ctx, "ProcessRequest") defer func() { elapsed := timer.Stop() core.Point(ctx, "rulesservice", "requestTime", elapsed/1000, "Microseconds") }() u, given := m["uri"] if !given { return nil, fmt.Errorf("No uri.") } uri := DWIMURI(ctx, u.(string)) switch uri { // case "/api/sys/config": // mutationStore,have,_ := getStringParam(m, "MutationStore", false) // if have { // return fmt.Errorf("You can't do that.") // } // current s.System.GetConfig() // logging,have,_ := getStringParam(m, "logging", false) // if have { // current.Logging = logging // } // err := StoreConfig(current) // if err != nil { // return err // } // err = s.System.SetConfig(current) // if err != nil { // return err // } // return `{"status":"happy"}` case "/api/version": fmt.Fprintf(out, `{"version":"%s","go":"%s"}`, APIVersion, runtime.Version()) case "/api/health": // Let's have a simple URI. fmt.Fprintf(out, `{"status":"good"}`) case "/api/health/shallow": fmt.Fprintf(out, `{"status":"good"}`) case "/api/health/deep": if err := s.HealthDeep(ctx); err != nil { return m, nil } fmt.Fprintf(out, `{"status":"good"}`) case "/api/health/deeper": if err := s.HealthDeeper(ctx); err != nil { return m, nil } fmt.Fprintf(out, `{"status":"good"}`) case "/api/sys/control": // Temporary implementation so I can test other things. control, given, _ := GetStringParam(m, "control", false) target := s.System.Control() if given { err := json.Unmarshal([]byte(control), target) if err != nil { return nil, err } s.System.SetControl(*target) target = s.System.Control() js, err := json.Marshal(target) if err != nil { return nil, err } out.Write([]byte(fmt.Sprintf(`{"result":"okay","control":%s}`, js))) } else { js, err := json.Marshal(target) if err != nil { return nil, err } out.Write(js) } case "/api/sys/params": control, given, _ := GetStringParam(m, "params", false) target := core.SystemParameters.Copy() if given { err := json.Unmarshal([]byte(control), target) if err != nil { return nil, err } core.SystemParameters = target target = core.SystemParameters js, err := json.Marshal(target) if err != nil { return nil, err } out.Write([]byte(fmt.Sprintf(`{"result":"okay","params":%s}`, js))) } else { js, err := json.Marshal(target) if err != nil { return nil, err } out.Write(js) } case "/api/sys/cachedlocations": js, err := json.Marshal(map[string]interface{}{ "locations": s.System.GetCachedLocations(ctx), }) if err != nil { return nil, err } out.Write(js) case "/api/sys/loccontrol": control, given, _ := GetStringParam(m, "control", false) ctl := s.System.Control() target := ctl.DefaultLocControl if given { err := json.Unmarshal([]byte(control), target) if err != nil { return nil, err } js, err := json.Marshal(target) if err != nil { return nil, err } ctl.DefaultLocControl = target out.Write([]byte(fmt.Sprintf(`{"result":"okay","control":%s}`, js))) } else { js, err := json.Marshal(target) if err != nil { return nil, err } out.Write(js) } case "/api/sys/stats": stats, err := s.System.GetStats(ctx) if err != nil { return nil, err } js, err := json.Marshal(&stats) if err != nil { return nil, err } out.Write(js) case "/api/sys/runtime": m, err := GetRuntimes(ctx) if nil != err { return nil, err } // m["stats"] = GetStats() js, err := json.Marshal(m) if err != nil { return nil, err } out.Write(js) case "/api/sys/util/nowsecs": fmt.Fprintf(out, `{"secs":%d}`, core.NowSecs()) case "/api/sys/util/js": code, _, err := GetStringParam(m, "code", true) bs := make(core.Bindings) x, err := core.RunJavascript(ctx, &bs, nil, code) if err != nil { return m, err } js, err := json.Marshal(&x) if err != nil { return m, err } fmt.Fprintf(out, `{"result":%s}`, js) case "/api/sys/util/setJavascriptTestValue": js, _, err := GetStringParam(m, "value", true) if err != nil { return nil, err } var x interface{} if err := json.Unmarshal([]byte(js), &x); err != nil { return nil, err } core.JavascriptTestValue = x fmt.Fprintf(out, `{"result":%s}`, js) case "/api/sys/admin/panic": // No required params message, _, _ := GetStringParam(m, "message", false) panic(message) case "/api/sys/admin/sleep": // Option d=duration duration, given, _ := GetStringParam(m, "d", false) if !given { duration = "1s" } d, err := time.ParseDuration(duration) if err != nil { return nil, err } time.Sleep(d) out.Write([]byte(fmt.Sprintf(`{"slept":"%s"}`, d.String()))) case "/api/sys/admin/shutdown": duration, given, _ := GetStringParam(m, "d", false) if given && s.Stopper == nil { return nil, errors.New("no Stopper for given duration") } if !given { duration = "1s" } d, err := time.ParseDuration(duration) if err != nil { return nil, err } go func() { if s.Stopper != nil { core.Log(core.INFO, ctx, "/api/admin/shutdown", "Stopper", true) if err := s.Stopper(ctx, d); err != nil { core.Log(core.ERROR, ctx, "/api/admin/shutdown", "error", err) } } core.Log(core.INFO, ctx, "/api/admin/shutdown", "Stopper", false) if err := s.Shutdown(ctx); err != nil { core.Log(core.ERROR, ctx, "/api/admin/shutdown", "error", err) } }() out.Write([]byte(`{"status":"okay"}`)) case "/api/sys/admin/gcpercent": percent, _, _ := GetStringParam(m, "percent", true) n, err := strconv.Atoi(percent) if err != nil { return nil, err } was := debug.SetGCPercent(n) fmt.Fprintf(out, `{"status":"okay","was":%d,"now":%d}`, was, n) case "/api/sys/admin/freemem": debug.FreeOSMemory() fmt.Fprintf(out, `{"status":"okay"}`) case "/api/sys/admin/purgeslurpcache": core.SlurpCache.Purge() fmt.Fprintf(out, `{"status":"okay"}`) case "/api/sys/admin/purgehttppcache": core.HTTPClientCache.Purge() fmt.Fprintf(out, `{"status":"okay"}`) case "/api/sys/admin/purgecaches": core.SlurpCache.Purge() core.HTTPClientCache.Purge() fmt.Fprintf(out, `{"status":"okay"}`) case "/api/sys/admin/gc": runtime.GC() fmt.Fprintf(out, `{"status":"okay"}`) case "/api/sys/admin/heapdump": filename, _, _ := GetStringParam(m, "filename", false) if filename == "" { filename = "heap.dump" } f, err := os.Create(filename) if err != nil { return nil, err } debug.WriteHeapDump(f.Fd()) if err = f.Close(); err != nil { return nil, err } fmt.Fprintf(out, `{"status":"okay","filename":"%s"}`, filename) case "/api/sys/util/match": // Params: fact or event,pattern fact, have, err := getMapParam(m, "fact", false) if err != nil { return nil, err } if !have { // Maybe we were given an 'event'. Fine. fact, _, err = getMapParam(m, "event", true) } if err != nil { return nil, err } pattern, _, err := getMapParam(m, "pattern", true) if err != nil { return nil, err } bss, err := core.Matches(ctx, pattern, fact) if err != nil { return nil, err } js, err := json.Marshal(&bss) if err != nil { return nil, err } out.Write(js) case "/api/sys/admin/timers/names": // No params names := core.GetTimerNames() js, err := json.Marshal(names) if err != nil { return nil, err } out.Write(js) case "/api/sys/admin/timers/get": // Param: "name", optional "after" int, optional "limit" int name, _, err := GetStringParam(m, "name", true) if err != nil { return nil, err } after, given, _ := GetStringParam(m, "after", false) if !given { after = "-1" } aft, err := strconv.Atoi(after) if err != nil { return nil, err } limit, given := m["limit"] if !given { limit = float64(-1) } history := core.GetTimerHistory(name, aft, int(limit.(float64))) js, err := json.Marshal(history) if err != nil { return m, err } out.Write(js) case "/api/sys/storage/get": // For testing storage, err := s.System.PeekStorage(ctx) if err != nil { return nil, err } var acc string switch impl := storage.(type) { case *core.MemStorage: state := impl.State(ctx) js, err := json.Marshal(&state) if err != nil { acc = fmt.Sprintf(`{"type":"%T","error":"%s"}`, storage, err.Error()) } else { acc = fmt.Sprintf(`{"type":"%T","state":%s}`, storage, js) } default: acc = fmt.Sprintf(`{"type":"%T"}`, storage) } if _, err = out.Write([]byte(acc)); err != nil { core.Log(core.ERROR, ctx, "/api/sys/storage", "error", err) } case "/api/sys/storage/set": // For testing state, _, err := getMapParam(m, "state", true) if err != nil { return nil, err } storage, err := s.System.PeekStorage(ctx) if err != nil { return nil, err } switch impl := storage.(type) { case *core.MemStorage: mms := make(map[string]map[string]string) for loc, pairs := range state { m, ok := pairs.(map[string]interface{}) if !ok { return nil, fmt.Errorf("bad pairs %#v (%T)", pairs, pairs) } locPairs := make(map[string]string) for id, val := range m { s, ok := val.(string) if !ok { return nil, fmt.Errorf("bad value %#v (%T)", val, val) } locPairs[id] = s } mms[loc] = locPairs } impl.SetState(ctx, mms) out.Write([]byte(`{"status":"okay"}`)) default: return nil, fmt.Errorf(`{"error":"set not supported for %T"}`, storage) } case "/api/sys/util/batch": // For testing // Execute a batch of requests batch, given := m["requests"] if !given { return nil, errors.New("missing 'requests' parameter") } var err error switch xs := batch.(type) { case []interface{}: _, err = out.Write([]byte("[")) for i, x := range xs { if 0 < i { _, err = out.Write([]byte(",")) } switch m := x.(type) { case map[string]interface{}: _, err = s.ProcessRequest(ctx, m, out) if err != nil { problem := fmt.Sprintf(`{"error":"%s"}`, err.Error()) _, err = out.Write([]byte(problem)) } default: problem := fmt.Sprintf(`"bad type %T"`, x) _, err = out.Write([]byte(problem)) } } _, err = out.Write([]byte("]")) default: return nil, errors.New("'requests' not an array") } if err != nil { core.Log(core.ERROR, ctx, "/api/sys/batch", "error", err) } case "/api/loc/admin/size": location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } n, err := s.System.GetSize(ctx, location) if err != nil { return nil, err } fmt.Fprintf(out, `{"size":%d}`, n) case "/api/loc/admin/stats": location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } stats, err := s.System.GetLocationStats(ctx, location) if err != nil { return nil, err } js, err := json.Marshal(&stats) if err != nil { return nil, err } if _, err = out.Write(js); err != nil { core.Log(core.ERROR, ctx, "/api/loc/stats", "warning", err) } case "/api/loc/util/js": location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } code, _, err := GetStringParam(m, "code", true) encoding, provided, err := GetStringParam(m, "encoding", false) if provided { code, err = core.DecodeString(encoding, code) if err != nil { return nil, err } } bs := make(core.Bindings) var props map[string]interface{} ctl := s.System.LocControl(ctx, location) if ctl != nil { props = ctl.CodeProps } libraries := make([]string, 0, 0) libs, given := m["libraries"] if given { switch vv := libs.(type) { case []interface{}: for _, lib := range vv { switch s := lib.(type) { case string: libraries = append(libraries, s) default: err := fmt.Errorf("Bad library type %T (value= %#v)", lib, lib) core.Log(core.UERR, ctx, "/api/loc/util/js", "error", err) return nil, err } } default: err := fmt.Errorf("Bad 'libraries' type %T (value= %#v)", libs, libs) core.Log(core.UERR, ctx, "/api/loc/util/js", "error", err) return nil, err } } x, err := s.System.RunJavascript(ctx, location, code, libraries, &bs, props) if err != nil { return m, err } js, err := json.Marshal(&x) if err != nil { return m, err } fmt.Fprintf(out, `{"result":%s}`, js) case "/api/loc/admin/create": // Params: location location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } created, err := s.System.CreateLocation(ctx, location) if err != nil { return nil, err } if !created { return nil, fmt.Errorf("%s already exists", location) } if _, err = out.Write([]byte(`{"status":"okay"}`)); err != nil { core.Log(core.ERROR, ctx, "/api/loc/admin/create", "warning", err) } case "/api/loc/admin/clear": // Params: location location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } err = s.System.ClearLocation(ctx, location) if err != nil { return nil, err } if _, err = out.Write([]byte(`{"status":"okay"}`)); err != nil { core.Log(core.ERROR, ctx, "/api/loc/admin/clear", "warning", err) } case "/api/loc/admin/updatedmem": // Params: location location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } updated, err := s.System.GetLastUpdatedMem(ctx, location) if err != nil { return nil, err } resp := fmt.Sprintf(`{"lastUpdated":"%s","source":"memory"}`, updated) if _, err = out.Write([]byte(resp)); err != nil { core.Log(core.INFO, ctx, "/api/loc/admin/updatedmem", "warning", err) } case "/api/loc/admin/delete": // Params: location location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } err = s.System.DeleteLocation(ctx, location) if err != nil { return nil, err } if _, err = out.Write([]byte(`{"status":"okay"}`)); err != nil { core.Log(core.ERROR, ctx, "/api/loc/admin/delete", "warning", err) } case "/api/loc/events/ingest": // Params: event event, _, err := getMapParam(m, "event", true) if err != nil { return nil, err } location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } // ToDo: Not this. js, err := json.Marshal(event) if err != nil { return nil, err } ctx.LogAccumulatorLevel = core.EVERYTHING work, err := s.System.ProcessEvent(ctx, location, string(js)) if err != nil { return nil, err } js, err = json.Marshal(work) if err != nil { return nil, err } s := fmt.Sprintf(`{"id":"%s","result":%s}`, ctx.Id(), js) core.Log(core.INFO, ctx, "/api/loc/events/ingest", "got", s) if _, err = out.Write([]byte(s)); err != nil { core.Log(core.ERROR, ctx, "/api/loc/events/ingest", "warning", err) } case "/api/loc/events/retry": // Params: work workStr, _, err := GetStringParam(m, "work", true) if err != nil { return nil, err } location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } fr := core.FindRules{} err = json.Unmarshal([]byte(workStr), &fr) if err != nil { return nil, err } ctx.LogAccumulatorLevel = core.EVERYTHING // ToDo: Support number of steps to take. err = s.System.RetryEventWork(ctx, location, &fr) js, err := json.Marshal(fr) if err != nil { return nil, err } s := fmt.Sprintf(`{"id":"%s","result":%s}`, ctx.Id(), js) core.Log(core.INFO, ctx, "/api/loc/events/retry", "got", s) if _, err = out.Write([]byte(s)); err != nil { core.Log(core.ERROR, ctx, "/api/loc/events/retry", "warning", err) } case "/api/loc/facts/add": // Params: fact fact, _, err := getMapParam(m, "fact", true) if err != nil { return nil, err } location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } id, _, err := GetStringParam(m, "id", false) // ToDo: Not this. js, err := json.Marshal(fact) if err != nil { return nil, err } id, err = s.System.AddFact(ctx, location, id, string(js)) if err != nil { return nil, err } m := map[string]interface{}{"id": id} js, err = json.Marshal(&m) if err != nil { return nil, err } if _, err = out.Write(js); err != nil { core.Log(core.ERROR, ctx, "/api/loc/facts/add", "warning", err) } case "/api/loc/facts/rem": // Params: id id, _, err := GetStringParam(m, "id", true) if err != nil { return nil, err } location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } rid, err := s.System.RemFact(ctx, location, id) if err != nil { return nil, err } m := map[string]interface{}{"removed": rid, "given": id} js, err := json.Marshal(&m) if err != nil { return nil, err } if _, err = out.Write(js); err != nil { core.Log(core.ERROR, ctx, "/api/loc/facts/rem", "warning", err) } case "/api/loc/facts/get": // Params: id id, _, err := GetStringParam(m, "id", true) if err != nil { return nil, err } location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } js, err := s.System.GetFact(ctx, location, id) if err != nil { return nil, err } bs := []byte(fmt.Sprintf(`{"fact":%s,"id":"%s"}`, js, id)) if _, err = out.Write(bs); err != nil { core.Log(core.ERROR, ctx, "/api/loc/facts/get", "warning", err) } case "/api/loc/facts/search": // Params: pattern, inherited pattern, _, err := getMapParam(m, "pattern", true) if err != nil { return nil, err } location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } includedInherited, _, err := getBoolParam(m, "inherited", false) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } // ToDo: Not this. js, err := json.Marshal(pattern) if err != nil { return nil, err } sr, err := s.System.SearchFacts(ctx, location, string(js), includedInherited) if err != nil { return nil, err } _, take := m["take"] if take { // Warning: Not (yet) atomic! for _, found := range sr.Found { _, err := s.System.RemFact(ctx, location, found.Id) if err != nil { core.Log(core.ERROR, ctx, "service.ProcessRequest", "app_tag", "/api/loc/facts/search", "error", err, "RemFact", found.Id) } // ToDo: Something with error. } } js, err = json.Marshal(sr) if err != nil { return nil, err } if _, err = out.Write(js); err != nil { core.Log(core.ERROR, ctx, "/api/loc/facts/take", "warning", err) } case "/api/loc/facts/take": // Params: pattern m["uri"] = "/api/loc/facts/search" m["take"] = true s.ProcessRequest(ctx, m, out) case "/api/loc/facts/replace": // Params: pattern, fact // Really a 'take' followed by a 'add'. m["uri"] = "/api/loc/facts/search" m["take"] = true core.Log(core.INFO, ctx, "service.ProcessRequest", "app_tag", "/api/loc/facts/replace", "phase", "take") s.ProcessRequest(ctx, m, ioutil.Discard) core.Log(core.INFO, ctx, "service.ProcessRequest", "app_tag", "/api/loc/facts/replace", "phase", "add") m["uri"] = "/api/loc/facts/add" s.ProcessRequest(ctx, m, out) case "/api/loc/facts/query": // Params: query query, _, err := getMapParam(m, "query", true) if err != nil { return nil, err } location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } // ToDo: Not this. js, err := json.Marshal(query) if err != nil { return nil, err } qr, err := s.System.Query(ctx, location, string(js)) if err != nil { return nil, err } js, err = json.Marshal(qr) if err != nil { return nil, err } if _, err = out.Write(js); err != nil { core.Log(core.ERROR, ctx, "/api/loc/facts/query", "warning", err) } case "/api/loc/rules/list": // Params: inherited location, _, err := GetStringParam(m, "location", true) if nil != err { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } includedInherited, _, err := getBoolParam(m, "inherited", false) if err != nil { return nil, err } ss, err := s.System.ListRules(ctx, location, includedInherited) if nil != err { return nil, err } js, err := json.Marshal(map[string][]string{"ids": ss}) if nil != err { return nil, err } if _, err = out.Write(js); err != nil { core.Log(core.ERROR, ctx, "/api/loc/rules/list", "warning", err) } case "/api/loc/rules/add": // Params: rule rule, _, err := getMapParam(m, "rule", true) if err != nil { return nil, err } location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } id, _, err := GetStringParam(m, "id", false) // ToDo: Not this. js, err := json.Marshal(rule) if err != nil { return nil, err } id, err = s.System.AddRule(ctx, location, id, string(js)) if err != nil { return nil, err } m := map[string]interface{}{"id": id} js, err = json.Marshal(&m) if err != nil { return nil, err } if _, err = out.Write(js); err != nil { core.Log(core.ERROR, ctx, "/api/loc/rules/add", "warning", err) } case "/api/loc/rules/rem": // Params: id id, _, err := GetStringParam(m, "id", true) if err != nil { return nil, err } location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } rid, err := s.System.RemRule(ctx, location, id) if err != nil { return nil, err } m := map[string]interface{}{"removed": rid, "given": id} js, err := json.Marshal(&m) if err != nil { return nil, err } if _, err = out.Write(js); err != nil { core.Log(core.ERROR, ctx, "/api/loc/rules/rem", "warning", err) } case "/api/loc/rules/disable": // Params: id id, _, err := GetStringParam(m, "id", true) if err != nil { return nil, err } location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } err = s.System.EnableRule(ctx, location, id, false) if err != nil { return nil, err } m := map[string]interface{}{"disabled": id} js, err := json.Marshal(&m) if err != nil { return nil, err } if _, err = out.Write(js); err != nil { core.Log(core.ERROR, ctx, "/api/loc/rules/disable", "warning", err) } case "/api/loc/rules/enable": // Params: id id, _, err := GetStringParam(m, "id", true) if err != nil { return nil, err } location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } err = s.System.EnableRule(ctx, location, id, true) if err != nil { return nil, err } m := map[string]interface{}{"enabled": id} js, err := json.Marshal(&m) if err != nil { return nil, err } if _, err = out.Write(js); err != nil { core.Log(core.ERROR, ctx, "/api/loc/rules/enable", "warning", err) } case "/api/loc/rules/enabled": // Params: id id, _, err := GetStringParam(m, "id", true) if err != nil { return nil, err } location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } enabled, err := s.System.RuleEnabled(ctx, location, id) if err != nil { return nil, err } m := map[string]interface{}{"ruleId": id, "enabled": enabled} js, err := json.Marshal(&m) if err != nil { return nil, err } if _, err = out.Write(js); err != nil { core.Log(core.ERROR, ctx, "/api/loc/rules/enabled", "warning", err) } case "/api/loc/parents": // Params: none means get; "set=[x,y]" means set. location, _, err := GetStringParam(m, "location", true) if err != nil { return nil, err } if err := s.checkLocal(ctx, location); err != nil { return nil, err } js, given, err := GetStringParam(m, "set", false) if given { var parents []string if err = json.Unmarshal([]byte(js), &parents); err != nil { return nil, err } _, err := s.System.SetParents(ctx, location, parents) if err != nil { return nil, err } js := fmt.Sprintf(`{"result": %s}`, js) if _, err = out.Write([]byte(js)); err != nil { core.Log(core.ERROR, ctx, "/api/loc/parents/set", "warning", err) } } else { ps, err := s.System.GetParents(ctx, location) if err != nil { return nil, err } bs, err := json.Marshal(&ps) if err != nil { return nil, err } bs = []byte(fmt.Sprintf(`{"result":%s}`, bs)) if _, err = out.Write(bs); err != nil { core.Log(core.ERROR, ctx, "/api/loc/parents/get", "warning", err) } } default: return nil, fmt.Errorf("Unknown URI '%s'", u) } return nil, nil }
// start launches the instance's processing loop. func (c *Cron) start(ctx *core.Context) error { core.Log(core.INFO|CRON, ctx, "Cron.start", "name", c.Name) // Make a control channel. c.Lock() if c.control != nil { c.Unlock() return fmt.Errorf("Already started") } c.control = make(chan string, 10) c.Unlock() // Either a broadcast or a local command can suspend or resume the loop. // Might want separate state. suspendedLocally := false // We'll receive a broadcast on this channel. broadcast, suspendedByBroadcast := c.broadcaster.Get() if suspendedByBroadcast { suspendedLocally = true c.stopTimerLocked() } LOOP: for { select { case <-(*broadcast): // Channel closed. Toggle our state. // Get the (new) control channel since our pointer now points to a dead one. broadcast, suspendedByBroadcast = c.broadcaster.Get() if suspendedByBroadcast { suspendedLocally = true c.stopTimerLocked() } else if suspendedLocally { suspendedLocally = false c.resetTimerLocked() } case command := <-c.control: var err error switch command { case "pause": c.stopTimerLocked() time.Sleep(c.PauseDuration) c.resetTimerLocked() case "suspend": suspendedLocally = true c.stopTimerLocked() continue case "resume": if suspendedLocally { suspendedLocally = false c.resetTimerLocked() } case "kill": // Danger. Can't restart from the control channel. c.stopTimerLocked() break LOOP default: err = fmt.Errorf("Cron %p %s unknown command '%s'", c, c.Name, command) core.Log(core.WARN|CRON, ctx, "Cron.start", "error", err, "name", c.Name) return err } case <-c.timer.C: // Let's check how well our timer is working. // delta := time.Now().Sub(c.timerTarget) now := time.Now() c.Lock() if 0 < len(c.Timeline) { job := c.Timeline[0] ready := !now.Before(job.Next) if ready { // Danger. ToDo: Be more careful c.Timeline = c.Timeline[1:] go func(job *CronJob) { c.run(ctx, job) }(job) c.resetTimer() } } c.Unlock() // elapsed := time.Now().Sub(now) } } c.Lock() c.control = nil c.Unlock() return nil }
func (s *HTTPService) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.incPending(true) ctx := s.Ctx.SubContext() timer := core.NewTimer(ctx, "ServeHTTP") defer func() { if r.Body != nil { if err := r.Body.Close(); err != nil { core.Log(core.WARN, nil, "ServerHTTP", "error", err, "when", "Close") } } s.incPending(false) timer.Stop() }() m, err := GetHTTPRequest(ctx, r) if err != nil { core.Log(core.ERROR, ctx, "service.ServeHTTP", "error", err) protest(ctx, err, w) return } switch DWIMURI(ctx, m["uri"].(string)) { // Sorry. case "/api/sys/admin/connstates": counts := s.connStates.Get() js, err := json.Marshal(&counts) if err != nil { w.Write([]byte(err.Error())) return } w.Write(js) return case "/api/sys/admin/pending": max, given := m["max"] if given { str, ok := max.(string) if !ok { protest(ctx, err, w) return } n, err := strconv.Atoi(str) if err != nil { protest(ctx, err, w) return } s.SetMaxPending(int32(n)) } maxed, pending := s.Maxed() w.Write([]byte(fmt.Sprintf(`{"pending":%d, "max":%d, "maxed":%v}`, pending, s.MaxPending(), maxed) + "\n")) return } _, err = s.Service.ProcessRequest(ctx, m, w) if err != nil { if redirect, is := err.(*Redirect); is { core.Log(core.INFO, ctx, "service.ServeHTTP", "redirect", redirect.To) http.Redirect(w, r, "http://"+redirect.To, 301) return } else { core.Log(core.ERROR, ctx, "service.ServeHTTP", "error", err) protest(ctx, err, w) } } w.Write([]byte{byte('\n')}) }
func (s *HTTPService) SetMaxPending(max int32) { core.Log(core.INFO, nil, "service.HTTPService", "maxPending", max) atomic.StoreInt32(&s.maxPending, max) }
func AddHooks(ctx *core.Context, cronner Cronner, state core.State) error { add := func(ctx *core.Context, state core.State, id string, fact core.Map, loading bool) error { if cronner.Persistent() && loading { return nil } schedule, err := getSchedule(ctx, fact) if err != nil { return err } if schedule == "" { return nil } if cronner == nil { return errors.New("no cron available") } location := ctx.Location().Name core.Log(core.INFO|CRON, ctx, "addHook", "id", id, "location", location, "schedule", schedule) event := fmt.Sprintf(`{"trigger!":"%s"}`, id) se := &ScheduledEvent{ Id: id, Event: event, Schedule: schedule, } if err = cronner.ScheduleEvent(ctx, se); err != nil { core.Log(core.WARN|CRON, ctx, "addHook", "id", id, "error", err) return err } return nil } state.AddHook(add) rem := func(ctx *core.Context, state core.State, id string) error { core.Log(core.INFO|CRON, ctx, "remHook", "id", id) // Sad that we have to get the whole fact. // Yikes! The caller of this hook already has the state lock! fact, err := state.Get(ctx, id) if err != nil { return err } if fact == nil { core.Log(core.WARN|CRON, ctx, "remHook", "missing", id) return nil } schedule, err := getSchedule(ctx, fact) if err != nil { return err } if schedule == "" { return nil } if cronner == nil { return errors.New("no cron available") } _, err = cronner.Rem(ctx, id) return err } state.RemHook(rem) return nil }