func handleSyncMaster(c *gin.Context, data string) { if !conf.IsMasterNode() { Error(c, BAD_REQUEST, "invalid req type for slave node: "+NODE_REQUEST_TYPE_SYNCMASTER) return } // no need to hold the locker(confWriteMux) to avoid dead-lock, slave will eventually be consistent with master, // confWriteMux.Lock() // defer confWriteMux.Unlock() history, err := models.GetAllConfigUpdateHistory(nil) if err != nil { Error(c, SERVER_ERROR, err.Error()) return } memConfMux.RLock() webHooks := memConfGlobalWebHooks for _, hooks := range memConfAppWebHooks { webHooks = append(webHooks, hooks...) } resData, _ := json.Marshal(syncAllDataT{ Nodes: memConfNodes, Users: memConfUsers, Apps: memConfApps, WebHooks: webHooks, Configs: memConfRawConfigs, DataVersion: memConfDataVersion, ConfHistory: history, }) memConfMux.RUnlock() Success(c, string(resData)) }
func init() { var err error nodeAuthToken := jwt.New(jwt.SigningMethodHS256) if nodeAuthString, err = nodeAuthToken.SignedString([]byte(conf.NodeAuth)); err != nil { log.Panicf("Failed to init node auth token: %s", err.Error()) } checkNodeValidity() loadAllData() initNodeData() if !conf.IsMasterNode() { if err = slaveCheckMaster(); err != nil { log.Printf("slave node failed to check master: %s", err.Error()) os.Exit(1) } go func() { for { time.Sleep(time.Duration(conf.CheckMasterInerval) * time.Second) go slaveCheckMaster() } }() } }
func init() { var dsn, driver string var err error if conf.IsMasterNode() { dsn = filepath.Join(conf.SqliteDir, conf.SqliteFileName) } else { dsn, err = getSlaveSqliteFile() if err != nil { log.Panicf("Failed to generate sqlie lite file: %s", err.Error()) } } driver = "sqlite3" initDBEngine(driver, dsn) }
func checkNodeValidity() { nodes, err := models.GetAllNode(nil) if err != nil { log.Panicf("failed to check node validity: %s" + err.Error()) } for _, node := range nodes { if conf.IsMasterNode() { // only one master in cluster if node.Type == models.NODE_TYPE_MASTER && node.URL != conf.ClientAddr { if err := models.DeleteDBModel(nil, node); err != nil { log.Panicf("failed to check node validity: %s" + err.Error()) } break } } } }
func handleSlaveCheckMaster(c *gin.Context, data string) { if !conf.IsMasterNode() { Error(c, BAD_REQUEST, "invalid req type for slave node: "+NODE_REQUEST_TYPE_CHECKMASTER) return } node := &models.Node{} if err := json.Unmarshal([]byte(data), node); err != nil { Error(c, BAD_REQUEST, "bad req body format") return } confWriteMux.Lock() defer confWriteMux.Unlock() oldNode := memConfNodes[node.URL] node.LastCheckUTC = utils.GetNowSecond() if oldNode == nil { if err := models.InsertRow(nil, node); err != nil { Error(c, SERVER_ERROR, err.Error()) return } } else { if err := models.UpdateDBModel(nil, node); err != nil { Error(c, SERVER_ERROR, err.Error()) return } } memConfMux.Lock() memConfNodes[node.URL] = node bs, _ := json.Marshal(memConfDataVersion) memConfMux.Unlock() go masterSyncNodeToSlave(node) Success(c, string(bs)) }
func ConfWriteCheck(c *gin.Context) { if !conf.IsMasterNode() { Error(c, NOT_PERMITTED, "You can not update config data as you connecting to slave node,") c.Abort() } }
func ClientConf(c *gin.Context) { clientData := &ClientData{ AppKey: c.Query("app_key"), OSType: c.Query("os_type"), OSVersion: c.Query("os_version"), AppVersion: c.Query("app_version"), Ip: c.Query("ip"), Lang: c.Query("lang"), DeviceId: c.Query("device_id"), DataSign: c.Query("data_sign"), TimeZone: c.Query("timezone"), NetWork: c.Query("network"), } if clientData.AppKey == "" { memConfMux.RLock() app := memConfAppsByName[c.Query("app")] memConfMux.RUnlock() if app != nil { clientData.AppKey = app.Key clientData.OSType = "ios" clientData.OSVersion = c.Query("osv") clientData.AppVersion = c.Query("v") clientData.DeviceId = c.Query("ida") clientData.Ip = c.Request.RemoteAddr } clientData = uniformClientParams(clientData) sendChanAsync(clientQueryParamCh, clientData) setClientData(c, clientData) c.JSON(http.StatusOK, getAppMatchConf(clientData.AppKey, clientData)) return } clientData = uniformClientParams(clientData) sendChanAsync(clientQueryParamCh, clientData) setClientData(c, clientData) memConfMux.RLock() if !conf.IsMasterNode() && conf.DataExpires > 0 { if memConfNodes[conf.ClientAddr].LastCheckUTC < utils.GetNowSecond()-conf.DataExpires { memConfMux.RUnlock() Error(c, DATA_EXPIRED) return } } nodes := []string{} nodes = make([]string, len(memConfNodes)) ix := 0 for _, node := range memConfNodes { nodes[ix] = node.URL ix++ } needConf := memConfApps[clientData.AppKey] != nil && clientData.DataSign != memConfApps[clientData.AppKey].DataSign memConfMux.RUnlock() if needConf { var dataSign string configs := getAppMatchConf(clientData.AppKey, clientData) if len(configs) > 0 { memConfMux.RLock() dataSign = memConfApps[clientData.AppKey].DataSign memConfMux.RUnlock() } Success(c, map[string]interface{}{ "nodes": nodes, "configs": configs, "data_sign": dataSign, }) } else { Success(c, map[string]interface{}{ "data_sign": clientData.DataSign, "nodes": nodes, }) } return }
func handleSlaveSyncUpdateData(c *gin.Context, data string) { if conf.IsMasterNode() { Error(c, BAD_REQUEST, "invalid req type for master node: "+NODE_REQUEST_TYPE_SYNCSLAVE) return } syncData := &syncDataT{} err := json.Unmarshal([]byte(data), syncData) if err != nil { Error(c, BAD_REQUEST, "bad req body format") return } confWriteMux.Lock() defer confWriteMux.Unlock() if syncData.Kind != NODE_REQUEST_SYNC_TYPE_NODE { if memConfDataVersion.Version+1 != syncData.DataVersion.Version { Error(c, DATA_VERSION_ERROR, "slave node data version [%d] error for master data version [%d]", memConfDataVersion.Version, syncData.DataVersion.Version) return } if memConfDataVersion.Sign != syncData.DataVersion.OldSign { Error(c, DATA_VERSION_ERROR, "slave node's data sign [%s] not equal master node's old data sign [%s]", memConfDataVersion.Sign, syncData.DataVersion.OldSign) return } } switch syncData.Kind { case NODE_REQUEST_SYNC_TYPE_USER: user := &models.User{} if err = json.Unmarshal([]byte(syncData.Data), user); err != nil { Error(c, BAD_REQUEST, "bad data format for user model") return } if _, err = updateUser(user, syncData.DataVersion); err != nil { Error(c, SERVER_ERROR, err.Error()) return } case NODE_REQUEST_SYNC_TYPE_APP: app := &models.App{} if err = json.Unmarshal([]byte(syncData.Data), app); err != nil { Error(c, BAD_REQUEST, "bad data format for app model") return } if _, err = updateApp(app, syncData.DataVersion, nil); err != nil { Error(c, SERVER_ERROR, err.Error()) return } case NODE_REQUEST_SYNC_TYPE_WEBHOOK: hook := &models.WebHook{} if err = json.Unmarshal([]byte(syncData.Data), hook); err != nil { Error(c, BAD_REQUEST, "bad data format for webHook model") return } if _, err = updateWebHook(hook, syncData.DataVersion); err != nil { Error(c, SERVER_ERROR, err.Error()) return } case NODE_REQUEST_SYNC_TYPE_CONFIG: config := &models.Config{} if err = json.Unmarshal([]byte(syncData.Data), config); err != nil { Error(c, BAD_REQUEST, "bad data format for user model") return } if _, err = updateConfig(config, syncData.OpUserKey, syncData.DataVersion, nil); err != nil { Error(c, SERVER_ERROR, err.Error()) return } case NODE_REQUEST_SYNC_TYPE_NODE: node := &models.Node{} if err = json.Unmarshal([]byte(syncData.Data), node); err != nil { Error(c, BAD_REQUEST, "bad data format for node model") return } if memConfNodes[node.URL] == nil { if err := models.InsertRow(nil, node); err != nil { Error(c, SERVER_ERROR, err.Error()) return } } else { if err := models.UpdateDBModel(nil, node); err != nil { Error(c, SERVER_ERROR, err.Error()) return } } memConfMux.Lock() memConfNodes[node.URL] = node memConfMux.Unlock() Success(c, nil) return case NODE_REQUEST_SYNC_TYPE_CLONE: data := &cloneData{} if err := json.Unmarshal([]byte(syncData.Data), data); err != nil { Error(c, BAD_REQUEST, "bad data format for clone app") return } if err := cloneConfigs(data.App, data.Configs, syncData.OpUserKey); err != nil { Error(c, SERVER_ERROR, err.Error()) return } default: Error(c, BAD_REQUEST, "unknown node data sync type: "+syncData.Kind) return } masterNode := getMasterNode() masterNode.DataVersion = syncData.DataVersion bs, _ := json.Marshal(syncData.DataVersion) masterNode.DataVersionStr = string(bs) if err = models.UpdateDBModel(nil, &masterNode); err != nil { memConfMux.Lock() memConfNodes[masterNode.URL] = &masterNode memConfMux.Unlock() } Success(c, nil) }
func main() { wd, _ := os.Getwd() pidFile, err := os.OpenFile(filepath.Join(wd, "instafig.pid"), os.O_CREATE|os.O_WRONLY, 0666) if err != nil { logger.Fatal(map[string]interface{}{ "type": "start_error", "error": fmt.Sprintf("failed to create pid file: %s", err.Error()), }) log.Printf("failed to create pid file: %s", err.Error()) os.Exit(1) } pidFile.WriteString(strconv.Itoa(os.Getpid())) pidFile.Close() if conf.DebugMode { gin.SetMode(gin.DebugMode) } else { gin.SetMode(gin.ReleaseMode) } ginIns := gin.New() ginIns.Use(gin.Recovery()) if conf.DebugMode { ginIns.Use(gin.Logger()) } if conf.RequestLogEnable { ginIns.Use(requestLoggerHandler()) } if conf.WebDebugMode { // static ginIns.Static("/web", "./web") } else { // bin static ginIns.GET("/web/*file", func(c *gin.Context) { fileName := c.Param("file") if fileName == "/" { fileName = "/index.html" } data, err := Asset("web" + fileName) if err != nil { c.String(http.StatusNotFound, err.Error()) return } switch { case strings.LastIndex(fileName, ".html") == len(fileName)-5: c.Header("Content-Type", "text/html; charset=utf-8") case strings.LastIndex(fileName, ".css") == len(fileName)-4: c.Header("Content-Type", "text/css") } c.String(http.StatusOK, string(data)) }) } // misc api miscAPIGroup := ginIns.Group("/misc") { miscAPIGroup.GET("/version", VersionHandler) } // client api clientAPIGroup := ginIns.Group("/client") { clientAPIGroup.GET("/config", StatisticHandler, ClientConf) } // compatible with old awconfig ginIns.GET("/conf", StatisticHandler, ClientConf) // op api opAPIGroup := ginIns.Group("/op") { opAPIGroup.POST("/login", Login) opAPIGroup.POST("/logout", OpAuth, Logout) opAPIGroup.GET("/users/:page/:count", InitUserCheck, OpAuth, GetUsers) opAPIGroup.POST("/user", OpAuth, ConfWriteCheck, NewUser, UpdateMasterLastDataUpdateUTC) opAPIGroup.PUT("/user", OpAuth, ConfWriteCheck, UpdateUser, UpdateMasterLastDataUpdateUTC) opAPIGroup.PUT("/user/status", OpAuth, ConfWriteCheck, UpdateUserStatus, UpdateMasterLastDataUpdateUTC) opAPIGroup.PUT("/user/passcode", OpAuth, ConfWriteCheck, UpdateUserPassCode, UpdateMasterLastDataUpdateUTC) opAPIGroup.POST("/user/init", ConfWriteCheck, InitUser, UpdateMasterLastDataUpdateUTC) opAPIGroup.GET("/user/info", OpAuth, GetLoginUserInfo) opAPIGroup.GET("/apps/user/:user_key", OpAuth, GetApps) opAPIGroup.GET("/apps/all/:page/:count", OpAuth, GetAllApps) opAPIGroup.GET("/app/:app_key", OpAuth, GetApp) if conf.IsMasterNode() { opAPIGroup.GET("/apps/search", OpAuth, SearchApps) opAPIGroup.GET("/apps/search/hint", OpAuth, SearchAppsHint) } opAPIGroup.POST("/app", OpAuth, ConfWriteCheck, NewApp, UpdateMasterLastDataUpdateUTC) opAPIGroup.PUT("/app", OpAuth, ConfWriteCheck, UpdateApp, UpdateMasterLastDataUpdateUTC) opAPIGroup.POST("/app/clone", OpAuth, ConfWriteCheck, CloneAppConfigs, UpdateMasterLastDataUpdateUTC) opAPIGroup.GET("/webhooks/global", OpAuth, GetGlobalWebHooks) opAPIGroup.GET("/webhooks/app/:app_key", OpAuth, GetAppWebHooks) opAPIGroup.POST("/webhook", OpAuth, ConfWriteCheck, NewWebHook, UpdateMasterLastDataUpdateUTC) opAPIGroup.PUT("/webhook", OpAuth, ConfWriteCheck, UpdateWebHook) opAPIGroup.GET("/configs/:app_key", OpAuth, GetConfigs) opAPIGroup.POST("/config", OpAuth, ConfWriteCheck, NewConfig, UpdateMasterLastDataUpdateUTC) opAPIGroup.PUT("/config", OpAuth, ConfWriteCheck, UpdateConfig, UpdateMasterLastDataUpdateUTC) opAPIGroup.GET("/config/history/:config_key", OpAuth, GetConfigUpdateHistory) opAPIGroup.GET("/config/apphistory/:app_key/:page/:count", OpAuth, GetAppConfigUpdateHistory) opAPIGroup.GET("/config/userhistory/:user_key/:page/:count", OpAuth, GetConfigUpdateHistoryOfUser) opAPIGroup.GET("/config/by/:config_key", OpAuth, GetConfigByKey) opAPIGroup.GET("/nodes", OpAuth, GetNodes) opAPIGroup.GET("/client/params/:symbol", OpAuth, GetClientSymbols) // for statistics opAPIGroup.GET("/stat/latest-config-device-count/:app_key", OpAuth, StatCheck, GetDeviceCountOfAppLatestConfig) opAPIGroup.GET("/stat/app-config-response/:app_key", OpAuth, StatCheck, GetAppConfigResponseData) opAPIGroup.GET("/stat/node-config-response/:node_url", OpAuth, StatCheck, GetNodeConfigResponseData) } ginInsNode := gin.New() if conf.DebugMode { ginInsNode.Use(gin.Logger()) } ginInsNode.Use(gin.Recovery()) ginInsNode.POST("/node/req/:req_type", NodeRequestHandler) err = gracehttp.Serve( &http.Server{Addr: fmt.Sprintf(":%d", conf.Port), Handler: ginIns}, &http.Server{Addr: conf.NodeAddr, Handler: ginInsNode}) if err != nil { logger.Fatal(map[string]interface{}{ "type": "start_error", "error": err.Error(), }) log.Printf("fatal error: %s", err.Error()) } }