github.com/apache/beam/sdks/v2@v2.48.2/go/test/integration/expansions.go (about) 1 // Licensed to the Apache Software Foundation (ASF) under one or more 2 // contributor license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright ownership. 4 // The ASF licenses this file to You under the Apache License, Version 2.0 5 // (the "License"); you may not use this file except in compliance with 6 // the License. You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package integration 17 18 import ( 19 "fmt" 20 "strconv" 21 "time" 22 23 "github.com/apache/beam/sdks/v2/go/test/integration/internal/jars" 24 "github.com/apache/beam/sdks/v2/go/test/integration/internal/ports" 25 ) 26 27 // ExpansionServices is a struct used for getting addresses and starting expansion services, based 28 // on the --expansion_jar and --expansion_addr flags in this package. The main reason to use this 29 // instead of accessing the flags directly is to let it handle jar startup and shutdown. 30 // 31 // # Usage 32 // 33 // Create an ExpansionServices object in TestMain with NewExpansionServices. Then use GetAddr for 34 // every expansion service needed for the test. Call Shutdown on it before finishing TestMain (or 35 // simply defer a call to it). 36 // 37 // ExpansionServices is not concurrency safe, and so a single instance should not be used within 38 // multiple individual tests, due to the possibility of those tests being run concurrently. It is 39 // recommended to only use ExpansionServices in TestMain to avoid this. 40 // 41 // Example: 42 // 43 // flag.Parse() 44 // beam.Init() 45 // services := integration.NewExpansionServices() 46 // defer func() { services.Shutdown() }() 47 // addr, err := services.GetAddr("example") 48 // if err != nil { 49 // panic(err) 50 // } 51 // expansionAddr = addr // Save address to a package-level variable used by tests. 52 // ptest.MainRet(m) 53 type ExpansionServices struct { 54 addrs map[string]string 55 jars map[string]string 56 procs []jars.Process 57 // Callback for running jars, stored this way for testing purposes. 58 run func(time.Duration, string, ...string) (jars.Process, error) 59 waitTime time.Duration // Time to sleep after running jar. Tests can adjust this. 60 } 61 62 // NewExpansionServices creates and initializes an ExpansionServices instance. 63 func NewExpansionServices() *ExpansionServices { 64 return &ExpansionServices{ 65 addrs: GetExpansionAddrs(), 66 jars: GetExpansionJars(), 67 procs: make([]jars.Process, 0), 68 run: jars.Run, 69 waitTime: 3 * time.Second, 70 } 71 } 72 73 // GetAddr gets the address for the expansion service with the given label. The label corresponds to 74 // the labels used in the --expansion_jar and --expansion_addr flags. If an expansion service is 75 // provided as a jar, then that jar will be run to retrieve the address, and the jars are not 76 // guaranteed to be shut down unless Shutdown is called. 77 // 78 // Note: If this function starts a jar, it waits a few seconds for it to initialize. Do not use 79 // this function if the possibility of a few seconds of latency is not acceptable. 80 func (es *ExpansionServices) GetAddr(label string) (string, error) { 81 // Always default to existing address before running a jar. 82 if addr, ok := es.addrs[label]; ok { 83 return addr, nil 84 } 85 jar, ok := es.jars[label] 86 if !ok { 87 err := fmt.Errorf("no --expansion_jar or --expansion_addr flag provided with label \"%s\"", label) 88 return "", fmt.Errorf("expansion service labeled \"%s\" not found: %w", label, err) 89 } 90 91 // Start jar on open port. 92 port, err := ports.GetOpenTCP() 93 if err != nil { 94 return "", fmt.Errorf("cannot get open port for expansion service labeled \"%s\": %w", label, err) 95 } 96 portStr := strconv.Itoa(port) 97 98 // Run jar and cache its info. 99 proc, err := es.run(*ExpansionTimeout, jar, portStr) 100 if err != nil { 101 return "", fmt.Errorf("cannot run jar for expansion service labeled \"%s\": %w", label, err) 102 } 103 time.Sleep(es.waitTime) // Wait a bit for the jar to start. 104 es.procs = append(es.procs, proc) 105 addr := "localhost:" + portStr 106 es.addrs[label] = addr 107 return addr, nil 108 } 109 110 // Shutdown shuts down any jars started by the ExpansionServices struct and should get called if it 111 // was used at all. 112 func (es *ExpansionServices) Shutdown() { 113 for _, p := range es.procs { 114 p.Kill() 115 } 116 es.jars = nil 117 es.addrs = nil 118 es.procs = nil 119 }