sigs.k8s.io/cluster-api-provider-aws@v1.5.5/test/e2e/shared/resource.go (about) 1 //go:build e2e 2 // +build e2e 3 4 /* 5 Copyright 2020 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package shared 21 22 import ( 23 "errors" 24 "fmt" 25 "os" 26 "path" 27 "path/filepath" 28 "time" 29 30 "github.com/gofrs/flock" 31 . "github.com/onsi/gomega" 32 "sigs.k8s.io/yaml" 33 ) 34 35 type TestResource struct { 36 EC2Normal int `json:"ec2-normal"` 37 VPC int `json:"vpc"` 38 EIP int `json:"eip"` 39 IGW int `json:"igw"` 40 NGW int `json:"ngw"` 41 ClassicLB int `json:"classiclb"` 42 EC2GPU int `json:"ec2-GPU"` 43 VolumeGP2 int `json:"volume-GP2"` 44 } 45 46 func WriteResourceQuotesToFile(logPath string, serviceQuotas map[string]*ServiceQuota) { 47 if _, err := os.Stat(logPath); err == nil { 48 // If resource-quotas file exists, remove it. Should not fail on error, another ginkgo node might have already deleted it. 49 os.Remove(logPath) 50 } 51 52 resources := TestResource{ 53 EC2Normal: serviceQuotas["ec2-normal"].Value, 54 VPC: serviceQuotas["vpc"].Value, 55 EIP: serviceQuotas["eip"].Value, 56 IGW: serviceQuotas["igw"].Value, 57 NGW: serviceQuotas["ngw"].Value, 58 ClassicLB: serviceQuotas["classiclb"].Value, 59 EC2GPU: serviceQuotas["ec2-GPU"].Value, 60 VolumeGP2: serviceQuotas["volume-GP2"].Value, 61 } 62 data, err := yaml.Marshal(resources) 63 Expect(err).NotTo(HaveOccurred()) 64 65 err = os.WriteFile(logPath, data, 0644) //nolint:gosec 66 Expect(err).NotTo(HaveOccurred()) 67 } 68 69 func (r *TestResource) String() string { 70 return fmt.Sprintf("{ec2-normal:%v, vpc:%v, eip:%v, ngw:%v, igw:%v, classiclb:%v, ec2-GPU:%v, volume-gp2:%v}", r.EC2Normal, r.VPC, r.EIP, r.NGW, r.IGW, r.ClassicLB, r.EC2GPU, r.VolumeGP2) 71 } 72 73 func (r *TestResource) WriteRequestedResources(e2eCtx *E2EContext, testName string) { 74 requestedResourceFilePath := path.Join(e2eCtx.Settings.ArtifactFolder, "requested-resources.yaml") 75 if _, err := os.Stat(ResourceQuotaFilePath); err != nil { 76 // If requested-resources file does not exist, create it 77 f, err := os.Create(filepath.Clean(requestedResourceFilePath)) 78 Expect(err).NotTo(HaveOccurred()) 79 Expect(f.Close()).NotTo(HaveOccurred()) 80 } 81 82 fileLock := flock.New(requestedResourceFilePath) 83 defer func() { 84 if err := fileLock.Unlock(); err != nil { 85 time.Sleep(1 * time.Second) 86 err = fileLock.Unlock() 87 Expect(err).NotTo(HaveOccurred()) 88 } 89 }() 90 91 err := fileLock.Lock() 92 Expect(err).NotTo(HaveOccurred()) 93 94 requestedResources, err := os.ReadFile(requestedResourceFilePath) //nolint:gosec 95 Expect(err).NotTo(HaveOccurred()) 96 97 resources := struct { 98 TestResourceMap map[string]TestResource `json:"requested-resources"` 99 }{} 100 err = yaml.Unmarshal(requestedResources, &resources) 101 Expect(err).NotTo(HaveOccurred()) 102 103 if resources.TestResourceMap == nil { 104 resources.TestResourceMap = make(map[string]TestResource) 105 } 106 resources.TestResourceMap[testName] = *r 107 str, err := yaml.Marshal(resources) 108 Expect(err).NotTo(HaveOccurred()) 109 Expect(os.WriteFile(requestedResourceFilePath, str, 0644)).To(Succeed()) //nolint:gosec 110 } 111 112 func (r *TestResource) doesSatisfy(request *TestResource) bool { 113 if request.EC2Normal != 0 && r.EC2Normal < request.EC2Normal { 114 return false 115 } 116 if request.IGW != 0 && r.IGW < request.IGW { 117 return false 118 } 119 if request.NGW != 0 && r.NGW < request.NGW { 120 return false 121 } 122 if request.ClassicLB != 0 && r.ClassicLB < request.ClassicLB { 123 return false 124 } 125 if request.VPC != 0 && r.VPC < request.VPC { 126 return false 127 } 128 if request.EIP != 0 && r.EIP < request.EIP { 129 return false 130 } 131 if request.EC2GPU != 0 && r.EC2GPU < request.EC2GPU { 132 return false 133 } 134 if request.VolumeGP2 != 0 && r.VolumeGP2 < request.VolumeGP2 { 135 return false 136 } 137 return true 138 } 139 140 func (r *TestResource) acquire(request *TestResource) { 141 r.EC2Normal -= request.EC2Normal 142 r.VPC -= request.VPC 143 r.EIP -= request.EIP 144 r.NGW -= request.NGW 145 r.IGW -= request.IGW 146 r.ClassicLB -= request.ClassicLB 147 r.EC2GPU -= request.EC2GPU 148 r.VolumeGP2 -= request.VolumeGP2 149 } 150 151 func (r *TestResource) release(request *TestResource) { 152 r.EC2Normal += request.EC2Normal 153 r.VPC += request.VPC 154 r.EIP += request.EIP 155 r.NGW += request.NGW 156 r.IGW += request.IGW 157 r.ClassicLB += request.ClassicLB 158 r.EC2GPU += request.EC2GPU 159 r.VolumeGP2 += request.VolumeGP2 160 } 161 162 func AcquireResources(request *TestResource, nodeNum int, fileLock *flock.Flock) error { 163 timeoutAfter := time.Now().Add(time.Hour * 6) 164 defer func() { 165 if err := fileLock.Unlock(); err != nil { 166 time.Sleep(1 * time.Second) 167 err = fileLock.Unlock() 168 Expect(err).NotTo(HaveOccurred()) 169 } 170 }() 171 172 Byf("Node %d acquiring resources: %s", nodeNum, request.String()) 173 for range time.Tick(time.Second) { //nolint:staticcheck 174 if time.Now().After(timeoutAfter) { 175 Byf("Timeout reached for node %d", nodeNum) 176 break 177 } 178 err := fileLock.Lock() 179 if err != nil { 180 continue 181 } 182 resourceText, err := os.ReadFile(ResourceQuotaFilePath) 183 if err != nil { 184 return err 185 } 186 187 resources := &TestResource{} 188 if err = yaml.Unmarshal(resourceText, resources); err != nil { 189 return err 190 } 191 192 if resources.doesSatisfy(request) { 193 resources.acquire(request) 194 data, err := yaml.Marshal(resources) 195 if err != nil { 196 return err 197 } 198 if err := os.WriteFile(ResourceQuotaFilePath, data, 0644); err != nil { //nolint:gosec 199 return err 200 } 201 Byf("Node %d acquired resources: %s", nodeNum, request.String()) 202 return nil 203 } 204 e2eDebugBy("Insufficient resources, retrying") 205 if err := fileLock.Unlock(); err != nil { 206 return err 207 } 208 } 209 return errors.New("giving up on acquiring resource due to timeout") 210 } 211 212 func e2eDebugBy(msg string) { 213 if os.Getenv("E2E_DEBUG") != "" { 214 Byf(msg) 215 } 216 } 217 218 func ReleaseResources(request *TestResource, nodeNum int, fileLock *flock.Flock) error { 219 timeoutInSec := 20 220 221 defer func() { 222 if err := fileLock.Unlock(); err != nil { 223 time.Sleep(1 * time.Second) 224 err = fileLock.Unlock() 225 Expect(err).NotTo(HaveOccurred()) 226 } 227 }() 228 229 var tryCount = 0 230 for range time.Tick(1 * time.Second) { //nolint:staticcheck 231 tryCount++ 232 if tryCount > timeoutInSec { 233 break 234 } 235 if err := fileLock.Lock(); err != nil { 236 continue 237 } 238 resourceText, err := os.ReadFile(ResourceQuotaFilePath) 239 if err != nil { 240 return err 241 } 242 resources := &TestResource{} 243 if err := yaml.Unmarshal(resourceText, resources); err != nil { 244 return err 245 } 246 resources.release(request) 247 data, err := yaml.Marshal(resources) 248 if err != nil { 249 return err 250 } 251 if err := os.WriteFile(ResourceQuotaFilePath, data, 0644); err != nil { //nolint:gosec 252 return err 253 } 254 Byf("Node %d released resources: %s", nodeNum, request.String()) 255 return nil 256 } 257 return errors.New("giving up on releasing resource due to timeout") 258 }