github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/misc/dashboard/app/build/notify.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package build
     6  
     7  import (
     8  	"appengine"
     9  	"appengine/datastore"
    10  	"appengine/delay"
    11  	"appengine/mail"
    12  	"bytes"
    13  	"encoding/gob"
    14  	"fmt"
    15  	"text/template"
    16  )
    17  
    18  const (
    19  	mailFrom   = "builder@golang.org" // use this for sending any mail
    20  	failMailTo = "golang-dev@googlegroups.com"
    21  	domain     = "build.golang.org"
    22  )
    23  
    24  // failIgnore is a set of builders that we don't email about because
    25  // they're too flaky.
    26  var failIgnore = map[string]bool{
    27  	"netbsd-386-bsiegert":   true,
    28  	"netbsd-amd64-bsiegert": true,
    29  }
    30  
    31  // notifyOnFailure checks whether the supplied Commit or the subsequent
    32  // Commit (if present) breaks the build for this builder.
    33  // If either of those commits break the build an email notification is sent
    34  // from a delayed task. (We use a task because this way the mail won't be
    35  // sent if the enclosing datastore transaction fails.)
    36  //
    37  // This must be run in a datastore transaction, and the provided *Commit must
    38  // have been retrieved from the datastore within that transaction.
    39  func notifyOnFailure(c appengine.Context, com *Commit, builder string) error {
    40  	if failIgnore[builder] {
    41  		return nil
    42  	}
    43  
    44  	// TODO(adg): implement notifications for packages
    45  	if com.PackagePath != "" {
    46  		return nil
    47  	}
    48  
    49  	p := &Package{Path: com.PackagePath}
    50  	var broken *Commit
    51  	cr := com.Result(builder, "")
    52  	if cr == nil {
    53  		return fmt.Errorf("no result for %s/%s", com.Hash, builder)
    54  	}
    55  	q := datastore.NewQuery("Commit").Ancestor(p.Key(c))
    56  	if cr.OK {
    57  		// This commit is OK. Notify if next Commit is broken.
    58  		next := new(Commit)
    59  		q = q.Filter("ParentHash=", com.Hash)
    60  		if err := firstMatch(c, q, next); err != nil {
    61  			if err == datastore.ErrNoSuchEntity {
    62  				// OK at tip, no notification necessary.
    63  				return nil
    64  			}
    65  			return err
    66  		}
    67  		if nr := next.Result(builder, ""); nr != nil && !nr.OK {
    68  			c.Debugf("commit ok: %#v\nresult: %#v", com, cr)
    69  			c.Debugf("next commit broken: %#v\nnext result:%#v", next, nr)
    70  			broken = next
    71  		}
    72  	} else {
    73  		// This commit is broken. Notify if the previous Commit is OK.
    74  		prev := new(Commit)
    75  		q = q.Filter("Hash=", com.ParentHash)
    76  		if err := firstMatch(c, q, prev); err != nil {
    77  			if err == datastore.ErrNoSuchEntity {
    78  				// No previous result, let the backfill of
    79  				// this result trigger the notification.
    80  				return nil
    81  			}
    82  			return err
    83  		}
    84  		if pr := prev.Result(builder, ""); pr != nil && pr.OK {
    85  			c.Debugf("commit broken: %#v\nresult: %#v", com, cr)
    86  			c.Debugf("previous commit ok: %#v\nprevious result:%#v", prev, pr)
    87  			broken = com
    88  		}
    89  	}
    90  	var err error
    91  	if broken != nil && !broken.FailNotificationSent {
    92  		c.Infof("%s is broken commit; notifying", broken.Hash)
    93  		sendFailMailLater.Call(c, broken, builder) // add task to queue
    94  		broken.FailNotificationSent = true
    95  		_, err = datastore.Put(c, broken.Key(c), broken)
    96  	}
    97  	return err
    98  }
    99  
   100  // firstMatch executes the query q and loads the first entity into v.
   101  func firstMatch(c appengine.Context, q *datastore.Query, v interface{}) error {
   102  	t := q.Limit(1).Run(c)
   103  	_, err := t.Next(v)
   104  	if err == datastore.Done {
   105  		err = datastore.ErrNoSuchEntity
   106  	}
   107  	return err
   108  }
   109  
   110  var (
   111  	sendFailMailLater = delay.Func("sendFailMail", sendFailMail)
   112  	sendFailMailTmpl  = template.Must(
   113  		template.New("notify.txt").
   114  			Funcs(template.FuncMap(tmplFuncs)).
   115  			ParseFiles("build/notify.txt"),
   116  	)
   117  )
   118  
   119  func init() {
   120  	gob.Register(&Commit{}) // for delay
   121  }
   122  
   123  // sendFailMail sends a mail notification that the build failed on the
   124  // provided commit and builder.
   125  func sendFailMail(c appengine.Context, com *Commit, builder string) {
   126  	// TODO(adg): handle packages
   127  
   128  	// get Result
   129  	r := com.Result(builder, "")
   130  	if r == nil {
   131  		c.Errorf("finding result for %q: %+v", builder, com)
   132  		return
   133  	}
   134  
   135  	// get Log
   136  	k := datastore.NewKey(c, "Log", r.LogHash, 0, nil)
   137  	l := new(Log)
   138  	if err := datastore.Get(c, k, l); err != nil {
   139  		c.Errorf("finding Log record %v: %v", r.LogHash, err)
   140  		return
   141  	}
   142  
   143  	// prepare mail message
   144  	var body bytes.Buffer
   145  	err := sendFailMailTmpl.Execute(&body, map[string]interface{}{
   146  		"Builder": builder, "Commit": com, "Result": r, "Log": l,
   147  		"Hostname": domain,
   148  	})
   149  	if err != nil {
   150  		c.Errorf("rendering mail template: %v", err)
   151  		return
   152  	}
   153  	subject := fmt.Sprintf("%s broken by %s", builder, shortDesc(com.Desc))
   154  	msg := &mail.Message{
   155  		Sender:  mailFrom,
   156  		To:      []string{failMailTo},
   157  		ReplyTo: failMailTo,
   158  		Subject: subject,
   159  		Body:    body.String(),
   160  	}
   161  
   162  	// send mail
   163  	if err := mail.Send(c, msg); err != nil {
   164  		c.Errorf("sending mail: %v", err)
   165  	}
   166  }