func (CreateDBCommand) Perform(info CommandInfo) { dl := cassandra_datalayer.NewDatalayer(info.Cfg) err := dl.PrepDb("canopy") if err != nil { fmt.Println(err) } }
func NewPigeonSystem(cfg config.Config) (System, error) { dl := cassandra_datalayer.NewDatalayer(cfg) // TODO: share DB connection conn, err := dl.Connect("canopy") if err != nil { return nil, err } dlpigeon := conn.PigeonSystem() return &PigeonSystem{dlpigeon}, nil }
func (WorkersCommand) Perform(info CommandInfo) { dl := cassandra_datalayer.NewDatalayer(info.Cfg) conn, err := dl.Connect("canopy") if err != nil { fmt.Println(err) } workers, err := conn.PigeonSystem().Workers() if err != nil { fmt.Println(err) } for _, worker := range workers { fmt.Println(worker) } }
func InitJobServer(cfg config.Config, pigeonServer jobqueue.Server) error { mailer, err := mail.NewMailClient(cfg) if err != nil { return err } userCtx := map[string]interface{}{ "cfg": cfg, "mailer": mailer, } dl := cassandra_datalayer.NewDatalayer(cfg) conn, err := dl.Connect("canopy") if err != nil { return err } userCtx["db-conn"] = conn routes := map[string]jobqueue.HandlerFunc{ "api/activate": rest.RestJobWrapper(rest.ApiActivateHandler), "api/create_devices": rest.RestJobWrapper(rest.ApiCreateDevicesHandler), "api/create_user": rest.RestJobWrapper(rest.ApiCreateUserHandler), "GET:api/device/id": rest.RestJobWrapper(rest.GET__api__device__id), "POST:api/device/id": rest.RestJobWrapper(rest.POST__api__device__id), "DELETE:api/device/id": rest.RestJobWrapper(rest.DELETE__api__device__id), "api/device/id/var": rest.RestJobWrapper(rest.GET__api__device__id__var), "api/devices": rest.RestJobWrapper(rest.GET__api__devices), "api/finish_share_transaction": rest.RestJobWrapper(rest.POST__api__finish_share_transaction), "api/info": rest.RestJobWrapper(rest.GET__api__info), "api/login": rest.RestJobWrapper(rest.POST__api__login), "api/logout": rest.RestJobWrapper(rest.GET_POST__api__logout), "GET:api/user/self": rest.RestJobWrapper(rest.GET__api__user__self), "POST:api/user/self": rest.RestJobWrapper(rest.POST__api__user__self), "DELETE:api/user/self": rest.RestJobWrapper(rest.DELETE__api__user__self), "api/reset_password": rest.RestJobWrapper(rest.POST__api__reset_password), "api/share": rest.RestJobWrapper(rest.POST__api__share), } // Register handlers for msgKey, handler := range routes { inbox, err := pigeonServer.CreateInbox(msgKey) if err != nil { return err } inbox.SetUserCtx(userCtx) inbox.SetHandlerFunc(handler) } return nil }
// Process communication payload from device (via websocket. or REST) // { // "device_id" : "9dfe2a00-efe2-45f9-a84c-8afc69caf4e7", // "sddl" : { // "optional inbound bool onoff" : {} // }, // "vars" : { // "temperature" : 38.0f; // "gps" : { // "latitude" : 38.0f; // "longitude" : 38.0f; // } // } // } // } // // <conn> is an optional datalayer connection. If provided, it is used. // Otherwise, a datalayer connection is opened by this routine. // // <device> is the device that sent the communication. If nil, then either // <deviceId> or, as a last resort, the payload's "device_id" will be used. // // <deviceId> is a string device ID of the device that sent the communication. // This is ignored if <device> is not nil. If nil, then the payload's // "device_id" will be used. // // <secretKey> is the device's secret key. A secret key is required if // <device> is nil. Either the value of <secretKey> or, as a last resort, the // payload's "secret_key" field will be used. // // <payload> is a string containing the JSON payload. func ProcessDeviceComm( cfg config.Config, conn datalayer.Connection, device datalayer.Device, deviceIdString string, secretKey string, payload string) ServiceResponse { var err error var out ServiceResponse var ok bool canolog.Info("ProcessDeviceComm STARTED") // If conn is nil, open a datalayer connection. if conn == nil { dl := cassandra_datalayer.NewDatalayer(cfg) conn, err = dl.Connect("canopy") if err != nil { return ServiceResponse{ HttpCode: http.StatusInternalServerError, Err: fmt.Errorf("Could not connect to database: %s", err), Response: `{"result" : "error", "error_type" : "could_not_connect_to_database"}`, Device: nil, } } defer conn.Close() } // Parse JSON payload var payloadObj map[string]interface{} err = json.Unmarshal([]byte(payload), &payloadObj) if err != nil { return ServiceResponse{ HttpCode: http.StatusBadRequest, Err: fmt.Errorf("Error JSON decoding payload: %s", err), Response: `{"result" : "error", "error_type" : "decoding_paylaod"}`, Device: nil, } } // Device can be provided to this routine in one of three ways: // 1) <device> parameter // 2) <deviceId> parameter // 3) "device_id" field in payload if device == nil && deviceIdString != "" { // Parse UUID uuid, err := gocql.ParseUUID(deviceIdString) if err != nil { return ServiceResponse{ HttpCode: http.StatusBadRequest, Err: fmt.Errorf("Invalid UUID %s: %s", deviceIdString, err), Response: `{"result" : "error", "error_type" : "device_uuid_required"}`, Device: nil, } } // Get secret key from payload if necessary if secretKey == "" { secretKey, ok = payloadObj["secret_key"].(string) if !ok { return ServiceResponse{ HttpCode: http.StatusBadRequest, Err: fmt.Errorf("\"secret_key\" field must be string"), Response: `{"result" : "error", "error_type" : "bad_payload"}`, Device: nil, } } } // lookup device device, err = conn.LookupDeviceVerifySecretKey(uuid, secretKey) if err != nil { return ServiceResponse{ HttpCode: http.StatusInternalServerError, Err: fmt.Errorf("Error looking up or verifying device: %s", err), Response: `{"result" : "error", "error_type" : "database_error"}`, Device: nil, } } } // Is "device_id" provided in payload? _, ok = payloadObj["device_id"] if ok { deviceIdStringFromPayload, ok := payloadObj["device_id"].(string) if !ok { return ServiceResponse{ HttpCode: http.StatusBadRequest, Err: fmt.Errorf("\"device_id\" field must be string"), Response: `{"result" : "error", "error_type" : "bad_payload"}`, Device: nil, } } // Parse UUID uuid, err := gocql.ParseUUID(deviceIdStringFromPayload) if err != nil { return ServiceResponse{ HttpCode: http.StatusBadRequest, Err: fmt.Errorf("Invalid UUID %s: %s", deviceIdStringFromPayload, err), Response: `{"result" : "error", "error_type" : "device_uuid_required"}`, Device: nil, } } // Is <device> already set? // If not: set it. // If so: ensure consistency if device == nil { // Get secret key from payload if necessary if secretKey == "" { secretKey, ok = payloadObj["secret_key"].(string) if !ok { return ServiceResponse{ HttpCode: http.StatusBadRequest, Err: fmt.Errorf("\"secret_key\" field must be string"), Response: `{"result" : "error", "error_type" : "bad_payload"}`, Device: nil, } } } // Lookup device device, err = conn.LookupDeviceVerifySecretKey(uuid, secretKey) if err != nil { return ServiceResponse{ HttpCode: http.StatusInternalServerError, Err: fmt.Errorf("Error looking up or verifying device: %s", err), Response: `{"result" : "error", "error_type" : "database_error"}`, Device: nil, } } } else { if device.ID().String() != deviceIdStringFromPayload { return ServiceResponse{ HttpCode: http.StatusBadRequest, Err: fmt.Errorf("Inconsistent device ID: %s %s", device.ID().String(), deviceIdStringFromPayload), Response: `{"result" : "error", "error_type" : "bad_payload"}`, Device: nil, } } } } // If device wasn't provided at all, throw error. if device == nil { return ServiceResponse{ HttpCode: http.StatusBadRequest, Err: fmt.Errorf("Device ID expected"), Response: `{"result" : "error", "error_type" : "bad_payload"}`, Device: nil, } } out.Device = device device.UpdateLastActivityTime(nil) // If "sddl" is present, create new / reconfigure Cloud Variables. _, ok = payloadObj["sddl"] if ok { updateMap, ok := payloadObj["sddl"].(map[string]interface{}) if !ok { return ServiceResponse{ HttpCode: http.StatusBadRequest, Err: fmt.Errorf("Expected object for \"sdd\" field"), Response: `{"result" : "error", "error_type" : "bad_payload"}`, Device: nil, } } err = device.ExtendSDDL(updateMap) if err != nil { return ServiceResponse{ HttpCode: http.StatusInternalServerError, Err: fmt.Errorf("Error updating device's SDDL: %s", err), Response: `{"result" : "error", "error_type" : "database_error"}`, Device: nil, } } } // If "vars" is present, update value of all Cloud Variables (creating new // Cloud Variables as necessary) doc := device.SDDLDocument() _, ok = payloadObj["vars"] canolog.Info("vars present:", ok) if ok { varsMap, ok := payloadObj["vars"].(map[string]interface{}) if !ok { return ServiceResponse{ HttpCode: http.StatusBadRequest, Err: fmt.Errorf("Expected object for \"vars\" field"), Response: `{"result" : "error", "error_type" : "bad_payload"}`, Device: nil, } } canolog.Info("varsMap: ", varsMap) for varName, value := range varsMap { varDef, err := doc.LookupVarDef(varName) // TODO: an error doesn't necessarily mean prop should be created? canolog.Info("Looking up property ", varName) if varDef == nil { // Property doesn't exist. Add it. canolog.Info("Not found. Add property ", varName) // TODO: What datatype? // TODO: What other parameters? varDef, err = doc.AddVarDef(varName, sddl.DATATYPE_FLOAT32) if err != nil { return ServiceResponse{ HttpCode: http.StatusInternalServerError, Err: fmt.Errorf("Error creating cloud variable %s: %s", varName, err), Response: `{"result" : "error", "error_type" : "database_error"}`, Device: nil, } } // save modified SDDL // TODO: Save at the end? canolog.Info("SetSDDLDocument ", doc) err = device.SetSDDLDocument(doc) if err != nil { return ServiceResponse{ HttpCode: http.StatusInternalServerError, Err: fmt.Errorf("Error updating SDDL: %s", err), Response: `{"result" : "error", "error_type" : "database_error"}`, Device: nil, } } } // Store property value. // Convert value datatype varVal, err := cloudvar.JsonToCloudVarValue(varDef, value) if err != nil { return ServiceResponse{ HttpCode: http.StatusInternalServerError, Err: fmt.Errorf("Error converting JSON to propertyValue: %s", err), Response: `{"result" : "error", "error_type" : "bad_payload"}`, Device: nil, } } canolog.Info("InsertStample") err = device.InsertSample(varDef, time.Now(), varVal) if err != nil { return ServiceResponse{ HttpCode: http.StatusInternalServerError, Err: fmt.Errorf("Error inserting sample %s: %s", varName, err), Response: `{"result" : "error", "error_type" : "database_error"}`, Device: nil, } } } } return ServiceResponse{ HttpCode: http.StatusOK, Err: nil, Response: `{"result" : "ok"}`, Device: device, } }
func main() { cfg := config.NewDefaultConfig("", "", "") err := cfg.LoadConfig() if err != nil { fmt.Printf("Error loading config") } err = canolog.Init(".canopy-ops.log") if err != nil { fmt.Println(err) return } flag.Parse() cmd := canopy_ops.FindCommand(cmds, flag.Arg(0)) info := canopy_ops.CommandInfo{ CmdList: cmds, Cfg: cfg, Args: flag.Args(), } if cmd != nil { cmd.Perform(info) } else if flag.Arg(0) == "create-account" { dl := cassandra_datalayer.NewDatalayer(cfg) conn, _ := dl.Connect("canopy") conn.CreateAccount(flag.Arg(1), flag.Arg(2), flag.Arg(3)) } else if flag.Arg(0) == "delete-account" { dl := cassandra_datalayer.NewDatalayer(cfg) conn, _ := dl.Connect("canopy") conn.DeleteAccount(flag.Arg(1)) } else if flag.Arg(0) == "create-device" { dl := cassandra_datalayer.NewDatalayer(cfg) conn, _ := dl.Connect("canopy") account, err := conn.LookupAccount(flag.Arg(1)) if err != nil { fmt.Println("Unable to lookup account ", flag.Arg(1), ":", err) return } device, err := conn.CreateDevice(flag.Arg(2), nil, "", datalayer.NoAccess) if err != nil { fmt.Println("Unable to create device: ", err) return } err = device.SetAccountAccess(account, datalayer.ReadWriteAccess, datalayer.ShareRevokeAllowed) if err != nil { fmt.Println("Unable to grant account access to device: ", err) return } } else if flag.Arg(0) == "list-devices" { dl := cassandra_datalayer.NewDatalayer(cfg) conn, _ := dl.Connect("canopy") account, err := conn.LookupAccount(flag.Arg(1)) if err != nil { fmt.Println("Unable to lookup account ", flag.Arg(1), ":", err) return } devices, err := account.Devices().DeviceList(0, -1) if err != nil { fmt.Println("Error reading devices: ", err) return } for _, device := range devices { fmt.Printf("%s %s\n", device.ID(), device.Name()) } } else if flag.Arg(0) == "gen-fake-sensor-data" { dl := cassandra_datalayer.NewDatalayer(cfg) conn, _ := dl.Connect("canopy") deviceId, err := gocql.ParseUUID(flag.Arg(1)) if err != nil { fmt.Println("Error parsing UUID: ", flag.Arg(1), ":", err) return } _, err = conn.LookupDevice(deviceId) if err != nil { fmt.Println("Device not found: ", flag.Arg(1), ":", err) return } for i := 0; i < 100; i++ { //val := float64(i % 16); //t := time.Now().Add(time.Duration(-i)*time.Second) //err = device.InsertSensorSample(flag.Arg(2), t, val) //if err != nil { fmt.Println("Error inserting sample: ", err) //} } } else if flag.Arg(0) == "clear-sensor-data" { dl := cassandra_datalayer.NewDatalayer(cfg) conn, _ := dl.Connect("canopy") conn.ClearSensorData() } else if flag.Arg(0) == "test-email" { mailer, err := mail.NewMailClient(cfg) if err != nil { fmt.Println("Error initializing mail client: ", err) return } mail := mailer.NewMail() err = mail.AddTo(flag.Arg(1), "Customer") if err != nil { fmt.Println("Invalid recipient: ", flag.Arg(1), err) return } mail.SetSubject("Test email from Canopy") mail.SetHTML("<b>Canopy Rulez</b>") mail.SetFrom("*****@*****.**", "The Canopy Team") err = mailer.Send(mail) if err != nil { fmt.Println("Error sending email:", err) return } fmt.Println("Email sent.") } else if flag.Arg(0) == "migrate-db" { startVersion := flag.Arg(1) if startVersion == "" { fmt.Println("<startVersion> required") return } endVersion := flag.Arg(2) if endVersion == "" { fmt.Println("<endVersion> required") return } dl := cassandra_datalayer.NewDatalayer(cfg) err := dl.MigrateDB("canopy", startVersion, endVersion) if err != nil { fmt.Println(err.Error()) } } else if len(flag.Args()) == 0 { cmds[0].Perform(info) } else { fmt.Println("Unknown command '" + flag.Arg(0) + "'. See 'canopy-ops help'.") } }
func NewCanopyWebsocketServer(cfg config.Config, outbox jobqueue.Outbox, pigeonServer jobqueue.Server) func(ws *websocket.Conn) { // Main websocket server routine. // This event loop runs until the websocket connection is broken. return func(ws *websocket.Conn) { canolog.Websocket("Websocket connection established") var cnt int32 var device datalayer.Device var inbox jobqueue.Inbox var inboxReciever jobqueue.RecieveHandler lastPingTime := time.Now() cnt = 0 // connect to cassandra dl := cassandra_datalayer.NewDatalayer(cfg) conn, err := dl.Connect("canopy") if err != nil { canolog.Error("Could not connect to database: ", err) return } defer conn.Close() for { var in string // check for message from client ws.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) err := websocket.Message.Receive(ws, &in) if err == nil { // success, payload received cnt++ resp := service.ProcessDeviceComm(cfg, conn, device, "", "", in) if resp.Device == nil { canolog.Error("Error processing device communications: ", resp.Err) } else { device = resp.Device if inbox == nil { deviceIdString := device.ID().String() inbox, err = pigeonServer.CreateInbox("canopy_ws:" + deviceIdString) if err != nil { canolog.Error("Error initializing inbox:", err) return } inboxReciever = jobqueue.NewRecieveHandler() inbox.SetHandler(inboxReciever) err = device.UpdateWSConnected(true) if err != nil { canolog.Error("Unexpected error: ", err) } } } } else if err == io.EOF { canolog.Websocket("Websocket connection closed") // connection closed if inbox != nil { if device != nil { err = device.UpdateWSConnected(false) if err != nil { canolog.Error("Unexpected error: ", err) } } inbox.Close() } return } else if nerr, ok := err.(net.Error); ok && nerr.Timeout() { // timeout reached, no data for me this time } else { canolog.Error("Unexpected error: ", err) } // Periodically send blank message if time.Now().After(lastPingTime.Add(30 * time.Second)) { err := websocket.Message.Send(ws, "{}") if err != nil { canolog.Websocket("Websocket connection closed during ping") // connection closed if inbox != nil { if device != nil { err = device.UpdateWSConnected(false) if err != nil { canolog.Error("Unexpected error: ", err) } } inbox.Close() } return } canolog.Info("Pinging WS") lastPingTime = time.Now() } if inbox != nil { msg, _ := inboxReciever.Recieve(time.Duration(100 * time.Millisecond)) if msg != nil { msgString, err := json.Marshal(msg) if err != nil { canolog.Error("Unexpected error: ", err) } canolog.Info("Websocket sending", msgString) canolog.Websocket("Websocket sending: ", msgString) websocket.Message.Send(ws, msgString) } } } } }
func (EraseDBCommand) Perform(info CommandInfo) { dl := cassandra_datalayer.NewDatalayer(info.Cfg) dl.EraseDb("canopy") }