github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/server/testserver/testserver.go (about) 1 // Copyright 2017 Google Inc. 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 // https://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 15 // Package testserver configures and creates a Fleetspeak server instance 16 // suitable for unit tests. It also provides utility methods for directly 17 // adjusting the server state in tests. 18 package testserver 19 20 import ( 21 "context" 22 "crypto" 23 "crypto/rand" 24 "crypto/rsa" 25 "net" 26 "path" 27 "testing" 28 "time" 29 30 log "github.com/golang/glog" 31 "google.golang.org/protobuf/proto" 32 33 "github.com/google/fleetspeak/fleetspeak/src/common" 34 "github.com/google/fleetspeak/fleetspeak/src/comtesting" 35 "github.com/google/fleetspeak/fleetspeak/src/server" 36 "github.com/google/fleetspeak/fleetspeak/src/server/comms" 37 "github.com/google/fleetspeak/fleetspeak/src/server/service" 38 "github.com/google/fleetspeak/fleetspeak/src/server/sqlite" 39 40 fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak" 41 spb "github.com/google/fleetspeak/fleetspeak/src/server/proto/fleetspeak_server" 42 ) 43 44 // Server is a test server, with related structures and interfaces to allow 45 // tests to manipulate it. 46 type Server struct { 47 S *server.Server 48 DS *sqlite.Datastore 49 CC comms.Context 50 } 51 52 // FakeCommunicator implements comms.Communicator to do nothing by 53 // save the comms.Context to a Server. Most users should simply call 54 // Make, but this is exposed in order to support more flexible setup 55 // of test servers. 56 type FakeCommunicator struct { 57 Dest *Server 58 } 59 60 func (c FakeCommunicator) Setup(cc comms.Context) error { 61 c.Dest.CC = cc 62 return nil 63 } 64 65 func (c FakeCommunicator) Start() error { return nil } 66 67 func (c FakeCommunicator) Stop() {} 68 69 // Make creates a server.Server using the provided communicators. It creates and 70 // attaches it to an sqlite datastore based on the test and test case names. 71 func Make(t *testing.T, testName, caseName string, comms []comms.Communicator) Server { 72 tempDir, tmpDirCleanup := comtesting.GetTempDir(testName) 73 defer tmpDirCleanup() 74 p := path.Join(tempDir, caseName+".sqlite") 75 ds, err := sqlite.MakeDatastore(p) 76 if err != nil { 77 t.Fatal(err) 78 } 79 log.Infof("Created database: %s", p) 80 81 var ret Server 82 83 s, err := server.MakeServer( 84 &spb.ServerConfig{ 85 Services: []*spb.ServiceConfig{{ 86 Name: "TestService", 87 Factory: "NOOP", 88 MaxParallelism: 5, 89 }}, 90 }, 91 server.Components{ 92 Datastore: ds, 93 ServiceFactories: map[string]service.Factory{"NOOP": service.NOOPFactory}, 94 Communicators: append(comms, FakeCommunicator{&ret}), 95 }) 96 if err != nil { 97 t.Fatal(err) 98 } 99 ret.S = s 100 ret.DS = ds 101 return ret 102 } 103 104 // MakeWithService creates a server.Server using the provided service. Like in Make(), a sqlite 105 // datastore is created for the provided test-case. 106 func MakeWithService(t *testing.T, testName, caseName string, serviceInstance service.Service) Server { 107 tempDir, tmpDirCleanup := comtesting.GetTempDir(testName) 108 defer tmpDirCleanup() 109 p := path.Join(tempDir, caseName+".sqlite") 110 ds, err := sqlite.MakeDatastore(p) 111 if err != nil { 112 t.Fatal(err) 113 } 114 log.Infof("Created database: %s", p) 115 116 serviceFactory := func(conf *spb.ServiceConfig) (service.Service, error) { 117 return serviceInstance, nil 118 } 119 120 var testServer Server 121 s, err := server.MakeServer( 122 &spb.ServerConfig{ 123 Services: []*spb.ServiceConfig{{ 124 Name: "TestService", 125 Factory: "CustomFactory", 126 MaxParallelism: 5, 127 }}, 128 }, 129 server.Components{ 130 Datastore: ds, 131 ServiceFactories: map[string]service.Factory{"CustomFactory": serviceFactory}, 132 Communicators: []comms.Communicator{FakeCommunicator{&testServer}}, 133 }) 134 if err != nil { 135 t.Fatal(err) 136 } 137 138 testServer.S = s 139 testServer.DS = ds 140 return testServer 141 } 142 143 // AddClient adds a new client with a random id to a server. 144 func (s Server) AddClient() (crypto.PublicKey, error) { 145 k, err := rsa.GenerateKey(rand.Reader, 2048) 146 if err != nil { 147 return common.ClientID{}, err 148 } 149 if _, _, _, err := s.CC.InitializeConnection( 150 context.Background(), 151 &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 123}, 152 k.Public(), 153 &fspb.WrappedContactData{}, 154 false, 155 ); err != nil { 156 return nil, err 157 } 158 return &k.PublicKey, nil 159 } 160 161 // ProcessMessageFromClient delivers a message to a server, simulating that it was 162 // provided by a client. It then waits up to 30 seconds for it to be processed. 163 func (s Server) ProcessMessageFromClient(k crypto.PublicKey, msg *fspb.Message) error { 164 ctx := context.Background() 165 166 mid, err := common.BytesToMessageID(msg.MessageId) 167 if err != nil { 168 return err 169 } 170 171 if _, err := s.SimulateContactFromClient(ctx, k, []*fspb.Message{msg}); err != nil { 172 return err 173 } 174 175 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 176 defer cancel() 177 for { 178 msg := s.GetMessage(ctx, mid) 179 if msg.Result != nil { 180 return nil 181 } 182 if ctx.Err() != nil { 183 return ctx.Err() 184 } 185 time.Sleep(100 * time.Millisecond) 186 } 187 } 188 189 // SimulateContactFromClient accepts zero or more messages as if they came from 190 // a client, and returns any messages pending for delivery to the client. 191 func (s Server) SimulateContactFromClient(ctx context.Context, key crypto.PublicKey, msgs []*fspb.Message) ([]*fspb.Message, error) { 192 cd := fspb.ContactData{Messages: msgs} 193 cdb, err := proto.Marshal(&cd) 194 if err != nil { 195 return nil, err 196 } 197 _, rcd, _, err := s.CC.InitializeConnection( 198 ctx, 199 &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 123}, 200 key, 201 &fspb.WrappedContactData{ContactData: cdb}, 202 false) 203 if err != nil { 204 return nil, err 205 } 206 return rcd.Messages, nil 207 } 208 209 // GetMessage retrieves a single message from the datastore, or dies trying. 210 func (s Server) GetMessage(ctx context.Context, id common.MessageID) *fspb.Message { 211 msgs, err := s.DS.GetMessages(ctx, []common.MessageID{id}, true) 212 if err != nil { 213 log.Fatal(err) 214 } 215 if len(msgs) != 1 { 216 log.Fatalf("Expected 1 message, got: %v", msgs) 217 } 218 return msgs[0] 219 }