github.com/mailru/activerecord@v1.12.2/pkg/octopus/mock_server.go (about) 1 package octopus 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "net" 8 "reflect" 9 "strconv" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/pkg/errors" 15 16 "github.com/mailru/activerecord/pkg/iproto/iproto" 17 ) 18 19 type CheckUsesFixtureType uint8 20 21 // Константы для проверки использования фикстур 22 const ( 23 AnyUsesFixtures CheckUsesFixtureType = iota 24 AllFixtureUses 25 AllFixtureUsesOnlyOnce 26 ) 27 28 type MockServer struct { 29 // Сервер 30 srv *iproto.Server 31 32 // Хост и порт на котором сервер запускается 33 host, port string 34 35 // листенер сервера 36 ln net.Listener 37 38 // Список фикстур с которым будет работать сервер 39 oft []FixtureType 40 41 // Мьютекс для работы с триггерами 42 sync.Mutex 43 44 // Контекст для остановки 45 cancelCtx context.CancelFunc 46 47 // Канал сигнализирующий об остановке сервера 48 stopServ chan struct{} 49 50 // Функция для логирования работы мок сервера 51 logger MockServerLogger 52 53 iprotoLogger iproto.Logger 54 } 55 56 type RepositoryDebugMeta interface { 57 GetSelectDebugInfo(ns uint32, indexnum uint32, offset uint32, limit uint32, keys [][][]byte, fixture ...SelectMockFixture) string 58 GetUpdateDebugInfo(ns uint32, primaryKey [][]byte, updateOps []Ops, fixture ...UpdateMockFixture) string 59 GetInsertDebugInfo(ns uint32, needRetVal bool, insertMode InsertMode, tuple TupleData, fixture ...InsertMockFixture) string 60 GetDeleteDebugInfo(ns uint32, primaryKey [][]byte, fixture ...DeleteMockFixture) string 61 GetCallDebugInfo(procName string, args [][]byte, fixture ...CallMockFixture) string 62 } 63 64 type DefaultLogger struct { 65 DebugMeta RepositoryDebugMeta 66 } 67 68 func (l *DefaultLogger) Debug(fmt string, args ...any) { 69 log.Printf("DEBUG: "+fmt, args...) 70 } 71 72 func (l *DefaultLogger) DebugSelectRequest(ns uint32, indexnum uint32, offset uint32, limit uint32, keys [][][]byte, fixture ...SelectMockFixture) { 73 if l.DebugMeta != nil { 74 l.Debug("Select: " + l.DebugMeta.GetSelectDebugInfo(ns, indexnum, offset, limit, keys)) 75 return 76 } 77 78 keyStr := "" 79 80 for _, key := range keys { 81 hexField := []string{} 82 83 for _, field := range key { 84 hexField = append(hexField, fmt.Sprintf("% X", field)) 85 } 86 87 keyStr += "[" + strings.Join(hexField, ", ") + "]" 88 } 89 90 l.Debug("Select: Space: %d, index: %d, offset: %d, limit: %d, Keys: %s", ns, indexnum, offset, limit, keyStr) 91 } 92 93 func (l *DefaultLogger) DebugUpdateRequest(ns uint32, primaryKey [][]byte, updateOps []Ops, fixture ...UpdateMockFixture) { 94 if l.DebugMeta != nil { 95 l.Debug("Update: " + l.DebugMeta.GetUpdateDebugInfo(ns, primaryKey, updateOps)) 96 return 97 } 98 99 opsStr := "" 100 101 for _, op := range updateOps { 102 opsStr += fmt.Sprintf("%d %d <= % X; ", op.Op, op.Field, op.Value) 103 } 104 105 l.Debug("Update: Space: %d, pk: %+v, updateOps: %s", ns, primaryKey, opsStr) 106 } 107 108 func (l *DefaultLogger) DebugDeleteRequest(ns uint32, primaryKey [][]byte, fixture ...DeleteMockFixture) { 109 if l.DebugMeta != nil { 110 l.Debug("Delete: " + l.DebugMeta.GetDeleteDebugInfo(ns, primaryKey)) 111 return 112 } 113 114 l.Debug("Delete: Space: %d, pk: %+v", ns, primaryKey) 115 } 116 117 func (l *DefaultLogger) DebugInsertRequest(ns uint32, needRetVal bool, insertMode InsertMode, tuple TupleData, fixture ...InsertMockFixture) { 118 if l.DebugMeta != nil { 119 l.Debug("Insert: " + l.DebugMeta.GetInsertDebugInfo(ns, needRetVal, insertMode, tuple)) 120 return 121 } 122 123 l.Debug("Inserty: Space: %d, need return value: %t, insertMode: %b, tuple: % X", ns, needRetVal, insertMode, tuple) 124 } 125 126 func (l *DefaultLogger) DebugCallRequest(procName string, args [][]byte, fixtures ...CallMockFixture) { 127 128 } 129 130 type NopIprotoLogger struct{} 131 132 func (l NopIprotoLogger) Printf(ctx context.Context, fmt string, v ...interface{}) {} 133 134 func (l NopIprotoLogger) Debugf(ctx context.Context, fmt string, v ...interface{}) {} 135 136 func InitMockServer(opts ...MockServerOption) (*MockServer, error) { 137 oms := &MockServer{ 138 oft: []FixtureType{}, 139 host: "127.0.0.1", 140 port: "0", 141 stopServ: make(chan struct{}), 142 } 143 144 oms.logger = &DefaultLogger{} 145 146 if oms.iprotoLogger == nil { 147 oms.iprotoLogger = &NopIprotoLogger{} 148 } 149 150 for _, opt := range opts { 151 err := opt.apply(oms) 152 if err != nil { 153 return nil, fmt.Errorf("error apply option: %s", err) 154 } 155 } 156 157 addr, err := net.ResolveTCPAddr("tcp", oms.host+":"+oms.port) 158 if err != nil { 159 return nil, err 160 } 161 162 ln, err := net.ListenTCP("tcp", addr) 163 if err != nil { 164 return nil, fmt.Errorf("can't start listener: %s", err) 165 } 166 167 oms.ln = ln 168 oms.port = strconv.Itoa(ln.Addr().(*net.TCPAddr).Port) 169 170 oms.srv = &iproto.Server{ 171 ChannelConfig: &iproto.ChannelConfig{ 172 Handler: iproto.HandlerFunc(oms.Handler), 173 Logger: oms.iprotoLogger, 174 }, 175 Log: oms.iprotoLogger, 176 } 177 178 return oms, nil 179 } 180 181 func (oms *MockServer) Handler(ctx context.Context, c iproto.Conn, p iproto.Packet) { 182 oms.iprotoLogger.Debugf(ctx, "% X (% X)", p.Header, p.Data) 183 184 resp, found := oms.ProcessRequest(uint8(p.Header.Msg), p.Data) 185 if !found { 186 res := append([]byte{0x01, 0x00, 0x00, 0x00}, []byte("Fixture not found")...) 187 if err := c.Send(ctx, iproto.ResponseTo(p, res)); err != nil { 188 oms.logger.Debug("error send from octopus server: %s", err) 189 } 190 191 return 192 } 193 194 if err := c.Send(ctx, iproto.ResponseTo(p, resp)); err != nil { 195 oms.logger.Debug("error send from octopus server: %s", err) 196 } 197 } 198 199 // DebugFixtureNotFound Prepares data for detailed content logging in human readable format 200 // and call the MockServerLogger function on a specific RequestType 201 // 202 //nolint:gocognit,gocyclo 203 func (oms *MockServer) DebugFixtureNotFound(msg uint8, req []byte) { 204 switch RequetsTypeType(msg) { 205 case RequestTypeSelect: 206 reqNs, indexnum, offset, limit, keys, err := UnpackSelect(req) 207 if err != nil { 208 oms.logger.Debug("error while unpack select (% X): %s", req, err) 209 return 210 } 211 212 var fixtures []SelectMockFixture 213 214 for _, fxt := range oms.oft { 215 // only "Select" fixture type 216 if fxt.Msg != RequetsTypeType(msg) { 217 continue 218 } 219 220 ns, idxNum, ofs, lmt, keysFxt, err := UnpackSelect(fxt.Request) 221 if err != nil { 222 oms.logger.Debug("error while unpack select (% X): %s", fxt.Request, err) 223 return 224 } 225 226 // only namespace for requestType "Select" 227 if reqNs != ns { 228 continue 229 } 230 231 selectFxt := SelectMockFixture{ 232 Indexnum: idxNum, 233 Offset: ofs, 234 Limit: lmt, 235 Keys: keysFxt, 236 } 237 238 if selectFxt.RespTuples, err = ProcessResp(fxt.Response, 0); err != nil { 239 oms.logger.Debug("error while unpack select response (% X): %s", fxt.Response, err) 240 return 241 } 242 243 fixtures = append(fixtures, selectFxt) 244 } 245 246 oms.logger.DebugSelectRequest(reqNs, indexnum, offset, limit, keys, fixtures...) 247 case RequestTypeUpdate: 248 reqNs, primaryKey, updateOps, err := UnpackUpdate(req) 249 if err != nil { 250 oms.logger.Debug("error while unpack update (% X): %s", req, err) 251 return 252 } 253 254 var fixtures []UpdateMockFixture 255 256 for _, fxt := range oms.oft { 257 // only "Update" fixture type 258 if fxt.Msg != RequetsTypeType(msg) { 259 continue 260 } 261 262 ns, pk, ops, err := UnpackUpdate(fxt.Request) 263 if err != nil { 264 oms.logger.Debug("error while unpack select (% X): %s", fxt.Request, err) 265 return 266 } 267 268 // only namespace of requestType "Update" 269 if reqNs != ns { 270 continue 271 } 272 273 updateFxt := UpdateMockFixture{ 274 PrimaryKey: pk, 275 UpdateOps: ops, 276 } 277 278 fixtures = append(fixtures, updateFxt) 279 } 280 281 oms.logger.DebugUpdateRequest(reqNs, primaryKey, updateOps, fixtures...) 282 case RequestTypeInsert: 283 reqNs, needRetVal, insertMode, reqTuple, err := UnpackInsertReplace(req) 284 if err != nil { 285 oms.logger.Debug("error while unpack insert (% X): %s", req, err) 286 return 287 } 288 289 var fixtures []InsertMockFixture 290 291 for _, fxt := range oms.oft { 292 // only "Insert" fixture type 293 if fxt.Msg != RequetsTypeType(msg) { 294 continue 295 } 296 297 ns, fxtNeedRetVal, mode, tuple, err := UnpackInsertReplace(fxt.Request) 298 if err != nil { 299 oms.logger.Debug("error while unpack insert (% X): %s", fxt.Request, err) 300 return 301 } 302 303 // only namespace of requestType "Insert" 304 if reqNs != ns { 305 continue 306 } 307 308 insertFxt := InsertMockFixture{ 309 NeedRetVal: fxtNeedRetVal, 310 InsertMode: mode, 311 Tuple: TupleData{Cnt: uint32(len(tuple)), Data: tuple}, 312 } 313 314 fixtures = append(fixtures, insertFxt) 315 } 316 317 oms.logger.DebugInsertRequest(reqNs, needRetVal, insertMode, TupleData{Cnt: uint32(len(reqTuple)), Data: reqTuple}, fixtures...) 318 case RequestTypeDelete: 319 reqNs, primaryKey, err := UnpackDelete(req) 320 if err != nil { 321 oms.logger.Debug("error while unpack delete (% X): %s", req, err) 322 return 323 } 324 325 var fixtures []DeleteMockFixture 326 327 for _, fxt := range oms.oft { 328 // only "Delete" fixture type 329 if fxt.Msg != RequetsTypeType(msg) { 330 continue 331 } 332 333 ns, pk, err := UnpackDelete(fxt.Request) 334 if err != nil { 335 oms.logger.Debug("error while unpack delete (% X): %s", fxt.Request, err) 336 return 337 } 338 339 // only namespace of requestType "Delete" 340 if reqNs != ns { 341 continue 342 } 343 344 deleteFxt := DeleteMockFixture{ 345 PrimaryKey: pk, 346 } 347 348 fixtures = append(fixtures, deleteFxt) 349 } 350 351 oms.logger.DebugDeleteRequest(reqNs, primaryKey, fixtures...) 352 case RequestTypeCall: 353 reqProcName, args, err := UnpackLua(req) 354 if err != nil { 355 oms.logger.Debug("error while unpack call proc (% X): %s", req, err) 356 return 357 } 358 359 var fixtures []CallMockFixture 360 361 for _, fxt := range oms.oft { 362 // only "Call" fixture type 363 if fxt.Msg != RequetsTypeType(msg) { 364 continue 365 } 366 367 procName, fixArgs, err := UnpackLua(fxt.Request) 368 if err != nil { 369 oms.logger.Debug("error while unpack select (% X): %s", fxt.Request, err) 370 return 371 } 372 373 // filter by name 374 if reqProcName != procName { 375 continue 376 } 377 378 callFxt := CallMockFixture{ 379 ProcName: procName, 380 Args: fixArgs, 381 } 382 383 if callFxt.RespTuples, err = ProcessResp(fxt.Response, 0); err != nil { 384 oms.logger.Debug("error while unpack call proc response (% X): %s", fxt.Response, err) 385 return 386 } 387 388 fixtures = append(fixtures, callFxt) 389 } 390 391 oms.logger.DebugCallRequest(reqProcName, args, fixtures...) 392 default: 393 oms.logger.Debug("Request type %d not support debug message", msg) 394 } 395 } 396 397 func (oms *MockServer) ProcessRequest(msg uint8, req []byte) ([]byte, bool) { 398 oms.Lock() 399 defer oms.Unlock() 400 401 for _, fix := range oms.oft { 402 if fix.Msg == RequetsTypeType(msg) && reflect.DeepEqual(fix.Request, req) { 403 if fix.Trigger != nil { 404 oms.oft = fix.Trigger(oms.oft) 405 } 406 407 return fix.Response, true 408 } 409 } 410 411 oms.DebugFixtureNotFound(msg, req) 412 413 return nil, false 414 } 415 416 func (oms *MockServer) SetFixtures(oft []FixtureType) { 417 oms.Lock() 418 oms.oft = oft 419 oms.Unlock() 420 } 421 422 func (oms *MockServer) Stop() error { 423 oms.logger.Debug("Try to stop server") 424 oms.cancelCtx() 425 426 timer := time.NewTimer(time.Second * 10) 427 428 oms.logger.Debug("Close listener") 429 430 if err := oms.ln.Close(); err != nil { 431 oms.logger.Debug("can't close listener: %s", err) 432 } 433 434 select { 435 case <-oms.stopServ: 436 oms.logger.Debug("Server stoped successfully") 437 case <-timer.C: 438 oms.logger.Debug("error stop server: timeout") 439 } 440 441 return nil 442 } 443 444 func (oms *MockServer) GetServerHostPort() string { 445 return oms.host + ":" + oms.port 446 } 447 448 func (oms *MockServer) Start() error { 449 ctx, cancel := context.WithCancel(context.Background()) 450 451 oms.cancelCtx = cancel 452 453 go func(ctx context.Context, ln net.Listener) { 454 oms.logger.Debug("Start octopus test server on %s", ln.Addr().String()) 455 456 err := oms.srv.Serve(ctx, ln) 457 if err != nil && !errors.Is(err, context.Canceled) { 458 oms.logger.Debug("Error get Serve: %s", err) 459 } 460 461 oms.stopServ <- struct{}{} 462 463 oms.logger.Debug("Stop octopus test server") 464 }(ctx, oms.ln) 465 466 return nil 467 }