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 }