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  }