bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/bosun/database/migrate.go (about)

     1  package database
     2  
     3  import (
     4  	"encoding/json"
     5  	"sort"
     6  
     7  	"bosun.org/models"
     8  	"bosun.org/slog"
     9  	"github.com/garyburd/redigo/redis"
    10  )
    11  
    12  // Version 0 is the schema that was never verisoned
    13  // Version 1 migrates rendered templates from
    14  
    15  var schemaKey = "schemaVersion"
    16  
    17  type Migration struct {
    18  	UID     string
    19  	Task    func(d *dataAccess) error
    20  	Version int64
    21  }
    22  
    23  // Be sure to increment the value of `var SchemaVersion` in database.go when adding a new migration
    24  var tasks = []Migration{
    25  	{
    26  		UID:     "Migrate Rendered Templates",
    27  		Task:    migrateRenderedTemplates,
    28  		Version: 1,
    29  	},
    30  	{
    31  		UID:     "Populate Previous IncidentIds",
    32  		Task:    populatePreviousIncidents,
    33  		Version: 2,
    34  	},
    35  }
    36  
    37  type oldIncidentState struct {
    38  	*models.IncidentState
    39  	*models.RenderedTemplates
    40  }
    41  
    42  func migrateRenderedTemplates(d *dataAccess) error {
    43  	slog.Infoln("Running rendered template migration. This can take several minutes.")
    44  
    45  	// Hacky Work better?
    46  	ids, err := d.getAllIncidentIdsByKeys()
    47  	slog.Infof("migrating %v incidents", len(ids))
    48  	if err != nil {
    49  		return err
    50  	}
    51  
    52  	conn := d.Get()
    53  	defer conn.Close()
    54  
    55  	for _, id := range ids {
    56  		b, err := redis.Bytes(conn.Do("GET", incidentStateKey(id)))
    57  		if err != nil {
    58  			return slog.Wrap(err)
    59  		}
    60  		oldState := &oldIncidentState{}
    61  		if err := json.Unmarshal(b, oldState); err != nil {
    62  			slog.Wrap(err)
    63  		}
    64  
    65  		incidentStateJSON, err := json.Marshal(oldState.IncidentState)
    66  		if err != nil {
    67  			return slog.Wrap(err)
    68  		}
    69  		if _, err := conn.Do("SET", incidentStateKey(oldState.Id), incidentStateJSON); err != nil {
    70  			return slog.Wrap(err)
    71  		}
    72  
    73  		renderedTemplatesJSON, err := json.Marshal(oldState.RenderedTemplates)
    74  		if err != nil {
    75  			return slog.Wrap(err)
    76  		}
    77  		if _, err := conn.Do("SET", renderedTemplatesKey(oldState.Id), renderedTemplatesJSON); err != nil {
    78  			return slog.Wrap(err)
    79  		}
    80  
    81  	}
    82  	return nil
    83  }
    84  
    85  func populatePreviousIncidents(d *dataAccess) error {
    86  	slog.Infoln("Adding fields for previous incidents and next incident on all incidents in order to link incidents together. This is a one time operation that can take several minutes.")
    87  
    88  	ids, err := d.getAllIncidentIdsByKeys()
    89  	slog.Infof("migrating %v incidents", len(ids))
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	conn := d.Get()
    95  	defer conn.Close()
    96  
    97  	prevIdCache := make(map[models.AlertKey]*[]int64)
    98  
    99  	for _, id := range ids {
   100  		incident, err := d.State().GetIncidentState(id)
   101  		if err != nil {
   102  			return err
   103  		}
   104  		if _, ok := prevIdCache[incident.AlertKey]; !ok {
   105  			prevList, err := d.State().GetAllIncidentIdsByAlertKey(incident.AlertKey)
   106  			if err != nil {
   107  				return err
   108  			}
   109  			sort.Slice(prevList, func(i, j int) bool {
   110  				return prevList[i] < prevList[j]
   111  			})
   112  			prevIdCache[incident.AlertKey] = &prevList
   113  		}
   114  		for _, pid := range *prevIdCache[incident.AlertKey] {
   115  			if incident.Id > pid {
   116  				incident.PreviousIds = append([]int64{pid}, incident.PreviousIds...)
   117  				continue
   118  			}
   119  			break
   120  		}
   121  
   122  		err = d.setIncident(incident, conn)
   123  		if err != nil {
   124  			return err
   125  		}
   126  		if len(incident.PreviousIds) > 0 {
   127  			err := d.State().SetIncidentNext(incident.PreviousIds[0], incident.Id)
   128  			if err != nil {
   129  				return err
   130  			}
   131  		}
   132  
   133  	}
   134  	return nil
   135  }
   136  
   137  func (d *dataAccess) Migrate() error {
   138  	slog.Infoln("checking migrations")
   139  	conn := d.Get()
   140  	defer conn.Close()
   141  
   142  	// Since we didn't record a schema version from the start
   143  	// we have to do some assumptions to see if this is a new
   144  	// database, or if was a database before we started recording
   145  	// a schema version number
   146  
   147  	version, err := redis.Int64(conn.Do("GET", schemaKey))
   148  	if err != nil {
   149  		if err == redis.ErrNil {
   150  			slog.Infoln("schema version not found in db")
   151  			if _, err := redis.Bool(conn.Do("Get", "allIncidents")); err == redis.ErrNil {
   152  				slog.Infoln("assuming new installation because allIncidents key not found")
   153  				slog.Infoln("writing current schema version")
   154  				if _, err := conn.Do("SET", schemaKey, SchemaVersion); err != nil {
   155  					return slog.Wrap(err)
   156  				}
   157  				version = SchemaVersion
   158  				return nil
   159  			}
   160  		} else {
   161  			return slog.Wrap(err)
   162  		}
   163  	}
   164  
   165  	for _, task := range tasks {
   166  		if task.Version > version {
   167  			// Check if migration has been run if not that run
   168  			err := task.Task(d)
   169  			if err != nil {
   170  				return slog.Wrap(err)
   171  			}
   172  			if _, err := conn.Do("SET", schemaKey, task.Version); err != nil {
   173  				return slog.Wrap(err)
   174  			}
   175  		}
   176  
   177  	}
   178  	return nil
   179  }