2018-02-12 17:29:03 +01:00
|
|
|
package database
|
|
|
|
|
2018-02-13 17:11:22 +01:00
|
|
|
import (
|
2018-09-06 15:34:56 +02:00
|
|
|
"database/sql"
|
2018-03-20 12:38:33 +01:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2018-02-13 17:11:22 +01:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2018-09-05 18:40:37 +02:00
|
|
|
"upper.io/db.v3/lib/sqlbuilder"
|
2018-02-13 17:11:22 +01:00
|
|
|
)
|
2018-02-12 17:29:03 +01:00
|
|
|
|
2018-09-07 15:16:20 +02:00
|
|
|
// ResultNetwork is used to represent the structure made from the JOIN
|
|
|
|
// between the results and networks tables.
|
|
|
|
type ResultNetwork struct {
|
2018-09-10 16:56:32 +02:00
|
|
|
Result `db:",inline"`
|
|
|
|
ResultID int64 `db:"result_id"`
|
|
|
|
Network `db:",inline"`
|
|
|
|
NetworkID int64 `db:"network_id"`
|
2018-09-07 15:16:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// MeasurementURLNetwork is used for the JOIN between Measurement and URL
|
|
|
|
type MeasurementURLNetwork struct {
|
2018-09-11 15:40:42 +02:00
|
|
|
Measurement `db:",inline"`
|
|
|
|
MeasurementStartTime time.Time `db:"measurement_start_time"`
|
|
|
|
MeasurementIsDone bool `db:"measurement_is_done"`
|
|
|
|
MeasurementRuntime float64 `db:"measurement_runtime"`
|
|
|
|
MsmtTblID int64 `db:"msmt_tbl_id"`
|
2018-09-11 18:06:15 +02:00
|
|
|
|
|
|
|
Network `db:",inline"`
|
|
|
|
NetworkID int64 `db:"network_id"`
|
|
|
|
NetworkCountryCode string `db:"network_country_code"`
|
|
|
|
|
|
|
|
Result `db:",inline"`
|
|
|
|
ResultID int64 `db:"result_id"`
|
|
|
|
ResultRuntime float64 `db:"result_runtime"`
|
|
|
|
ResultStartTime time.Time `db:"result_start_time"`
|
|
|
|
ResultIsDone bool `db:"result_is_done"`
|
|
|
|
|
|
|
|
URL `db:",inline"`
|
|
|
|
URLCountryCode sql.NullString `db:"url_country_code"`
|
2018-09-07 15:16:20 +02:00
|
|
|
}
|
|
|
|
|
2018-09-05 18:40:37 +02:00
|
|
|
// Network represents a network tested by the user
|
|
|
|
type Network struct {
|
2018-09-10 12:41:28 +02:00
|
|
|
ID int64 `db:"id,omitempty"`
|
2018-09-05 18:40:37 +02:00
|
|
|
NetworkName string `db:"network_name"`
|
2018-09-07 14:06:08 +02:00
|
|
|
NetworkType string `db:"network_type"`
|
2018-09-05 18:40:37 +02:00
|
|
|
IP string `db:"ip"`
|
2018-09-06 16:13:04 +02:00
|
|
|
ASN uint `db:"asn"`
|
2018-09-05 18:40:37 +02:00
|
|
|
CountryCode string `db:"country_code"`
|
2018-03-19 16:23:30 +01:00
|
|
|
}
|
|
|
|
|
2018-09-06 16:13:04 +02:00
|
|
|
// URL represents URLs from the testing lists
|
2018-09-05 18:40:37 +02:00
|
|
|
type URL struct {
|
2018-09-10 12:41:28 +02:00
|
|
|
ID sql.NullInt64 `db:"id,omitempty"`
|
2018-09-07 15:16:20 +02:00
|
|
|
URL sql.NullString `db:"url"`
|
|
|
|
CategoryCode sql.NullString `db:"category_code"`
|
|
|
|
CountryCode sql.NullString `db:"country_code"`
|
2018-06-22 12:12:35 +02:00
|
|
|
}
|
|
|
|
|
2018-02-12 17:29:03 +01:00
|
|
|
// Measurement model
|
|
|
|
type Measurement struct {
|
2018-09-10 12:41:28 +02:00
|
|
|
ID int64 `db:"id,omitempty"`
|
2018-09-06 15:34:56 +02:00
|
|
|
TestName string `db:"test_name"`
|
|
|
|
StartTime time.Time `db:"start_time"`
|
2018-09-06 16:13:04 +02:00
|
|
|
Runtime float64 `db:"runtime"` // Fractional number of seconds
|
2018-09-06 15:34:56 +02:00
|
|
|
IsDone bool `db:"is_done"`
|
|
|
|
IsUploaded bool `db:"is_uploaded"`
|
2018-09-11 15:19:08 +02:00
|
|
|
IsFailed bool `db:"is_failed"`
|
2018-09-06 15:34:56 +02:00
|
|
|
FailureMsg sql.NullString `db:"failure_msg,omitempty"`
|
|
|
|
IsUploadFailed bool `db:"is_upload_failed"`
|
|
|
|
UploadFailureMsg sql.NullString `db:"upload_failure_msg,omitempty"`
|
|
|
|
IsRerun bool `db:"is_rerun"`
|
|
|
|
ReportID sql.NullString `db:"report_id,omitempty"`
|
2018-09-07 12:55:27 +02:00
|
|
|
URLID sql.NullInt64 `db:"url_id,omitempty"` // Used to reference URL
|
2018-09-06 15:34:56 +02:00
|
|
|
MeasurementID sql.NullInt64 `db:"measurement_id,omitempty"`
|
|
|
|
IsAnomaly sql.NullBool `db:"is_anomaly,omitempty"`
|
|
|
|
// FIXME we likely want to support JSON. See: https://github.com/upper/db/issues/462
|
|
|
|
TestKeys string `db:"test_keys"`
|
|
|
|
ResultID int64 `db:"result_id"`
|
|
|
|
ReportFilePath string `db:"report_file_path"`
|
2018-09-05 18:40:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Result model
|
|
|
|
type Result struct {
|
2018-09-10 12:41:28 +02:00
|
|
|
ID int64 `db:"id,omitempty"`
|
2018-09-05 18:40:37 +02:00
|
|
|
TestGroupName string `db:"test_group_name"`
|
2018-02-13 17:11:22 +01:00
|
|
|
StartTime time.Time `db:"start_time"`
|
2018-09-06 16:13:04 +02:00
|
|
|
NetworkID int64 `db:"network_id"` // Used to include a Network
|
|
|
|
Runtime float64 `db:"runtime"` // Runtime is expressed in fractional seconds
|
2018-09-05 18:40:37 +02:00
|
|
|
IsViewed bool `db:"is_viewed"`
|
|
|
|
IsDone bool `db:"is_done"`
|
|
|
|
DataUsageUp int64 `db:"data_usage_up"`
|
|
|
|
DataUsageDown int64 `db:"data_usage_down"`
|
|
|
|
MeasurementDir string `db:"measurement_dir"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finished marks the result as done and sets the runtime
|
2018-09-10 12:41:28 +02:00
|
|
|
func (r *Result) Finished(sess sqlbuilder.Database) error {
|
2018-09-05 18:40:37 +02:00
|
|
|
if r.IsDone == true || r.Runtime != 0 {
|
|
|
|
return errors.New("Result is already finished")
|
|
|
|
}
|
|
|
|
r.Runtime = time.Now().UTC().Sub(r.StartTime).Seconds()
|
|
|
|
r.IsDone = true
|
|
|
|
|
|
|
|
err := sess.Collection("results").Find("id", r.ID).Update(r)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "updating finished result")
|
|
|
|
}
|
|
|
|
return nil
|
2018-02-13 17:11:22 +01:00
|
|
|
}
|
|
|
|
|
2018-03-19 16:23:30 +01:00
|
|
|
// Failed writes the error string to the measurement
|
2018-09-05 18:40:37 +02:00
|
|
|
func (m *Measurement) Failed(sess sqlbuilder.Database, failure string) error {
|
2018-09-06 15:34:56 +02:00
|
|
|
m.FailureMsg = sql.NullString{String: failure, Valid: true}
|
2018-09-11 15:19:08 +02:00
|
|
|
m.IsFailed = true
|
2018-09-05 18:40:37 +02:00
|
|
|
err := sess.Collection("measurements").Find("id", m.ID).Update(m)
|
2018-03-19 16:23:30 +01:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "updating measurement")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Done marks the measurement as completed
|
2018-09-05 18:40:37 +02:00
|
|
|
func (m *Measurement) Done(sess sqlbuilder.Database) error {
|
2018-03-20 12:38:33 +01:00
|
|
|
runtime := time.Now().UTC().Sub(m.StartTime)
|
|
|
|
m.Runtime = runtime.Seconds()
|
2018-09-05 18:40:37 +02:00
|
|
|
m.IsDone = true
|
2018-03-19 16:23:30 +01:00
|
|
|
|
2018-09-05 18:40:37 +02:00
|
|
|
err := sess.Collection("measurements").Find("id", m.ID).Update(m)
|
2018-03-19 16:23:30 +01:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "updating measurement")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UploadFailed writes the error string for the upload failure to the measurement
|
2018-09-05 18:40:37 +02:00
|
|
|
func (m *Measurement) UploadFailed(sess sqlbuilder.Database, failure string) error {
|
2018-09-06 15:34:56 +02:00
|
|
|
m.UploadFailureMsg = sql.NullString{String: failure, Valid: true}
|
2018-09-05 18:40:37 +02:00
|
|
|
m.IsUploaded = false
|
2018-03-19 16:23:30 +01:00
|
|
|
|
2018-09-05 18:40:37 +02:00
|
|
|
err := sess.Collection("measurements").Find("id", m.ID).Update(m)
|
2018-03-19 16:23:30 +01:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "updating measurement")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UploadSucceeded writes the error string for the upload failure to the measurement
|
2018-09-05 18:40:37 +02:00
|
|
|
func (m *Measurement) UploadSucceeded(sess sqlbuilder.Database) error {
|
|
|
|
m.IsUploaded = true
|
2018-03-19 16:23:30 +01:00
|
|
|
|
2018-09-05 18:40:37 +02:00
|
|
|
err := sess.Collection("measurements").Find("id", m.ID).Update(m)
|
2018-03-19 16:23:30 +01:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "updating measurement")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-03-20 14:31:05 +01:00
|
|
|
// AddToResult adds a measurement to a result
|
2018-09-05 18:40:37 +02:00
|
|
|
func (m *Measurement) AddToResult(sess sqlbuilder.Database, result *Result) error {
|
2018-03-23 13:17:39 +01:00
|
|
|
var err error
|
|
|
|
|
2018-03-20 12:38:33 +01:00
|
|
|
m.ResultID = result.ID
|
|
|
|
finalPath := filepath.Join(result.MeasurementDir,
|
|
|
|
filepath.Base(m.ReportFilePath))
|
|
|
|
|
2018-03-23 13:17:39 +01:00
|
|
|
// If the finalPath already exists, it means it has already been moved there.
|
|
|
|
// This happens in multi input reports
|
|
|
|
if _, err = os.Stat(finalPath); os.IsNotExist(err) {
|
|
|
|
err = os.Rename(m.ReportFilePath, finalPath)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "moving report file")
|
|
|
|
}
|
2018-03-20 12:38:33 +01:00
|
|
|
}
|
|
|
|
m.ReportFilePath = finalPath
|
|
|
|
|
2018-09-05 18:40:37 +02:00
|
|
|
err = sess.Collection("measurements").Find("id", m.ID).Update(m)
|
2018-03-20 12:38:33 +01:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "updating measurement")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|