checksum.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package db
  15. import (
  16. "crypto/md5"
  17. "crypto/sha256"
  18. "fmt"
  19. "reflect"
  20. "sort"
  21. "strings"
  22. "yunion.io/x/jsonutils"
  23. "yunion.io/x/log"
  24. "yunion.io/x/pkg/errors"
  25. "yunion.io/x/pkg/util/reflectutils"
  26. "yunion.io/x/pkg/utils"
  27. "yunion.io/x/sqlchemy"
  28. "yunion.io/x/onecloud/pkg/cloudcommon/consts"
  29. "yunion.io/x/onecloud/pkg/util/splitable"
  30. )
  31. var checksumTestFailedNotifier func(obj *jsonutils.JSONDict)
  32. func SetChecksumTestFailedNotifier(notifier func(obj *jsonutils.JSONDict)) {
  33. checksumTestFailedNotifier = notifier
  34. }
  35. type IRecordChecksumResourceBase interface {
  36. GetRecordChecksum() string
  37. SetRecordChecksum(checksum string)
  38. }
  39. type IRecordChecksumModelManager interface {
  40. IModelManager
  41. EnableRecordChecksum() bool
  42. SetEnableRecordChecksum(bool)
  43. }
  44. type IRecordChecksumModel interface {
  45. IModel
  46. IRecordChecksumResourceBase
  47. }
  48. type SRecordChecksumResourceBaseManager struct {
  49. enableRecordChecksum bool
  50. }
  51. func NewRecordChecksumResourceBaseManager() *SRecordChecksumResourceBaseManager {
  52. return &SRecordChecksumResourceBaseManager{
  53. enableRecordChecksum: true,
  54. }
  55. }
  56. func (man *SRecordChecksumResourceBaseManager) EnableRecordChecksum() bool {
  57. return man.enableRecordChecksum
  58. }
  59. func (man *SRecordChecksumResourceBaseManager) SetEnableRecordChecksum(enable bool) {
  60. man.enableRecordChecksum = enable
  61. }
  62. // +onecloud:model-api-gen
  63. type SRecordChecksumResourceBase struct {
  64. RecordChecksum string `width:"256" charset:"ascii" nullable:"true" list:"user" get:"user" json:"record_checksum"`
  65. }
  66. func (model *SRecordChecksumResourceBase) SetRecordChecksum(checksum string) {
  67. model.RecordChecksum = checksum
  68. }
  69. func (model *SRecordChecksumResourceBase) GetRecordChecksum() string {
  70. return model.RecordChecksum
  71. }
  72. func IsModelEnableRecordChecksum(model IModel) (IRecordChecksumModel, bool) {
  73. obj, ok := model.(IRecordChecksumModel)
  74. if !ok {
  75. return nil, false
  76. }
  77. man := obj.GetModelManager().(IRecordChecksumModelManager)
  78. return obj, man.EnableRecordChecksum()
  79. }
  80. func CheckRecordChecksumConsistent(model IModel) error {
  81. obj, ok := IsModelEnableRecordChecksum(model)
  82. if !ok {
  83. return nil
  84. }
  85. calChecksum, err := CalculateModelChecksum(obj)
  86. if err != nil {
  87. return errors.Wrap(err, "CalculateModelChecksum")
  88. }
  89. savedChecksum := obj.GetRecordChecksum()
  90. if calChecksum != savedChecksum {
  91. log.Errorf("Record %s(%s) checksum changed, expected(%s) != calculated(%s)", obj.Keyword(), obj.GetId(), savedChecksum, calChecksum)
  92. ts := model.GetModelManager().TableSpec()
  93. // notify
  94. data := jsonutils.NewDict()
  95. spt := ts.GetSplitTable()
  96. tableName := ts.Name()
  97. if spt != nil {
  98. tableName = spt.Name()
  99. }
  100. data.Set("db_name", jsonutils.NewString(string(ts.GetDBName())))
  101. data.Set("table_name", jsonutils.NewString(tableName))
  102. data.Set("name", jsonutils.NewString(fmt.Sprintf("%s(%s)", obj.Keyword(), obj.GetId())))
  103. data.Set("expected_checksum", jsonutils.NewString(savedChecksum))
  104. data.Set("calculated_checksum", jsonutils.NewString(calChecksum))
  105. if checksumTestFailedNotifier != nil {
  106. checksumTestFailedNotifier(data)
  107. }
  108. return errors.Errorf("Record %s(%s) checksum changed, expected(%s) != calculated(%s)", obj.Keyword(), obj.GetId(), savedChecksum, calChecksum)
  109. }
  110. return nil
  111. }
  112. func calculateRecordChecksumByValues(vals []interface{}) string {
  113. ss := strings.Builder{}
  114. for _, val := range vals {
  115. ss.WriteString(fmt.Sprintf("\n%v", val))
  116. }
  117. var sum string
  118. algStr := consts.DefaultDBChecksumHashAlgorithm()
  119. switch algStr {
  120. case "md5":
  121. sum = fmt.Sprintf("%x", md5.Sum([]byte(ss.String())))
  122. default:
  123. sum = fmt.Sprintf("%x", sha256.Sum256([]byte(ss.String())))
  124. }
  125. // log.Debugf("calculate values string: %s alg: %s, checksum: %s", ss, algStr, sum)
  126. return sum
  127. }
  128. func CalculateModelChecksum(dbObj IRecordChecksumModel) (string, error) {
  129. objMan := dbObj.GetModelManager()
  130. if objMan == nil {
  131. return "", errors.Errorf("Object %#v not set model manager", dbObj)
  132. }
  133. dataValue := reflect.ValueOf(dbObj).Elem()
  134. dataFields := reflectutils.FetchStructFieldValueSet(dataValue)
  135. cols := objMan.TableSpec().Columns()
  136. vals := []interface{}{}
  137. keys := make([]string, 0)
  138. for _, c := range cols {
  139. keys = append(keys, c.Name())
  140. }
  141. sort.Strings(keys)
  142. for _, k := range keys {
  143. if utils.IsInStringArray(k, []string{COLUMN_RECORD_CHECKSUM, COLUMN_UPDATE_VERSION, COLUMN_UPDATED_AT}) {
  144. continue
  145. }
  146. v, find := dataFields.GetInterface(k)
  147. if !find {
  148. continue
  149. }
  150. vals = append(vals, v)
  151. }
  152. return calculateRecordChecksumByValues(vals), nil
  153. }
  154. func UpdateModelChecksum(dbObj IRecordChecksumModel) error {
  155. var ts sqlchemy.ITableSpec
  156. tss := dbObj.GetModelManager().TableSpec().GetSplitTable()
  157. if tss != nil {
  158. meta, err := tss.GetTableMetaByObject(dbObj.(splitable.ISplitTableObject))
  159. if err != nil {
  160. return errors.Wrapf(err, "GetTableMetaByObject")
  161. }
  162. ts = tss.GetTableSpec(*meta)
  163. } else {
  164. ts = dbObj.GetModelManager().TableSpec().GetTableSpec()
  165. }
  166. updateChecksum, err := CalculateModelChecksum(dbObj)
  167. if err != nil {
  168. return errors.Wrap(err, "CalculateModelChecksum for update")
  169. }
  170. _, err = ts.Update(dbObj, func() error {
  171. dbObj.SetRecordChecksum(updateChecksum)
  172. return nil
  173. })
  174. if err != nil {
  175. return errors.Wrapf(err, "UpdateModelChecksum to %s", updateChecksum)
  176. }
  177. return nil
  178. }
  179. func InjectModelsChecksum(man IRecordChecksumModelManager) error {
  180. limit := 2048
  181. q := man.Query()
  182. totalCnt, err := q.CountWithError()
  183. if err != nil {
  184. return errors.Wrap(err, "Get total records count")
  185. }
  186. setCnt := 0
  187. for {
  188. if setCnt >= totalCnt {
  189. break
  190. }
  191. q = q.Limit(limit).Offset(setCnt)
  192. objs, err := FetchIModelObjects(man, q)
  193. if err != nil {
  194. return errors.Wrap(err, "FetchModelObjects")
  195. }
  196. for i := range objs {
  197. obj := objs[i].(IRecordChecksumModel)
  198. err := UpdateModelChecksum(obj)
  199. if err != nil {
  200. return errors.Wrapf(err, "UpdateModelChecksum for %s %s", man.Keyword(), obj.GetId())
  201. } else {
  202. log.Debugf("object %s %s %s calculate checksum completed", obj.Keyword(), obj.GetId(), obj.GetName())
  203. }
  204. }
  205. setCnt += len(objs)
  206. }
  207. return nil
  208. }