コード例 #1
0
ファイル: create_db.go プロジェクト: lonycell/canopy-server
func (CreateDBCommand) Perform(info CommandInfo) {
	dl := cassandra_datalayer.NewDatalayer(info.Cfg)
	err := dl.PrepDb("canopy")
	if err != nil {
		fmt.Println(err)
	}
}
コード例 #2
0
ファイル: pigeon_itf.go プロジェクト: lonycell/canopy-server
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
}
コード例 #3
0
ファイル: workers.go プロジェクト: lonycell/canopy-server
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)
	}
}
コード例 #4
0
ファイル: job_router.go プロジェクト: lonycell/canopy-server
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
}
コード例 #5
0
// 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,
	}
}
コード例 #6
0
ファイル: canopy-ops.go プロジェクト: lonycell/canopy-server
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'.")
	}
}
コード例 #7
0
ファイル: canopy_ws.go プロジェクト: lonycell/canopy-server
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)
				}
			}
		}
	}
}
コード例 #8
0
ファイル: erase_db.go プロジェクト: lonycell/canopy-server
func (EraseDBCommand) Perform(info CommandInfo) {
	dl := cassandra_datalayer.NewDatalayer(info.Cfg)
	dl.EraseDb("canopy")
}