2018-02-12 17:29:03 +01:00
|
|
|
package database
|
|
|
|
|
2018-02-13 17:11:22 +01:00
|
|
|
import (
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/apex/log"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
2018-02-12 17:29:03 +01:00
|
|
|
|
2018-03-19 16:23:30 +01:00
|
|
|
// UpdateOne will run the specified update query and check that it only affected one row
|
|
|
|
func UpdateOne(db *sqlx.DB, query string, arg interface{}) error {
|
|
|
|
res, err := db.NamedExec(query, arg)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "updating table")
|
|
|
|
}
|
|
|
|
count, err := res.RowsAffected()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "updating table")
|
|
|
|
}
|
|
|
|
if count != 1 {
|
|
|
|
return errors.New("inconsistent update count")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-02-12 17:29:03 +01:00
|
|
|
// Measurement model
|
|
|
|
type Measurement struct {
|
2018-02-13 17:11:22 +01:00
|
|
|
ID int64 `db:"id"`
|
2018-02-12 17:29:03 +01:00
|
|
|
Name string `db:"name"`
|
2018-02-13 17:11:22 +01:00
|
|
|
StartTime time.Time `db:"start_time"`
|
2018-03-19 16:23:30 +01:00
|
|
|
Runtime float64 `db:"runtime"`
|
2018-02-12 17:29:03 +01:00
|
|
|
Summary string `db:"summary"` // XXX this should be JSON
|
2018-03-19 16:23:30 +01:00
|
|
|
ASN string `db:"asn"`
|
2018-02-12 17:29:03 +01:00
|
|
|
IP string `db:"ip"`
|
|
|
|
CountryCode string `db:"country"`
|
|
|
|
State string `db:"state"`
|
|
|
|
Failure string `db:"failure"`
|
2018-03-19 16:23:30 +01:00
|
|
|
UploadFailure string `db:"upload_failure"`
|
|
|
|
Uploaded bool `db:"uploaded"`
|
2018-02-13 17:11:22 +01:00
|
|
|
ReportFilePath string `db:"report_file"`
|
|
|
|
ReportID string `db:"report_id"`
|
2018-02-12 17:29:03 +01:00
|
|
|
Input string `db:"input"`
|
2018-03-19 16:23:30 +01:00
|
|
|
ResultID int64 `db:"result_id"`
|
2018-02-13 17:11:22 +01:00
|
|
|
}
|
|
|
|
|
2018-03-08 13:46:21 +01:00
|
|
|
// SetGeoIPInfo for the Measurement
|
|
|
|
func (m *Measurement) SetGeoIPInfo() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-03-19 16:23:30 +01:00
|
|
|
// Failed writes the error string to the measurement
|
|
|
|
func (m *Measurement) Failed(db *sqlx.DB, failure string) error {
|
|
|
|
m.Failure = failure
|
|
|
|
|
|
|
|
err := UpdateOne(db, `UPDATE measurements
|
|
|
|
SET failure = :failure, state = :state
|
|
|
|
WHERE id = :id`, m)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "updating measurement")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Done marks the measurement as completed
|
|
|
|
func (m *Measurement) Done(db *sqlx.DB) error {
|
|
|
|
m.State = "done"
|
|
|
|
|
|
|
|
err := UpdateOne(db, `UPDATE measurements
|
|
|
|
SET state = :state
|
|
|
|
WHERE id = :id`, m)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "updating measurement")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UploadFailed writes the error string for the upload failure to the measurement
|
|
|
|
func (m *Measurement) UploadFailed(db *sqlx.DB, failure string) error {
|
|
|
|
m.UploadFailure = failure
|
|
|
|
m.Uploaded = false
|
|
|
|
|
|
|
|
err := UpdateOne(db, `UPDATE measurements
|
|
|
|
SET upload_failure = :upload_failure
|
|
|
|
WHERE id = :id`, m)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "updating measurement")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UploadSucceeded writes the error string for the upload failure to the measurement
|
|
|
|
func (m *Measurement) UploadSucceeded(db *sqlx.DB) error {
|
|
|
|
m.Uploaded = true
|
|
|
|
|
|
|
|
err := UpdateOne(db, `UPDATE measurements
|
|
|
|
SET uploaded = :uploaded
|
|
|
|
WHERE id = :id`, m)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "updating measurement")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteSummary writes the summary to the measurement
|
|
|
|
func (m *Measurement) WriteSummary(db *sqlx.DB, summary string) error {
|
|
|
|
m.Summary = summary
|
|
|
|
|
|
|
|
err := UpdateOne(db, `UPDATE measurements
|
|
|
|
SET summary = :summary
|
|
|
|
WHERE id = :id`, m)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "updating measurement")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-02-13 17:11:22 +01:00
|
|
|
// CreateMeasurement writes the measurement to the database a returns a pointer
|
|
|
|
// to the Measurement
|
2018-03-19 16:23:30 +01:00
|
|
|
func CreateMeasurement(db *sqlx.DB, m Measurement, i string) (*Measurement, error) {
|
|
|
|
// XXX Do we want to have this be part of something else?
|
|
|
|
m.StartTime = time.Now().UTC()
|
|
|
|
m.Input = i
|
|
|
|
m.State = "active"
|
|
|
|
|
2018-02-13 17:11:22 +01:00
|
|
|
res, err := db.NamedExec(`INSERT INTO measurements
|
|
|
|
(name, start_time,
|
2018-03-19 16:23:30 +01:00
|
|
|
asn, ip, country,
|
2018-02-13 17:11:22 +01:00
|
|
|
state, failure, report_file,
|
2018-03-19 19:28:32 +01:00
|
|
|
report_id, input,
|
2018-02-13 17:11:22 +01:00
|
|
|
result_id)
|
|
|
|
VALUES (:name,:start_time,
|
2018-02-21 16:06:30 +01:00
|
|
|
:asn,:ip,:country,
|
2018-02-13 17:11:22 +01:00
|
|
|
:state,:failure,:report_file,
|
2018-02-21 16:06:30 +01:00
|
|
|
:report_id,:input,
|
2018-02-13 17:11:22 +01:00
|
|
|
:result_id)`,
|
|
|
|
m)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "creating measurement")
|
|
|
|
}
|
|
|
|
id, err := res.LastInsertId()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "creating measurement")
|
|
|
|
}
|
|
|
|
m.ID = id
|
|
|
|
return &m, nil
|
|
|
|
}
|
|
|
|
|
2018-02-12 17:29:03 +01:00
|
|
|
// Result model
|
|
|
|
type Result struct {
|
2018-02-13 17:11:22 +01:00
|
|
|
ID int64 `db:"id"`
|
|
|
|
Name string `db:"name"`
|
|
|
|
StartTime time.Time `db:"start_time"`
|
2018-03-08 13:46:21 +01:00
|
|
|
Runtime float64 `db:"runtime"` // Runtime is expressed in Microseconds
|
2018-02-12 17:29:03 +01:00
|
|
|
Summary string `db:"summary"` // XXX this should be JSON
|
|
|
|
Done bool `db:"done"`
|
2018-02-13 17:11:22 +01:00
|
|
|
DataUsageUp int64 `db:"data_usage_up"`
|
|
|
|
DataUsageDown int64 `db:"data_usage_down"`
|
2018-03-08 13:46:21 +01:00
|
|
|
|
|
|
|
started time.Time
|
2018-02-13 17:11:22 +01:00
|
|
|
}
|
|
|
|
|
2018-03-08 13:46:21 +01:00
|
|
|
// Started marks the Result as having started
|
|
|
|
func (r *Result) Started(db *sqlx.DB) error {
|
|
|
|
r.started = time.Now()
|
|
|
|
return nil
|
|
|
|
}
|
2018-03-08 11:53:04 +01:00
|
|
|
|
2018-03-08 13:46:21 +01:00
|
|
|
// Finished marks the result as done and sets the runtime
|
|
|
|
func (r *Result) Finished(db *sqlx.DB) error {
|
|
|
|
if r.Done == true || r.Runtime != 0 {
|
|
|
|
return errors.New("Result is already finished")
|
|
|
|
}
|
|
|
|
r.Runtime = float64(time.Now().Sub(r.started)) / float64(time.Microsecond)
|
|
|
|
r.Done = true
|
|
|
|
|
2018-03-19 16:23:30 +01:00
|
|
|
err := UpdateOne(db, `UPDATE results
|
2018-03-08 13:46:21 +01:00
|
|
|
SET done = true, runtime = :runtime
|
|
|
|
WHERE id = :id`, r)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "updating result")
|
|
|
|
}
|
2018-02-13 17:11:22 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateResult writes the Result to the database a returns a pointer
|
|
|
|
// to the Result
|
|
|
|
func CreateResult(db *sqlx.DB, r Result) (*Result, error) {
|
2018-03-08 11:53:04 +01:00
|
|
|
log.Debugf("Creating result %v", r)
|
2018-02-13 17:11:22 +01:00
|
|
|
res, err := db.NamedExec(`INSERT INTO results
|
|
|
|
(name, start_time)
|
|
|
|
VALUES (:name,:start_time)`,
|
|
|
|
r)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "creating result")
|
|
|
|
}
|
|
|
|
id, err := res.LastInsertId()
|
|
|
|
if err != nil {
|
2018-03-08 13:46:21 +01:00
|
|
|
return nil, errors.Wrap(err, "creating result")
|
2018-02-13 17:11:22 +01:00
|
|
|
}
|
|
|
|
r.ID = id
|
|
|
|
return &r, nil
|
2018-02-12 17:29:03 +01:00
|
|
|
}
|