/* POST /databases/register PUT /databases/assign */ func DatabasesHandler(w http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) log.Trace(fmt.Sprintf("admin.DatabasesHandler() > %s /databases/%s %+v", request.Method, vars["action"], vars)) switch request.Method { case "GET": switch vars["action"] { case "": // List All Databases instances, err := instances.All() if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`, http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): instances.All() %s %+v ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } jsonInstances, err := json.Marshal(instances) if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`, http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): json.Marshal(instances) %s %+v ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) } else { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusOK) w.Write(jsonInstances) } case "available": // Lists Available Databases instances, err := instances.Available() if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`, http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): instances.Available() %s %+v ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } jsonInstances, err := json.Marshal(instances) if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`, http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): json.Marshal(instances) %s %+v ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) } else { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusOK) w.Write(jsonInstances) } default: msg := fmt.Sprintf(`{"status": %d, "description": "Invalid Action %s"}`, http.StatusBadRequest, vars["action"]) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): %s %s`, msg, vars)) http.Error(w, msg, http.StatusBadRequest) } case `POST`: var i instances.Instance decoder := json.NewDecoder(request.Body) err := decoder.Decode(&i) if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`, http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): decoder.Decode() %s %s ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } switch vars[`action`] { case `register`: // Creates a new record. err = i.Register() if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`, http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): Instance#Register() %s %s ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } else { w.Header().Set(`Content-Type`, `application/json; charset=UTF-8`) w.WriteHeader(http.StatusOK) w.Write([]byte(`{}`)) return } default: msg := fmt.Sprintf(`{"status": %d, "description": "Invalid Action %s"}`, http.StatusBadRequest, vars[`action`]) log.Error(msg) http.Error(w, msg, http.StatusBadRequest) } case `PUT`: var i instances.Instance decoder := json.NewDecoder(request.Body) err := decoder.Decode(&i) if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`, http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): decoder.Decode() %s %s ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } switch vars[`action`] { case `assign`: // updates an existing record. err = i.Assign() if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`, http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): instances.Assign() %s %s ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } else { w.Header().Set(`Content-Type`, `application/json; charset=UTF-8`) w.WriteHeader(http.StatusOK) w.Write([]byte(`{}`)) return } default: msg := fmt.Sprintf(`{"status": %d, "description": "Invalid Action %s"}`, http.StatusBadRequest, vars[`action`]) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): %s %s`, msg, vars)) http.Error(w, msg, http.StatusBadRequest) } case `DELETE`: switch vars[`action`] { case `decommission`: i, err := instances.FindByDatabase(vars[`database`]) if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`, http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): instance.FindByDatabase() %s %s ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } else { err = i.Decommission() if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "There was an error decommissioning the database (%s)"}`, http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): instance#Decommission() %s %s ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) } } default: msg := fmt.Sprintf(`{"status": %d, "description": "Invalid Action %s"}`, http.StatusBadRequest, vars[`action`]) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): %s %s`, msg, vars)) http.Error(w, msg, http.StatusBadRequest) } default: msg := fmt.Sprintf(`{"status": %d, "description": "Method not allowed %s"}`, http.StatusMethodNotAllowed, request.Method) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): %s %s`, msg, vars)) http.Error(w, msg, http.StatusMethodNotAllowed) return } }
/* PrecreateDatabases is called as a scheduled task for precreating databaes. */ func (t *Task) PrecreateDatabases() (err error) { if globals.ServiceRole != "service" { //Safety valve... log.Error(fmt.Sprintf("tasks.Task#PrecreateDatabases() ! Not precreating databases as we are not running on a service node...")) return } t.ClusterID = os.Getenv(`RDPGD_CLUSTER`) if t.ClusterID == "" { matrixName := os.Getenv(`RDPGD_MATRIX`) matrixNameSplit := strings.SplitAfterN(matrixName, `-`, -1) matrixColumn := os.Getenv(`RDPGD_MATRIX_COLUMN`) for i := 0; i < len(matrixNameSplit)-1; i++ { t.ClusterID = t.ClusterID + matrixNameSplit[i] } t.ClusterID = t.ClusterID + "c" + matrixColumn } client, err := consulapi.NewClient(consulapi.DefaultConfig()) if err != nil { log.Error(fmt.Sprintf("tasks.Task#PrecreateDatabases() ! %s", err)) return } // Lock Database Creation (and Deletion) via Consul Lock key := fmt.Sprintf(`rdpg/%s/database/existance/lock`, t.ClusterID) lo := &consulapi.LockOptions{ Key: key, SessionName: fmt.Sprintf(`rdpg/%s/databases/existance`, t.ClusterID), } log.Trace(fmt.Sprintf(`tasks.Task<%s>#PrecreateDatabases() Attempting to acquire database existance lock %s...`, t.ClusterID, key)) databaseCreateLock, err := client.LockOpts(lo) if err != nil { log.Error(fmt.Sprintf(`tasks.Task<%s>#PrecreateDatabases() LockKey() Error locking database existance Key %s ! %s`, t.ClusterID, key, err)) return } databaseCreateLockCh, err := databaseCreateLock.Lock(nil) if err != nil { log.Error(fmt.Sprintf(`tasks.Task<%s>#PrecreateDatabases() Lock() database/existance lock %s ! %s`, t.ClusterID, key, err)) return } defer databaseCreateLock.Unlock() if databaseCreateLockCh == nil { log.Error(fmt.Sprintf(`tasks.Task<%s>#PrecreateDatabases() database/existance Lock not aquired, halting Creation!!!`, t.ClusterID)) return } // We have the database existance lock... key = fmt.Sprintf(`rdpg/%s/capacity/instances/limit`, t.ClusterID) kv := client.KV() kvp, _, err := kv.Get(key, nil) if err != nil { log.Error(fmt.Sprintf(`tasks.Task<%s>#PrecreateDatabases() kv.Get(%s) ! %s`, t.ClusterID, key, err)) return } if kvp == nil { log.Trace(fmt.Sprintf(`tasks.Task<%s>#PrecreateDatabases() kv.Get(%s) : key is not set...`, t.ClusterID, key)) return } maxLimitString := string(kvp.Value) maxLimit, err := strconv.Atoi(maxLimitString) if err != nil { log.Error(fmt.Sprintf(`tasks.Task<%s>#PrecreateDatabases() strconv.Atoi(%s) (Limit)`, t.ClusterID, kvp.Value)) return } log.Trace(fmt.Sprintf(`tasks.Task<%s>#PrecreateDatabases() retrived max_instances_limit: %d`, t.ClusterID, maxLimit)) key = fmt.Sprintf(`rdpg/%s/capacity/instances/allowed`, t.ClusterID) kvp, _, err = kv.Get(key, nil) if err != nil { log.Error(fmt.Sprintf(`tasks.Task<%s>#PrecreateDatabases() kvp.Get(%s) ! %s`, t.ClusterID, key, err)) return } if kvp == nil { log.Trace(fmt.Sprintf(`tasks.Task<%s>#PrecreateDatabases() kv.Get(%s) : key is not set...`, t.ClusterID, key)) return } maxAllowedString := string(kvp.Value) maxAllowed, err := strconv.Atoi(maxAllowedString) if err != nil { log.Error(fmt.Sprintf(`tasks.Task<%s>#PrecreateDatabases() strconv.Atoi(%s) (Allowed)`, t.ClusterID, kvp.Value)) return } log.Trace(fmt.Sprintf(`tasks.Task<%s>#PrecreateDatabases() retrived max_instances_allowed: %d`, t.ClusterID, maxAllowed)) maxCapacity := maxAllowed if maxLimit < maxCapacity { maxCapacity = maxLimit } i, err := instances.Available() if err != nil { log.Error(fmt.Sprintf("tasks.Task<%s>#PrecreateDatabases() instances.Available()! %s", t.ClusterID, err)) return } numAvailable := len(i) //i, err = instances.All() i, err = instances.Undecommissioned() if err != nil { log.Error(fmt.Sprintf("tasks.Task<%s>#PrecreateDatabases() instances.Undecommissioned() ! %s", t.ClusterID, err)) return } numInstances := len(i) totalNeeded := poolSize - numAvailable if len(t.Data) != 0 { // Admin creating a database. // In this case we were called with a number to precreate, such as from // admin api "precreate 100": for 1 .. N TODO: admin API endpoint for this n, err := strconv.Atoi(t.Data) if err != nil { log.Error(fmt.Sprintf("tasks.Task#PrecreateDatabases() strconv.Atoi(%s) (t.Data) ! %s", t.Data, err)) } else { totalNeeded = n // TODO: Account for maxAllowed } } log.Trace(fmt.Sprintf("tasks.Task#PrecreateDatabases() The total databases which need to be Precreated is %d, based on there being %d instances already, %d unused databases, a desired pool size of %d and a maximum capacity of %d...", totalNeeded, numInstances, numAvailable, poolSize, maxCapacity)) if totalNeeded > 0 { totalCreated := numInstances for index := 0; (index < totalNeeded) && (totalCreated < maxCapacity); index++ { log.Trace(fmt.Sprintf("tasks.Task#PrecreateDatabases() Precreating a new database for cluster type %s...", t.ClusterService)) if t.ClusterService == "pgbdr" { err = t.bdrPrecreateDatabase(client) } else if t.ClusterService == "postgresql" { err = t.postgresqlPrecreateDatabase(client) } else { log.Error(fmt.Sprintf("tasks.Task#PrecreateDatabases() Bad ClusterService for Precreate ! Attempted to determine the service cluster type and do not know how to handle type '%s' ", t.ClusterService)) return } if err != nil { log.Error(fmt.Sprintf("tasks.Task#PrecreateDatabases() t.PrecreateDatabases() ! %s", err)) return err } totalCreated++ } } return }
/* POST /databases/register PUT /databases/assign */ func DatabasesHandler(w http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) log.Trace(fmt.Sprintf("admin.DatabasesHandler() > %s /databases/%s %+v", request.Method, vars["action"], vars)) switch request.Method { case "GET": switch vars["action"] { case "": // List All Databases instances, err := instances.All() if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`+"\n", http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): instances.All() %s %+v ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } jsonInstances, err := json.Marshal(instances) if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`+"\n", http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): json.Marshal(instances) %s %+v ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) } else { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusOK) w.Write(jsonInstances) } case "available": // Lists Available Databases instances, err := instances.Available() if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`+"\n", http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): instances.Available() %s %+v ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } jsonInstances, err := json.Marshal(instances) if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`+"\n", http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): json.Marshal(instances) %s %+v ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) } else { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusOK) w.Write(jsonInstances) } default: msg := fmt.Sprintf(`{"status": %d, "description": "Invalid Action %s"}`+"\n", http.StatusBadRequest, vars["action"]) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): %s %s`, msg, vars)) http.Error(w, msg, http.StatusBadRequest) } case `POST`: var i instances.Instance decoder := json.NewDecoder(request.Body) err := decoder.Decode(&i) if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`+"\n", http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): decoder.Decode() %s %s ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } switch vars[`action`] { case `register`: err = i.Register() if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`+"\n", http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): Instance#Register() %s %s ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } else { w.Header().Set(`Content-Type`, `application/json; charset=UTF-8`) w.WriteHeader(http.StatusOK) w.Write([]byte(`{}`)) return } default: msg := fmt.Sprintf(`{"status": %d, "description": "Invalid Action %s"}`+"\n", http.StatusBadRequest, vars[`action`]) log.Error(msg) http.Error(w, msg, http.StatusBadRequest) } case `PUT`: switch vars[`action`] { case `assign`: // updates an existing record. // PUT /databases/assign/database var i instances.Instance decoder := json.NewDecoder(request.Body) err := decoder.Decode(&i) if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`+"\n", http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): decoder.Decode() assign %s %s ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } err = i.Assign() if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`+"\n", http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): instances.Assign() %s %s ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } else { w.Header().Set(`Content-Type`, `application/json; charset=UTF-8`) w.WriteHeader(http.StatusOK) w.Write([]byte(`{}`)) return } case `decommissioned`: // updates an existing record to show it was deprovisioned. // PUT /databases/decommissioned // This is requested from service cluster to master cluster type decomm struct { Database string `json:"database"` Timestamp string `json:"timestamp"` } dc := decomm{} decoder := json.NewDecoder(request.Body) err := decoder.Decode(&dc) if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`+"\n", http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): decoder.Decode() decommissioned %s %s ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } if len(dc.Timestamp) < 1 { err = errors.New(`Timestamp query parameter assignment is required!`) msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`+"\n", http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): decommissioned %s ! %s`, msg, err)) http.Error(w, msg, http.StatusInternalServerError) return } i, err := instances.FindByDatabase(dc.Database) if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`+"\n", http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): instances.FindByDatabase(%s) %s ! %s`, dc.Database, msg, err)) http.Error(w, msg, http.StatusInternalServerError) return } err = i.DecommissionedAt(dc.Timestamp) if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}"`+"\n", http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): instances.DecommissionedAt() %s %s ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } else { w.Header().Set(`Content-Type`, `application/json; charset=UTF-8`) w.WriteHeader(http.StatusOK) w.Write([]byte(`{}`)) return } default: msg := fmt.Sprintf(`{"status": %d, "description": "Invalid Action %s"}`+"\n", http.StatusBadRequest, vars[`action`]) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): %s %s`, msg, vars)) http.Error(w, msg, http.StatusBadRequest) } case `DELETE`: switch vars[`action`] { case `decommission`: i, err := instances.FindByDatabase(vars[`database`]) if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "%s"}`+"\n", http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): instance.FindByDatabase() %s %s ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) return } else { err = i.Decommission() if err != nil { msg := fmt.Sprintf(`{"status": %d, "description": "There was an error decommissioning the database (%s)"}`+"\n", http.StatusInternalServerError, err) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): instance#Decommission() %s %s ! %s`, msg, vars, err)) http.Error(w, msg, http.StatusInternalServerError) } } default: msg := fmt.Sprintf(`{"status": %d, "description": "Invalid Action %s"}`+"\n", http.StatusBadRequest, vars[`action`]) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): %s %s`, msg, vars)) http.Error(w, msg, http.StatusBadRequest) } default: msg := fmt.Sprintf(`{"status": %d, "description": "Method not allowed %s"}`+"\n", http.StatusMethodNotAllowed, request.Method) log.Error(fmt.Sprintf(`admin.DatabasesHandler(): %s %s`, msg, vars)) http.Error(w, msg, http.StatusMethodNotAllowed) return } }