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  }