Skip to content

Commit

Permalink
machineactuator: Initial Delete implementation (#80)
Browse files Browse the repository at this point in the history
We also add some vim files to the .gitignore here.
  • Loading branch information
phyber authored and k8s-ci-robot committed Sep 20, 2018
1 parent 0adcf20 commit 8c827ef
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 2 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Expand Up @@ -15,4 +15,8 @@
vendor

# Ansible
*.retry
*.retry

# vim
*~
*.swp
29 changes: 28 additions & 1 deletion cloud/aws/actuators/machine/actuator.go
Expand Up @@ -20,6 +20,7 @@ import (
ec2svc "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services/ec2"

"github.com/golang/glog"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime"
clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1"
)
Expand All @@ -34,6 +35,7 @@ type machinesSvc interface {
type ec2Svc interface {
CreateInstance(*clusterv1.Machine) (*ec2svc.Instance, error)
InstanceIfExists(*string) (*ec2svc.Instance, error)
TerminateInstance(*string) error
}

// codec are the functions off the generated codec that this actuator uses.
Expand Down Expand Up @@ -109,7 +111,32 @@ func (a *Actuator) Create(cluster *clusterv1.Cluster, machine *clusterv1.Machine
// Delete deletes a machine and is invoked by the Machine Controller
func (a *Actuator) Delete(cluster *clusterv1.Cluster, machine *clusterv1.Machine) error {
glog.Infof("Deleting machine %v for cluster %v.", machine.Name, cluster.Name)
return fmt.Errorf("TODO: Not yet implemented")

status, err := a.machineProviderStatus(machine)
if err != nil {
return errors.Wrap(err, "failed to get machine provider status")
}

instance, err := a.ec2.InstanceIfExists(status.InstanceID)
if err != nil {
return errors.Wrap(err, "failed to get instance")
}

// Check the instance state. If it's already shutting down or terminated,
// do nothing. Otherwise attempt to delete it.
// This decision is based on the ec2-instance-lifecycle graph at
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-lifecycle.html
switch instance.State {
case ec2svc.InstanceStateShuttingDown, ec2svc.InstanceStateTerminated:
return nil
default:
err = a.ec2.TerminateInstance(status.InstanceID)
if err != nil {
return errors.Wrap(err, "failed to terminate instance")
}
}

return nil
}

// Update updates a machine and is invoked by the Machine Controller
Expand Down
41 changes: 41 additions & 0 deletions cloud/aws/actuators/machine/actuator_test.go
Expand Up @@ -14,6 +14,7 @@
package machine_test

import (
"errors"
"testing"

"sigs.k8s.io/cluster-api-provider-aws/cloud/aws/actuators/machine"
Expand Down Expand Up @@ -41,6 +42,14 @@ func (e *ec2) InstanceIfExists(id *string) (*ec2svc.Instance, error) {
return nil, nil
}

func (e *ec2) TerminateInstance(instanceID *string) error {
if instanceID == nil {
return errors.New("didn't receive an instanceID")
}

return nil
}

type machines struct{}

func (m *machines) UpdateMachineStatus(machine *clusterv1.Machine) (*clusterv1.Machine, error) {
Expand All @@ -66,3 +75,35 @@ func TestCreate(t *testing.T) {
t.Fatalf("failed to create machine: %v", err)
}
}

func TestDelete(t *testing.T) {
codec, err := v1alpha1.NewCodec()
if err != nil {
t.Fatalf("failed to create a codec: %v", err)
}

ap := machine.ActuatorParams{
Codec: codec,
MachinesService: &machines{},
EC2Service: &ec2{},
}

actuator, err := machine.NewActuator(ap)
if err != nil {
t.Fatalf("failed to create an actuator: %v", err)
}

// Get some empty cluster and machine structs.
testCluster := &clusterv1.Cluster{}
testMachine := &clusterv1.Machine{}

// Create a machine that we can delete.
if err := actuator.Create(testCluster, testMachine); err != nil {
t.Fatalf("failed to create machine: %v", err)
}

// Delete the machine.
if err := actuator.Delete(testCluster, testMachine); err != nil {
t.Fatalf("failed to delete machine: %v", err)
}
}
25 changes: 25 additions & 0 deletions cloud/aws/services/ec2/instances.go
Expand Up @@ -19,6 +19,14 @@ import (
clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1"
)

const (
// InstanceStateShuttingDown indicates the instance is shutting-down
InstanceStateShuttingDown = ec2.InstanceStateNameShuttingDown

// InstanceStateTerminated indicates the instance has been terminated
InstanceStateTerminated = ec2.InstanceStateNameTerminated
)

// Instance is an internal representation of an AWS instance.
// This contains more data than the provider config struct tracked in the status.
type Instance struct {
Expand Down Expand Up @@ -67,3 +75,20 @@ func (s *Service) CreateInstance(machine *clusterv1.Machine) (*Instance, error)
ID: *reservation.Instances[0].InstanceId,
}, nil
}

// TerminateInstance terminates an EC2 instance.
// Returns nil on success, error in all other cases.
func (s *Service) TerminateInstance(instanceID *string) error {
input := &ec2.TerminateInstancesInput{
InstanceIds: []*string{
instanceID,
},
}

_, err := s.ec2.TerminateInstances(input)
if err != nil {
return err
}

return nil
}
58 changes: 58 additions & 0 deletions cloud/aws/services/ec2/instances_test.go
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/golang/mock/gomock"
"github.com/pkg/errors"
ec2svc "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services/ec2"
"sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services/ec2/mock_ec2iface"
)
Expand Down Expand Up @@ -103,3 +104,60 @@ func TestInstanceIfExists(t *testing.T) {
})
}
}

func TestTerminateInstance(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

instanceNotFoundError := errors.New("instance not found")

testCases := []struct {
name string
instanceID string
expect func(m *mock_ec2iface.MockEC2API)
check func(err error)
}{
{
name: "instance exists",
instanceID: "i-exist",
expect: func(m *mock_ec2iface.MockEC2API) {
m.EXPECT().
TerminateInstances(gomock.Eq(&ec2.TerminateInstancesInput{
InstanceIds: []*string{aws.String("i-exist")},
})).
Return(&ec2.TerminateInstancesOutput{}, nil)
},
check: func(err error) {
if err != nil {
t.Fatalf("did not expect error: %v", err)
}
},
},
{
name: "instance does not exist",
instanceID: "i-donotexist",
expect: func(m *mock_ec2iface.MockEC2API) {
m.EXPECT().
TerminateInstances(gomock.Eq(&ec2.TerminateInstancesInput{
InstanceIds: []*string{aws.String("i-donotexist")},
})).
Return(&ec2.TerminateInstancesOutput{}, instanceNotFoundError)
},
check: func(err error) {
if err == nil {
t.Fatalf("did not expect error: %v", err)
}
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ec2Mock := mock_ec2iface.NewMockEC2API(mockCtrl)
tc.expect(ec2Mock)
s := ec2svc.NewService(ec2Mock)
err := s.TerminateInstance(&tc.instanceID)
tc.check(err)
})
}
}

0 comments on commit 8c827ef

Please sign in to comment.