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  }