github.com/emc-advanced-dev/unik@v0.0.0-20190717152701-a58d3e8e33b7/pkg/providers/aws/run_instance.go (about)

     1  package aws
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/json"
     6  	"github.com/sirupsen/logrus"
     7  	"github.com/aws/aws-sdk-go/aws"
     8  	"github.com/aws/aws-sdk-go/service/ec2"
     9  	"github.com/emc-advanced-dev/pkg/errors"
    10  	"github.com/solo-io/unik/pkg/providers/common"
    11  	"github.com/solo-io/unik/pkg/types"
    12  	"time"
    13  )
    14  
    15  func (p *AwsProvider) RunInstance(params types.RunInstanceParams) (_ *types.Instance, err error) {
    16  	logrus.WithFields(logrus.Fields{
    17  		"image-id": params.ImageId,
    18  		"mounts":   params.MntPointsToVolumeIds,
    19  		"env":      params.Env,
    20  	}).Infof("running instance %s", params.Name)
    21  
    22  	var instanceId string
    23  	ec2svc := p.newEC2()
    24  
    25  	defer func() {
    26  		if err != nil {
    27  			logrus.WithError(err).Errorf("aws running instance encountered an error")
    28  			if instanceId != "" {
    29  				if params.NoCleanup {
    30  					logrus.Warnf("because --no-cleanup flag was provided, not cleaning up failed instance %s0", instanceId)
    31  					return
    32  				}
    33  				logrus.Warnf("cleaning up instance %s", instanceId)
    34  				terminateInstanceInput := &ec2.TerminateInstancesInput{
    35  					InstanceIds: []*string{aws.String(instanceId)},
    36  				}
    37  				ec2svc.TerminateInstances(terminateInstanceInput)
    38  				if cleanupErr := p.state.ModifyInstances(func(instances map[string]*types.Instance) error {
    39  					delete(instances, instanceId)
    40  					return nil
    41  				}); cleanupErr != nil {
    42  					logrus.Error(errors.New("modifying instance map in state", cleanupErr))
    43  				}
    44  			}
    45  		}
    46  	}()
    47  
    48  	image, err := p.GetImage(params.ImageId)
    49  	if err != nil {
    50  		return nil, errors.New("getting image", err)
    51  	}
    52  
    53  	if err := common.VerifyMntsInput(p, image, params.MntPointsToVolumeIds); err != nil {
    54  		return nil, errors.New("invalid mapping for volume", err)
    55  	}
    56  
    57  	envData, err := json.Marshal(params.Env)
    58  	if err != nil {
    59  		return nil, errors.New("could not convert instance env to json", err)
    60  	}
    61  	encodedData := base64.StdEncoding.EncodeToString(envData)
    62  
    63  	//if not set, use default
    64  	if params.InstanceMemory <= 0 {
    65  		params.InstanceMemory = image.RunSpec.DefaultInstanceMemory
    66  	}
    67  
    68  	instanceType, err := getInstanceType(image.StageSpec.XenVirtualizationType, params.InstanceMemory)
    69  	if err != nil {
    70  		return nil, errors.New("could not find instance type for specified memory", err)
    71  	}
    72  
    73  	logrus.Debugf("determined intstance type %s for memory requirement %v", instanceType, params.InstanceMemory)
    74  
    75  	runInstanceInput := &ec2.RunInstancesInput{
    76  		ImageId:  aws.String(image.Id),
    77  		MinCount: aws.Int64(1),
    78  		MaxCount: aws.Int64(1),
    79  		Placement: &ec2.Placement{
    80  			AvailabilityZone: aws.String(p.config.Zone),
    81  		},
    82  		InstanceType: aws.String(instanceType),
    83  		UserData:     aws.String(encodedData),
    84  	}
    85  
    86  	runInstanceOutput, err := ec2svc.RunInstances(runInstanceInput)
    87  	if err != nil {
    88  		return nil, errors.New("failed to run instance", err)
    89  	}
    90  	if len(runInstanceOutput.Instances) < 1 {
    91  		logrus.WithFields(logrus.Fields{"output": runInstanceOutput}).Errorf("run instance %s failed, produced %v instances, expected 1", params.Name, len(runInstanceOutput.Instances))
    92  		return nil, errors.New("expected 1 instance to be created", nil)
    93  	}
    94  	instanceId = *runInstanceOutput.Instances[0].InstanceId
    95  
    96  	if len(runInstanceOutput.Instances) > 1 {
    97  		logrus.WithFields(logrus.Fields{"output": runInstanceOutput}).Errorf("run instance %s failed, produced %v instances, expected 1", params.Name, len(runInstanceOutput.Instances))
    98  		return nil, errors.New("expected 1 instance to be created", nil)
    99  	}
   100  
   101  	//must add instance to state before attaching volumes
   102  	instance := &types.Instance{
   103  		Id:             instanceId,
   104  		Name:           params.Name,
   105  		State:          types.InstanceState_Pending,
   106  		Infrastructure: types.Infrastructure_AWS,
   107  		ImageId:        image.Id,
   108  		Created:        time.Now(),
   109  	}
   110  
   111  	if err := p.state.ModifyInstances(func(instances map[string]*types.Instance) error {
   112  		instances[instance.Id] = instance
   113  		return nil
   114  	}); err != nil {
   115  		return nil, errors.New("modifying instance map in state", err)
   116  	}
   117  
   118  	if len(params.MntPointsToVolumeIds) > 0 {
   119  		logrus.Debugf("stopping instance for volume attach")
   120  		waitParam := &ec2.DescribeInstancesInput{
   121  			InstanceIds: []*string{aws.String(instanceId)},
   122  		}
   123  		logrus.Debugf("waiting for instance to reach running state")
   124  		if err := ec2svc.WaitUntilInstanceRunning(waitParam); err != nil {
   125  			return nil, errors.New("waiting for instance to reach running state", err)
   126  		}
   127  		if err := p.StopInstance(instanceId); err != nil {
   128  			return nil, errors.New("failed to stop instance for attaching volumes", err)
   129  		}
   130  		for mountPoint, volumeId := range params.MntPointsToVolumeIds {
   131  			logrus.WithFields(logrus.Fields{"volume-id": volumeId}).Debugf("attaching volume %s to intance %s", volumeId, instanceId)
   132  			if err := p.AttachVolume(volumeId, instanceId, mountPoint); err != nil {
   133  				return nil, errors.New("attaching volume to instance", err)
   134  			}
   135  		}
   136  		if err := p.StartInstance(instanceId); err != nil {
   137  			return nil, errors.New("starting instance after volume attach", err)
   138  		}
   139  	}
   140  
   141  	tagObjects := &ec2.CreateTagsInput{
   142  		Resources: []*string{
   143  			aws.String(instanceId),
   144  		},
   145  		Tags: []*ec2.Tag{
   146  			&ec2.Tag{
   147  				Key:   aws.String("Name"),
   148  				Value: aws.String(params.Name),
   149  			},
   150  		},
   151  	}
   152  	_, err = ec2svc.CreateTags(tagObjects)
   153  	if err != nil {
   154  		return nil, errors.New("tagging snapshot, image, and volume", err)
   155  	}
   156  
   157  	logrus.WithFields(logrus.Fields{"instance": instance}).Infof("instance created succesfully")
   158  
   159  	return instance, nil
   160  }
   161  
   162  type instanceType struct {
   163  	memory int
   164  	name   string
   165  }
   166  
   167  var hvmInstanceTypes = []instanceType{
   168  	instanceType{memory: 512, name: "t2.nano"},
   169  	instanceType{memory: 1024, name: "t2.micro"},
   170  	instanceType{memory: 2048, name: "t2.small"},
   171  	instanceType{memory: 4096, name: "t2.medium"},
   172  	instanceType{memory: 8192, name: "t2.large"},
   173  	instanceType{memory: 16384, name: "m4.xlarge"},
   174  }
   175  
   176  var pvInstanceTypes = []instanceType{
   177  	instanceType{memory: 1741, name: "m1.small"},
   178  	instanceType{memory: 3789, name: "m1.medium"},
   179  	instanceType{memory: 7680, name: "m1.large"},
   180  	instanceType{memory: 15360, name: "m1.xlarge"},
   181  }
   182  
   183  func getInstanceType(virtualizationType types.XenVirtualizationType, memoryRequirement int) (string, error) {
   184  	switch virtualizationType {
   185  	case types.XenVirtualizationType_HVM:
   186  		for _, instanceType := range hvmInstanceTypes {
   187  			if instanceType.memory >= memoryRequirement {
   188  				return instanceType.name, nil
   189  			}
   190  		}
   191  		return "", errors.New("memory requirement too large", nil)
   192  	case types.XenVirtualizationType_Paravirtual:
   193  		for _, instanceType := range pvInstanceTypes {
   194  			if instanceType.memory >= memoryRequirement {
   195  				return instanceType.name, nil
   196  			}
   197  		}
   198  		return "", errors.New("memory requirement too large", nil)
   199  	}
   200  	return "", errors.New("unknown virtualization type "+string(virtualizationType), nil)
   201  }