vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletmanager/vreplication/utils.go (about) 1 /* 2 Copyright 2021 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package vreplication 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "strconv" 23 24 "vitess.io/vitess/go/vt/log" 25 26 "vitess.io/vitess/go/mysql" 27 "vitess.io/vitess/go/vt/sqlparser" 28 29 "vitess.io/vitess/go/sqltypes" 30 "vitess.io/vitess/go/vt/vtgate/evalengine" 31 ) 32 33 const ( 34 vreplicationLogTableName = "_vt.vreplication_log" 35 ) 36 37 const ( 38 // Enum values for type column of _vt.vreplication_log 39 40 // LogStreamCreate is used when a row in _vt.vreplication is inserted via VReplicationExec 41 LogStreamCreate = "Stream Created" 42 // LogStreamUpdate is used when a row in _vt.vreplication is updated via VReplicationExec 43 LogStreamUpdate = "Stream Updated" 44 // LogStreamDelete is used when a row in _vt.vreplication is deleted via VReplicationExec 45 LogStreamDelete = "Stream Deleted" 46 // LogMessage is used for generic log messages 47 LogMessage = "Message" 48 // LogCopyStart is used when the copy phase is started 49 LogCopyStart = "Started Copy Phase" 50 // LogCopyEnd is used when the copy phase is done 51 LogCopyEnd = "Ended Copy Phase" 52 // LogStateChange is used when the state of the stream changes 53 LogStateChange = "State Changed" 54 55 // TODO: LogError is not used atm. Currently irrecoverable errors, resumable errors and informational messages 56 // are all treated the same: the message column is updated and state left as Running. 57 // Every five seconds we reset the message and retry streaming so that we can automatically resume from a temporary 58 // loss of connectivity or a reparent. We need to detect if errors are not recoverable and set an Error status. 59 // Since this device (of overloading the message) is strewn across the code, and incorrectly flagging resumable 60 // errors can stall workflows, we have deferred implementing it. 61 62 // LogError indicates that there is an error from which we cannot recover and the operator needs to intervene. 63 LogError = "Error" 64 ) 65 66 func getLastLog(dbClient *vdbClient, vreplID uint32) (id int64, typ, state, message string, err error) { 67 var qr *sqltypes.Result 68 query := fmt.Sprintf("select id, type, state, message from _vt.vreplication_log where vrepl_id = %d order by id desc limit 1", vreplID) 69 if qr, err = dbClient.Execute(query); err != nil { 70 return 0, "", "", "", err 71 } 72 if len(qr.Rows) != 1 { 73 return 0, "", "", "", nil 74 } 75 row := qr.Rows[0] 76 id, _ = evalengine.ToInt64(row[0]) 77 typ = row[1].ToString() 78 state = row[2].ToString() 79 message = row[3].ToString() 80 return id, typ, state, message, nil 81 } 82 83 func insertLog(dbClient *vdbClient, typ string, vreplID uint32, state, message string) error { 84 // getLastLog returns the last log for a stream. During insertion, if the type/state/message match we do not insert 85 // a new log but increment the count. This prevents spamming of the log table in case the same message is logged continuously. 86 id, _, lastLogState, lastLogMessage, err := getLastLog(dbClient, vreplID) 87 if err != nil { 88 return err 89 } 90 if typ == LogStateChange && state == lastLogState { 91 // handles case where current state is Running, controller restarts after an error and initializes the state Running 92 return nil 93 } 94 var query string 95 if id > 0 && message == lastLogMessage { 96 query = fmt.Sprintf("update _vt.vreplication_log set count = count + 1 where id = %d", id) 97 } else { 98 buf := sqlparser.NewTrackedBuffer(nil) 99 buf.Myprintf("insert into _vt.vreplication_log(vrepl_id, type, state, message) values(%s, %s, %s, %s)", 100 strconv.Itoa(int(vreplID)), encodeString(typ), encodeString(state), encodeString(message)) 101 query = buf.ParsedQuery().Query 102 } 103 if _, err = dbClient.ExecuteFetch(query, 10000); err != nil { 104 return fmt.Errorf("could not insert into log table: %v: %v", query, err) 105 } 106 return nil 107 } 108 109 // insertLogWithParams is called when a stream is created. The attributes of the stream are stored as a json string 110 func insertLogWithParams(dbClient *vdbClient, action string, vreplID uint32, params map[string]string) error { 111 var message string 112 if params != nil { 113 obj, _ := json.Marshal(params) 114 message = string(obj) 115 } 116 if err := insertLog(dbClient, action, vreplID, params["state"], message); err != nil { 117 return err 118 } 119 return nil 120 } 121 122 // isUnrecoverableError returns true if vreplication cannot recover from the given error and should completely terminate 123 func isUnrecoverableError(err error) bool { 124 if err == nil { 125 return false 126 } 127 sqlErr, isSQLErr := mysql.NewSQLErrorFromError(err).(*mysql.SQLError) 128 if !isSQLErr { 129 return false 130 } 131 if sqlErr.Num == mysql.ERUnknownError { 132 return false 133 } 134 switch sqlErr.Num { 135 case 136 // in case-insensitive alphabetical order 137 mysql.ERAccessDeniedError, 138 mysql.ERBadFieldError, 139 mysql.ERBadNullError, 140 mysql.ERCantDropFieldOrKey, 141 mysql.ERDataOutOfRange, 142 mysql.ERDataTooLong, 143 mysql.ERDBAccessDenied, 144 mysql.ERDupEntry, 145 mysql.ERDupFieldName, 146 mysql.ERDupKeyName, 147 mysql.ERDupUnique, 148 mysql.ERFeatureDisabled, 149 mysql.ERFunctionNotDefined, 150 mysql.ERIllegalValueForType, 151 mysql.ERInvalidCastToJSON, 152 mysql.ERInvalidJSONBinaryData, 153 mysql.ERInvalidJSONCharset, 154 mysql.ERInvalidJSONText, 155 mysql.ERInvalidJSONTextInParams, 156 mysql.ERJSONDocumentTooDeep, 157 mysql.ERJSONValueTooBig, 158 mysql.ERNoDefault, 159 mysql.ERNoDefaultForField, 160 mysql.ERNonUniq, 161 mysql.ERNonUpdateableTable, 162 mysql.ERNoSuchTable, 163 mysql.ERNotAllowedCommand, 164 mysql.ERNotSupportedYet, 165 mysql.EROptionPreventsStatement, 166 mysql.ERParseError, 167 mysql.ERPrimaryCantHaveNull, 168 mysql.ErrCantCreateGeometryObject, 169 mysql.ErrGISDataWrongEndianess, 170 mysql.ErrNonPositiveRadius, 171 mysql.ErrNotImplementedForCartesianSRS, 172 mysql.ErrNotImplementedForProjectedSRS, 173 mysql.ErrWrongValueForType, 174 mysql.ERSPDoesNotExist, 175 mysql.ERSpecifiedAccessDenied, 176 mysql.ERSyntaxError, 177 mysql.ERTooBigRowSize, 178 mysql.ERTooBigSet, 179 mysql.ERTruncatedWrongValue, 180 mysql.ERTruncatedWrongValueForField, 181 mysql.ERUnknownCollation, 182 mysql.ERUnknownProcedure, 183 mysql.ERUnknownTable, 184 mysql.ERWarnDataOutOfRange, 185 mysql.ERWarnDataTruncated, 186 mysql.ERWrongFKDef, 187 mysql.ERWrongFieldSpec, 188 mysql.ERWrongParamCountToProcedure, 189 mysql.ERWrongParametersToProcedure, 190 mysql.ERWrongUsage, 191 mysql.ERWrongValue, 192 mysql.ERWrongValueCountOnRow: 193 log.Errorf("Got unrecoverable error: %v", sqlErr) 194 return true 195 } 196 return false 197 }