Skip to content

Commit

Permalink
Add first version of the windows ASG provider
Browse files Browse the repository at this point in the history
  • Loading branch information
marcosnils committed Aug 5, 2017
1 parent f810c0c commit ed7cefc
Show file tree
Hide file tree
Showing 10 changed files with 388 additions and 29 deletions.
10 changes: 10 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ func GetDindImageName() string {
}
return dindImage
}

func GetSSHImage() string {
sshImage := os.Getenv("SSH_IMAGE")
defaultSSHImage := "franela/ssh"
if len(sshImage) == 0 {
return defaultSSHImage
}
return sshImage
}

func GetDuration(reqDur string) time.Duration {
var defaultDuration = 4 * time.Hour
if reqDur != "" {
Expand Down
23 changes: 13 additions & 10 deletions docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,16 +192,17 @@ func (d *docker) DeleteContainer(id string) error {
}

type CreateContainerOpts struct {
Image string
SessionId string
PwdIpAddress string
ContainerName string
Hostname string
ServerCert []byte
ServerKey []byte
CACert []byte
Privileged bool
HostFQDN string
Image string
WindowsEndpoint string
SessionId string
PwdIpAddress string
ContainerName string
Hostname string
ServerCert []byte
ServerKey []byte
CACert []byte
Privileged bool
HostFQDN string
}

func (d *docker) CreateContainer(opts CreateContainerOpts) (string, error) {
Expand All @@ -211,6 +212,8 @@ func (d *docker) CreateContainer(opts CreateContainerOpts) (string, error) {

env := []string{}

env = append(env, fmt.Sprintf("WINDOWS_ENDPOINT=%s", opts.WindowsEndpoint))

// Write certs to container cert dir
if len(opts.ServerCert) > 0 {
env = append(env, `DOCKER_TLSCERT=\/var\/run\/pwd\/certs\/cert.pem`)
Expand Down
212 changes: 206 additions & 6 deletions provisioner/windows.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,151 @@
package provisioner

import (
"errors"
"fmt"
"io"
"net"
"strings"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/play-with-docker/play-with-docker/config"
"github.com/play-with-docker/play-with-docker/docker"
"github.com/play-with-docker/play-with-docker/pwd/types"
"github.com/play-with-docker/play-with-docker/router"
"github.com/play-with-docker/play-with-docker/storage"
)

var asgService *autoscaling.AutoScaling
var ec2Service *ec2.EC2

func init() {
// Create a session to share configuration, and load external configuration.
sess := session.Must(session.NewSession())
//
// // Create the service's client with the session.
asgService = autoscaling.New(sess)
ec2Service = ec2.New(sess)
}

type windows struct {
factory docker.FactoryApi
storage storage.StorageApi
}

type instanceInfo struct {
publicIP string
privateIP string
id string
}

func NewWindows(f docker.FactoryApi) *windows {
return &windows{factory: f}
func NewWindowsASG(f docker.FactoryApi, st storage.StorageApi) *windows {
return &windows{factory: f, storage: st}
}

func (d *windows) InstanceNew(session *types.Session, conf types.InstanceConfig) (*types.Instance, error) {
return nil, fmt.Errorf("Not implemented")

conf.ImageName = config.GetSSHImage()

winfo, err := d.getWindowsInstanceInfo(session.Id)
if err != nil {
return nil, err
}

if conf.Hostname == "" {
var nodeName string
for i := 1; ; i++ {
nodeName = fmt.Sprintf("node%d", i)
exists := checkHostnameExists(session, nodeName)
if !exists {
break
}
}
conf.Hostname = nodeName
}
containerName := fmt.Sprintf("%s_%s", session.Id[:8], conf.Hostname)
opts := docker.CreateContainerOpts{
Image: conf.ImageName,
WindowsEndpoint: winfo.publicIP,
SessionId: session.Id,
PwdIpAddress: session.PwdIpAddress,
ContainerName: containerName,
Hostname: conf.Hostname,
ServerCert: conf.ServerCert,
ServerKey: conf.ServerKey,
CACert: conf.CACert,
Privileged: false,
HostFQDN: conf.Host,
}

dockerClient, err := d.factory.GetForSession(session.Id)
if err != nil {
return nil, err
}
ip, err := dockerClient.CreateContainer(opts)
if err != nil {
return nil, err
}

instance := &types.Instance{}
instance.Image = opts.Image
instance.IP = winfo.privateIP
instance.SessionId = session.Id
instance.Name = containerName
instance.WindowsId = winfo.id
instance.Hostname = conf.Hostname
instance.Cert = conf.Cert
instance.Key = conf.Key
instance.Type = conf.Type
instance.ServerCert = conf.ServerCert
instance.ServerKey = conf.ServerKey
instance.CACert = conf.CACert
instance.Session = session
instance.ProxyHost = router.EncodeHost(session.Id, ip, router.HostOpts{})
instance.SessionHost = session.Host
// For now this condition holds through. In the future we might need a more complex logic.
instance.IsDockerHost = opts.Privileged

return instance, nil

}

func (d *windows) InstanceDelete(session *types.Session, instance *types.Instance) error {
return fmt.Errorf("Not implemented")
dockerClient, err := d.factory.GetForSession(session.Id)
if err != nil {
return err
}
err = dockerClient.DeleteContainer(instance.Name)
if err != nil && !strings.Contains(err.Error(), "No such container") {
return err
}

err = d.storage.InstanceDeleteWindows(session.Id, instance.WindowsId)
if err != nil {
return err
}

// TODO trigger deletion in AWS

return nil
}

func (d *windows) InstanceResizeTerminal(instance *types.Instance, cols, rows uint) error {
return fmt.Errorf("Not implemented")
dockerClient, err := d.factory.GetForSession(instance.SessionId)
if err != nil {
return err
}
return dockerClient.ContainerResize(instance.Name, rows, cols)
}

func (d *windows) InstanceGetTerminal(instance *types.Instance) (net.Conn, error) {
return nil, fmt.Errorf("Not implemented")
dockerClient, err := d.factory.GetForSession(instance.SessionId)
if err != nil {
return nil, err
}
return dockerClient.CreateAttachConnection(instance.Name)
}

func (d *windows) InstanceUploadFromUrl(instance *types.Instance, fileName, dest, url string) error {
Expand All @@ -40,3 +155,88 @@ func (d *windows) InstanceUploadFromUrl(instance *types.Instance, fileName, dest
func (d *windows) InstanceUploadFromReader(instance *types.Instance, fileName, dest string, reader io.Reader) error {
return fmt.Errorf("Not implemented")
}

func (d *windows) getWindowsInstanceInfo(sessionId string) (*instanceInfo, error) {

input := &autoscaling.DescribeAutoScalingGroupsInput{
AutoScalingGroupNames: []*string{aws.String("pwd-windows")},
}
out, err := asgService.DescribeAutoScalingGroups(input)

if err != nil {
return nil, err
}

// there should always be one asg
instances := out.AutoScalingGroups[0].Instances
availInstances := make([]string, len(instances))

for i, inst := range instances {
availInstances[i] = *inst.InstanceId
}

assignedInstances, err := d.storage.InstanceGetAllWindows()
assignedInstancesIds := []string{}
for _, ai := range assignedInstances {
assignedInstancesIds = append(assignedInstancesIds, ai.ID)
}

if err != nil {
return nil, err
}

avInstanceId := d.pickFreeInstance(sessionId, availInstances, assignedInstancesIds)

if len(avInstanceId) == 0 {
return nil, errors.New("No Windows instance available")
}

iout, err := ec2Service.DescribeInstances(&ec2.DescribeInstancesInput{
InstanceIds: []*string{aws.String(avInstanceId)},
})
if err != nil {
// TODO retry x times and free the instance that was picked?
return nil, err
}

instance := iout.Reservations[0].Instances[0]

instanceInfo := &instanceInfo{
publicIP: *instance.PublicIpAddress,
privateIP: *instance.PrivateIpAddress,
id: avInstanceId,
}

//TODO check for free instance, ASG capacity and return

return instanceInfo, nil
}

// select free instance and lock it into db.
// additionally check if ASG needs to be resized
func (d *windows) pickFreeInstance(sessionId string, availInstances, assignedInstances []string) string {

for _, av := range availInstances {
found := false
for _, as := range assignedInstances {
if av == as {
found = true
break
}

}

if !found {
fmt.Println("ABOUT TO PERSIST", av)
err := d.storage.InstanceCreateWindows(&types.WindowsInstance{SessionId: sessionId, ID: av})
if err != nil {
// TODO either storage error or instance is already assigned (race condition)
}
return av
}
}

// all availalbe instances are assigned
return ""

}
2 changes: 1 addition & 1 deletion pwd/pwd.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ type PWDApi interface {
}

func NewPWD(f docker.FactoryApi, e event.EventApi, s storage.StorageApi) *pwd {
return &pwd{dockerFactory: f, event: e, storage: s, generator: xidGenerator{}, windowsProvisioner: provisioner.NewWindows(f), dindProvisioner: provisioner.NewDinD(f)}
return &pwd{dockerFactory: f, event: e, storage: s, generator: xidGenerator{}, windowsProvisioner: provisioner.NewWindowsASG(f, s), dindProvisioner: provisioner.NewDinD(f)}
}

func (p *pwd) getProvisioner(t string) (provisioner.ProvisionerApi, error) {
Expand Down
6 changes: 6 additions & 0 deletions pwd/types/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ type Instance struct {
Type string `json:"type" bson:"type"`
Session *Session `json:"-" bson:"-"`
ctx context.Context `json:"-" bson:"-"`
WindowsId string `json:"-" bson:"windows_id"`
}

type WindowsInstance struct {
ID string `bson:"id"`
SessionId string `bson:"session_id"`
}

type InstanceConfig struct {
Expand Down
25 changes: 13 additions & 12 deletions pwd/types/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ import (
)

type Session struct {
Id string `json:"id"`
Instances map[string]*Instance `json:"instances" bson:"-"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt time.Time `json:"expires_at"`
PwdIpAddress string `json:"pwd_ip_address"`
Ready bool `json:"ready"`
Stack string `json:"stack"`
StackName string `json:"stack_name"`
ImageName string `json:"image_name"`
Host string `json:"host"`
Clients []*Client `json:"-" bson:"-"`
rw sync.Mutex `json:"-"`
Id string `json:"id"`
Instances map[string]*Instance `json:"instances" bson:"-"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt time.Time `json:"expires_at"`
PwdIpAddress string `json:"pwd_ip_address"`
Ready bool `json:"ready"`
Stack string `json:"stack"`
StackName string `json:"stack_name"`
ImageName string `json:"image_name"`
Host string `json:"host"`
Clients []*Client `json:"-" bson:"-"`
WindowsAssigned []*WindowsInstance `json:"-" bson:"-"`
rw sync.Mutex `json:"-"`
}

func (s *Session) Lock() {
Expand Down
Loading

0 comments on commit ed7cefc

Please sign in to comment.