mirror of
https://github.com/zrepl/zrepl.git
synced 2024-11-24 01:14:32 +01:00
bc5e1ede04
This PR adds a Prometheus counter called `zrepl_zfs_list_unmatched_user_specified_dataset_count`. Monitor for increases of the counter to detect filesystem filter rules that have no effect because they don't match any local filesystem. An example use case for this is the following story: 1. Someone sets up zrepl with `filesystems` filter for `zroot/pg14<`. 2. During the upgrade to Postgres 15, they rename the dataset to `zroot/pg15`, but forget to update the zrepl `filesystems` filter. 3. zrepl will not snapshot / replicate the `zroot/pg15<` datasets. Since `filesystems` rules are always evaluated on the side that has the datasets, we can smuggle this functionality into the `zfs` module's `ZFSList` function that is used by all jobs with a `filesystems` filter. Dashboard changes: - histogram with increase in $__interval, one row per job - table with increase in $__range - explainer text box, so, people know what the previous two are about We had to re-arrange some panels, hence the Git diff isn't great. closes https://github.com/zrepl/zrepl/pull/653 Co-authored-by: Christian Schwarz <me@cschwarz.com> Co-authored-by: Goran Mekić <meka@tilda.center>
108 lines
2.6 KiB
Go
108 lines
2.6 KiB
Go
package zfs
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/zrepl/zrepl/zfs/zfscmd"
|
|
)
|
|
|
|
type DatasetFilter interface {
|
|
Filter(p *DatasetPath) (pass bool, err error)
|
|
// The caller owns the returned set.
|
|
// Implementations should return a copy.
|
|
UserSpecifiedDatasets() UserSpecifiedDatasetsSet
|
|
}
|
|
|
|
// A set of dataset names that the user specified in the configuration file.
|
|
type UserSpecifiedDatasetsSet map[string]bool
|
|
|
|
// Returns a DatasetFilter that does not filter (passes all paths)
|
|
func NoFilter() DatasetFilter {
|
|
return noFilter{}
|
|
}
|
|
|
|
type noFilter struct{}
|
|
|
|
var _ DatasetFilter = noFilter{}
|
|
|
|
func (noFilter) Filter(p *DatasetPath) (pass bool, err error) { return true, nil }
|
|
func (noFilter) UserSpecifiedDatasets() UserSpecifiedDatasetsSet { return nil }
|
|
|
|
func ZFSListMapping(ctx context.Context, filter DatasetFilter) (datasets []*DatasetPath, err error) {
|
|
res, err := ZFSListMappingProperties(ctx, filter, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
datasets = make([]*DatasetPath, len(res))
|
|
for i, r := range res {
|
|
datasets[i] = r.Path
|
|
}
|
|
return datasets, nil
|
|
}
|
|
|
|
type ZFSListMappingPropertiesResult struct {
|
|
Path *DatasetPath
|
|
// Guaranteed to have the same length as properties in the originating call
|
|
Fields []string
|
|
}
|
|
|
|
// properties must not contain 'name'
|
|
func ZFSListMappingProperties(ctx context.Context, filter DatasetFilter, properties []string) (datasets []ZFSListMappingPropertiesResult, err error) {
|
|
|
|
if filter == nil {
|
|
panic("filter must not be nil")
|
|
}
|
|
|
|
for _, p := range properties {
|
|
if p == "name" {
|
|
panic("properties must not contain 'name'")
|
|
}
|
|
}
|
|
newProps := make([]string, len(properties)+1)
|
|
newProps[0] = "name"
|
|
copy(newProps[1:], properties)
|
|
properties = newProps
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
rchan := make(chan ZFSListResult)
|
|
|
|
go ZFSListChan(ctx, rchan, properties, nil, "-r", "-t", "filesystem,volume")
|
|
|
|
unmatchedUserSpecifiedDatasets := filter.UserSpecifiedDatasets()
|
|
datasets = make([]ZFSListMappingPropertiesResult, 0)
|
|
for r := range rchan {
|
|
|
|
if r.Err != nil {
|
|
err = r.Err
|
|
return
|
|
}
|
|
|
|
var path *DatasetPath
|
|
if path, err = NewDatasetPath(r.Fields[0]); err != nil {
|
|
return
|
|
}
|
|
|
|
delete(unmatchedUserSpecifiedDatasets, path.ToString())
|
|
|
|
pass, filterErr := filter.Filter(path)
|
|
if filterErr != nil {
|
|
return nil, fmt.Errorf("error calling filter: %s", filterErr)
|
|
}
|
|
if pass {
|
|
datasets = append(datasets, ZFSListMappingPropertiesResult{
|
|
Path: path,
|
|
Fields: r.Fields[1:],
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
jobid := zfscmd.GetJobIDOrDefault(ctx, "__nojobid")
|
|
metric := prom.ZFSListUnmatchedUserSpecifiedDatasetCount.WithLabelValues(jobid)
|
|
metric.Add(float64(len(unmatchedUserSpecifiedDatasets)))
|
|
|
|
return
|
|
}
|