// ControlWorkSpec makes changes to a work spec that are not directly // reflected in the work spec definition. This allows work specs to // be paused or to stop generating new continuous jobs. // ControlWorkSpecOptions has a complete listing of what can be done. func (jobs *JobServer) ControlWorkSpec(workSpecName string, options map[string]interface{}) (bool, string, error) { var ( cwsOptions ControlWorkSpecOptions decoder *mapstructure.Decoder err error metadata mapstructure.Metadata workSpec coordinate.WorkSpec wsMeta coordinate.WorkSpecMeta ) workSpec, err = jobs.Namespace.WorkSpec(workSpecName) if err == nil { // We care a lot about "false" vs. not present for // these things. Manually create the decoder. config := mapstructure.DecoderConfig{ Result: &cwsOptions, Metadata: &metadata, } decoder, err = mapstructure.NewDecoder(&config) } if err == nil { err = decoder.Decode(options) } // Get the existing metadata, then change it based on what // we got provided if err == nil { wsMeta, err = workSpec.Meta(false) } if err == nil { for _, key := range metadata.Keys { switch key { case "Continuous": wsMeta.Continuous = cwsOptions.Continuous case "Status": wsMeta.Paused = cwsOptions.Status == Paused case "Weight": wsMeta.Weight = cwsOptions.Weight case "Interval": wsMeta.Interval = time.Duration(cwsOptions.Interval) * time.Second case "MaxRunning": wsMeta.MaxRunning = cwsOptions.MaxRunning } } } if err == nil { err = workSpec.SetMeta(wsMeta) } return err == nil, "", err }
// ExtractAddWorkUnitItem converts an arbitrary object (which really // should be a cborpc.PythonTuple or a list) into an AddWorkUnitItem. func ExtractAddWorkUnitItem(obj interface{}) (result AddWorkUnitItem, err error) { var ( decoder *mapstructure.Decoder haveMetadata bool havePriority bool kvpList []interface{} kvpMap map[string]interface{} str string bstr []byte ok bool ) // If we got handed a string (or a byte string) turn it into // a work unit with no data if str, ok = obj.(string); ok { result.Key = str result.Data = make(map[string]interface{}) return } if bstr, ok = obj.([]byte); ok { result.Key = string(bstr) result.Data = make(map[string]interface{}) return } // Otherwise obj must be a tuple (or a list) if kvpList, ok = cborrpc.Detuplify(obj); !ok { err = ErrWorkUnitNotList return } // Turn that list into a string-keyed map if len(kvpList) < 2 { err = ErrWorkUnitTooShort return } kvpMap = make(map[string]interface{}) kvpMap["key"] = kvpList[0] kvpMap["data"] = kvpList[1] if len(kvpList) >= 3 && kvpList[2] != nil { kvpMap["metadata"] = kvpList[2] haveMetadata = true } if len(kvpList) >= 4 && kvpList[3] != nil { kvpMap["priority"] = kvpList[3] havePriority = true } // Now we can invoke mapstructure config := mapstructure.DecoderConfig{ DecodeHook: cborrpc.DecodeBytesAsString, Result: &result, } decoder, err = mapstructure.NewDecoder(&config) if err == nil { err = decoder.Decode(kvpMap) } if err == nil && haveMetadata && !havePriority { // See if the caller passed metadata["priority"] // instead of an explicit priority field. if priority, ok := result.Metadata["priority"]; ok { if result.Priority, ok = priority.(float64); !ok { err = ErrBadPriority } } } return }
// GetWorkUnits retrieves the keys and data dictionaries for some number // of work units. If options contains "work_unit_keys", those specific // work units are retrieved; otherwise the work units are based on // which of GetWorkUnitsOptions are present. // // On success, the return value is a slice of cborrpc.PythonTuple // objects where each contains the work unit key as a byte slice and // the data dictionary. func (jobs *JobServer) GetWorkUnits(workSpecName string, options map[string]interface{}) ([]interface{}, string, error) { var workUnits map[string]coordinate.WorkUnit gwuOptions := GetWorkUnitsOptions{ Limit: 1000, } spec, err := jobs.Namespace.WorkSpec(workSpecName) var decoder *mapstructure.Decoder if err == nil { config := mapstructure.DecoderConfig{ DecodeHook: mapstructure.ComposeDecodeHookFunc(gwuStateHook, cborrpc.DecodeBytesAsString), Result: &gwuOptions, } decoder, err = mapstructure.NewDecoder(&config) } if err == nil { err = decoder.Decode(options) } if err == nil { query := coordinate.WorkUnitQuery{ Names: gwuOptions.WorkUnitKeys, } if gwuOptions.WorkUnitKeys == nil { query.PreviousName = gwuOptions.Start query.Limit = gwuOptions.Limit } if gwuOptions.WorkUnitKeys == nil && gwuOptions.State != nil { query.Statuses = make([]coordinate.WorkUnitStatus, len(gwuOptions.State)) for i, state := range gwuOptions.State { query.Statuses[i], err = translateWorkUnitStatus(state) if err != nil { break } } } if err == nil { workUnits, err = spec.WorkUnits(query) } } if err != nil { return nil, "", err } // The marshalled result is a list of pairs of (key, data). var result []interface{} for name, unit := range workUnits { var data map[string]interface{} attempt, err := unit.ActiveAttempt() if err == nil && attempt != nil { data, err = attempt.Data() } if err == nil && data == nil { data, err = unit.Data() } if err != nil { return nil, "", err } tuple := cborrpc.PythonTuple{Items: []interface{}{[]byte(name), data}} result = append(result, tuple) } return result, "", nil }
// ExtractAddWorkUnitItem converts an arbitrary object (which really // should be a cborpc.PythonTuple or a list) into an AddWorkUnitItem. func ExtractAddWorkUnitItem(obj interface{}, now time.Time) (result AddWorkUnitItem, err error) { var ( decoder *mapstructure.Decoder kvpList []interface{} kvpMap map[string]interface{} ok bool ) // If we got handed a string (or a byte string) turn it into // a work unit with no data if result.Key, ok = cborrpc.Destringify(obj); ok { result.Data = make(map[string]interface{}) return } // Otherwise obj must be a tuple (or a list) if kvpList, ok = cborrpc.Detuplify(obj); !ok { err = ErrWorkUnitNotList return } // Turn that list into a string-keyed map if len(kvpList) < 2 { err = ErrWorkUnitTooShort return } kvpMap = make(map[string]interface{}) kvpMap["key"] = kvpList[0] kvpMap["data"] = kvpList[1] if len(kvpList) >= 3 && kvpList[2] != nil { kvpMap["meta"] = kvpList[2] } // Now we can invoke mapstructure, on this modified struct var partial struct { Key string Data map[string]interface{} Meta AddWorkUnitMeta } config := mapstructure.DecoderConfig{ DecodeHook: cborrpc.DecodeBytesAsString, Result: &partial, } decoder, err = mapstructure.NewDecoder(&config) if err == nil { err = decoder.Decode(kvpMap) } // If that worked, copy data over to a real result if err == nil { result.Key = partial.Key result.Data = partial.Data result.Meta = partial.Meta.ToMeta(now) } // If a priority value was given in the list, that overrides // what got extracted if err == nil && len(kvpList) >= 4 && kvpList[3] != nil { if result.Meta.Priority, ok = kvpList[3].(float64); !ok { err = ErrBadPriority } } return }
// Execute takes the callback and executes it via the LIFX protocol func Execute(callback map[string]interface{}) { var ( cb lifxCallback decoder *mapstructure.Decoder err error ) log.WithField(`request`, callback).Debug(`Sending to LIFX`) decoder, err = mapstructure.NewDecoder(&mapstructure.DecoderConfig{ DecodeHook: mapstructure.StringToTimeDurationHookFunc(), Result: &cb, }) if err != nil { log.WithField(`error`, err).Error(`Initializing LIFX callback decoder`) return } if err = decoder.Decode(callback); err != nil { log.WithFields(log.Fields{ `request`: callback, `error`: err, }).Error(`Decoding LIFX callback`) return } boblights.cancel(&cb) if _, ok := callback[`power`]; ok { if len(cb.Lights) == 0 && len(cb.Groups) == 0 { if err = client.SetPowerDuration(cb.Power, cb.PowerDuration); err != nil { log.WithField(`error`, err).Error(`Setting power`) } } if len(cb.Lights) > 0 { for _, label := range cb.Lights { light, e := client.GetLightByLabel(label) if e != nil { log.WithFields(log.Fields{ `label`: label, `error`: e, }).Error(`Finding light`) continue } if e = light.SetPowerDuration(cb.Power, cb.PowerDuration); e != nil { log.WithFields(log.Fields{ `label`: label, `error`: e, }).Error(`Setting power`) continue } } } if len(cb.Groups) > 0 { for _, label := range cb.Groups { group, e := client.GetGroupByLabel(label) if e != nil { log.WithFields(log.Fields{ `label`: label, `error`: e, }).Error(`Finding group`) continue } if e = group.SetPowerDuration(cb.Power, cb.PowerDuration); e != nil { log.WithFields(log.Fields{ `label`: label, `error`: e, }).Error(`Setting power`) continue } } } } if _, ok := callback[`color`]; ok { if len(cb.Lights) == 0 && len(cb.Groups) == 0 { if err = client.SetColor(cb.Color, cb.ColorDuration); err != nil { log.WithField(`error`, err).Error(`Setting color`) } } if len(cb.Lights) > 0 { for _, label := range cb.Lights { light, err := client.GetLightByLabel(label) if err != nil { log.WithFields(log.Fields{ `label`: label, `error`: err, }).Error(`Finding light`) continue } if err = light.SetColor(cb.Color, cb.ColorDuration); err != nil { log.WithFields(log.Fields{ `label`: label, `error`: err, }).Error(`Setting color`) continue } } } if len(cb.Groups) > 0 { for _, label := range cb.Groups { group, err := client.GetGroupByLabel(label) if err != nil { log.WithFields(log.Fields{ `label`: label, `error`: err, }).Error(`Finding group`) continue } if err = group.SetColor(cb.Color, cb.ColorDuration); err != nil { log.WithFields(log.Fields{ `label`: label, `error`: err, }).Error(`Setting color`) continue } } } } if _, ok := callback[`boblight`]; ok { // Recommended LIFX device rate limit rateLimit := time.Second / 20 // If requested rate limit is longer than minimum, use it if cb.Boblight.RateLimit > rateLimit { rateLimit = cb.Boblight.RateLimit } if len(cb.Lights) == 0 && len(cb.Groups) == 0 { lights, err := client.GetLights() if err != nil { return } for _, light := range lights { boblights.add(light, cb.Boblight.Lights, rateLimit) } } if len(cb.Lights) > 0 { for _, label := range cb.Lights { light, err := client.GetLightByLabel(label) if err != nil { log.WithFields(log.Fields{ `label`: label, `error`: err, }).Error(`Finding light`) continue } boblights.add(light, cb.Boblight.Lights, rateLimit) } } if len(cb.Groups) > 0 { for _, label := range cb.Groups { group, err := client.GetGroupByLabel(label) if err != nil { log.WithFields(log.Fields{ `label`: label, `error`: err, }).Error(`Finding group`) continue } for _, light := range group.Lights() { boblights.add(light, cb.Boblight.Lights, rateLimit) } } } } }
// Execute takes the callback and executes it via the LIFX protocol func Execute(callback map[string]interface{}) { var ( cb lifxCallback decoder *mapstructure.Decoder err error ) log.WithField(`request`, callback).Debug(`Sending to LIFX`) decoder, err = mapstructure.NewDecoder(&mapstructure.DecoderConfig{ DecodeHook: mapstructure.StringToTimeDurationHookFunc(), Result: &cb, }) if err != nil { log.WithField(`error`, err).Error(`Initializing LIFX callback decoder`) return } if err = decoder.Decode(callback); err != nil { log.WithFields(log.Fields{ `request`: callback, `error`: err, }).Error(`Decoding LIFX callback`) return } if _, ok := callback[`power`]; ok { if len(cb.Lights) == 0 { if err = client.SetPowerDuration(cb.Power, cb.PowerDuration); err != nil { log.WithField(`error`, err).Error(`Setting power`) } } else { for _, label := range cb.Lights { light, err := client.GetLightByLabel(label) if err != nil { log.WithFields(log.Fields{ `label`: label, `error`: err, }).Error(`Finding light`) continue } if err = light.SetPowerDuration(cb.Power, cb.PowerDuration); err != nil { log.WithFields(log.Fields{ `label`: label, `error`: err, }).Error(`Setting power`) continue } } } } if _, ok := callback[`color`]; ok { if len(cb.Lights) == 0 { if err = client.SetColor(cb.Color, cb.ColorDuration); err != nil { log.WithField(`error`, err).Error(`Setting color`) } } else { for _, label := range cb.Lights { light, err := client.GetLightByLabel(label) if err != nil { log.WithFields(log.Fields{ `label`: label, `error`: err, }).Error(`Finding light`) continue } if err = light.SetColor(cb.Color, cb.ColorDuration); err != nil { log.WithFields(log.Fields{ `label`: label, `error`: err, }).Error(`Setting color`) continue } } } } }