3 Star 2 Fork 0

Gitee 极速下载/orchestrator

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
此仓库是为了提升国内下载速度的镜像仓库,每日同步一次。 原始仓库: https://github.com/outbrain/orchestrator/
克隆/下载
api.go 82.45 KB
一键复制 编辑 原始数据 按行查看 历史
Brian Ip 提交于 2016-07-25 13:49 . fix custom command param name typo
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374
/*
Copyright 2014 Outbrain Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package http
import (
"encoding/json"
"fmt"
"net"
"net/http"
"strconv"
"github.com/go-martini/martini"
"github.com/martini-contrib/auth"
"github.com/martini-contrib/render"
"github.com/outbrain/golib/util"
"github.com/outbrain/orchestrator/go/agent"
"github.com/outbrain/orchestrator/go/config"
"github.com/outbrain/orchestrator/go/inst"
"github.com/outbrain/orchestrator/go/logic"
"github.com/outbrain/orchestrator/go/process"
)
// APIResponseCode is an OK/ERROR response code
type APIResponseCode int
const (
ERROR APIResponseCode = iota
OK
)
func (this *APIResponseCode) MarshalJSON() ([]byte, error) {
return json.Marshal(this.String())
}
func (this *APIResponseCode) String() string {
switch *this {
case ERROR:
return "ERROR"
case OK:
return "OK"
}
return "unknown"
}
// APIResponse is a response returned as JSON to various requests.
type APIResponse struct {
Code APIResponseCode
Message string
Details interface{}
}
type HttpAPI struct{}
var API HttpAPI = HttpAPI{}
func (this *HttpAPI) getInstanceKey(host string, port string) (inst.InstanceKey, error) {
instanceKey, err := inst.NewInstanceKeyFromStrings(host, port)
return *instanceKey, err
}
func (this *HttpAPI) getBinlogCoordinates(logFile string, logPos string) (inst.BinlogCoordinates, error) {
coordinates := inst.BinlogCoordinates{LogFile: logFile}
var err error
if coordinates.LogPos, err = strconv.ParseInt(logPos, 10, 0); err != nil {
return coordinates, fmt.Errorf("Invalid logPos: %s", logPos)
}
return coordinates, err
}
// Instance reads and returns an instance's details.
func (this *HttpAPI) Instance(params martini.Params, r render.Render, req *http.Request) {
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, found, err := inst.ReadInstance(&instanceKey)
if (!found) || (err != nil) {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("Cannot read instance: %+v", instanceKey)})
return
}
r.JSON(200, instance)
}
// Discover issues a synchronous read on an instance
func (this *HttpAPI) Discover(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.ReadTopologyInstance(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Instance discovered: %+v", instance.Key), Details: instance})
}
// Refresh synchronuously re-reads a topology instance
func (this *HttpAPI) Refresh(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
_, err = inst.RefreshTopologyInstance(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Instance refreshed: %+v", instanceKey), Details: instanceKey})
}
// Forget removes an instance entry fro backend database
func (this *HttpAPI) Forget(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
// We ignore errors: we're looking to do a destructive operation anyhow.
rawInstanceKey, _ := inst.NewRawInstanceKey(fmt.Sprintf("%s:%s", params["host"], params["port"]))
inst.ForgetInstance(rawInstanceKey)
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Instance forgotten: %+v", *rawInstanceKey)})
}
// Resolve tries to resolve hostname and then checks to see if port is open on that host.
func (this *HttpAPI) Resolve(params martini.Params, r render.Render, req *http.Request) {
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
if conn, err := net.Dial("tcp", instanceKey.DisplayString()); err == nil {
conn.Close()
} else {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: "Instance resolved", Details: instanceKey})
}
// BeginMaintenance begins maintenance mode for given instance
func (this *HttpAPI) BeginMaintenance(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
key, err := inst.BeginBoundedMaintenance(&instanceKey, params["owner"], params["reason"], 0, true)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error(), Details: key})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Maintenance begun: %+v", instanceKey)})
}
// EndMaintenance terminates maintenance mode
func (this *HttpAPI) EndMaintenance(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
maintenanceKey, err := strconv.ParseInt(params["maintenanceKey"], 10, 0)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
err = inst.EndMaintenance(maintenanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Maintenance ended: %+v", maintenanceKey)})
}
// EndMaintenanceByInstanceKey terminates maintenance mode for given instance
func (this *HttpAPI) EndMaintenanceByInstanceKey(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
err = inst.EndMaintenanceByInstanceKey(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Maintenance ended: %+v", instanceKey)})
}
// Maintenance provides list of instance under active maintenance
func (this *HttpAPI) Maintenance(params martini.Params, r render.Render, req *http.Request) {
instanceKeys, err := inst.ReadActiveMaintenance()
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, instanceKeys)
}
// BeginDowntime sets a downtime flag with default duration
func (this *HttpAPI) BeginDowntime(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
var durationSeconds int = 0
if params["duration"] != "" {
durationSeconds, err = util.SimpleTimeToSeconds(params["duration"])
if durationSeconds < 0 {
err = fmt.Errorf("Duration value must be non-negative. Given value: %d", durationSeconds)
}
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
}
err = inst.BeginDowntime(&instanceKey, params["owner"], params["reason"], uint(durationSeconds))
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error(), Details: instanceKey})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Downtime begun: %+v", instanceKey)})
}
// EndDowntime terminates downtime (removes downtime flag) for an instance
func (this *HttpAPI) EndDowntime(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
err = inst.EndDowntime(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Downtime ended: %+v", instanceKey)})
}
// MoveUp attempts to move an instance up the topology
func (this *HttpAPI) MoveUp(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.MoveUp(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Instance %+v moved up", instanceKey), Details: instance})
}
// MoveUpSlaves attempts to move up all slaves of an instance
func (this *HttpAPI) MoveUpSlaves(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
slaves, newMaster, err, errs := inst.MoveUpSlaves(&instanceKey, req.URL.Query().Get("pattern"))
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Moved up %d slaves of %+v below %+v; %d errors: %+v", len(slaves), instanceKey, newMaster.Key, len(errs), errs), Details: newMaster.Key})
}
// MoveUpSlaves attempts to move up all slaves of an instance
func (this *HttpAPI) RepointSlaves(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
slaves, err, _ := inst.RepointSlaves(&instanceKey, req.URL.Query().Get("pattern"))
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Repointed %d slaves of %+v", len(slaves), instanceKey), Details: instanceKey})
}
// MakeCoMaster attempts to make an instance co-master with its own master
func (this *HttpAPI) MakeCoMaster(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.MakeCoMaster(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Instance made co-master: %+v", instance.Key), Details: instance})
}
// ResetSlave makes a slave forget about its master, effectively breaking the replication
func (this *HttpAPI) ResetSlave(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.ResetSlaveOperation(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Slave reset on %+v", instance.Key), Details: instance})
}
// DetachSlave corrupts a slave's binlog corrdinates (though encodes it in such way
// that is reversible), effectively breaking replication
func (this *HttpAPI) DetachSlave(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.DetachSlaveOperation(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Slave detached: %+v", instance.Key), Details: instance})
}
// ReattachSlave reverts a DetachSlave commands by reassigning the correct
// binlog coordinates to an instance
func (this *HttpAPI) ReattachSlave(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.ReattachSlaveOperation(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Slave reattached: %+v", instance.Key), Details: instance})
}
// ReattachSlaveMasterHost reverts a DetachSlaveMasterHost command
// by resoting the original master hostname in CHANGE MASTER TO
func (this *HttpAPI) ReattachSlaveMasterHost(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.ReattachSlaveMasterHost(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Slave reattached: %+v", instance.Key), Details: instance})
}
// EnableGTID attempts to enable GTID on a slave
func (this *HttpAPI) EnableGTID(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.EnableGTID(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Enabled GTID on %+v", instance.Key), Details: instance})
}
// DisableGTID attempts to disable GTID on a slave, and revert to binlog file:pos
func (this *HttpAPI) DisableGTID(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.DisableGTID(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Disabled GTID on %+v", instance.Key), Details: instance})
}
// MoveBelow attempts to move an instance below its supposed sibling
func (this *HttpAPI) MoveBelow(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
siblingKey, err := this.getInstanceKey(params["siblingHost"], params["siblingPort"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.MoveBelow(&instanceKey, &siblingKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Instance %+v moved below %+v", instanceKey, siblingKey), Details: instance})
}
// MoveBelowGTID attempts to move an instance below another, via GTID
func (this *HttpAPI) MoveBelowGTID(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
belowKey, err := this.getInstanceKey(params["belowHost"], params["belowPort"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.MoveBelowGTID(&instanceKey, &belowKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Instance %+v moved below %+v via GTID", instanceKey, belowKey), Details: instance})
}
// MoveSlavesGTID attempts to move an instance below another, via GTID
func (this *HttpAPI) MoveSlavesGTID(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
belowKey, err := this.getInstanceKey(params["belowHost"], params["belowPort"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
movedSlaves, _, err, errs := inst.MoveSlavesGTID(&instanceKey, &belowKey, req.URL.Query().Get("pattern"))
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Moved %d slaves of %+v below %+v via GTID; %d errors: %+v", len(movedSlaves), instanceKey, belowKey, len(errs), errs), Details: belowKey})
}
// EnslaveSiblings
func (this *HttpAPI) EnslaveSiblings(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, count, err := inst.EnslaveSiblings(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Enslaved %d siblings of %+v", count, instanceKey), Details: instance})
}
// EnslaveMaster
func (this *HttpAPI) EnslaveMaster(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.EnslaveMaster(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("%+v enslaved its master", instanceKey), Details: instance})
}
// RelocateBelow attempts to move an instance below another, orchestrator choosing the best (potentially multi-step)
// relocation method
func (this *HttpAPI) RelocateBelow(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
belowKey, err := this.getInstanceKey(params["belowHost"], params["belowPort"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.RelocateBelow(&instanceKey, &belowKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Instance %+v relocated below %+v", instanceKey, belowKey), Details: instance})
}
// RelocateSlaves attempts to smartly relocate slaves of a given instance below another
func (this *HttpAPI) RelocateSlaves(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
belowKey, err := this.getInstanceKey(params["belowHost"], params["belowPort"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
slaves, _, err, errs := inst.RelocateSlaves(&instanceKey, &belowKey, req.URL.Query().Get("pattern"))
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Relocated %d slaves of %+v below %+v; %d errors: %+v", len(slaves), instanceKey, belowKey, len(errs), errs), Details: slaves})
}
// MoveEquivalent attempts to move an instance below another, baseed on known equivalence master coordinates
func (this *HttpAPI) MoveEquivalent(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
belowKey, err := this.getInstanceKey(params["belowHost"], params["belowPort"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.MoveEquivalent(&instanceKey, &belowKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Instance %+v relocated via equivalence coordinates below %+v", instanceKey, belowKey), Details: instance})
}
// LastPseudoGTID attempts to find the last pseugo-gtid entry in an instance
func (this *HttpAPI) LastPseudoGTID(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.ReadTopologyInstance(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
if instance == nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("Instance not found: %+v", instanceKey)})
return
}
coordinates, text, err := inst.FindLastPseudoGTIDEntry(instance, instance.RelaylogCoordinates, nil, false, nil)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("%+v", *coordinates), Details: text})
}
// MatchBelow attempts to move an instance below another via pseudo GTID matching of binlog entries
func (this *HttpAPI) MatchBelow(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
belowKey, err := this.getInstanceKey(params["belowHost"], params["belowPort"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, matchedCoordinates, err := inst.MatchBelow(&instanceKey, &belowKey, true)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Instance %+v matched below %+v at %+v", instanceKey, belowKey, *matchedCoordinates), Details: instance})
}
// MatchBelow attempts to move an instance below another via pseudo GTID matching of binlog entries
func (this *HttpAPI) MatchUp(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, matchedCoordinates, err := inst.MatchUp(&instanceKey, true)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Instance %+v matched up at %+v", instanceKey, *matchedCoordinates), Details: instance})
}
// MultiMatchSlaves attempts to match all slaves of a given instance below another, efficiently
func (this *HttpAPI) MultiMatchSlaves(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
belowKey, err := this.getInstanceKey(params["belowHost"], params["belowPort"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
slaves, newMaster, err, errs := inst.MultiMatchSlaves(&instanceKey, &belowKey, req.URL.Query().Get("pattern"))
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Matched %d slaves of %+v below %+v; %d errors: %+v", len(slaves), instanceKey, newMaster.Key, len(errs), errs), Details: newMaster.Key})
}
// MatchUpSlaves attempts to match up all slaves of an instance
func (this *HttpAPI) MatchUpSlaves(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
slaves, newMaster, err, errs := inst.MatchUpSlaves(&instanceKey, req.URL.Query().Get("pattern"))
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Matched up %d slaves of %+v below %+v; %d errors: %+v", len(slaves), instanceKey, newMaster.Key, len(errs), errs), Details: newMaster.Key})
}
// RegroupSlaves attempts to pick a slave of a given instance and make it enslave its siblings, using any
// method possible (GTID, Pseudo-GTID, binlog servers)
func (this *HttpAPI) RegroupSlaves(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
lostSlaves, equalSlaves, aheadSlaves, cannotReplicateSlaves, promotedSlave, err := inst.RegroupSlaves(&instanceKey, false, nil, nil)
lostSlaves = append(lostSlaves, cannotReplicateSlaves...)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("promoted slave: %s, lost: %d, trivial: %d, pseudo-gtid: %d",
promotedSlave.Key.DisplayString(), len(lostSlaves), len(equalSlaves), len(aheadSlaves)), Details: promotedSlave.Key})
}
// RegroupSlaves attempts to pick a slave of a given instance and make it enslave its siblings, efficiently,
// using pseudo-gtid if necessary
func (this *HttpAPI) RegroupSlavesPseudoGTID(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
lostSlaves, equalSlaves, aheadSlaves, cannotReplicateSlaves, promotedSlave, err := inst.RegroupSlavesPseudoGTID(&instanceKey, false, nil, nil)
lostSlaves = append(lostSlaves, cannotReplicateSlaves...)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("promoted slave: %s, lost: %d, trivial: %d, pseudo-gtid: %d",
promotedSlave.Key.DisplayString(), len(lostSlaves), len(equalSlaves), len(aheadSlaves)), Details: promotedSlave.Key})
}
// RegroupSlavesGTID attempts to pick a slave of a given instance and make it enslave its siblings, efficiently, using GTID
func (this *HttpAPI) RegroupSlavesGTID(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
lostSlaves, movedSlaves, cannotReplicateSlaves, promotedSlave, err := inst.RegroupSlavesGTID(&instanceKey, false, nil)
lostSlaves = append(lostSlaves, cannotReplicateSlaves...)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("promoted slave: %s, lost: %d, moved: %d",
promotedSlave.Key.DisplayString(), len(lostSlaves), len(movedSlaves)), Details: promotedSlave.Key})
}
// RegroupSlavesBinlogServers attempts to pick a slave of a given instance and make it enslave its siblings, efficiently, using GTID
func (this *HttpAPI) RegroupSlavesBinlogServers(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
_, promotedBinlogServer, err := inst.RegroupSlavesBinlogServers(&instanceKey, false)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("promoted binlog server: %s",
promotedBinlogServer.Key.DisplayString()), Details: promotedBinlogServer.Key})
}
// MakeMaster attempts to make the given instance a master, and match its siblings to be its slaves
func (this *HttpAPI) MakeMaster(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.MakeMaster(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Instance %+v now made master", instanceKey), Details: instance})
}
// MakeLocalMaster attempts to make the given instance a local master: take over its master by
// enslaving its siblings and replicating from its grandparent.
func (this *HttpAPI) MakeLocalMaster(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.MakeLocalMaster(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Instance %+v now made local master", instanceKey), Details: instance})
}
// SkipQuery skips a single query on a failed replication instance
func (this *HttpAPI) SkipQuery(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.SkipQuery(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Query skipped on %+v", instance.Key), Details: instance})
}
// StartSlave starts replication on given instance
func (this *HttpAPI) StartSlave(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.StartSlave(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Slave started: %+v", instance.Key), Details: instance})
}
// RestartSlave stops & starts replication on given instance
func (this *HttpAPI) RestartSlave(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.RestartSlave(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Slave restarted: %+v", instance.Key), Details: instance})
}
// StopSlave stops replication on given instance
func (this *HttpAPI) StopSlave(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.StopSlave(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Slave stopped: %+v", instance.Key), Details: instance})
}
// StopSlaveNicely stops replication on given instance, such that sql thead is aligned with IO thread
func (this *HttpAPI) StopSlaveNicely(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.StopSlaveNicely(&instanceKey, 0)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Slave stopped nicely: %+v", instance.Key), Details: instance})
}
// MasterEquivalent provides (possibly empty) list of master coordinates equivalent to the given ones
func (this *HttpAPI) MasterEquivalent(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
coordinates, err := this.getBinlogCoordinates(params["logFile"], params["logPos"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instanceCoordinates := &inst.InstanceBinlogCoordinates{Key: instanceKey, Coordinates: coordinates}
equivalentCoordinates, err := inst.GetEquivalentMasterCoordinates(instanceCoordinates)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Found %+v equivalent coordinates", len(equivalentCoordinates)), Details: equivalentCoordinates})
}
// SetReadOnly sets the global read_only variable
func (this *HttpAPI) SetReadOnly(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.SetReadOnly(&instanceKey, true)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: "Server set as read-only", Details: instance})
}
// SetWriteable clear the global read_only variable
func (this *HttpAPI) SetWriteable(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.SetReadOnly(&instanceKey, false)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: "Server set as writeable", Details: instance})
}
// KillQuery kills a query running on a server
func (this *HttpAPI) KillQuery(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
processId, err := strconv.ParseInt(params["process"], 10, 0)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, err := inst.KillQuery(&instanceKey, processId)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Query killed on : %+v", instance.Key), Details: instance})
}
// Cluster provides list of instances in given cluster
func (this *HttpAPI) Cluster(params martini.Params, r render.Render, req *http.Request) {
instances, err := inst.ReadClusterInstances(params["clusterName"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, instances)
}
// ClusterByAlias provides list of instances in given cluster
func (this *HttpAPI) ClusterByAlias(params martini.Params, r render.Render, req *http.Request) {
clusterName, err := inst.GetClusterByAlias(params["clusterAlias"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
params["clusterName"] = clusterName
this.Cluster(params, r, req)
}
// ClusterByInstance provides list of instances in cluster an instance belongs to
func (this *HttpAPI) ClusterByInstance(params martini.Params, r render.Render, req *http.Request) {
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
instance, found, err := inst.ReadInstance(&instanceKey)
if (!found) || (err != nil) {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("Cannot read instance: %+v", instanceKey)})
return
}
params["clusterName"] = instance.ClusterName
this.Cluster(params, r, req)
}
// ClusterInfo provides details of a given cluster
func (this *HttpAPI) ClusterInfo(params martini.Params, r render.Render, req *http.Request) {
clusterInfo, err := inst.ReadClusterInfo(params["clusterName"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, clusterInfo)
}
// Cluster provides list of instances in given cluster
func (this *HttpAPI) ClusterInfoByAlias(params martini.Params, r render.Render, req *http.Request) {
clusterName, err := inst.GetClusterByAlias(params["clusterAlias"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
params["clusterName"] = clusterName
this.ClusterInfo(params, r, req)
}
// ClusterOSCSlaves returns heuristic list of OSC slaves
func (this *HttpAPI) ClusterOSCSlaves(params martini.Params, r render.Render, req *http.Request) {
instances, err := inst.GetClusterOSCSlaves(params["clusterName"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, instances)
}
// SetClusterAlias will change an alias for a given clustername
func (this *HttpAPI) SetClusterAlias(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
clusterName := params["clusterName"]
alias := req.URL.Query().Get("alias")
err := inst.SetClusterAlias(clusterName, alias)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Cluster %s now has alias '%s'", clusterName, alias)})
}
// Clusters provides list of known clusters
func (this *HttpAPI) Clusters(params martini.Params, r render.Render, req *http.Request) {
clusterNames, err := inst.ReadClusters()
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, clusterNames)
}
// ClustersInfo provides list of known clusters, along with some added metadata per cluster
func (this *HttpAPI) ClustersInfo(params martini.Params, r render.Render, req *http.Request) {
clustersInfo, err := inst.ReadClustersInfo("")
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, clustersInfo)
}
// Search provides list of instances matching given search param via various criteria.
func (this *HttpAPI) Search(params martini.Params, r render.Render, req *http.Request) {
searchString := params["searchString"]
if searchString == "" {
searchString = req.URL.Query().Get("s")
}
instances, err := inst.SearchInstances(searchString)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, instances)
}
// Problems provides list of instances with known problems
func (this *HttpAPI) Problems(params martini.Params, r render.Render, req *http.Request) {
clusterName := params["clusterName"]
instances, err := inst.ReadProblemInstances(clusterName)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, instances)
}
// Audit provides list of audit entries by given page number
func (this *HttpAPI) Audit(params martini.Params, r render.Render, req *http.Request) {
page, err := strconv.Atoi(params["page"])
if err != nil || page < 0 {
page = 0
}
var auditedInstanceKey *inst.InstanceKey
if instanceKey, err := this.getInstanceKey(params["host"], params["port"]); err == nil {
auditedInstanceKey = &instanceKey
}
audits, err := inst.ReadRecentAudit(auditedInstanceKey, page)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, audits)
}
// LongQueries lists queries running for a long time, on all instances, optionally filtered by
// arbitrary text
func (this *HttpAPI) LongQueries(params martini.Params, r render.Render, req *http.Request) {
longQueries, err := inst.ReadLongRunningProcesses(params["filter"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, longQueries)
}
// HostnameResolveCache shows content of in-memory hostname cache
func (this *HttpAPI) HostnameResolveCache(params martini.Params, r render.Render, req *http.Request) {
content, err := inst.HostnameResolveCache()
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: "Cache retrieved", Details: content})
}
// ResetHostnameResolveCache clears in-memory hostname resovle cache
func (this *HttpAPI) ResetHostnameResolveCache(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
err := inst.ResetHostnameResolveCache()
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: "Hostname cache cleared"})
}
// SubmitPoolInstances (re-)applies the list of hostnames for a given pool
func (this *HttpAPI) SubmitPoolInstances(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
pool := params["pool"]
instances := req.URL.Query().Get("instances")
err := inst.ApplyPoolInstances(pool, instances)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Applied %s pool instances", pool)})
}
// SubmitPoolHostnames (re-)applies the list of hostnames for a given pool
func (this *HttpAPI) ReadClusterPoolInstancesMap(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
clusterName := params["clusterName"]
pool := params["pool"]
poolInstancesMap, err := inst.ReadClusterPoolInstancesMap(clusterName, pool)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Read pool instances for cluster %s", clusterName), Details: poolInstancesMap})
}
// GetHeuristicClusterPoolInstances returns instances belonging to a cluster's pool
func (this *HttpAPI) GetHeuristicClusterPoolInstances(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
clusterName, err := inst.ReadClusterNameByAlias(params["clusterName"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
pool := params["pool"]
instances, err := inst.GetHeuristicClusterPoolInstances(clusterName, pool)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Heuristic pool instances for cluster %s", clusterName), Details: instances})
}
// GetHeuristicClusterPoolInstances returns instances belonging to a cluster's pool
func (this *HttpAPI) GetHeuristicClusterPoolInstancesLag(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
clusterName, err := inst.ReadClusterNameByAlias(params["clusterName"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
pool := params["pool"]
lag, err := inst.GetHeuristicClusterPoolInstancesLag(clusterName, pool)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Heuristic pool lag for cluster %s", clusterName), Details: lag})
}
// ReloadClusterAlias clears in-memory hostname resovle cache
func (this *HttpAPI) ReloadClusterAlias(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
r.JSON(200, &APIResponse{Code: ERROR, Message: "This API call has been retired"})
}
// Agents provides complete list of registered agents (See https://github.com/github/orchestrator-agent)
func (this *HttpAPI) Agents(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
agents, err := agent.ReadAgents()
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, agents)
}
// Agent returns complete information of a given agent
func (this *HttpAPI) Agent(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
agent, err := agent.GetAgent(params["host"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, agent)
}
// AgentUnmount instructs an agent to unmount the designated mount point
func (this *HttpAPI) AgentUnmount(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
output, err := agent.Unmount(params["host"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, output)
}
// AgentMountLV instructs an agent to mount a given volume on the designated mount point
func (this *HttpAPI) AgentMountLV(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
output, err := agent.MountLV(params["host"], req.URL.Query().Get("lv"))
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, output)
}
// AgentCreateSnapshot instructs an agent to create a new snapshot. Agent's DIY implementation.
func (this *HttpAPI) AgentCreateSnapshot(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
output, err := agent.CreateSnapshot(params["host"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, output)
}
// AgentRemoveLV instructs an agent to remove a logical volume
func (this *HttpAPI) AgentRemoveLV(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
output, err := agent.RemoveLV(params["host"], req.URL.Query().Get("lv"))
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, output)
}
// AgentMySQLStop stops MySQL service on agent
func (this *HttpAPI) AgentMySQLStop(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
output, err := agent.MySQLStop(params["host"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, output)
}
// AgentMySQLStart starts MySQL service on agent
func (this *HttpAPI) AgentMySQLStart(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
output, err := agent.MySQLStart(params["host"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, output)
}
func (this *HttpAPI) AgentCustomCommand(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
output, err := agent.CustomCommand(params["host"], params["command"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, output)
}
// AgentSeed completely seeds a host with another host's snapshots. This is a complex operation
// governed by orchestrator and executed by the two agents involved.
func (this *HttpAPI) AgentSeed(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
output, err := agent.Seed(params["targetHost"], params["sourceHost"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, output)
}
// AgentActiveSeeds lists active seeds and their state
func (this *HttpAPI) AgentActiveSeeds(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
output, err := agent.ReadActiveSeedsForHost(params["host"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, output)
}
// AgentRecentSeeds lists recent seeds of a given agent
func (this *HttpAPI) AgentRecentSeeds(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
output, err := agent.ReadRecentCompletedSeedsForHost(params["host"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, output)
}
// AgentSeedDetails provides details of a given seed
func (this *HttpAPI) AgentSeedDetails(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
seedId, err := strconv.ParseInt(params["seedId"], 10, 0)
output, err := agent.AgentSeedDetails(seedId)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, output)
}
// AgentSeedStates returns the breakdown of states (steps) of a given seed
func (this *HttpAPI) AgentSeedStates(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
seedId, err := strconv.ParseInt(params["seedId"], 10, 0)
output, err := agent.ReadSeedStates(seedId)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, output)
}
// Seeds retruns all recent seeds
func (this *HttpAPI) Seeds(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
output, err := agent.ReadRecentSeeds()
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, output)
}
// AbortSeed instructs agents to abort an active seed
func (this *HttpAPI) AbortSeed(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
if !config.Config.ServeAgentsHttp {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Agents not served"})
return
}
seedId, err := strconv.ParseInt(params["seedId"], 10, 0)
err = agent.AbortSeed(seedId)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, err == nil)
}
// Headers is a self-test call which returns HTTP headers
func (this *HttpAPI) Headers(params martini.Params, r render.Render, req *http.Request) {
r.JSON(200, req.Header)
}
// Health performs a self test
func (this *HttpAPI) Health(params martini.Params, r render.Render, req *http.Request) {
health, err := process.HealthTest()
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("Application node is unhealthy %+v", err), Details: health})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Application node is healthy"), Details: health})
}
// LBCheck returns a constant respnse, and this can be used by load balancers that expect a given string.
func (this *HttpAPI) LBCheck(params martini.Params, r render.Render, req *http.Request) {
r.JSON(200, "OK")
}
// A configurable endpoint that can be for regular status checks or whatever. While similar to
// Health() this returns 500 on failure. This will prevent issues for those that have come to
// expect a 200
// It might be a good idea to deprecate the current Health() behavior and roll this in at some
// point
func (this *HttpAPI) StatusCheck(params martini.Params, r render.Render, req *http.Request) {
// SimpleHealthTest just checks to see if we can connect to the database. Lighter weight if you intend to call it a lot
var health *process.HealthStatus
var err error
if config.Config.StatusSimpleHealth {
health, err = process.SimpleHealthTest()
} else {
health, err = process.HealthTest()
}
if err != nil {
r.JSON(500, &APIResponse{Code: ERROR, Message: fmt.Sprintf("Application node is unhealthy %+v", err), Details: health})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Application node is healthy"), Details: health})
}
// GrabElection forcibly grabs leadership. Use with care!!
func (this *HttpAPI) GrabElection(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
err := process.GrabElection()
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("Unable to grab election: %+v", err)})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Node elected as leader")})
}
// Reelect causes re-elections for an active node
func (this *HttpAPI) Reelect(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
err := process.Reelect()
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("Unable to re-elect: %+v", err)})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Set re-elections")})
}
// ReloadConfiguration reloads confiug settings (not all of which will apply after change)
func (this *HttpAPI) ReloadConfiguration(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
config.Reload()
inst.AuditOperation("reload-configuration", nil, "Triggered via API")
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Config reloaded")})
}
// ReplicationAnalysis retuens list of issues
func (this *HttpAPI) ReplicationAnalysis(params martini.Params, r render.Render, req *http.Request) {
analysis, err := inst.GetReplicationAnalysis(params["clusterName"], true, false)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("Cannot get analysis: %+v", err)})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Analysis"), Details: analysis})
}
// RecoverLite attempts recovery on a given instance, without executing external processes
func (this *HttpAPI) RecoverLite(params martini.Params, r render.Render, req *http.Request, user auth.User) {
params["skipProcesses"] = "true"
this.Recover(params, r, req, user)
}
// Recover attempts recovery on a given instance
func (this *HttpAPI) Recover(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
var candidateKey *inst.InstanceKey
if key, err := this.getInstanceKey(params["candidateHost"], params["candidatePort"]); err == nil {
candidateKey = &key
}
skipProcesses := (req.URL.Query().Get("skipProcesses") == "true") || (params["skipProcesses"] == "true")
recoveryAttempted, _, err := logic.CheckAndRecover(&instanceKey, candidateKey, skipProcesses)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
if recoveryAttempted {
r.JSON(200, &APIResponse{Code: OK, Message: "Action taken", Details: instanceKey})
} else {
r.JSON(200, &APIResponse{Code: OK, Message: "No action taken", Details: instanceKey})
}
}
// Registers promotion preference for given instance
func (this *HttpAPI) RegisterCandidate(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
promotionRule, err := inst.ParseCandidatePromotionRule(params["promotionRule"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
err = inst.RegisterCandidateInstance(&instanceKey, promotionRule)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
r.JSON(200, &APIResponse{Code: OK, Message: "Registered candidate", Details: instanceKey})
}
// AutomatedRecoveryFilters retuens list of clusters which are configured with automated recovery
func (this *HttpAPI) AutomatedRecoveryFilters(params martini.Params, r render.Render, req *http.Request) {
automatedRecoveryMap := make(map[string]interface{})
automatedRecoveryMap["RecoverMasterClusterFilters"] = config.Config.RecoverMasterClusterFilters
automatedRecoveryMap["RecoverIntermediateMasterClusterFilters"] = config.Config.RecoverIntermediateMasterClusterFilters
automatedRecoveryMap["RecoveryIgnoreHostnameFilters"] = config.Config.RecoveryIgnoreHostnameFilters
r.JSON(200, &APIResponse{Code: OK, Message: fmt.Sprintf("Automated recovery configuration details"), Details: automatedRecoveryMap})
}
// AuditFailureDetection provides list of topology_failure_detection entries
func (this *HttpAPI) AuditFailureDetection(params martini.Params, r render.Render, req *http.Request) {
var audits []logic.TopologyRecovery
var err error
if detectionId, derr := strconv.ParseInt(params["id"], 10, 0); derr == nil && detectionId > 0 {
audits, err = logic.ReadFailureDetection(detectionId)
} else {
page, derr := strconv.Atoi(params["page"])
if derr != nil || page < 0 {
page = 0
}
audits, err = logic.ReadRecentFailureDetections(page)
}
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, audits)
}
// ReadReplicationAnalysisChangelog lists instances and their analysis changelog
func (this *HttpAPI) ReadReplicationAnalysisChangelog(params martini.Params, r render.Render, req *http.Request) {
changelogs, err := inst.ReadReplicationAnalysisChangelog()
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, changelogs)
}
// AuditRecovery provides list of topology-recovery entries
func (this *HttpAPI) AuditRecovery(params martini.Params, r render.Render, req *http.Request) {
var audits []logic.TopologyRecovery
var err error
if recoveryId, derr := strconv.ParseInt(params["id"], 10, 0); derr == nil && recoveryId > 0 {
audits, err = logic.ReadRecovery(recoveryId)
} else {
page, derr := strconv.Atoi(params["page"])
if derr != nil || page < 0 {
page = 0
}
unacknowledgedOnly := (req.URL.Query().Get("unacknowledged") == "true")
audits, err = logic.ReadRecentRecoveries(params["clusterName"], unacknowledgedOnly, page)
}
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, audits)
}
// ActiveClusterRecovery returns recoveries in-progress for a given cluster
func (this *HttpAPI) ActiveClusterRecovery(params martini.Params, r render.Render, req *http.Request) {
recoveries, err := logic.ReadActiveClusterRecovery(params["clusterName"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, recoveries)
}
// RecentlyActiveClusterRecovery returns recoveries in-progress for a given cluster
func (this *HttpAPI) RecentlyActiveClusterRecovery(params martini.Params, r render.Render, req *http.Request) {
recoveries, err := logic.ReadRecentlyActiveClusterRecovery(params["clusterName"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, recoveries)
}
// RecentlyActiveClusterRecovery returns recoveries in-progress for a given cluster
func (this *HttpAPI) RecentlyActiveInstanceRecovery(params martini.Params, r render.Render, req *http.Request) {
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
recoveries, err := logic.ReadRecentlyActiveInstanceRecovery(&instanceKey)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, recoveries)
}
// ClusterInfo provides details of a given cluster
func (this *HttpAPI) AcknowledgeClusterRecoveries(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
clusterName := params["clusterName"]
if params["clusterAlias"] != "" {
var err error
clusterName, err = inst.GetClusterByAlias(params["clusterAlias"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
}
comment := req.URL.Query().Get("comment")
if comment == "" {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("No acknowledge comment given")})
return
}
userId := getUserId(req, user)
if userId == "" {
userId = inst.GetMaintenanceOwner()
}
countAcnowledgedRecoveries, err := logic.AcknowledgeClusterRecoveries(clusterName, userId, comment)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, countAcnowledgedRecoveries)
}
// ClusterInfo provides details of a given cluster
func (this *HttpAPI) AcknowledgeInstanceRecoveries(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
instanceKey, err := this.getInstanceKey(params["host"], params["port"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
comment := req.URL.Query().Get("comment")
if comment == "" {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("No acknowledge comment given")})
return
}
userId := getUserId(req, user)
if userId == "" {
userId = inst.GetMaintenanceOwner()
}
countAcnowledgedRecoveries, err := logic.AcknowledgeInstanceRecoveries(&instanceKey, userId, comment)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, countAcnowledgedRecoveries)
}
// ClusterInfo provides details of a given cluster
func (this *HttpAPI) AcknowledgeRecovery(params martini.Params, r render.Render, req *http.Request, user auth.User) {
if !isAuthorizedForAction(req, user) {
r.JSON(200, &APIResponse{Code: ERROR, Message: "Unauthorized"})
return
}
recoveryId, err := strconv.ParseInt(params["recoveryId"], 10, 0)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: err.Error()})
return
}
comment := req.URL.Query().Get("comment")
if comment == "" {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("No acknowledge comment given")})
return
}
userId := getUserId(req, user)
if userId == "" {
userId = inst.GetMaintenanceOwner()
}
countAcnowledgedRecoveries, err := logic.AcknowledgeRecovery(recoveryId, userId, comment)
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, countAcnowledgedRecoveries)
}
// BlockedRecoveries reads list of currently blocked recoveries, optionally filtered by cluster name
func (this *HttpAPI) BlockedRecoveries(params martini.Params, r render.Render, req *http.Request) {
blockedRecoveries, err := logic.ReadBlockedRecoveries(params["clusterName"])
if err != nil {
r.JSON(200, &APIResponse{Code: ERROR, Message: fmt.Sprintf("%+v", err)})
return
}
r.JSON(200, blockedRecoveries)
}
// RegisterRequests makes for the de-facto list of known API calls
func (this *HttpAPI) RegisterRequests(m *martini.ClassicMartini) {
// Smart relocation:
m.Get("/api/relocate/:host/:port/:belowHost/:belowPort", this.RelocateBelow)
m.Get("/api/relocate-below/:host/:port/:belowHost/:belowPort", this.RelocateBelow)
m.Get("/api/relocate-slaves/:host/:port/:belowHost/:belowPort", this.RelocateSlaves)
m.Get("/api/regroup-slaves/:host/:port", this.RegroupSlaves)
// Classic file:pos relocation:
m.Get("/api/move-up/:host/:port", this.MoveUp)
m.Get("/api/move-up-slaves/:host/:port", this.MoveUpSlaves)
m.Get("/api/move-below/:host/:port/:siblingHost/:siblingPort", this.MoveBelow)
m.Get("/api/move-equivalent/:host/:port/:belowHost/:belowPort", this.MoveEquivalent)
m.Get("/api/repoint-slaves/:host/:port", this.RepointSlaves)
m.Get("/api/make-co-master/:host/:port", this.MakeCoMaster)
m.Get("/api/enslave-siblings/:host/:port", this.EnslaveSiblings)
m.Get("/api/enslave-master/:host/:port", this.EnslaveMaster)
m.Get("/api/master-equivalent/:host/:port/:logFile/:logPos", this.MasterEquivalent)
// Binlog server relocation:
m.Get("/api/regroup-slaves-bls/:host/:port", this.RegroupSlavesBinlogServers)
// GTID relocation:
m.Get("/api/move-below-gtid/:host/:port/:belowHost/:belowPort", this.MoveBelowGTID)
m.Get("/api/move-slaves-gtid/:host/:port/:belowHost/:belowPort", this.MoveSlavesGTID)
m.Get("/api/regroup-slaves-gtid/:host/:port", this.RegroupSlavesGTID)
// Pseudo-GTID relocation:
m.Get("/api/match/:host/:port/:belowHost/:belowPort", this.MatchBelow)
m.Get("/api/match-below/:host/:port/:belowHost/:belowPort", this.MatchBelow)
m.Get("/api/match-up/:host/:port", this.MatchUp)
m.Get("/api/match-slaves/:host/:port/:belowHost/:belowPort", this.MultiMatchSlaves)
m.Get("/api/multi-match-slaves/:host/:port/:belowHost/:belowPort", this.MultiMatchSlaves)
m.Get("/api/match-up-slaves/:host/:port", this.MatchUpSlaves)
m.Get("/api/regroup-slaves-pgtid/:host/:port", this.RegroupSlavesPseudoGTID)
// Legacy, need to revisit:
m.Get("/api/make-master/:host/:port", this.MakeMaster)
m.Get("/api/make-local-master/:host/:port", this.MakeLocalMaster)
// Replication, general:
m.Get("/api/enable-gtid/:host/:port", this.EnableGTID)
m.Get("/api/disable-gtid/:host/:port", this.DisableGTID)
m.Get("/api/skip-query/:host/:port", this.SkipQuery)
m.Get("/api/start-slave/:host/:port", this.StartSlave)
m.Get("/api/restart-slave/:host/:port", this.RestartSlave)
m.Get("/api/stop-slave/:host/:port", this.StopSlave)
m.Get("/api/stop-slave-nice/:host/:port", this.StopSlaveNicely)
m.Get("/api/reset-slave/:host/:port", this.ResetSlave)
m.Get("/api/detach-slave/:host/:port", this.DetachSlave)
m.Get("/api/reattach-slave/:host/:port", this.ReattachSlave)
m.Get("/api/reattach-slave-master-host/:host/:port", this.ReattachSlaveMasterHost)
// Instance:
m.Get("/api/set-read-only/:host/:port", this.SetReadOnly)
m.Get("/api/set-writeable/:host/:port", this.SetWriteable)
m.Get("/api/kill-query/:host/:port/:process", this.KillQuery)
// Binary logs:
m.Get("/api/last-pseudo-gtid/:host/:port", this.LastPseudoGTID)
// Pools:
m.Get("/api/submit-pool-instances/:pool", this.SubmitPoolInstances)
m.Get("/api/cluster-pool-instances/:clusterName", this.ReadClusterPoolInstancesMap)
m.Get("/api/cluster-pool-instances/:clusterName/:pool", this.ReadClusterPoolInstancesMap)
m.Get("/api/heuristic-cluster-pool-instances/:clusterName", this.GetHeuristicClusterPoolInstances)
m.Get("/api/heuristic-cluster-pool-instances/:clusterName/:pool", this.GetHeuristicClusterPoolInstances)
m.Get("/api/heuristic-cluster-pool-lag/:clusterName", this.GetHeuristicClusterPoolInstancesLag)
m.Get("/api/heuristic-cluster-pool-lag/:clusterName/:pool", this.GetHeuristicClusterPoolInstancesLag)
// Information:
m.Get("/api/search/:searchString", this.Search)
m.Get("/api/search", this.Search)
// Cluster
m.Get("/api/cluster/:clusterName", this.Cluster)
m.Get("/api/cluster/alias/:clusterAlias", this.ClusterByAlias)
m.Get("/api/cluster/instance/:host/:port", this.ClusterByInstance)
m.Get("/api/cluster-info/:clusterName", this.ClusterInfo)
m.Get("/api/cluster-info/alias/:clusterAlias", this.ClusterInfoByAlias)
m.Get("/api/cluster-osc-slaves/:clusterName", this.ClusterOSCSlaves)
m.Get("/api/set-cluster-alias/:clusterName", this.SetClusterAlias)
m.Get("/api/clusters", this.Clusters)
m.Get("/api/clusters-info", this.ClustersInfo)
// Instance management:
m.Get("/api/instance/:host/:port", this.Instance)
m.Get("/api/discover/:host/:port", this.Discover)
m.Get("/api/refresh/:host/:port", this.Refresh)
m.Get("/api/forget/:host/:port", this.Forget)
m.Get("/api/begin-maintenance/:host/:port/:owner/:reason", this.BeginMaintenance)
m.Get("/api/end-maintenance/:host/:port", this.EndMaintenanceByInstanceKey)
m.Get("/api/end-maintenance/:maintenanceKey", this.EndMaintenance)
m.Get("/api/begin-downtime/:host/:port/:owner/:reason", this.BeginDowntime)
m.Get("/api/begin-downtime/:host/:port/:owner/:reason/:duration", this.BeginDowntime)
m.Get("/api/end-downtime/:host/:port", this.EndDowntime)
// Recovery:
m.Get("/api/replication-analysis", this.ReplicationAnalysis)
m.Get("/api/replication-analysis/:clusterName", this.ReplicationAnalysis)
m.Get("/api/recover/:host/:port", this.Recover)
m.Get("/api/recover/:host/:port/:candidateHost/:candidatePort", this.Recover)
m.Get("/api/recover-lite/:host/:port", this.RecoverLite)
m.Get("/api/recover-lite/:host/:port/:candidateHost/:candidatePort", this.RecoverLite)
m.Get("/api/register-candidate/:host/:port/:promotionRule", this.RegisterCandidate)
m.Get("/api/automated-recovery-filters", this.AutomatedRecoveryFilters)
m.Get("/api/audit-failure-detection", this.AuditFailureDetection)
m.Get("/api/audit-failure-detection/:page", this.AuditFailureDetection)
m.Get("/api/audit-failure-detection/id/:id", this.AuditFailureDetection)
m.Get("/api/replication-analysis-changelog", this.ReadReplicationAnalysisChangelog)
m.Get("/api/audit-recovery", this.AuditRecovery)
m.Get("/api/audit-recovery/:page", this.AuditRecovery)
m.Get("/api/audit-recovery/id/:id", this.AuditRecovery)
m.Get("/api/audit-recovery/cluster/:clusterName", this.AuditRecovery)
m.Get("/api/audit-recovery/cluster/:clusterName/:page", this.AuditRecovery)
m.Get("/api/active-cluster-recovery/:clusterName", this.ActiveClusterRecovery)
m.Get("/api/recently-active-cluster-recovery/:clusterName", this.RecentlyActiveClusterRecovery)
m.Get("/api/recently-active-instance-recovery/:host/:port", this.RecentlyActiveInstanceRecovery)
m.Get("/api/ack-recovery/cluster/:clusterName", this.AcknowledgeClusterRecoveries)
m.Get("/api/ack-recovery/cluster/alias/:clusterAlias", this.AcknowledgeClusterRecoveries)
m.Get("/api/ack-recovery/instance/:host/:port", this.AcknowledgeInstanceRecoveries)
m.Get("/api/ack-recovery/:recoveryId", this.AcknowledgeRecovery)
m.Get("/api/blocked-recoveries", this.BlockedRecoveries)
m.Get("/api/blocked-recoveries/cluster/:clusterName", this.BlockedRecoveries)
// General
m.Get("/api/problems", this.Problems)
m.Get("/api/problems/:clusterName", this.Problems)
m.Get("/api/long-queries", this.LongQueries)
m.Get("/api/long-queries/:filter", this.LongQueries)
m.Get("/api/audit", this.Audit)
m.Get("/api/audit/:page", this.Audit)
m.Get("/api/audit/instance/:host/:port", this.Audit)
m.Get("/api/audit/instance/:host/:port/:page", this.Audit)
m.Get("/api/resolve/:host/:port", this.Resolve)
// Meta
m.Get("/api/maintenance", this.Maintenance)
m.Get("/api/headers", this.Headers)
m.Get("/api/health", this.Health)
m.Get("/api/lb-check", this.LBCheck)
m.Get("/api/grab-election", this.GrabElection)
m.Get("/api/reelect", this.Reelect)
m.Get("/api/reload-configuration", this.ReloadConfiguration)
m.Get("/api/reload-cluster-alias", this.ReloadClusterAlias)
m.Get("/api/hostname-resolve-cache", this.HostnameResolveCache)
m.Get("/api/reset-hostname-resolve-cache", this.ResetHostnameResolveCache)
// Agents
m.Get("/api/agents", this.Agents)
m.Get("/api/agent/:host", this.Agent)
m.Get("/api/agent-umount/:host", this.AgentUnmount)
m.Get("/api/agent-mount/:host", this.AgentMountLV)
m.Get("/api/agent-create-snapshot/:host", this.AgentCreateSnapshot)
m.Get("/api/agent-removelv/:host", this.AgentRemoveLV)
m.Get("/api/agent-mysql-stop/:host", this.AgentMySQLStop)
m.Get("/api/agent-mysql-start/:host", this.AgentMySQLStart)
m.Get("/api/agent-seed/:targetHost/:sourceHost", this.AgentSeed)
m.Get("/api/agent-active-seeds/:host", this.AgentActiveSeeds)
m.Get("/api/agent-recent-seeds/:host", this.AgentRecentSeeds)
m.Get("/api/agent-seed-details/:seedId", this.AgentSeedDetails)
m.Get("/api/agent-seed-states/:seedId", this.AgentSeedStates)
m.Get("/api/agent-abort-seed/:seedId", this.AbortSeed)
m.Get("/api/agent-custom-command/:host/:command", this.AgentCustomCommand)
m.Get("/api/seeds", this.Seeds)
// Configurable status check endpoint
m.Get(config.Config.StatusEndpoint, this.StatusCheck)
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/mirrors/orchestrator.git
git@gitee.com:mirrors/orchestrator.git
mirrors
orchestrator
orchestrator
v1.5.6

搜索帮助