github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/service/api_spawn.go (about) 1 package service 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "net/http" 7 8 "github.com/evergreen-ci/evergreen" 9 "github.com/evergreen-ci/evergreen/alerts" 10 "github.com/evergreen-ci/evergreen/cloud/providers" 11 "github.com/evergreen-ci/evergreen/model/distro" 12 "github.com/evergreen-ci/evergreen/model/event" 13 "github.com/evergreen-ci/evergreen/model/host" 14 "github.com/evergreen-ci/evergreen/notify" 15 "github.com/evergreen-ci/evergreen/spawn" 16 "github.com/evergreen-ci/evergreen/util" 17 "github.com/gorilla/mux" 18 "github.com/mongodb/grip" 19 "github.com/pkg/errors" 20 ) 21 22 type spawnResponse struct { 23 Hosts []host.Host `json:"hosts,omitempty"` 24 HostInfo host.Host `json:"host_info,omitempty"` 25 Distros []string `json:"distros,omitempty"` 26 27 // empty if the request succeeded 28 ErrorMessage string `json:"error_message,omitempty"` 29 } 30 31 func (as *APIServer) listDistros(w http.ResponseWriter, r *http.Request) { 32 distros, err := distro.Find(distro.BySpawnAllowed()) 33 if err != nil { 34 as.LoggedError(w, r, http.StatusInternalServerError, err) 35 return 36 } 37 distroList := []string{} 38 for _, d := range distros { 39 distroList = append(distroList, d.Id) 40 } 41 as.WriteJSON(w, http.StatusOK, spawnResponse{Distros: distroList}) 42 } 43 44 func (as *APIServer) requestHost(w http.ResponseWriter, r *http.Request) { 45 user := MustHaveUser(r) 46 hostRequest := struct { 47 Distro string `json:"distro"` 48 PublicKey string `json:"public_key"` 49 UserData string `json:"userdata"` 50 }{} 51 err := util.ReadJSONInto(util.NewRequestReader(r), &hostRequest) 52 if err != nil { 53 http.Error(w, err.Error(), http.StatusBadRequest) 54 return 55 } 56 57 if hostRequest.Distro == "" { 58 http.Error(w, "distro may not be blank", http.StatusBadRequest) 59 return 60 } 61 if hostRequest.PublicKey == "" { 62 http.Error(w, "public key may not be blank", http.StatusBadRequest) 63 return 64 } 65 66 opts := spawn.Options{ 67 Distro: hostRequest.Distro, 68 UserName: user.Id, 69 PublicKey: hostRequest.PublicKey, 70 UserData: hostRequest.UserData, 71 } 72 73 spawner := spawn.New(&as.Settings) 74 err = spawner.Validate(opts) 75 if err != nil { 76 errCode := http.StatusBadRequest 77 if _, ok := err.(spawn.BadOptionsErr); !ok { 78 errCode = http.StatusInternalServerError 79 } 80 as.LoggedError(w, r, errCode, errors.Wrap(err, "Spawn request failed validation")) 81 return 82 } 83 84 err = spawner.CreateHost(opts, user) 85 if err != nil { 86 grip.Error(err) 87 mailErr := notify.TrySendNotificationToUser(opts.UserName, "Spawning failed", 88 fmt.Sprintf("For distro '%s'.\n\nEncountered with error: %+v", hostRequest.Distro, err.Error()), 89 notify.ConstructMailer(as.Settings.Notify)) 90 if mailErr != nil { 91 grip.Errorln("Failed to send notification:", mailErr) 92 } 93 return 94 } 95 96 as.WriteJSON(w, http.StatusOK, "") 97 } 98 99 func (as *APIServer) spawnHostReady(w http.ResponseWriter, r *http.Request) { 100 vars := mux.Vars(r) 101 instanceId := vars["instance_id"] 102 status := vars["status"] 103 104 // mark the host itself as provisioned 105 host, err := host.FindOne(host.ById(instanceId)) 106 if err != nil { 107 as.LoggedError(w, r, http.StatusInternalServerError, err) 108 return 109 } 110 111 if host == nil { 112 http.Error(w, "host not found", http.StatusNotFound) 113 return 114 } 115 116 if status == evergreen.HostStatusSuccess { 117 if err = host.SetRunning(); err != nil { 118 grip.Errorf("Error marking host id %s as %s: %+v", 119 instanceId, evergreen.HostStatusSuccess, err) 120 } 121 } else { 122 grip.Warning(errors.WithStack(alerts.RunHostProvisionFailTriggers(host))) 123 if err = host.SetDecommissioned(); err != nil { 124 grip.Errorf("Error marking host %s for user %s as decommissioned: %+v", 125 host.Host, host.StartedBy, err) 126 } 127 grip.Infof("Decommissioned %s for user %s because provisioning failed", 128 host.Host, host.StartedBy) 129 130 // send notification to the Evergreen team about this provisioning failure 131 subject := fmt.Sprintf("%v Spawn provisioning failure on %v", notify.ProvisionFailurePreface, host.Distro.Id) 132 message := fmt.Sprintf("Provisioning failed on %v host %v for user %v", host.Distro.Id, host.Host, host.StartedBy) 133 if err = notify.NotifyAdmins(subject, message, &as.Settings); err != nil { 134 grip.Errorln("issue sending email:", err) 135 } 136 137 // get/store setup logs 138 body := util.NewRequestReader(r) 139 defer body.Close() 140 setupLog, err := ioutil.ReadAll(body) 141 if err != nil { 142 grip.Errorln("problem reading request:", err) 143 as.LoggedError(w, r, http.StatusInternalServerError, err) 144 return 145 } 146 event.LogProvisionFailed(instanceId, string(setupLog)) 147 } 148 149 message := fmt.Sprintf(` 150 Host with id %v spawned. 151 The host's dns name is %v. 152 To ssh in: ssh -i <your private key> %v@%v`, 153 host.Id, host.Host, host.User, host.Host) 154 155 if status == evergreen.HostStatusFailed { 156 message += fmt.Sprintf("\nUnfortunately, the host's setup script did not run fully - check the setup.log " + 157 "file in the machine's home directory to see more details") 158 } 159 err = notify.TrySendNotificationToUser(host.StartedBy, "Your host is ready", message, notify.ConstructMailer(as.Settings.Notify)) 160 grip.ErrorWhenln(err != nil, "Error sending email", err) 161 162 as.WriteJSON(w, http.StatusOK, spawnResponse{HostInfo: *host}) 163 } 164 165 // returns info on the host specified 166 func (as *APIServer) hostInfo(w http.ResponseWriter, r *http.Request) { 167 vars := mux.Vars(r) 168 instanceId := vars["instance_id"] 169 170 host, err := host.FindOne(host.ById(instanceId)) 171 if err != nil { 172 as.LoggedError(w, r, http.StatusInternalServerError, err) 173 return 174 } 175 176 if host == nil { 177 http.Error(w, "not found", http.StatusNotFound) 178 return 179 } 180 181 as.WriteJSON(w, http.StatusOK, spawnResponse{HostInfo: *host}) 182 } 183 184 // returns info on all of the hosts spawned by a user 185 func (as *APIServer) hostsInfoForUser(w http.ResponseWriter, r *http.Request) { 186 vars := mux.Vars(r) 187 user := vars["user"] 188 189 hosts, err := host.Find(host.ByUserWithUnterminatedStatus(user)) 190 if err != nil { 191 as.LoggedError(w, r, http.StatusInternalServerError, err) 192 return 193 } 194 195 as.WriteJSON(w, http.StatusOK, spawnResponse{Hosts: hosts}) 196 } 197 198 func (as *APIServer) modifyHost(w http.ResponseWriter, r *http.Request) { 199 vars := mux.Vars(r) 200 instanceId := vars["instance_id"] 201 hostAction := r.FormValue("action") 202 203 host, err := host.FindOne(host.ById(instanceId)) 204 if err != nil { 205 as.LoggedError(w, r, http.StatusInternalServerError, err) 206 return 207 } 208 if host == nil { 209 http.Error(w, "not found", http.StatusNotFound) 210 return 211 } 212 213 user := GetUser(r) 214 if user == nil || user.Id != host.StartedBy { 215 message := fmt.Sprintf("Only %v is authorized to terminate this host", host.StartedBy) 216 http.Error(w, message, http.StatusUnauthorized) 217 return 218 } 219 220 switch hostAction { 221 case "terminate": 222 if host.Status == evergreen.HostTerminated { 223 message := fmt.Sprintf("Host %v is already terminated", host.Id) 224 http.Error(w, message, http.StatusBadRequest) 225 return 226 } 227 228 cloudHost, err := providers.GetCloudHost(host, &as.Settings) 229 if err != nil { 230 as.LoggedError(w, r, http.StatusInternalServerError, err) 231 return 232 } 233 if err = cloudHost.TerminateInstance(); err != nil { 234 as.LoggedError(w, r, http.StatusInternalServerError, errors.Wrap(err, "Failed to terminate spawn host")) 235 return 236 } 237 as.WriteJSON(w, http.StatusOK, spawnResponse{HostInfo: *host}) 238 default: 239 http.Error(w, fmt.Sprintf("Unrecognized action %v", hostAction), http.StatusBadRequest) 240 } 241 242 }