Esempio n. 1
0
func TestBackend(t *testing.T) {
	conf := getConfig()
	devEUI := lorawan.EUI64{1, 1, 1, 1, 1, 1, 1, 1}
	appEUI := lorawan.EUI64{2, 2, 2, 2, 2, 2, 2, 2}

	Convey("Given a MQTT client and Redis database", t, func() {
		opts := mqtt.NewClientOptions().AddBroker(conf.Server).SetUsername(conf.Username).SetPassword(conf.Password)
		c := mqtt.NewClient(opts)
		token := c.Connect()
		token.Wait()
		So(token.Error(), ShouldBeNil)

		p := loraserver.NewRedisPool(conf.RedisURL)

		Convey("Given a new Backend", func() {
			backend, err := NewBackend(p, conf.Server, conf.Username, conf.Password)
			So(err, ShouldBeNil)
			defer backend.Close()
			time.Sleep(time.Millisecond * 100) // give the backend some time to subscribe

			Convey("Given the MQTT client is subscribed to application/+/node/+/rxinfo", func() {
				rxInfoPLChan := make(chan models.RXInfoPayload)
				token := c.Subscribe("application/+/node/+/rxinfo", 0, func(c mqtt.Client, msg mqtt.Message) {
					var rxInfoPayload models.RXInfoPayload
					if err := json.Unmarshal(msg.Payload(), &rxInfoPayload); err != nil {
						t.Fatal(err)
					}
					rxInfoPLChan <- rxInfoPayload
				})
				token.Wait()
				So(token.Error(), ShouldBeNil)

				Convey("When sending a RXInfoPayload from the backend", func() {
					pl := models.RXInfoPayload{
						RXInfo: []models.RXInfo{
							{Time: time.Now().UTC()},
						},
					}
					So(backend.SendRXInfoPayload(appEUI, devEUI, pl), ShouldBeNil)

					Convey("Then the same packet was received by the MQTT client", func() {
						pl2 := <-rxInfoPLChan
						So(pl2, ShouldResemble, pl)
					})
				})
			})

			Convey("Given the MQTT client is subscribed to application/+/node/+/mac/rx", func() {
				macChan := make(chan models.MACPayload)
				token := c.Subscribe("application/+/node/+/mac/rx", 0, func(c mqtt.Client, msg mqtt.Message) {
					var mac models.MACPayload
					if err := json.Unmarshal(msg.Payload(), &mac); err != nil {
						panic(err)
					}
					macChan <- mac
				})
				token.Wait()
				So(token.Error(), ShouldBeNil)

				Convey("When sending a MACCommand from the backend", func() {
					mac := models.MACPayload{
						MACCommand: []byte{1, 2, 3},
					}
					So(backend.SendMACPayload(appEUI, devEUI, mac), ShouldBeNil)

					Convey("Then the same MACCommand was received by the MQTT client", func() {
						mac2 := <-macChan
						So(mac2, ShouldResemble, mac)
					})
				})
			})

			Convey("Given the MQTT client is subscribed to application/+/node/+/mac/error", func() {
				errChan := make(chan models.ErrorPayload)
				token := c.Subscribe("application/+/node/+/mac/error", 0, func(c mqtt.Client, msg mqtt.Message) {
					var pl models.ErrorPayload
					if err := json.Unmarshal(msg.Payload(), &pl); err != nil {
						panic(err)
					}
					errChan <- pl
				})
				token.Wait()
				So(token.Error(), ShouldBeNil)

				Convey("When sending an ErrorPayload from the backend", func() {
					pl := models.ErrorPayload{
						Message: "boom boom!",
					}
					So(backend.SendErrorPayload(appEUI, devEUI, pl), ShouldBeNil)

					Convey("Then the same ErrorPayload was received by the MQTT client", func() {
						pl2 := <-errChan
						So(pl2, ShouldResemble, pl)
					})
				})
			})

			Convey("Given the MQTT client publishes a MAC command to application/01010101010101/node/0202020202020202/mac/tx", func() {
				topic := "application/01010101010101/node/0202020202020202/mac/tx"
				mac := models.MACPayload{
					DevEUI:     [8]byte{2, 2, 2, 2, 2, 2, 2, 2},
					Reference:  "abc123",
					MACCommand: []byte{1, 2, 3},
				}
				b, err := json.Marshal(mac)
				So(err, ShouldBeNil)
				token := c.Publish(topic, 0, false, b)
				token.Wait()
				So(token.Error(), ShouldBeNil)

				Convey("The same MAC command is received by the backend", func() {
					p := <-backend.TXMACPayloadChan()
					So(p, ShouldResemble, mac)

					Convey("When the topic DevEUI does noet match the DevEUI in the message", func() {
						token := c.Publish("application/01010101010101/node/0303030303030303/mac/tx", 0, false, b)
						token.Wait()
						So(token.Error(), ShouldBeNil)

						Convey("Then the command is discarded", func() {
							var received bool
							select {
							case <-backend.TXMACPayloadChan():
								received = true
							case <-time.After(time.Millisecond * 100):
								// nothing to do
							}
							So(received, ShouldBeFalse)
						})
					})
				})
			})
		})
	})
}
Esempio n. 2
0
func run(c *cli.Context) {
	// parse the NetID
	var netID lorawan.NetID
	if err := netID.UnmarshalText([]byte(c.String("net-id"))); err != nil {
		log.Fatalf("NetID parse error: %s", err)
	}

	// get the band config
	if c.String("band") == "" {
		log.Fatalf("--band is undefined, valid options are: %s", strings.Join(bands, ", "))
	}
	bandConfig, err := band.GetConfig(band.Name(c.String("band")))
	if err != nil {
		log.Fatal(err)
	}
	loraserver.Band = bandConfig

	log.WithFields(log.Fields{
		"version": version,
		"net_id":  netID.String(),
		"band":    c.String("band"),
	}).Info("starting LoRa Server")

	// connect to the database
	log.Info("connecting to postgresql")
	db, err := loraserver.OpenDatabase(c.String("postgres-dsn"))
	if err != nil {
		log.Fatalf("database connection error: %s", err)
	}

	// setup redis pool
	log.Info("setup redis connection pool")
	rp := loraserver.NewRedisPool(c.String("redis-url"))

	// setup gateway backend
	gw, err := gateway.NewBackend(c.String("gw-mqtt-server"), c.String("gw-mqtt-username"), c.String("gw-mqtt-password"))
	if err != nil {
		log.Fatalf("gateway-backend setup failed: %s", err)
	}

	// setup application backend
	app, err := application.NewBackend(rp, c.String("app-mqtt-server"), c.String("app-mqtt-username"), c.String("app-mqtt-password"))
	if err != nil {
		log.Fatalf("application-backend setup failed: %s", err)
	}

	// auto-migrate the database
	if c.Bool("db-automigrate") {
		log.Info("applying database migrations")
		m := &migrate.AssetMigrationSource{
			Asset:    migrations.Asset,
			AssetDir: migrations.AssetDir,
			Dir:      "",
		}
		n, err := migrate.Exec(db.DB, "postgres", m, migrate.Up)
		if err != nil {
			log.Fatalf("applying migrations failed: %s", err)
		}
		log.WithField("count", n).Info("migrations applied")
	}

	ctx := loraserver.Context{
		DB:          db,
		RedisPool:   rp,
		Gateway:     gw,
		Application: app,
		NetID:       netID,
	}

	// start the loraserver
	server := loraserver.NewServer(ctx)
	if err := server.Start(); err != nil {
		log.Fatal(err)
	}

	// setup json-rpc api handler
	apiHandler, err := loraserver.NewJSONRPCHandler(
		loraserver.NewApplicationAPI(ctx),
		loraserver.NewNodeAPI(ctx),
		loraserver.NewNodeSessionAPI(ctx),
		loraserver.NewChannelListAPI(ctx),
		loraserver.NewChannelAPI(ctx),
	)
	if err != nil {
		log.Fatal(err)
	}
	log.WithField("path", "/rpc").Info("registering json-rpc handler")
	http.Handle("/rpc", apiHandler)

	// setup static file server (for the gui)
	log.WithField("path", "/").Info("registering gui handler")
	http.Handle("/", http.FileServer(&assetfs.AssetFS{
		Asset:     static.Asset,
		AssetDir:  static.AssetDir,
		AssetInfo: static.AssetInfo,
		Prefix:    "",
	}))

	// start the http server
	go func() {
		log.WithField("bind", c.String("http-bind")).Info("starting http server")
		log.Fatal(http.ListenAndServe(c.String("http-bind"), nil))
	}()

	sigChan := make(chan os.Signal)
	exitChan := make(chan struct{})
	signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
	log.WithField("signal", <-sigChan).Info("signal received")
	go func() {
		log.Warning("stopping loraserver")
		if err := server.Stop(); err != nil {
			log.Fatal(err)
		}
		exitChan <- struct{}{}
	}()
	select {
	case <-exitChan:
	case s := <-sigChan:
		log.WithField("signal", s).Info("signal received, stopping immediately")
	}
}