From 42693e2a3b0a3bf856aca3fc64303e78cbdaf7de Mon Sep 17 00:00:00 2001 From: Jimmy Zelinskie Date: Tue, 24 Jun 2014 03:59:30 -0400 Subject: [PATCH] docs, signatures; glog for main; config.New() --- config/config.go | 58 +++++++++++++++++++++++++--------- config/mock_config.go | 26 --------------- drivers/backend/backend.go | 4 +-- drivers/backend/mock/driver.go | 2 +- drivers/tracker/mock/driver.go | 2 +- drivers/tracker/tracker.go | 4 +-- main.go | 46 +++++++++++++++------------ models/models.go | 50 +++++++++++++++++------------ server/serve_announce.go | 34 ++++++++++---------- server/serve_announce_test.go | 8 ++--- server/serve_stats_test.go | 6 +--- 11 files changed, 125 insertions(+), 115 deletions(-) delete mode 100644 config/mock_config.go diff --git a/config/config.go b/config/config.go index 5b9bdd0..eebcaa2 100644 --- a/config/config.go +++ b/config/config.go @@ -10,6 +10,8 @@ import ( "io" "os" "time" + + log "github.com/golang/glog" ) // Duration wraps a time.Duration and adds JSON marshalling. @@ -30,8 +32,9 @@ func (d *Duration) UnmarshalJSON(b []byte) error { return err } -// DataStore represents the configuration used to connect to a data store. -type DataStore struct { +// DriverConfig is the configuration used to connect to a tracker.Driver or +// a backend.Driver. +type DriverConfig struct { Driver string `json:"driver"` Network string `json:"network` Host string `json:"host"` @@ -46,41 +49,66 @@ type DataStore struct { IdleTimeout *Duration `json:"idle_timeout,omitempty"` } -// Config represents a configuration for a server.Server. +// Config is a configuration for a Server. type Config struct { - Addr string `json:"addr"` - Tracker DataStore `json:"tracker"` - Backend DataStore `json:"backend"` + Addr string `json:"addr"` + Tracker DriverConfig `json:"tracker"` + Backend DriverConfig `json:"backend"` Private bool `json:"private"` Freeleech bool `json:"freeleech"` - Announce Duration `json:"announce"` - MinAnnounce Duration `json:"min_announce"` - ReadTimeout Duration `json:"read_timeout"` - DefaultNumWant int `json:"default_num_want"` + Announce Duration `json:"announce"` + MinAnnounce Duration `json:"min_announce"` + ReadTimeout Duration `json:"read_timeout"` + NumWantFallback int `json:"default_num_want"` +} + +// New returns a default configuration. +func New() *Config { + return &Config{ + Addr: ":6881", + Tracker: DriverConfig{ + Driver: "mock", + }, + Backend: DriverConfig{ + Driver: "mock", + }, + Private: true, + Freeleech: false, + Announce: Duration{30 * time.Minute}, + MinAnnounce: Duration{15 * time.Minute}, + ReadTimeout: Duration{20 % time.Second}, + NumWantFallback: 50, + } } // Open is a shortcut to open a file, read it, and generate a Config. -// It supports relative and absolute paths. +// It supports relative and absolute paths. Given "", it returns the result of +// New. func Open(path string) (*Config, error) { + if path == "" { + log.Info("chihaya: using default configuration") + return New(), nil + } + f, err := os.Open(os.ExpandEnv(path)) if err != nil { return nil, err } defer f.Close() - conf, err := decode(f) + conf, err := Decode(f) if err != nil { return nil, err } return conf, nil } -// decode transforms Reader populated with JSON into a *Config. -func decode(raw io.Reader) (*Config, error) { +// Decode attempts to decode a JSON encoded reader into a *Config. +func Decode(r io.Reader) (*Config, error) { conf := &Config{} - err := json.NewDecoder(raw).Decode(conf) + err := json.NewDecoder(r).Decode(conf) if err != nil { return nil, err } diff --git a/config/mock_config.go b/config/mock_config.go deleted file mode 100644 index a3d2a63..0000000 --- a/config/mock_config.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2013 The Chihaya Authors. All rights reserved. -// Use of this source code is governed by the BSD 2-Clause license, -// which can be found in the LICENSE file. - -package config - -import ( - "time" -) - -// MockConfig is a pre-initialized config that can be used for testing purposes. -var MockConfig = Config{ - Addr: ":34000", - Tracker: DataStore{ - Driver: "mock", - }, - Backend: DataStore{ - Driver: "mock", - }, - Private: true, - Freeleech: false, - Announce: Duration{30 * time.Minute}, - MinAnnounce: Duration{15 * time.Minute}, - ReadTimeout: Duration{20 % time.Second}, - DefaultNumWant: 50, -} diff --git a/drivers/backend/backend.go b/drivers/backend/backend.go index 06b6f11..4c0f17e 100644 --- a/drivers/backend/backend.go +++ b/drivers/backend/backend.go @@ -19,7 +19,7 @@ var drivers = make(map[string]Driver) // Driver represents an interface to a long-running connection with a // consistent data store. type Driver interface { - New(*config.DataStore) Conn + New(*config.DriverConfig) Conn } // Register makes a database driver available by the provided name. @@ -36,7 +36,7 @@ func Register(name string, driver Driver) { } // Open creates a connection specified by a models configuration. -func Open(conf *config.DataStore) (Conn, error) { +func Open(conf *config.DriverConfig) (Conn, error) { driver, ok := drivers[conf.Driver] if !ok { return nil, fmt.Errorf( diff --git a/drivers/backend/mock/driver.go b/drivers/backend/mock/driver.go index 1abd909..20b7fca 100644 --- a/drivers/backend/mock/driver.go +++ b/drivers/backend/mock/driver.go @@ -24,7 +24,7 @@ type Mock struct { deltaHistoryM sync.RWMutex } -func (d *driver) New(conf *config.DataStore) backend.Conn { +func (d *driver) New(conf *config.DriverConfig) backend.Conn { return &Mock{} } diff --git a/drivers/tracker/mock/driver.go b/drivers/tracker/mock/driver.go index 3bd1a29..36d3908 100644 --- a/drivers/tracker/mock/driver.go +++ b/drivers/tracker/mock/driver.go @@ -15,7 +15,7 @@ import ( type driver struct{} -func (d *driver) New(conf *config.DataStore) tracker.Pool { +func (d *driver) New(conf *config.DriverConfig) tracker.Pool { return &Pool{ users: make(map[string]*models.User), torrents: make(map[string]*models.Torrent), diff --git a/drivers/tracker/tracker.go b/drivers/tracker/tracker.go index 01fed03..2d85d0d 100644 --- a/drivers/tracker/tracker.go +++ b/drivers/tracker/tracker.go @@ -30,7 +30,7 @@ var ( // Driver represents an interface to pool of connections to models used for // the tracker. type Driver interface { - New(*config.DataStore) Pool + New(*config.DriverConfig) Pool } // Register makes a database driver available by the provided name. @@ -47,7 +47,7 @@ func Register(name string, driver Driver) { } // Open creates a pool of data store connections specified by a models configuration. -func Open(conf *config.DataStore) (Pool, error) { +func Open(conf *config.DriverConfig) (Pool, error) { driver, ok := drivers[conf.Driver] if !ok { return nil, fmt.Errorf( diff --git a/main.go b/main.go index 6128bef..5e6a1a5 100644 --- a/main.go +++ b/main.go @@ -6,17 +6,17 @@ package main import ( "flag" - "log" "os" "os/signal" "runtime" "runtime/pprof" - "github.com/chihaya/chihaya/config" - "github.com/chihaya/chihaya/server" + log "github.com/golang/glog" + "github.com/chihaya/chihaya/config" _ "github.com/chihaya/chihaya/drivers/backend/mock" _ "github.com/chihaya/chihaya/drivers/tracker/mock" + "github.com/chihaya/chihaya/server" ) var ( @@ -25,8 +25,8 @@ var ( ) func init() { - flag.BoolVar(&profile, "profile", false, "Generate profiling data for pprof into chihaya.cpu") - flag.StringVar(&configPath, "config", "", "The location of a valid configuration file.") + flag.BoolVar(&profile, "profile", false, "Generate profiling data for pprof into ./chihaya.cpu") + flag.StringVar(&configPath, "config", "", "Provide the filesystem path of a valid configuration file.") } func main() { @@ -35,54 +35,58 @@ func main() { // Enable the profile if flagged. if profile { - log.Println("running with profiling enabled") f, err := os.Create("chihaya.cpu") if err != nil { - log.Fatalf("failed to create profile file: %s\n", err) + log.Fatalf("chihaya: failed to create profile file: %s\n", err) } defer f.Close() + pprof.StartCPUProfile(f) + log.Info("chihaya: started profiling") } // Load the config file. - if configPath == "" { - log.Fatalf("must specify a configuration file") - } conf, err := config.Open(configPath) if err != nil { - log.Fatalf("failed to parse configuration file: %s\n", err) + log.Fatalf("chihaya: failed to parse configuration file: %s\n", err) } - log.Println("succesfully loaded config") + log.Infoln("chihaya: succesfully loaded config") // Create a new server. s, err := server.New(conf) if err != nil { - log.Fatalf("failed to create server: %s\n", err) + log.Fatalf("chihaya: failed to create server: %s\n", err) } // Spawn a goroutine to handle interrupts and safely shut down. go func() { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - <-c + interrupts := make(chan os.Signal, 1) + signal.Notify(interrupts, os.Interrupt) + + <-interrupts + log.Info("chihaya: caught interrupt, shutting down...") if profile { pprof.StopCPUProfile() + log.Info("chihaya: stopped profiling") } - log.Println("caught interrupt, shutting down.") err := s.Stop() if err != nil { - panic("failed to shutdown cleanly") + log.Fatalf("chihaya: failed to shutdown cleanly: %s", err) } - log.Println("shutdown successfully") - <-c + + log.Info("chihaya: shutdown cleanly") + + <-interrupts + + log.Flush() os.Exit(0) }() // Start the server listening and handling requests. err = s.ListenAndServe() if err != nil { - log.Fatalf("failed to start server: %s\n", err) + log.Fatalf("chihaya: failed to start server: %s\n", err) } } diff --git a/models/models.go b/models/models.go index 5b70931..840f91e 100644 --- a/models/models.go +++ b/models/models.go @@ -18,12 +18,12 @@ import ( ) var ( - // ErrMalformedRequest is returned when a request does no have the required - // parameters. + // ErrMalformedRequest is returned when an http.Request does no have the + // required parameters to create a model. ErrMalformedRequest = errors.New("malformed request") ) -// Peer is the internal representation of a participant in a swarm. +// Peer is a participant in a swarm. type Peer struct { ID string `json:"id"` UserID uint64 `json:"user_id"` @@ -38,13 +38,13 @@ type Peer struct { LastAnnounce int64 `json:"last_announce"` } -// Key is a helper that returns the proper format for keys used for maps -// of peers (i.e. torrent.Seeders & torrent.Leechers). +// Key returns the unique key used to look-up a peer in a swarm (i.e +// Torrent.Seeders & Torrent.Leechers). func (p Peer) Key() string { return p.ID + ":" + strconv.FormatUint(p.UserID, 36) } -// Torrent is the internal representation of a swarm for a given torrent file. +// Torrent is a swarm for a given torrent file. type Torrent struct { ID uint64 `json:"id"` Infohash string `json:"infohash"` @@ -59,20 +59,20 @@ type Torrent struct { LastAction int64 `json:"last_action"` } -// InSeederPool returns true if a peer is within a torrent's pool of seeders. +// InSeederPool returns true if a peer is within a Torrent's pool of seeders. func (t *Torrent) InSeederPool(p *Peer) bool { _, exists := t.Seeders[p.Key()] return exists } -// InLeecherPool returns true if a peer is within a torrent's pool of leechers. +// InLeecherPool returns true if a peer is within a Torrent's pool of leechers. func (t *Torrent) InLeecherPool(p *Peer) bool { _, exists := t.Leechers[p.Key()] return exists } -// NewPeer creates a new peer using the information provided by an announce. -func NewPeer(t *Torrent, u *User, a *Announce) *Peer { +// NewPeer returns the Peer representation of an Announce. +func NewPeer(a *Announce, u *User, t *Torrent) *Peer { return &Peer{ ID: a.PeerID, UserID: u.ID, @@ -86,7 +86,7 @@ func NewPeer(t *Torrent, u *User, a *Announce) *Peer { } } -// User is the internal representation of registered user for private trackers. +// User is a registered user for private trackers. type User struct { ID uint64 `json:"id"` Passkey string `json:"passkey"` @@ -96,7 +96,7 @@ type User struct { Snatches uint64 `json:"snatches"` } -// Announce represents all of the data from an announce request. +// Announce is an Announce by a Peer. type Announce struct { Config *config.Config `json:"config"` Request *http.Request `json:"request"` @@ -127,7 +127,7 @@ func NewAnnounce(r *http.Request, conf *config.Config) (*Announce, error) { infohash, _ := q.Params["info_hash"] ip, _ := q.RequestedIP(r) left, leftErr := q.Uint64("left") - numWant := q.RequestedPeerCount(conf.DefaultNumWant) + numWant := q.RequestedPeerCount(conf.NumWantFallback) dir, _ := path.Split(r.URL.Path) peerID, _ := q.Params["peer_id"] port, portErr := q.Uint64("port") @@ -161,7 +161,8 @@ func NewAnnounce(r *http.Request, conf *config.Config) (*Announce, error) { }, nil } -// ClientID returns the part of a PeerID that identifies the client software. +// ClientID returns the part of a PeerID that identifies a Peer's client +// software. func (a Announce) ClientID() (clientID string) { length := len(a.PeerID) if length >= 6 { @@ -177,8 +178,8 @@ func (a Announce) ClientID() (clientID string) { return } -// AnnounceDelta contains a difference in statistics for a peer. -// It is used for communicating changes to be recorded by the driver. +// AnnounceDelta contains the changes to a Peer's state. These changes are +// recorded by the backend driver. type AnnounceDelta struct { Peer *Peer Torrent *Torrent @@ -196,10 +197,17 @@ type AnnounceDelta struct { Downloaded uint64 } -// NewAnnounceDelta does stuff -func NewAnnounceDelta(p *Peer, u *User, a *Announce, t *Torrent, created, snatched bool) *AnnounceDelta { - rawDeltaUp := p.Uploaded - a.Uploaded - rawDeltaDown := p.Downloaded - a.Downloaded +// NewAnnounceDelta calculates a Peer's download and upload deltas between +// Announces and generates an AnnounceDelta. +func NewAnnounceDelta(a *Announce, p *Peer, u *User, t *Torrent, created, snatched bool) *AnnounceDelta { + var ( + rawDeltaUp = p.Uploaded - a.Uploaded + rawDeltaDown uint64 + ) + + if !a.Config.Freeleech { + rawDeltaDown = p.Downloaded - a.Downloaded + } // Restarting a torrent may cause a delta to be negative. if rawDeltaUp < 0 { @@ -222,7 +230,7 @@ func NewAnnounceDelta(p *Peer, u *User, a *Announce, t *Torrent, created, snatch } } -// Scrape represents all of the data from an scrape request. +// Scrape is a Scrape by a Peer. type Scrape struct { Config *config.Config `json:"config"` Request *http.Request `json:"request"` diff --git a/server/serve_announce.go b/server/serve_announce.go index 1e987e6..27f819a 100644 --- a/server/serve_announce.go +++ b/server/serve_announce.go @@ -51,15 +51,15 @@ func (s Server) serveAnnounce(w http.ResponseWriter, r *http.Request) { return } - peer := models.NewPeer(torrent, user, announce) + peer := models.NewPeer(announce, user, torrent) - created, err := updateTorrent(peer, torrent, conn, announce) + created, err := updateTorrent(conn, announce, peer, torrent) if err != nil { fail(err, w, r) return } - snatched, err := handleEvent(announce, user, torrent, peer, conn) + snatched, err := handleEvent(conn, announce, peer, user, torrent) if err != nil { fail(err, w, r) return @@ -67,15 +67,15 @@ func (s Server) serveAnnounce(w http.ResponseWriter, r *http.Request) { writeAnnounceResponse(w, announce, user, torrent) - delta := models.NewAnnounceDelta(peer, user, announce, torrent, created, snatched) + delta := models.NewAnnounceDelta(announce, peer, user, torrent, created, snatched) s.backendConn.RecordAnnounce(delta) - log.V(3).Infof("chihaya: handled announce from %s", announce.IP) + log.Infof("chihaya: handled announce from %s", announce.IP) } -func updateTorrent(p *models.Peer, t *models.Torrent, conn tracker.Conn, a *models.Announce) (created bool, err error) { +func updateTorrent(c tracker.Conn, a *models.Announce, p *models.Peer, t *models.Torrent) (created bool, err error) { if !t.Active && a.Left == 0 { - err = conn.MarkActive(t) + err = c.MarkActive(t) if err != nil { return } @@ -83,25 +83,25 @@ func updateTorrent(p *models.Peer, t *models.Torrent, conn tracker.Conn, a *mode switch { case t.InSeederPool(p): - err = conn.SetSeeder(t, p) + err = c.SetSeeder(t, p) if err != nil { return } case t.InLeecherPool(p): - err = conn.SetLeecher(t, p) + err = c.SetLeecher(t, p) if err != nil { return } default: if a.Left == 0 { - err = conn.AddSeeder(t, p) + err = c.AddSeeder(t, p) if err != nil { return } } else { - err = conn.AddLeecher(t, p) + err = c.AddLeecher(t, p) if err != nil { return } @@ -112,31 +112,31 @@ func updateTorrent(p *models.Peer, t *models.Torrent, conn tracker.Conn, a *mode return } -func handleEvent(a *models.Announce, u *models.User, t *models.Torrent, p *models.Peer, conn tracker.Conn) (snatched bool, err error) { +func handleEvent(c tracker.Conn, a *models.Announce, p *models.Peer, u *models.User, t *models.Torrent) (snatched bool, err error) { switch { case a.Event == "stopped" || a.Event == "paused": if t.InSeederPool(p) { - err = conn.RemoveSeeder(t, p) + err = c.RemoveSeeder(t, p) if err != nil { return } } if t.InLeecherPool(p) { - err = conn.RemoveLeecher(t, p) + err = c.RemoveLeecher(t, p) if err != nil { return } } case a.Event == "completed": - err = conn.IncrementSnatches(t) + err = c.IncrementSnatches(t) if err != nil { return } snatched = true if t.InLeecherPool(p) { - err = tracker.LeecherFinished(conn, t, p) + err = tracker.LeecherFinished(c, t, p) if err != nil { return } @@ -144,7 +144,7 @@ func handleEvent(a *models.Announce, u *models.User, t *models.Torrent, p *model case t.InLeecherPool(p) && a.Left == 0: // A leecher completed but the event was never received - err = tracker.LeecherFinished(conn, t, p) + err = tracker.LeecherFinished(c, t, p) if err != nil { return } diff --git a/server/serve_announce_test.go b/server/serve_announce_test.go index 6805021..b28c25c 100644 --- a/server/serve_announce_test.go +++ b/server/serve_announce_test.go @@ -9,16 +9,16 @@ import ( "net/http/httptest" "testing" + "github.com/chihaya/chihaya/config" "github.com/chihaya/chihaya/drivers/backend" - "github.com/chihaya/chihaya/drivers/tracker" - "github.com/chihaya/chihaya/models" - _ "github.com/chihaya/chihaya/drivers/backend/mock" + "github.com/chihaya/chihaya/drivers/tracker" _ "github.com/chihaya/chihaya/drivers/tracker/mock" + "github.com/chihaya/chihaya/models" ) func TestAnnounce(t *testing.T) { - s, err := newTestServer() + s, err := New(config.New()) if err != nil { t.Error(err) } diff --git a/server/serve_stats_test.go b/server/serve_stats_test.go index 8609ced..b6db80c 100644 --- a/server/serve_stats_test.go +++ b/server/serve_stats_test.go @@ -14,12 +14,8 @@ import ( _ "github.com/chihaya/chihaya/drivers/tracker/mock" ) -func newTestServer() (*Server, error) { - return New(&config.MockConfig) -} - func TestStats(t *testing.T) { - s, err := newTestServer() + s, err := New(config.New()) if err != nil { t.Error(err) }