github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/integration/e2e/scenario.go (about) 1 package e2e 2 3 import ( 4 "fmt" 5 "os" 6 "strings" 7 8 "github.com/pkg/errors" 9 ) 10 11 const ( 12 ContainerSharedDir = "/shared" 13 ) 14 15 type Service interface { 16 Name() string 17 Start(networkName, dir string) error 18 WaitReady() error 19 20 // It should be ok to Stop and Kill more than once, with next invokes being noop. 21 Kill() error 22 Stop() error 23 } 24 25 type Scenario struct { 26 services []Service 27 28 networkName string 29 sharedDir string 30 } 31 32 func NewScenario(networkName string) (*Scenario, error) { 33 s := &Scenario{networkName: networkName} 34 35 var err error 36 s.sharedDir, err = GetTempDirectory() 37 if err != nil { 38 return nil, err 39 } 40 41 // Force a shutdown in order to cleanup from a spurious situation in case 42 // the previous tests run didn't cleanup correctly. 43 s.shutdown() 44 45 // Setup the docker network. 46 if out, err := RunCommandAndGetOutput("docker", "network", "create", networkName); err != nil { 47 logger.Log(string(out)) 48 s.clean() 49 return nil, errors.Wrapf(err, "create docker network '%s'", networkName) 50 } 51 52 return s, nil 53 } 54 55 // SharedDir returns the absolute path of the directory on the host that is shared with all services in docker. 56 func (s *Scenario) SharedDir() string { 57 return s.sharedDir 58 } 59 60 // NetworkName returns the network name that scenario is responsible for. 61 func (s *Scenario) NetworkName() string { 62 return s.networkName 63 } 64 65 func (s *Scenario) isRegistered(name string) bool { 66 for _, service := range s.services { 67 if service.Name() == name { 68 return true 69 } 70 } 71 return false 72 } 73 74 func (s *Scenario) StartAndWaitReady(services ...Service) error { 75 if err := s.Start(services...); err != nil { 76 return err 77 } 78 return s.WaitReady(services...) 79 } 80 81 func (s *Scenario) Start(services ...Service) error { 82 for _, service := range services { 83 logger.Log("Starting", service.Name()) 84 85 // Ensure another service with the same name doesn't exist. 86 if s.isRegistered(service.Name()) { 87 return fmt.Errorf("another service with the same name '%s' has already been started", service.Name()) 88 } 89 90 // Start the service. 91 if err := service.Start(s.networkName, s.SharedDir()); err != nil { 92 return err 93 } 94 95 // Add to the list of services. 96 s.services = append(s.services, service) 97 } 98 99 return nil 100 } 101 102 func (s *Scenario) Stop(services ...Service) error { 103 for _, service := range services { 104 if !s.isRegistered(service.Name()) { 105 return fmt.Errorf("unable to stop service %s because it does not exist", service.Name()) 106 } 107 if err := service.Stop(); err != nil { 108 return err 109 } 110 111 // Remove the service from the list of services. 112 for i, entry := range s.services { 113 if entry.Name() == service.Name() { 114 s.services = append(s.services[:i], s.services[i+1:]...) 115 break 116 } 117 } 118 } 119 return nil 120 } 121 122 func (s *Scenario) WaitReady(services ...Service) error { 123 for _, service := range services { 124 if !s.isRegistered(service.Name()) { 125 return fmt.Errorf("unable to wait for service %s because it does not exist", service.Name()) 126 } 127 if err := service.WaitReady(); err != nil { 128 return err 129 } 130 } 131 return nil 132 } 133 134 func (s *Scenario) Close() { 135 if s == nil { 136 return 137 } 138 s.shutdown() 139 s.clean() 140 } 141 142 // TODO(bwplotka): Add comments. 143 func (s *Scenario) clean() { 144 if err := os.RemoveAll(s.sharedDir); err != nil { 145 logger.Log("error while removing sharedDir", s.sharedDir, "err:", err) 146 } 147 } 148 149 func (s *Scenario) shutdown() { 150 // Kill the services in the opposite order. 151 for i := len(s.services) - 1; i >= 0; i-- { 152 if err := s.services[i].Kill(); err != nil { 153 logger.Log("Unable to kill service", s.services[i].Name(), ":", err.Error()) 154 } 155 } 156 157 // Ensure there are no leftover containers. 158 if out, err := RunCommandAndGetOutput( 159 "docker", 160 "ps", 161 "-a", 162 "--quiet", 163 "--filter", 164 fmt.Sprintf("network=%s", s.networkName), 165 ); err == nil { 166 for _, containerID := range strings.Split(string(out), "\n") { 167 containerID = strings.TrimSpace(containerID) 168 if containerID == "" { 169 continue 170 } 171 172 if out, err = RunCommandAndGetOutput("docker", "rm", "--force", containerID); err != nil { 173 logger.Log(string(out)) 174 logger.Log("Unable to cleanup leftover container", containerID, ":", err.Error()) 175 } 176 } 177 } else { 178 logger.Log(string(out)) 179 logger.Log("Unable to cleanup leftover containers:", err.Error()) 180 } 181 182 // Teardown the docker network. In case the network does not exists (ie. this function 183 // is called during the setup of the scenario) we skip the removal in order to not log 184 // an error which may be misleading. 185 if ok, err := existDockerNetwork(s.networkName); ok || err != nil { 186 if out, err := RunCommandAndGetOutput("docker", "network", "rm", s.networkName); err != nil { 187 logger.Log(string(out)) 188 logger.Log("Unable to remove docker network", s.networkName, ":", err.Error()) 189 } 190 } 191 } 192 193 func existDockerNetwork(networkName string) (bool, error) { 194 out, err := RunCommandAndGetOutput("docker", "network", "ls", "--quiet", "--filter", fmt.Sprintf("name=%s", networkName)) 195 if err != nil { 196 logger.Log(string(out)) 197 logger.Log("Unable to check if docker network", networkName, "exists:", err.Error()) 198 } 199 200 return strings.TrimSpace(string(out)) != "", nil 201 }