github.com/cs3org/reva/v2@v2.27.7/tests/integration/grpc/grpc_suite_test.go (about) 1 // Copyright 2018-2021 CERN 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 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package grpc_test 20 21 import ( 22 "encoding/json" 23 "fmt" 24 "net" 25 "os" 26 "os/exec" 27 "path" 28 "path/filepath" 29 "strings" 30 "sync" 31 "testing" 32 "time" 33 34 "github.com/google/uuid" 35 "github.com/pkg/errors" 36 37 . "github.com/onsi/ginkgo/v2" 38 . "github.com/onsi/gomega" 39 ) 40 41 const timeoutMs = 30000 42 43 var mutex = sync.Mutex{} 44 var port = 19000 45 46 func TestGrpc(t *testing.T) { 47 RegisterFailHandler(Fail) 48 RunSpecs(t, "Grpc Suite") 49 } 50 51 type cleanupFunc func(bool) error 52 53 // Revad represents a running revad process 54 type Revad struct { 55 TmpRoot string // Temporary directory on disk. Will be cleaned up by the Cleanup func. 56 StorageRoot string // Temporary directory used for the revad storage on disk. Will be cleaned up by the Cleanup func. 57 GrpcAddress string // Address of the grpc service 58 ID string // ID of the grpc service 59 Cleanup cleanupFunc // Function to kill the process and cleanup the temp. root. If the given parameter is true the files will be kept to make debugging failures easier. 60 } 61 62 type res struct{} 63 64 func (res) isResource() {} 65 66 type Resource interface { 67 isResource() 68 } 69 70 type Folder struct { 71 res 72 } 73 74 type File struct { 75 res 76 Content any 77 Encoding string // json, plain 78 } 79 80 type RevadConfig struct { 81 Name string 82 Config string 83 Files map[string]string 84 Resources map[string]Resource 85 } 86 87 // startRevads takes a list of revad configuration files plus a map of 88 // variables that need to be substituted in them and starts them. 89 // 90 // A unique port is assigned to each spawned instance. 91 // Placeholders in the config files can be replaced the variables from the 92 // `variables` map, e.g. the config 93 // 94 // redis = "{{redis_address}}" 95 // 96 // and the variables map 97 // 98 // variables = map[string]string{"redis_address": "localhost:6379"} 99 // 100 // will result in the config 101 // 102 // redis = "localhost:6379" 103 // 104 // Special variables are created for the revad addresses, e.g. having a 105 // `storage` and a `users` revad will make `storage_address` and 106 // `users_address` available wit the dynamically assigned ports so that 107 // the services can be made available to each other. 108 func startRevads(configs []RevadConfig, variables map[string]string) (map[string]*Revad, error) { 109 mutex.Lock() 110 defer mutex.Unlock() 111 112 revads := map[string]*Revad{} 113 addresses := map[string]string{} 114 ids := map[string]string{} 115 roots := map[string]string{} 116 117 tmpBase, err := os.MkdirTemp("", "reva-grpc-integration-tests") 118 if err != nil { 119 return nil, errors.Wrapf(err, "Could not create tmpdir") 120 } 121 122 for _, c := range configs { 123 ids[c.Name] = uuid.New().String() 124 // Create a temporary root for this revad 125 tmpRoot := path.Join(tmpBase, c.Name) 126 roots[c.Name] = tmpRoot 127 addresses[c.Name] = fmt.Sprintf("localhost:%d", port) 128 port++ 129 addresses[c.Name+"+1"] = fmt.Sprintf("localhost:%d", port) 130 port++ 131 addresses[c.Name+"+2"] = fmt.Sprintf("localhost:%d", port) 132 port++ 133 } 134 135 for _, c := range configs { 136 ownAddress := addresses[c.Name] 137 ownID := ids[c.Name] 138 filesPath := map[string]string{} 139 140 tmpRoot := roots[c.Name] 141 err := os.Mkdir(tmpRoot, 0755) 142 if err != nil { 143 return nil, errors.Wrapf(err, "Could not create tmpdir") 144 } 145 146 newCfgPath := path.Join(tmpRoot, "config.toml") 147 rawCfg, err := os.ReadFile(path.Join("fixtures", c.Config)) 148 if err != nil { 149 return nil, errors.Wrapf(err, "Could not read config file") 150 } 151 152 for name, p := range c.Files { 153 rawFile, err := os.ReadFile(path.Join("fixtures", p)) 154 if err != nil { 155 return nil, errors.Wrapf(err, "error reading file") 156 } 157 cfg := string(rawFile) 158 for v, value := range variables { 159 cfg = strings.ReplaceAll(cfg, "{{"+v+"}}", value) 160 } 161 for name, address := range addresses { 162 cfg = strings.ReplaceAll(cfg, "{{"+name+"_address}}", address) 163 } 164 newFilePath := path.Join(tmpRoot, p) 165 err = os.WriteFile(newFilePath, []byte(cfg), 0600) 166 if err != nil { 167 return nil, errors.Wrapf(err, "error writing file") 168 } 169 filesPath[name] = newFilePath 170 } 171 for name, resource := range c.Resources { 172 tmpResourcePath := filepath.Join(tmpRoot, name) 173 174 switch r := resource.(type) { 175 case File: 176 // fill the file with the initial content 177 switch r.Encoding { 178 case "", "plain": 179 if err := os.WriteFile(tmpResourcePath, []byte(r.Content.(string)), 0644); err != nil { 180 return nil, err 181 } 182 case "json": 183 d, err := json.Marshal(r.Content) 184 if err != nil { 185 return nil, err 186 } 187 if err := os.WriteFile(tmpResourcePath, d, 0644); err != nil { 188 return nil, err 189 } 190 default: 191 return nil, errors.New("encoding not known " + r.Encoding) 192 } 193 case Folder: 194 if err := os.MkdirAll(tmpResourcePath, 0755); err != nil { 195 return nil, err 196 } 197 } 198 199 filesPath[name] = tmpResourcePath 200 } 201 202 cfg := string(rawCfg) 203 cfg = strings.ReplaceAll(cfg, "{{root}}", tmpRoot) 204 cfg = strings.ReplaceAll(cfg, "{{id}}", ownID) 205 cfg = strings.ReplaceAll(cfg, "{{grpc_address}}", ownAddress) 206 cfg = strings.ReplaceAll(cfg, "{{grpc_address+1}}", addresses[c.Name+"+1"]) 207 cfg = strings.ReplaceAll(cfg, "{{grpc_address+2}}", addresses[c.Name+"+2"]) 208 for name, path := range filesPath { 209 cfg = strings.ReplaceAll(cfg, "{{file_"+name+"}}", path) 210 cfg = strings.ReplaceAll(cfg, "{{"+name+"}}", path) 211 } 212 for v, value := range variables { 213 cfg = strings.ReplaceAll(cfg, "{{"+v+"}}", value) 214 } 215 for name, address := range addresses { 216 cfg = strings.ReplaceAll(cfg, "{{"+name+"_address}}", address) 217 } 218 for name, id := range ids { 219 cfg = strings.ReplaceAll(cfg, "{{"+name+"_id}}", id) 220 } 221 for name, root := range roots { 222 cfg = strings.ReplaceAll(cfg, "{{"+name+"_root}}", root) 223 } 224 err = os.WriteFile(newCfgPath, []byte(cfg), 0600) 225 if err != nil { 226 return nil, errors.Wrapf(err, "Could not write config file") 227 } 228 229 // Run revad 230 cmd := exec.Command("../../../cmd/revad/revad", "-log", "debug", "-c", newCfgPath) 231 232 outfile, err := os.Create(path.Join(tmpRoot, c.Name+"-out.log")) 233 if err != nil { 234 panic(err) 235 } 236 defer outfile.Close() 237 cmd.Stdout = outfile 238 cmd.Stderr = outfile 239 240 err = cmd.Start() 241 if err != nil { 242 return nil, errors.Wrapf(err, "Could not start revad") 243 } 244 245 err = waitForPort(ownAddress, "open") 246 if err != nil { 247 return nil, err 248 } 249 250 // even the port is open the service might not be available yet 251 time.Sleep(2 * time.Second) 252 253 revad := &Revad{ 254 TmpRoot: tmpRoot, 255 StorageRoot: path.Join(tmpRoot, "storage"), 256 GrpcAddress: ownAddress, 257 ID: ownID, 258 Cleanup: func(keepLogs bool) error { 259 err := cmd.Process.Signal(os.Kill) 260 if err != nil { 261 return errors.Wrap(err, "Could not kill revad") 262 } 263 _ = waitForPort(ownAddress, "close") 264 if keepLogs { 265 fmt.Println("Test failed, keeping root", tmpRoot, "around for debugging") 266 } else { 267 os.RemoveAll(tmpRoot) 268 os.Remove(tmpBase) // Remove base temp dir if it's empty 269 } 270 return nil 271 }, 272 } 273 revads[c.Name] = revad 274 } 275 return revads, nil 276 } 277 278 func waitForPort(grpcAddress, expectedStatus string) error { 279 if expectedStatus != "open" && expectedStatus != "close" { 280 return errors.New("status can only be 'open' or 'close'") 281 } 282 timoutCounter := 0 283 for timoutCounter <= timeoutMs { 284 conn, err := net.Dial("tcp", grpcAddress) 285 if err == nil { 286 _ = conn.Close() 287 if expectedStatus == "open" { 288 break 289 } 290 } else if expectedStatus == "close" { 291 break 292 } 293 294 time.Sleep(1 * time.Millisecond) 295 timoutCounter++ 296 } 297 return nil 298 }