1
// Copyright 2012 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
4
package v5 // import "gopkg.in/juju/charmstore.v5-unstable/internal/v5"
14
"gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
16
"gopkg.in/juju/charmstore.v5-unstable/internal/charmstore"
17
"gopkg.in/juju/charmstore.v5-unstable/internal/mongodoc"
20
const dateFormat = "2006-01-02"
22
// parseDateRange parses a date range as specified in an http
23
// request. The returned times will be zero if not specified.
24
func parseDateRange(form url.Values) (start, stop time.Time, err error) {
25
if v := form.Get("start"); v != "" {
27
start, err = time.Parse(dateFormat, v)
29
return time.Time{}, time.Time{}, badRequestf(err, "invalid 'start' value %q", v)
32
if v := form.Get("stop"); v != "" {
34
stop, err = time.Parse(dateFormat, v)
36
return time.Time{}, time.Time{}, badRequestf(err, "invalid 'stop' value %q", v)
38
// Cover all timestamps within the stop day.
39
stop = stop.Add(24*time.Hour - 1*time.Second)
44
// GET stats/counter/key[:key]...?[by=unit]&start=date][&stop=date][&list=1]
45
// https://github.com/juju/charmstore/blob/v4/docs/API.md#get-statscounter
46
func (h *ReqHandler) serveStatsCounter(_ http.Header, r *http.Request) (interface{}, error) {
47
base := strings.TrimPrefix(r.URL.Path, "/")
48
if strings.Index(base, "/") > 0 {
49
return nil, errgo.WithCausef(nil, params.ErrNotFound, "invalid key")
52
return nil, params.ErrForbidden
54
var by charmstore.CounterRequestBy
55
switch v := r.Form.Get("by"); v {
61
by = charmstore.ByWeek
63
return nil, badRequestf(nil, "invalid 'by' value %q", v)
65
req := charmstore.CounterRequest{
66
Key: strings.Split(base, ":"),
67
List: r.Form.Get("list") == "1",
71
req.Start, req.Stop, err = parseDateRange(r.Form)
73
return nil, errgo.Mask(err, errgo.Is(params.ErrBadRequest))
75
if req.Key[len(req.Key)-1] == "*" {
77
req.Key = req.Key[:len(req.Key)-1]
78
if len(req.Key) == 0 {
79
return nil, errgo.WithCausef(nil, params.ErrForbidden, "unknown key")
82
entries, err := h.Store.Counters(&req)
84
return nil, errgo.Notef(err, "cannot query counters")
88
var items []params.Statistic
89
for i := range entries {
93
for j := range entry.Key {
94
buf = append(buf, entry.Key[j]...)
95
buf = append(buf, ':')
98
buf = append(buf, '*')
100
buf = buf[:len(buf)-1]
103
stat := params.Statistic{
107
if !entry.Time.IsZero() {
108
stat.Date = entry.Time.Format("2006-01-02")
110
items = append(items, stat)
117
// https://github.com/juju/charmstore/blob/v4/docs/API.md#put-statsupdate
118
func (h *ReqHandler) serveStatsUpdate(w http.ResponseWriter, r *http.Request) error {
119
if _, err := h.authorize(authorizeParams{
121
acls: []mongodoc.ACL{{
122
Write: []string{"statsupdate@cs"},
124
ops: []string{OpWrite},
128
if r.Method != "PUT" {
129
return errgo.WithCausef(nil, params.ErrMethodNotAllowed, "%s not allowed", r.Method)
132
var req params.StatsUpdateRequest
133
if ct := r.Header.Get("Content-Type"); ct != "application/json" {
134
return errgo.WithCausef(nil, params.ErrBadRequest, "unexpected Content-Type %q; expected %q", ct, "application/json")
137
dec := json.NewDecoder(r.Body)
138
if err := dec.Decode(&req); err != nil {
139
return errgo.Notef(err, "cannot unmarshal body")
142
errors := make([]error, 0)
143
for _, entry := range req.Entries {
144
rid, err := h.Router.Context.ResolveURL(entry.CharmReference)
146
errors = append(errors, errgo.Notef(err, "cannot find entity for url %s", entry.CharmReference))
150
logger.Infof("Increase download stats for id: %s at time: %s", rid, entry.Timestamp)
152
if err := h.Store.IncrementDownloadCountsAtTime(rid, entry.Timestamp); err != nil {
153
errors = append(errors, err)
158
if len(errors) != 0 {
159
logger.Infof("Errors detected during /stats/update processing: %v", errors)
161
return errgo.Newf("%s (and %d more errors)", errors[0], len(errors)-1)
169
// StatsEnabled reports whether statistics should be gathered for
170
// the given HTTP request.
171
func StatsEnabled(req *http.Request) bool {
172
// It's fine to parse the form more than once, and it avoids
173
// bugs from not parsing it.
175
return req.Form.Get("stats") != "0"