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 }