Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

main merge from release (0810) #131

Merged
merged 2 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func init() {
flag.String("git-base-url", "https://github.com", "git base url")
flag.String("git-account", "decapod10", "git account of admin cluster")
flag.String("revision", "main", "revision")
flag.String("aws-secret", "awsconfig-secret", "aws secret")
flag.Int("migrate-db", 1, "If the values is true, enable db migration. recommend only development")

// console
Expand Down
36 changes: 24 additions & 12 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ go 1.18

require (
github.com/Nerzal/gocloak/v13 v13.1.0
github.com/aws/aws-sdk-go-v2 v1.17.8
github.com/aws/aws-sdk-go-v2/config v1.18.21
github.com/aws/aws-sdk-go-v2 v1.20.1
github.com/aws/aws-sdk-go-v2/config v1.18.32
github.com/aws/aws-sdk-go-v2/service/ses v1.15.7
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-playground/locales v0.14.1
Expand Down Expand Up @@ -36,16 +36,28 @@ require (

require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.20 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.32 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.26 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.33 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.26 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.8 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.8 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.18.9 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/aws/aws-sdk-go v1.44.317 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.12 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.31 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.38 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.32 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.38 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ec2 v1.110.1 // indirect
github.com/aws/aws-sdk-go-v2/service/eks v1.29.2 // indirect
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.16.2 // indirect
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.20.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.33 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.32 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.1 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.2 // indirect
github.com/aws/aws-sdk-go-v2/service/servicequotas v1.15.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.13.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.21.1 // indirect
github.com/aws/smithy-go v1.14.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
Expand Down
70 changes: 70 additions & 0 deletions go.sum

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions internal/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package kubernetes

import (
"context"
"fmt"
"strings"

"github.com/spf13/viper"

Expand Down Expand Up @@ -50,6 +52,46 @@ func GetClientAdminCluster() (*kubernetes.Clientset, error) {
return clientset, nil
}

func GetAwsSecret() (awsAccessKeyId string, awsSecretAccessKey string, err error) {
clientset, err := GetClientAdminCluster()
if err != nil {
return "", "", err
}

secrets, err := clientset.CoreV1().Secrets("argo").Get(context.TODO(), "awsconfig-secret", metav1.GetOptions{})
if err != nil {
log.Error(err)
return "", "", err
}

strCredentials := string(secrets.Data["credentials"][:])
arr := strings.Split(strCredentials, "\n")
if len(arr) < 3 {
return "", "", err
}

fmt.Sscanf(arr[1], "aws_access_key_id = %s", &awsAccessKeyId)
fmt.Sscanf(arr[2], "aws_secret_access_key = %s", &awsSecretAccessKey)

return
}

func GetAwsAccountIdSecret() (awsAccountId string, err error) {
clientset, err := GetClientAdminCluster()
if err != nil {
return "", err
}

secrets, err := clientset.CoreV1().Secrets("argo").Get(context.TODO(), "tks-aws-user", metav1.GetOptions{})
if err != nil {
log.Error(err)
return "", err
}

awsAccountId = string(secrets.Data["account_id"][:])
return
}

func GetKubeConfig(clusterId string) ([]byte, error) {
clientset, err := GetClientAdminCluster()
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions internal/usecase/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,11 @@ func (u *DashboardUsecase) getChartFromPrometheus(organizationId string, chartTy
}
}
}
sort.Slice(xAxisData, func(i, j int) bool {
a, _ := strconv.Atoi(xAxisData[i])
b, _ := strconv.Atoi(xAxisData[j])
return a < b
})

// cluster 별 y축 계산
for _, val := range result.Data.Result {
Expand Down
192 changes: 191 additions & 1 deletion internal/usecase/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ import (
"strings"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/eks"
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing"
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
"github.com/aws/aws-sdk-go-v2/service/servicequotas"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/openinfradev/tks-api/internal/helper"
"github.com/openinfradev/tks-api/internal/kubernetes"
"github.com/openinfradev/tks-api/internal/middleware/auth/request"
Expand Down Expand Up @@ -69,7 +79,7 @@ func (u *StackUsecase) Create(ctx context.Context, dto domain.Stack) (stackId do
return "", httpErrors.NewInternalServerError(errors.Wrap(err, "Invalid stackTemplateId"), "S_INVALID_STACK_TEMPLATE", "")
}

_, err = u.cloudAccountRepo.Get(dto.CloudAccountId)
cloudAccount, err := u.cloudAccountRepo.Get(dto.CloudAccountId)
if err != nil {
return "", httpErrors.NewInternalServerError(errors.Wrap(err, "Invalid cloudAccountId"), "S_INVALID_CLOUD_ACCOUNT", "")
}
Expand Down Expand Up @@ -99,6 +109,11 @@ func (u *StackUsecase) Create(ctx context.Context, dto domain.Stack) (stackId do
log.InfoWithContext(ctx, err)
}

// Check service quota
if err = u.checkAwsResourceQuota(ctx, cloudAccount); err != nil {
return "", err
}

workflowId, err := u.argo.SumbitWorkflowFromWftpl(workflow, argowf.SubmitOptions{
Parameters: []string{
fmt.Sprintf("tks_api_url=%s", viper.GetString("external-address")),
Expand Down Expand Up @@ -145,6 +160,168 @@ func (u *StackUsecase) Create(ctx context.Context, dto domain.Stack) (stackId do
return dto.ID, nil
}

func (u *StackUsecase) checkAwsResourceQuota(ctx context.Context, cloudAccount domain.CloudAccount) (err error) {
awsAccessKeyId, awsSecretAccessKey, _ := kubernetes.GetAwsSecret()
if err != nil || awsAccessKeyId == "" || awsSecretAccessKey == "" {
log.ErrorWithContext(ctx, err)
return httpErrors.NewInternalServerError(fmt.Errorf("Invalid aws secret."), "", "")
}

cfg, err := config.LoadDefaultConfig(ctx,
config.WithCredentialsProvider(credentials.StaticCredentialsProvider{
Value: aws.Credentials{
AccessKeyID: awsAccessKeyId, SecretAccessKey: awsSecretAccessKey,
},
}))
if err != nil {
log.ErrorWithContext(ctx, err)
}

stsSvc := sts.NewFromConfig(cfg)

if !strings.Contains(cloudAccount.Name, domain.CLOUD_ACCOUNT_INCLUSTER) {
log.InfoWithContext(ctx, "Use assume role. awsAccountId : ", cloudAccount.AwsAccountId)
creds := stscreds.NewAssumeRoleProvider(stsSvc, "arn:aws:iam::"+cloudAccount.AwsAccountId+":role/controllers.cluster-api-provider-aws.sigs.k8s.io")
cfg.Credentials = aws.NewCredentialsCache(creds)
}
client := servicequotas.NewFromConfig(cfg)

quotaMap := map[string]string{
"L-69A177A2": "elasticloadbalancing", // NLB
"L-E9E9831D": "elasticloadbalancing", // Classic
"L-A4707A72": "vpc", // IGW
"L-1194D53C": "eks", // Cluster
"L-0263D0A3": "ec2", // Elastic IP
}

// current usage
type CurrentUsage struct {
NLB int
CLB int
IGW int
Cluster int
EIP int
}

// get current usage
currentUsage := CurrentUsage{}
{
c := elasticloadbalancingv2.NewFromConfig(cfg)
pageSize := int32(100)
res, err := c.DescribeLoadBalancers(ctx, &elasticloadbalancingv2.DescribeLoadBalancersInput{
PageSize: &pageSize,
}, func(o *elasticloadbalancingv2.Options) {
o.Region = "ap-northeast-2"
})
if err != nil {
return err
}

for _, elb := range res.LoadBalancers {
switch elb.Type {
case "network":
currentUsage.NLB += 1
}
}
}

{
c := elasticloadbalancing.NewFromConfig(cfg)
pageSize := int32(100)
res, err := c.DescribeLoadBalancers(ctx, &elasticloadbalancing.DescribeLoadBalancersInput{
PageSize: &pageSize,
}, func(o *elasticloadbalancing.Options) {
o.Region = "ap-northeast-2"
})
if err != nil {
return err
}
currentUsage.CLB = len(res.LoadBalancerDescriptions)
}

{
c := ec2.NewFromConfig(cfg)
res, err := c.DescribeInternetGateways(ctx, &ec2.DescribeInternetGatewaysInput{}, func(o *ec2.Options) {
o.Region = "ap-northeast-2"
})
if err != nil {
return err
}
currentUsage.IGW = len(res.InternetGateways)
}

{
c := eks.NewFromConfig(cfg)
res, err := c.ListClusters(ctx, &eks.ListClustersInput{}, func(o *eks.Options) {
o.Region = "ap-northeast-2"
})
if err != nil {
return err
}
currentUsage.Cluster = len(res.Clusters)
}

{
c := ec2.NewFromConfig(cfg)
res, err := c.DescribeAddresses(ctx, &ec2.DescribeAddressesInput{}, func(o *ec2.Options) {
o.Region = "ap-northeast-2"
})
if err != nil {
log.ErrorWithContext(ctx, err)
return err
}
currentUsage.EIP = len(res.Addresses)
}

for key, val := range quotaMap {
res, err := getServiceQuota(client, key, val)
if err != nil {
return err
}
log.DebugfWithContext(ctx, "%s %s %v", *res.Quota.QuotaName, *res.Quota.QuotaCode, *res.Quota.Value)

quotaValue := int(*res.Quota.Value)

// stack 1개 생성하는데 필요한 quota
// Classic 1
// Network 5
// IGW 1
// EIP 3
// Cluster 1
switch key {
case "L-69A177A2": // NLB
log.InfofWithContext(ctx, "NLB : usage %d, quota %d", currentUsage.NLB, quotaValue)
if quotaValue < currentUsage.NLB+5 {
return httpErrors.NewInternalServerError(fmt.Errorf("Not enough quota (NLB). current[%d], quota[%d]", currentUsage.NLB, quotaValue), "S_NOT_ENOUGH_QUOTA", "")
}
case "L-E9E9831D": // Classic
log.InfofWithContext(ctx, "CLB : usage %d, quota %d", currentUsage.CLB, quotaValue)
if quotaValue < currentUsage.CLB+1 {
return httpErrors.NewInternalServerError(fmt.Errorf("Not enough quota (Classic ELB). current[%d], quota[%d]", currentUsage.CLB, quotaValue), "S_NOT_ENOUGH_QUOTA", "")
}
case "L-A4707A72": // IGW
log.InfofWithContext(ctx, "IGW : usage %d, quota %d", currentUsage.IGW, quotaValue)
if quotaValue < currentUsage.IGW+1 {
return httpErrors.NewInternalServerError(fmt.Errorf("Not enough quota (Internet Gateway). current[%d], quota[%d]", currentUsage.IGW, quotaValue), "S_NOT_ENOUGH_QUOTA", "")
}
case "L-1194D53C": // Cluster
log.InfofWithContext(ctx, "Cluster : usage %d, quota %d", currentUsage.Cluster, quotaValue)
if quotaValue < currentUsage.Cluster+1 {
return httpErrors.NewInternalServerError(fmt.Errorf("Not enough quota (EKS cluster quota). current[%d], quota[%d]", currentUsage.Cluster, quotaValue), "S_NOT_ENOUGH_QUOTA", "")
}
case "L-0263D0A3": // Elastic IP
log.InfofWithContext(ctx, "Elastic IP : usage %d, quota %d", currentUsage.EIP, quotaValue)
if quotaValue < currentUsage.EIP+3 {
return httpErrors.NewInternalServerError(fmt.Errorf("Not enough quota (Elastic IP). current[%d], quota[%d]", currentUsage.EIP, quotaValue), "S_NOT_ENOUGH_QUOTA", "")
}
}

}

//return fmt.Errorf("Always return err")
return nil
}

func (u *StackUsecase) Get(ctx context.Context, stackId domain.StackId) (out domain.Stack, err error) {
cluster, err := u.clusterRepo.Get(domain.ClusterId(stackId))
if err != nil {
Expand Down Expand Up @@ -590,3 +767,16 @@ func parseStatusDescription(statusDesc string) (step int) {
}
return
}

func getServiceQuota(client *servicequotas.Client, quotaCode string, serviceCode string) (res *servicequotas.GetServiceQuotaOutput, err error) {
res, err = client.GetServiceQuota(context.TODO(), &servicequotas.GetServiceQuotaInput{
QuotaCode: &quotaCode,
ServiceCode: &serviceCode,
}, func(o *servicequotas.Options) {
o.Region = "ap-northeast-2"
})
if err != nil {
return nil, err
}
return
}
4 changes: 2 additions & 2 deletions pkg/domain/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ func (m StackStatus) FromString(s string) StackStatus {
return StackStatus_PENDING
}

const MAX_STEP_CLUSTER_CREATE = 13
const MAX_STEP_CLUSTER_CREATE = 16
const MAX_STEP_CLUSTER_REMOVE = 11
const MAX_STEP_LMA_CREATE_PRIMARY = 39
const MAX_STEP_LMA_CREATE_PRIMARY = 42
const MAX_STEP_LMA_CREATE_MEMBER = 27
const MAX_STEP_LMA_REMOVE = 11
const MAX_STEP_SM_CREATE = 22
Expand Down
2 changes: 2 additions & 0 deletions pkg/httpErrors/errorCode.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type ErrorCode string

var errorMap = map[ErrorCode]string{
// Common
"C_INTERNAL_ERROR": "예상하지 못한 오류가 발생했습니다. 문제가 계속되면 관리자에게 문의해주세요.",
"C_INVALID_ACCOUNT_ID": "유효하지 않은 어카운트 아이디입니다. 어카운트 아이디를 확인하세요.",
"C_INVALID_STACK_ID": "유효하지 않은 스택 아이디입니다. 스택 아이디를 확인하세요.",
"C_INVALID_CLUSTER_ID": "유효하지 않은 클러스터 아이디입니다. 클러스터 아이디를 확인하세요.",
Expand Down Expand Up @@ -53,6 +54,7 @@ var errorMap = map[ErrorCode]string{
"S_REMAIN_CLUSTER_FOR_DELETION": "프라이머리 클러스터를 지우기 위해서는 조직내의 모든 클러스터를 삭제해야 합니다.",
"S_FAILED_GET_CLUSTERS": "클러스터를 가져오는데 실패했습니다.",
"S_FAILED_DELETE_EXISTED_ASA": "지우고자 하는 스택에 남아 있는 앱서빙앱이 있습니다.",
"S_NOT_ENOUGH_QUOTA": "AWS 의 resource quota 가 부족합니다. 관리자에게 문의하세요.",

// Alert
"AL_NOT_FOUND_ALERT": "지정한 앨럿이 존재하지 않습니다.",
Expand Down
5 changes: 5 additions & 0 deletions pkg/httpErrors/httpErrors.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,16 @@ func (e RestError) Text() string {
}

func NewRestError(status int, err error, code ErrorCode, text string) IRestError {
if code == "" && text == "" {
code = ErrorCode("C_INTERNAL_ERROR")
}

t := code.GetText()
if text != "" {
t = text
}
log.Info(t)

return RestError{
ErrStatus: status,
ErrCode: string(code),
Expand Down
Loading
Loading