github.com/Cloud-Foundations/Dominator@v0.3.4/lib/slavedriver/smallstack/impl.go (about)

     1  package smallstack
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net"
     7  	"net/http"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/Cloud-Foundations/Dominator/hypervisor/client"
    12  	"github.com/Cloud-Foundations/Dominator/lib/backoffdelay"
    13  	"github.com/Cloud-Foundations/Dominator/lib/constants"
    14  	"github.com/Cloud-Foundations/Dominator/lib/log"
    15  	"github.com/Cloud-Foundations/Dominator/lib/slavedriver"
    16  	"github.com/Cloud-Foundations/Dominator/lib/srpc"
    17  	"github.com/Cloud-Foundations/Dominator/lib/tags"
    18  	hyper_proto "github.com/Cloud-Foundations/Dominator/proto/hypervisor"
    19  )
    20  
    21  type slaveTrader struct {
    22  	closeChannel      <-chan closeRequestMessage
    23  	hypervisor        *srpc.Client
    24  	hypervisorChannel chan<- *srpc.Client
    25  	logger            log.DebugLogger
    26  	nextPing          time.Time
    27  	options           SlaveTraderOptions
    28  }
    29  
    30  var (
    31  	myVmInfo hyper_proto.VmInfo
    32  )
    33  
    34  func createVm(hyperClient *srpc.Client, request hyper_proto.CreateVmRequest,
    35  	reply *hyper_proto.CreateVmResponse, timeout time.Duration,
    36  	logger log.DebugLogger) error {
    37  	errorChannel := make(chan error, 1)
    38  	timer := time.NewTimer(timeout)
    39  	go func() {
    40  		errorChannel <- client.CreateVm(hyperClient, request, reply, logger)
    41  	}()
    42  	select {
    43  	case <-timer.C:
    44  		return fmt.Errorf("timed out creating VM")
    45  	case err := <-errorChannel:
    46  		return err
    47  	}
    48  }
    49  
    50  func destroyVm(hyperClient *srpc.Client, ipAddr net.IP, accessToken []byte,
    51  	timeout time.Duration) error {
    52  	errorChannel := make(chan error, 1)
    53  	timer := time.NewTimer(timeout)
    54  	go func() {
    55  		errorChannel <- client.DestroyVm(hyperClient, ipAddr, accessToken)
    56  	}()
    57  	select {
    58  	case <-timer.C:
    59  		return fmt.Errorf("timed out destroying VM")
    60  	case err := <-errorChannel:
    61  		return err
    62  	}
    63  }
    64  
    65  func readVmInfo(vmInfo *hyper_proto.VmInfo) error {
    66  	url := constants.MetadataUrl + constants.MetadataIdentityDoc
    67  	resp, err := http.Get(url)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	if resp.StatusCode != 200 {
    72  		return fmt.Errorf("error getting: %s: %s", url, resp.Status)
    73  	}
    74  	defer resp.Body.Close()
    75  	decoder := json.NewDecoder(resp.Body)
    76  	if err := decoder.Decode(vmInfo); err != nil {
    77  		return fmt.Errorf("error decoding identity document: %s", err)
    78  	}
    79  	return nil
    80  }
    81  
    82  func newSlaveTrader(options SlaveTraderOptions,
    83  	logger log.DebugLogger) (*SlaveTrader, error) {
    84  	if options.HypervisorAddress == "" {
    85  		options.HypervisorAddress = fmt.Sprintf("%s:%d",
    86  			constants.LinklocalAddress, constants.HypervisorPortNumber)
    87  	} else if !strings.Contains(options.HypervisorAddress, ":") {
    88  		options.HypervisorAddress += fmt.Sprintf(":%d",
    89  			constants.HypervisorPortNumber)
    90  	}
    91  	if err := readVmInfo(&myVmInfo); err != nil {
    92  		return nil, err
    93  	}
    94  	if options.CreateRequest.Hostname == "" {
    95  		options.CreateRequest.Hostname = myVmInfo.Hostname + "-slave"
    96  	}
    97  	if options.CreateRequest.ImageName == "" {
    98  		options.CreateRequest.ImageName = myVmInfo.ImageName
    99  	}
   100  	if options.CreateRequest.MemoryInMiB < 1 {
   101  		options.CreateRequest.MemoryInMiB = myVmInfo.MemoryInMiB
   102  	}
   103  	if options.CreateRequest.MilliCPUs < 1 {
   104  		options.CreateRequest.MilliCPUs = myVmInfo.MilliCPUs
   105  	}
   106  	if options.CreateRequest.MinimumFreeBytes < 1 {
   107  		options.CreateRequest.MinimumFreeBytes = 256 << 20
   108  	}
   109  	if options.CreateRequest.RoundupPower < 1 {
   110  		options.CreateRequest.RoundupPower = 26
   111  	}
   112  	if options.CreateRequest.SubnetId == "" {
   113  		options.CreateRequest.SubnetId = myVmInfo.SubnetId
   114  	}
   115  	if options.CreateRequest.Tags["Name"] == "" {
   116  		options.CreateRequest.Tags = tags.Tags{
   117  			"Name": options.CreateRequest.Hostname}
   118  	}
   119  	if options.CreateTimeout == 0 {
   120  		options.CreateTimeout = 5 * time.Minute
   121  	}
   122  	if options.DestroyTimeout == 0 {
   123  		options.DestroyTimeout = time.Minute
   124  	}
   125  	closeChannel := make(chan closeRequestMessage)
   126  	hypervisorChannel := make(chan *srpc.Client)
   127  	privateTrader := &slaveTrader{
   128  		closeChannel:      closeChannel,
   129  		hypervisorChannel: hypervisorChannel,
   130  		logger:            logger,
   131  		options:           options,
   132  	}
   133  	publicTrader := &SlaveTrader{
   134  		closeChannel:      closeChannel,
   135  		hypervisorChannel: hypervisorChannel,
   136  		logger:            logger,
   137  		options:           options,
   138  	}
   139  	go privateTrader.ultraVisor()
   140  	return publicTrader, nil
   141  }
   142  
   143  func (trader *SlaveTrader) close() error {
   144  	errorChannel := make(chan error)
   145  	trader.closeChannel <- closeRequestMessage{errorChannel: errorChannel}
   146  	close(trader.closeChannel)
   147  	return <-errorChannel
   148  }
   149  
   150  func (trader *SlaveTrader) createSlave(
   151  	acknowledgeChannel <-chan chan<- error) (slavedriver.SlaveInfo, error) {
   152  	if hyperClient, err := trader.getHypervisor(); err != nil {
   153  		return slavedriver.SlaveInfo{}, err
   154  	} else {
   155  		var reply hyper_proto.CreateVmResponse
   156  		err := createVm(hyperClient, trader.options.CreateRequest,
   157  			&reply, trader.options.CreateTimeout, trader.logger)
   158  		if err != nil {
   159  			return slavedriver.SlaveInfo{},
   160  				fmt.Errorf("error creating VM: %s", err)
   161  		}
   162  		if reply.DhcpTimedOut {
   163  			client.DestroyVm(hyperClient, reply.IpAddress, nil)
   164  			return slavedriver.SlaveInfo{},
   165  				fmt.Errorf("DHCP timeout for: %s", reply.IpAddress)
   166  		}
   167  		if acknowledgeChannel == nil {
   168  			err := client.AcknowledgeVm(hyperClient, reply.IpAddress)
   169  			if err != nil {
   170  				client.DestroyVm(hyperClient, reply.IpAddress, nil)
   171  				return slavedriver.SlaveInfo{},
   172  					fmt.Errorf("error acknowledging VM: %s", err)
   173  			}
   174  		} else {
   175  			go func() {
   176  				errorChannel := <-acknowledgeChannel
   177  				errorChannel <- client.AcknowledgeVm(hyperClient,
   178  					reply.IpAddress)
   179  			}()
   180  		}
   181  		return slavedriver.SlaveInfo{
   182  			Identifier: reply.IpAddress.String(),
   183  			IpAddress:  reply.IpAddress,
   184  		}, nil
   185  	}
   186  }
   187  
   188  func (trader *SlaveTrader) destroySlave(identifier string) error {
   189  	ipAddr := net.ParseIP(identifier)
   190  	if len(ipAddr) < 1 {
   191  		return fmt.Errorf("error parsing: %s", identifier)
   192  	}
   193  	if ip4 := ipAddr.To4(); ip4 != nil {
   194  		ipAddr = ip4
   195  	}
   196  	timeout := trader.options.DestroyTimeout
   197  	if hyperClient, err := trader.getHypervisor(); err != nil {
   198  		return err
   199  	} else if err := destroyVm(hyperClient, ipAddr, nil, timeout); err != nil {
   200  		if !strings.Contains(err.Error(), "no VM with IP address") {
   201  			return err
   202  		}
   203  		trader.logger.Printf("error destroying VM: %s\n", err)
   204  	}
   205  	return nil
   206  }
   207  
   208  func (trader *SlaveTrader) getHypervisor() (*srpc.Client, error) {
   209  	timer := time.NewTimer(5 * time.Minute)
   210  	select {
   211  	case client := <-trader.hypervisorChannel:
   212  		if !timer.Stop() {
   213  			<-timer.C
   214  		}
   215  		return client, nil
   216  	case <-timer.C:
   217  		return nil, fmt.Errorf("timed out connecting to Hypervisor")
   218  	}
   219  }
   220  
   221  func (trader *slaveTrader) getHypervisor() *srpc.Client {
   222  	sleeper := backoffdelay.NewExponential(100*time.Millisecond, 10*time.Second,
   223  		1)
   224  	for ; ; sleeper.Sleep() {
   225  		client, err := srpc.DialHTTP("tcp", trader.options.HypervisorAddress,
   226  			time.Second*5)
   227  		if err != nil {
   228  			trader.logger.Printf("error connecting to Hypervisor: %s: %s\n",
   229  				trader.options.HypervisorAddress, err)
   230  			continue
   231  		}
   232  		return client
   233  	}
   234  }
   235  
   236  func (trader *slaveTrader) ultraVisor() {
   237  	for {
   238  		if trader.hypervisor == nil {
   239  			trader.hypervisor = trader.getHypervisor()
   240  			trader.nextPing = time.Now().Add(5 * time.Second)
   241  		}
   242  		pingTimeout := time.Until(trader.nextPing)
   243  		if pingTimeout < 0 {
   244  			pingTimeout = 0
   245  		}
   246  		pingTimer := time.NewTimer(pingTimeout)
   247  		select {
   248  		case closeMessage := <-trader.closeChannel:
   249  			closeMessage.errorChannel <- trader.hypervisor.Close()
   250  			return
   251  		case trader.hypervisorChannel <- trader.hypervisor:
   252  		case <-pingTimer.C:
   253  			if err := trader.hypervisor.Ping(); err != nil {
   254  				trader.logger.Printf(
   255  					"error pinging Hypervisor: %s, reconnecting: %s\n",
   256  					trader.options.HypervisorAddress, err)
   257  				trader.hypervisor.Close()
   258  				trader.hypervisor = nil
   259  			} else {
   260  				trader.nextPing = time.Now().Add(5 * time.Second)
   261  			}
   262  		}
   263  		pingTimer.Stop()
   264  		select {
   265  		case <-pingTimer.C:
   266  		default:
   267  		}
   268  	}
   269  }