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

feature: add aws experiment #9

Merged
merged 1 commit into from
Jul 15, 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
2 changes: 2 additions & 0 deletions build/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package main

import (
"github.com/chaosblade-io/chaosblade-exec-cloud/exec/aliyun"
"github.com/chaosblade-io/chaosblade-exec-cloud/exec/aws"
"github.com/chaosblade-io/chaosblade-exec-cloud/exec/model"
"log"
"os"
Expand All @@ -41,6 +42,7 @@ func main() {
func getModels() *spec.Models {
modelCommandSpecs := []spec.ExpModelCommandSpec{
aliyun.NewAliyunCommandSpec(),
aws.NewAwsCommandSpec(),
}
specModels := make([]*spec.Models, 0)
for _, modeSpec := range modelCommandSpecs {
Expand Down
54 changes: 54 additions & 0 deletions exec/aws/aws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package aws

import (
"github.com/chaosblade-io/chaosblade-spec-go/spec"
)

type AwsCommandSpec struct {
spec.BaseExpModelCommandSpec
}

func NewAwsCommandSpec() spec.ExpModelCommandSpec {
return &AwsCommandSpec{
spec.BaseExpModelCommandSpec{
ExpActions: []spec.ExpActionCommandSpec{
NewEcsActionSpec(),
//NewVSwitchActionSpec(),
//NewSecurityGroupActionSpec(),
//NewNetworkInterfaceActionSpec(),
//NewPublicIpActionSpec(),
//NewPrivateIpActionSpec(),
//NewDiskActionSpec(),
},
ExpFlags: []spec.ExpFlagSpec{},
},
}
}

func (*AwsCommandSpec) Name() string {
return "aws"
}

func (*AwsCommandSpec) ShortDesc() string {
return "Aws experiment"
}

func (*AwsCommandSpec) LongDesc() string {
return "Aws experiment contains ec2"
}
323 changes: 323 additions & 0 deletions exec/aws/aws_ec2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package aws

import (
"context"
"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/service/ec2"
"github.com/chaosblade-io/chaosblade-spec-go/log"
"os"
"strings"

"github.com/chaosblade-io/chaosblade-exec-cloud/exec"
"github.com/chaosblade-io/chaosblade-exec-cloud/exec/category"
"github.com/chaosblade-io/chaosblade-spec-go/spec"
"github.com/chaosblade-io/chaosblade-spec-go/util"
)

const Ec2Bin = "chaos_aws_ec2"

type EcsActionSpec struct {
spec.BaseExpActionCommandSpec
}

func NewEcsActionSpec() spec.ExpActionCommandSpec {
return &EcsActionSpec{
spec.BaseExpActionCommandSpec{
ActionFlags: []spec.ExpFlagSpec{
&spec.ExpFlag{
Name: "accessKeyId",
Desc: "the accessKeyId of aws, if not provided, get from env ACCESS_KEY_ID",
},
&spec.ExpFlag{
Name: "accessKeySecret",
Desc: "the accessKeySecret of aws, if not provided, get from env ACCESS_KEY_SECRET",
},
&spec.ExpFlag{
Name: "regionId",
Desc: "the regionId of aws",
},
&spec.ExpFlag{
Name: "type",
Desc: "the operation of instances, support start, stop, reboot, etc",
},
&spec.ExpFlag{
Name: "instances",
Desc: "the instances list, split by comma",
},
},
ActionExecutor: &EcsExecutor{},
ActionExample: `
# stop instances which instance id is i-x,i-y
blade create aws ecs --accessKeyId xxx --accessKeySecret yyy --regionId us-west-2 --type stop --instances i-x,i-y

# start instances which instance id is i-x,i-y
blade create aws ecs --accessKeyId xxx --accessKeySecret yyy --regionId us-west-2 --type start --instances i-x,i-y

# reboot instances which instance id is i-x,i-y
blade create aws ecs --accessKeyId xxx --accessKeySecret yyy --regionId us-west-2 --type reboot --instances i-x,i-y`,
ActionPrograms: []string{Ec2Bin},
ActionCategories: []string{category.Cloud + "_" + category.Aws + "_" + category.Ec2},
},
}
}

func (*EcsActionSpec) Name() string {
return "ecs"
}

func (*EcsActionSpec) Aliases() []string {
return []string{}
}
func (*EcsActionSpec) ShortDesc() string {
return "do some aws ecs Operations, like stop, start, reboot"
}

func (b *EcsActionSpec) LongDesc() string {
if b.ActionLongDesc != "" {
return b.ActionLongDesc
}
return "do some aws ecs Operations, like stop, start, reboot"
}

type EcsExecutor struct {
channel spec.Channel
}

func (*EcsExecutor) Name() string {
return "ecs"
}

func (be *EcsExecutor) Exec(uid string, ctx context.Context, model *spec.ExpModel) *spec.Response {
if be.channel == nil {
util.Errorf(uid, util.GetRunFuncName(), spec.ChannelNil.Msg)
return spec.ResponseFailWithFlags(spec.ChannelNil)
}
accessKeyId := model.ActionFlags["accessKeyId"]
accessKeySecret := model.ActionFlags["accessKeySecret"]
regionId := model.ActionFlags["regionId"]
operationType := model.ActionFlags["type"]
instances := model.ActionFlags["instances"]
if accessKeyId == "" {
val, ok := os.LookupEnv("ACCESS_KEY_ID")
if !ok {
log.Errorf(ctx, "could not get ACCESS_KEY_ID from env or parameter!")
return spec.ResponseFailWithFlags(spec.ParameterLess, "accessKeyId")
}
accessKeyId = val
}

if accessKeySecret == "" {
val, ok := os.LookupEnv("ACCESS_KEY_SECRET")
if !ok {
log.Errorf(ctx, "could not get ACCESS_KEY_SECRET from env or parameter!")
return spec.ResponseFailWithFlags(spec.ParameterLess, "accessKeySecret")
}
accessKeySecret = val
}

if regionId == "" {
log.Errorf(ctx, "regionId is required!")
return spec.ResponseFailWithFlags(spec.ParameterLess, "regionId")
}

if operationType == "" {
log.Errorf(ctx, "operationType is required!")
return spec.ResponseFailWithFlags(spec.ParameterLess, "type")
}

if instances == "" {
log.Errorf(ctx, "instances is required!")
return spec.ResponseFailWithFlags(spec.ParameterLess, "instances")
}
instancesArray := strings.Split(instances, ",")
instanceStatusMap, _err := describeInstancesStatus(ctx, accessKeyId, accessKeySecret, regionId, instancesArray)
if _err != nil {
return spec.ResponseFailWithFlags(spec.ParameterRequestFailed, "describe instances status failed")
}

for _, instance := range instancesArray {
if (instanceStatusMap[instance] == "Running" && operationType == "start") || (instanceStatusMap[instance] == "Stopped" && operationType == "stop") {
return be.stop(ctx, operationType, accessKeyId, accessKeySecret, regionId, instancesArray)
}
}
return be.start(ctx, operationType, accessKeyId, accessKeySecret, regionId, instancesArray)
}

func (be *EcsExecutor) start(ctx context.Context, operationType, accessKeyId, accessKeySecret, regionId string, instancesArray []string) *spec.Response {
switch operationType {
case "start":
return startAwsInstances(ctx, accessKeyId, accessKeySecret, regionId, instancesArray)
case "stop":
return stopAwsInstances(ctx, accessKeyId, accessKeySecret, regionId, instancesArray)
case "reboot":
return rebootAwsInstances(ctx, accessKeyId, accessKeySecret, regionId, instancesArray)
//case "delete":
// return deleteAwsInstances(ctx, accessKeyId, accessKeySecret, regionId, instancesArray)
default:
return spec.ResponseFailWithFlags(spec.ParameterInvalid, "type is not support(support start, stop, reboot)")
}
select {}
}

func (be *EcsExecutor) stop(ctx context.Context, operationType, accessKeyId, accessKeySecret, regionId string, instancesArray []string) *spec.Response {
switch operationType {
case "start":
return stopAwsInstances(ctx, accessKeyId, accessKeySecret, regionId, instancesArray)
case "stop":
return startAwsInstances(ctx, accessKeyId, accessKeySecret, regionId, instancesArray)
//case "reboot":
// return rebootAwsInstances(ctx, accessKeyId, accessKeySecret, regionId, instancesArray)
//case "delete":
// return deleteAwsInstances(ctx, accessKeyId, accessKeySecret, regionId, instancesArray)
default:
return spec.ResponseFailWithFlags(spec.ParameterInvalid, "type is not support(support start, stop, reboot)")
}
ctx = context.WithValue(ctx, "bin", Ec2Bin)
return exec.Destroy(ctx, be.channel, "aws es")
}

func (be *EcsExecutor) SetChannel(channel spec.Channel) {
be.channel = channel
}

func CreateConfig(accessKeyId, accessKeySecret, regionId string) (aws.Config, error) {
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(regionId),
config.WithCredentialsProvider(credentials.StaticCredentialsProvider{
Value: aws.Credentials{
AccessKeyID: accessKeyId,
SecretAccessKey: accessKeySecret,
Source: "chaosblade hard coded credentials",
},
}),
)
return cfg, err
}

// start instances
func startAwsInstances(ctx context.Context, accessKeyId, accessKeySecret, regionId string, instances []string) *spec.Response {
cfg, _err := CreateConfig(accessKeyId, accessKeySecret, regionId)
if _err != nil {
log.Errorf(ctx, "create aws config failed, err: %s", _err.Error())
return spec.ResponseFailWithFlags(spec.ContainerInContextNotFound, "create aws config failed")
}
client := ec2.NewFromConfig(cfg)

input := &ec2.StartInstancesInput{
InstanceIds: instances,
}

_, _err = client.StartInstances(context.TODO(), input)
if _err != nil {
log.Errorf(ctx, "start aws instances failed, err: %s", _err.Error())
return spec.ResponseFailWithFlags(spec.ContainerInContextNotFound, "start aws instances failed")
}
return spec.Success()
}

// stop instances
func stopAwsInstances(ctx context.Context, accessKeyId, accessKeySecret, regionId string, instances []string) *spec.Response {
cfg, _err := CreateConfig(accessKeyId, accessKeySecret, regionId)
if _err != nil {
log.Errorf(ctx, "create aws config failed, err: %s", _err.Error())
return spec.ResponseFailWithFlags(spec.ContainerInContextNotFound, "create aws config failed")
}
client := ec2.NewFromConfig(cfg)

input := &ec2.StopInstancesInput{
InstanceIds: instances,
}
_, _err = client.StopInstances(context.TODO(), input)
if _err != nil {
log.Errorf(ctx, "stop aws instances failed, err: %s", _err.Error())
return spec.ResponseFailWithFlags(spec.ContainerInContextNotFound, "stop aws instances failed")
}
return spec.Success()
}

// reboot instances
func rebootAwsInstances(ctx context.Context, accessKeyId, accessKeySecret, regionId string, instances []string) *spec.Response {
cfg, _err := CreateConfig(accessKeyId, accessKeySecret, regionId)
if _err != nil {
log.Errorf(ctx, "create aws config failed, err: %s", _err.Error())
return spec.ResponseFailWithFlags(spec.ContainerInContextNotFound, "create aws config failed")
}
client := ec2.NewFromConfig(cfg)

input := &ec2.RebootInstancesInput{
InstanceIds: instances,
}
_, _err = client.RebootInstances(context.TODO(), input)
if _err != nil {
log.Errorf(ctx, "reboot aws instances failed, err: %s", _err.Error())
return spec.ResponseFailWithFlags(spec.ContainerInContextNotFound, "restart aws instances failed")
}
return spec.Success()
}

// delete instances
func deleteAwsInstances(ctx context.Context, accessKeyId, accessKeySecret, regionId string, instances []string) *spec.Response {
cfg, _err := CreateConfig(accessKeyId, accessKeySecret, regionId)
if _err != nil {
log.Errorf(ctx, "create aws config failed, err: %s", _err.Error())
return spec.ResponseFailWithFlags(spec.ContainerInContextNotFound, "create aws config failed")
}
client := ec2.NewFromConfig(cfg)

input := &ec2.TerminateInstancesInput{
InstanceIds: instances,
}
_, _err = client.TerminateInstances(context.TODO(), input)
if _err != nil {
log.Errorf(ctx, "delete aws instances failed, err: %s", _err.Error())
return spec.ResponseFailWithFlags(spec.ContainerInContextNotFound, "delete aws instances failed")
}
return spec.Success()
}

// describe instances status
func describeInstancesStatus(ctx context.Context, accessKeyId, accessKeySecret, regionId string, instances []string) (_result map[string]string, _err error) {
cfg, _err := CreateConfig(accessKeyId, accessKeySecret, regionId)
if _err != nil {
log.Errorf(ctx, "create aws config failed, err: %s", _err.Error())
return _result, _err
}
client := ec2.NewFromConfig(cfg)

// Create an input object with the instance IDs
input := &ec2.DescribeInstanceStatusInput{
InstanceIds: instances,
}

// Describe the instance status
resp, _err := client.DescribeInstanceStatus(context.TODO(), input)
if _err != nil {
log.Errorf(ctx, "describe aws instances status failed, err: %s", _err.Error())
return _result, _err
}

statusMap := map[string]string{}
for _, status := range resp.InstanceStatuses {
statusMap[*status.InstanceId] = string(status.InstanceState.Name)
}

_result = statusMap
return _result, _err
}
Loading