<commit_before>package main

import (
	"errors"
	"fmt"
	"io"
	"reflect"
	"sort"
	"strings"

	"github.com/mistifyio/gozfs/nv"
)

// ZFS Dataset Types
const (
	DatasetFilesystem = "filesystem"
	DatasetSnapshot   = "snapshot"
	DatasetVolume     = "volume"
)

// Dataset is a ZFS dataset containing a simplified set of information
type Dataset struct {
	Name          string
	Origin        string
	Used          uint64
	Avail         uint64
	Mountpoint    string
	Compression   string
	Type          string
	Written       uint64
	Volsize       uint64
	Usedbydataset uint64
	Logicalused   uint64
	Quota         uint64
	ds            *ds
}

// By is the type of a "less" function that defines the ordering of its Dataset arguments.
type By func(p1, p2 *Dataset) bool

// Sort is a method on the function type, By, that sorts the argument slice according to the function.
func (by By) Sort(datasets []*Dataset) {
	ds := &datasetSorter{
		datasets: datasets,
		by:       by, // The Sort method's receiver is the function (closure) that defines the sort order.
	}
	sort.Sort(ds)
}

// datasetSorter joins a By function and a slice of Datasets to be sorted.
type datasetSorter struct {
	datasets []*Dataset
	by       func(p1, p2 *Dataset) bool // Closure used in the Less method.
}

// Len is part of sort.Interface.
func (s *datasetSorter) Len() int {
	return len(s.datasets)
}

// Swap is part of sort.Interface.
func (s *datasetSorter) Swap(i, j int) {
	s.datasets[i], s.datasets[j] = s.datasets[j], s.datasets[i]
}

// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
func (s *datasetSorter) Less(i, j int) bool {
	return s.by(s.datasets[i], s.datasets[j])
}

type ds struct {
	DMUObjsetStats *dmuObjsetStats `nv:"dmu_objset_stats"`
	Name           string          `nv:"name"`
	Properties     *dsProperties   `nv:"properties"`
}

type dmuObjsetStats struct {
	CreationTxg  uint64 `nv:"dds_creation_txg"`
	GUID         uint64 `nv:"dds_guid"`
	Inconsistent bool   `nv:"dds_inconsistent"`
	IsSnapshot   bool   `nv:"dds_is_snapshot"`
	NumClones    uint64 `nv:"dds_num_clonse"`
	Origin       string `nv:"dds_origin"`
	Type         string `nv:"dds_type"`
}

type dsProperties struct {
	Available            propUint64           `nv:"available"`
	Clones               propClones           `nv:"clones"`
	Compression          propStringWithSource `nv:"compression"`
	CompressRatio        propUint64           `nv:"compressratio"`
	CreateTxg            propUint64           `nv:"createtxg"`
	Creation             propUint64           `nv:"creation"`
	DeferDestroy         propUint64           `nv:"defer_destroy"`
	GUID                 propUint64           `nv:"guid"`
	LogicalReferenced    propUint64           `nv:"logicalreferenced"`
	LogicalUsed          propUint64           `nv:"logicalused"`
	Mountpoint           propStringWithSource `nv:"mountpoint"`
	ObjsetID             propUint64           `nv:"objsetid"`
	Origin               propString           `nv:"origin"`
	Quota                propUint64WithSource `nv:"quota"`
	RefCompressRatio     propUint64           `nv:"refcompressratio"`
	RefQuota             propUint64WithSource `nv:"refquota"`
	RefReservation       propUint64WithSource `nv:"refreservation"`
	Referenced           propUint64           `nv:"referenced"`
	Reservation          propUint64WithSource `nv:"reservation"`
	Type                 propUint64           `nv:"type"`
	Unique               propUint64           `nv:"unique"`
	Used                 propUint64           `nv:"used"`
	UsedByChildren       propUint64           `nv:"usedbychildren"`
	UsedByDataset        propUint64           `nv:"usedbydataset"`
	UsedByRefReservation propUint64           `nv:"usedbyrefreservation"`
	UsedBySnapshots      propUint64           `nv:"usedbysnapshots"`
	UserAccounting       propUint64           `nv:"useraccounting"`
	UserRefs             propUint64           `nv:"userrefs"`
	Volsize              propUint64           `nv:"volsize"`
	VolBlockSize         propUint64           `nv:"volblocksize"`
	Written              propUint64           `nv:"written"`
}

var dsPropertyIndexes map[string]int

type dsProperty interface {
	value() interface{}
}

type propClones struct {
	Value map[string]nv.Boolean `nv:"value"`
}

func (p propClones) value() []string {
	clones := make([]string, len(p.Value))
	i := 0
	for clone := range p.Value {
		clones[i] = clone
		i++
	}
	return clones
}

type propUint64 struct {
	Value uint64 `nv:"value"`
}

func (p propUint64) value() uint64 {
	return p.Value
}

type propUint64WithSource struct {
	Source string `nv:"source"`
	Value  uint64 `nv:"value"`
}

func (p propUint64WithSource) value() uint64 {
	return p.Value
}

type propString struct {
	Value string `nv:"value"`
}

func (p propString) value() string {
	return p.Value
}

type propStringWithSource struct {
	Source string `nv:"source"`
	Value  string `nv:"value"`
}

func (p propStringWithSource) value() string {
	return p.Value
}

func dsToDataset(in *ds) *Dataset {
	var dsType string
	if in.DMUObjsetStats.IsSnapshot {
		dsType = DatasetSnapshot
	} else if dmuType(in.Properties.Type.Value) == dmuTypes["zvol"] {
		dsType = DatasetVolume
	} else {
		dsType = DatasetFilesystem
	}

	compression := in.Properties.Compression.Value
	if compression == "" {
		compression = "off"
	}

	mountpoint := in.Properties.Mountpoint.Value
	if mountpoint == "" && dsType != DatasetSnapshot {
		mountpoint = fmt.Sprintf("/%s", in.Name)
	}

	return &Dataset{
		Name:          in.Name,
		Origin:        in.Properties.Origin.Value,
		Used:          in.Properties.Used.Value,
		Avail:         in.Properties.Available.Value,
		Mountpoint:    mountpoint,
		Compression:   compression,
		Type:          dsType,
		Written:       in.Properties.Available.Value,
		Volsize:       in.Properties.Volsize.Value,
		Usedbydataset: in.Properties.UsedByDataset.Value,
		Logicalused:   in.Properties.LogicalUsed.Value,
		Quota:         in.Properties.Quota.Value,
		ds:            in,
	}
}

func getDatasets(name, dsType string, recurse bool, depth uint64) ([]*Dataset, error) {
	types := map[string]bool{
		dsType: true,
	}

	dss, err := list(name, types, recurse, depth)
	if err != nil {
		return nil, err
	}

	datasets := make([]*Dataset, len(dss))
	for i, ds := range dss {
		datasets[i] = dsToDataset(ds)
	}

	return datasets, nil
}

// Datasets retrieves a list of all datasets, regardless of type
func Datasets(name string) ([]*Dataset, error) {
	return getDatasets(name, "all", true, 0)
}

// Filesystems retrieves a list of all filesystems
func Filesystems(name string) ([]*Dataset, error) {
	return getDatasets(name, DatasetFilesystem, true, 0)
}

// Snapshots retrieves a list of all snapshots
func Snapshots(name string) ([]*Dataset, error) {
	return getDatasets(name, DatasetSnapshot, true, 0)
}

// Volumes retrieves a list of all volumes
func Volumes(name string) ([]*Dataset, error) {
	return getDatasets(name, DatasetVolume, true, 0)
}

// GetDataset retrieves a single dataset
func GetDataset(name string) (*Dataset, error) {
	datasets, err := getDatasets(name, "all", false, 0)
	if err != nil {
		return nil, err
	}
	if len(datasets) != 1 {
		return nil, fmt.Errorf("expected 1 dataset, got %d", len(datasets))
	}
	return datasets[0], nil
}

func createDataset(name string, createType dmuType, properties map[string]interface{}) (*Dataset, error) {
	if err := create(name, dmuZFS, properties); err != nil {
		return nil, err
	}

	return GetDataset(name)
}

// CreateFilesystem creates a new filesystem
func CreateFilesystem(name string, properties map[string]interface{}) (*Dataset, error) {
	// TODO: Sort out handling of properties. Custom struct?
	return createDataset(name, dmuZFS, properties)
}

// CreateVolume creates a new volume
func CreateVolume(name string, size uint64, properties map[string]interface{}) (*Dataset, error) {
	// TODO: Sort out handling of properties. Custom struct?
	properties["volsize"] = size
	return createDataset(name, dmuZVOL, properties)
}

// ReceiveSnapshot creates a snapshot from a zfs send stream
func ReceiveSnapshot(input io.Reader, name string) (*Dataset, error) {
	// TODO: Fix when zfs receive is implemented
	return nil, errors.New("zfs receive not yet implemented")
}

// Children returns a list of children of the dataset
func (d *Dataset) Children(depth uint64) ([]*Dataset, error) {
	datasets, err := getDatasets(d.Name, "all", true, depth)
	if err != nil {
		return nil, err
	}
	return datasets[1:], nil
}

// Clones returns a list of clones of the dataset
func (d *Dataset) Clones() {

}

// Clone clones a snapshot and returns a clone dataset
func (d *Dataset) Clone(name string, properties map[string]interface{}) (*Dataset, error) {
	if err := clone(name, d.Name, properties); err != nil {
		return nil, err
	}
	return GetDataset(name)
}

// DestroyOptions are used to determine the behavior when destroying a dataset
type DestroyOptions struct {
	Recursive       bool
	RecursiveClones bool
	ForceUnmount    bool
	Defer           bool
}

// Destroy destroys a zfs dataset, optionally recursive for descendants and
// clones. Note that recursive destroys are not an atomic operation.
func (d *Dataset) Destroy(opts *DestroyOptions) error {
	// Recurse
	if opts.Recursive {
		children, err := d.Children(1)
		if err != nil {
			return err
		}
		for _, child := range children {
			if err := child.Destroy(opts); err != nil {
				return err
			}
		}
	}

	// Recurse Clones
	if opts.RecursiveClones {
		for cloneName := range d.ds.Properties.Clones.Value {
			clone, err := GetDataset(cloneName)
			if err != nil {
				return err
			}
			if err := clone.Destroy(opts); err != nil {
				return err
			}
		}
	}

	// Unmount this dataset
	// TODO: Implement when we have unmount

	// Destroy this dataset
	return destroy(d.Name, opts.Defer)
}

// Diff returns changes between a snapshot and the given dataset.
func (d *Dataset) Diff(name string) {
	// TODO: Implement when we have a zfs diff implementation
}

// GetProperty returns the current value of a property from the dataset
func (d *Dataset) GetProperty(name string) (interface{}, error) {
	dV := reflect.ValueOf(d.ds.Properties)
	propertyIndex, ok := dsPropertyIndexes[strings.ToLower(name)]
	if !ok {
		return nil, errors.New("not a valid property name")
	}
	property := reflect.Indirect(dV).Field(propertyIndex).Interface().(dsProperty)
	return property.value(), nil
}

// SetProperty sets the value of a property of the dataset
func (d *Dataset) SetProperty(name string, value interface{}) error {
	// TODO: Implement when we have a zfs set property implementation
	return errors.New("zfs set property not implemented yet")
}

// Rollback rolls back a dataset to a previous snapshot
func (d *Dataset) Rollback(destroyMoreRecent bool) error {
	if destroyMoreRecent {
		// Get the dataset's snapshots
		dsName := strings.Split(d.Name, "@")[0]
		snapshots, err := getDatasets(dsName, DatasetSnapshot, true, 1)
		if err != nil {
			return err
		}

		// Order from oldest to newest
		creation := func(d1, d2 *Dataset) bool {
			return d1.ds.Properties.Creation.Value < d2.ds.Properties.Creation.Value
		}
		By(creation).Sort(snapshots)

		// Destroy any snapshots newer than the target
		found := false
		for _, snapshot := range snapshots {
			// Ignore this snapshot and all older
			if !found {
				if snapshot.Name == d.Name {
					found = true
					continue
				}
			}

			if err := snapshot.Destroy(&DestroyOptions{}); err != nil {
				return err
			}
		}
	}

	// Rollback to the target snapshot
	_, err := rollback(d.Name)
	return err
}

// SendSnapshot sends a stream of a snapshot to the filedescriptor
// TODO: Decide whether asking for a fd here instead of an io.Writer is ok
func (d *Dataset) SendSnapshot(outputFD uintptr) error {
	return send(d.Name, outputFD, "", false, false)
}

// Snapshot creates a new snapshot of the dataset
func (d *Dataset) Snapshot(name string, recursive bool) error {
	zpool := strings.Split(d.Name, "/")[0]
	snapName := fmt.Sprintf("%s@%s", d.Name, name)
	_, err := snapshot(zpool, []string{snapName}, map[string]string{})
	return err
}

// Snapshots returns a list of snapshots of the dataset
func (d *Dataset) Snapshots() ([]*Dataset, error) {
	return Snapshots(d.Name)
}

func init() {
	dsPropertyIndexes = make(map[string]int)
	dsPropertiesT := reflect.TypeOf(dsProperties{})
	for i := 0; i < dsPropertiesT.NumField(); i++ {
		field := dsPropertiesT.Field(i)
		name := field.Name
		tags := strings.Split(field.Tag.Get("nv"), ",")
		if len(tags) > 0 && tags[0] != "" {
			name = tags[0]
		}
		dsPropertyIndexes[strings.ToLower(name)] = i
	}
}
<commit_msg>Sort results of getDatasets by name<commit_after>package main

import (
	"errors"
	"fmt"
	"io"
	"reflect"
	"sort"
	"strings"

	"github.com/mistifyio/gozfs/nv"
)

// ZFS Dataset Types
const (
	DatasetFilesystem = "filesystem"
	DatasetSnapshot   = "snapshot"
	DatasetVolume     = "volume"
)

// Dataset is a ZFS dataset containing a simplified set of information
type Dataset struct {
	Name          string
	Origin        string
	Used          uint64
	Avail         uint64
	Mountpoint    string
	Compression   string
	Type          string
	Written       uint64
	Volsize       uint64
	Usedbydataset uint64
	Logicalused   uint64
	Quota         uint64
	ds            *ds
}

// By is the type of a "less" function that defines the ordering of its Dataset arguments.
type By func(p1, p2 *Dataset) bool

// Sort is a method on the function type, By, that sorts the argument slice according to the function.
func (by By) Sort(datasets []*Dataset) {
	ds := &datasetSorter{
		datasets: datasets,
		by:       by, // The Sort method's receiver is the function (closure) that defines the sort order.
	}
	sort.Sort(ds)
}

// datasetSorter joins a By function and a slice of Datasets to be sorted.
type datasetSorter struct {
	datasets []*Dataset
	by       func(p1, p2 *Dataset) bool // Closure used in the Less method.
}

// Len is part of sort.Interface.
func (s *datasetSorter) Len() int {
	return len(s.datasets)
}

// Swap is part of sort.Interface.
func (s *datasetSorter) Swap(i, j int) {
	s.datasets[i], s.datasets[j] = s.datasets[j], s.datasets[i]
}

// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
func (s *datasetSorter) Less(i, j int) bool {
	return s.by(s.datasets[i], s.datasets[j])
}

type ds struct {
	DMUObjsetStats *dmuObjsetStats `nv:"dmu_objset_stats"`
	Name           string          `nv:"name"`
	Properties     *dsProperties   `nv:"properties"`
}

type dmuObjsetStats struct {
	CreationTxg  uint64 `nv:"dds_creation_txg"`
	GUID         uint64 `nv:"dds_guid"`
	Inconsistent bool   `nv:"dds_inconsistent"`
	IsSnapshot   bool   `nv:"dds_is_snapshot"`
	NumClones    uint64 `nv:"dds_num_clonse"`
	Origin       string `nv:"dds_origin"`
	Type         string `nv:"dds_type"`
}

type dsProperties struct {
	Available            propUint64           `nv:"available"`
	Clones               propClones           `nv:"clones"`
	Compression          propStringWithSource `nv:"compression"`
	CompressRatio        propUint64           `nv:"compressratio"`
	CreateTxg            propUint64           `nv:"createtxg"`
	Creation             propUint64           `nv:"creation"`
	DeferDestroy         propUint64           `nv:"defer_destroy"`
	GUID                 propUint64           `nv:"guid"`
	LogicalReferenced    propUint64           `nv:"logicalreferenced"`
	LogicalUsed          propUint64           `nv:"logicalused"`
	Mountpoint           propStringWithSource `nv:"mountpoint"`
	ObjsetID             propUint64           `nv:"objsetid"`
	Origin               propString           `nv:"origin"`
	Quota                propUint64WithSource `nv:"quota"`
	RefCompressRatio     propUint64           `nv:"refcompressratio"`
	RefQuota             propUint64WithSource `nv:"refquota"`
	RefReservation       propUint64WithSource `nv:"refreservation"`
	Referenced           propUint64           `nv:"referenced"`
	Reservation          propUint64WithSource `nv:"reservation"`
	Type                 propUint64           `nv:"type"`
	Unique               propUint64           `nv:"unique"`
	Used                 propUint64           `nv:"used"`
	UsedByChildren       propUint64           `nv:"usedbychildren"`
	UsedByDataset        propUint64           `nv:"usedbydataset"`
	UsedByRefReservation propUint64           `nv:"usedbyrefreservation"`
	UsedBySnapshots      propUint64           `nv:"usedbysnapshots"`
	UserAccounting       propUint64           `nv:"useraccounting"`
	UserRefs             propUint64           `nv:"userrefs"`
	Volsize              propUint64           `nv:"volsize"`
	VolBlockSize         propUint64           `nv:"volblocksize"`
	Written              propUint64           `nv:"written"`
}

var dsPropertyIndexes map[string]int

type dsProperty interface {
	value() interface{}
}

type propClones struct {
	Value map[string]nv.Boolean `nv:"value"`
}

func (p propClones) value() []string {
	clones := make([]string, len(p.Value))
	i := 0
	for clone := range p.Value {
		clones[i] = clone
		i++
	}
	return clones
}

type propUint64 struct {
	Value uint64 `nv:"value"`
}

func (p propUint64) value() uint64 {
	return p.Value
}

type propUint64WithSource struct {
	Source string `nv:"source"`
	Value  uint64 `nv:"value"`
}

func (p propUint64WithSource) value() uint64 {
	return p.Value
}

type propString struct {
	Value string `nv:"value"`
}

func (p propString) value() string {
	return p.Value
}

type propStringWithSource struct {
	Source string `nv:"source"`
	Value  string `nv:"value"`
}

func (p propStringWithSource) value() string {
	return p.Value
}

func dsToDataset(in *ds) *Dataset {
	var dsType string
	if in.DMUObjsetStats.IsSnapshot {
		dsType = DatasetSnapshot
	} else if dmuType(in.Properties.Type.Value) == dmuTypes["zvol"] {
		dsType = DatasetVolume
	} else {
		dsType = DatasetFilesystem
	}

	compression := in.Properties.Compression.Value
	if compression == "" {
		compression = "off"
	}

	mountpoint := in.Properties.Mountpoint.Value
	if mountpoint == "" && dsType != DatasetSnapshot {
		mountpoint = fmt.Sprintf("/%s", in.Name)
	}

	return &Dataset{
		Name:          in.Name,
		Origin:        in.Properties.Origin.Value,
		Used:          in.Properties.Used.Value,
		Avail:         in.Properties.Available.Value,
		Mountpoint:    mountpoint,
		Compression:   compression,
		Type:          dsType,
		Written:       in.Properties.Available.Value,
		Volsize:       in.Properties.Volsize.Value,
		Usedbydataset: in.Properties.UsedByDataset.Value,
		Logicalused:   in.Properties.LogicalUsed.Value,
		Quota:         in.Properties.Quota.Value,
		ds:            in,
	}
}

func getDatasets(name, dsType string, recurse bool, depth uint64) ([]*Dataset, error) {
	types := map[string]bool{
		dsType: true,
	}

	dss, err := list(name, types, recurse, depth)
	if err != nil {
		return nil, err
	}

	datasets := make([]*Dataset, len(dss))
	for i, ds := range dss {
		datasets[i] = dsToDataset(ds)
	}

	byName := func(d1, d2 *Dataset) bool {
		return d1.Name < d2.Name
	}
	By(byName).Sort(datasets)

	return datasets, nil
}

// Datasets retrieves a list of all datasets, regardless of type
func Datasets(name string) ([]*Dataset, error) {
	return getDatasets(name, "all", true, 0)
}

// Filesystems retrieves a list of all filesystems
func Filesystems(name string) ([]*Dataset, error) {
	return getDatasets(name, DatasetFilesystem, true, 0)
}

// Snapshots retrieves a list of all snapshots
func Snapshots(name string) ([]*Dataset, error) {
	return getDatasets(name, DatasetSnapshot, true, 0)
}

// Volumes retrieves a list of all volumes
func Volumes(name string) ([]*Dataset, error) {
	return getDatasets(name, DatasetVolume, true, 0)
}

// GetDataset retrieves a single dataset
func GetDataset(name string) (*Dataset, error) {
	datasets, err := getDatasets(name, "all", false, 0)
	if err != nil {
		return nil, err
	}
	if len(datasets) != 1 {
		return nil, fmt.Errorf("expected 1 dataset, got %d", len(datasets))
	}
	return datasets[0], nil
}

func createDataset(name string, createType dmuType, properties map[string]interface{}) (*Dataset, error) {
	if err := create(name, dmuZFS, properties); err != nil {
		return nil, err
	}

	return GetDataset(name)
}

// CreateFilesystem creates a new filesystem
func CreateFilesystem(name string, properties map[string]interface{}) (*Dataset, error) {
	// TODO: Sort out handling of properties. Custom struct?
	return createDataset(name, dmuZFS, properties)
}

// CreateVolume creates a new volume
func CreateVolume(name string, size uint64, properties map[string]interface{}) (*Dataset, error) {
	// TODO: Sort out handling of properties. Custom struct?
	properties["volsize"] = size
	return createDataset(name, dmuZVOL, properties)
}

// ReceiveSnapshot creates a snapshot from a zfs send stream
func ReceiveSnapshot(input io.Reader, name string) (*Dataset, error) {
	// TODO: Fix when zfs receive is implemented
	return nil, errors.New("zfs receive not yet implemented")
}

// Children returns a list of children of the dataset
func (d *Dataset) Children(depth uint64) ([]*Dataset, error) {
	datasets, err := getDatasets(d.Name, "all", true, depth)
	if err != nil {
		return nil, err
	}
	return datasets[1:], nil
}

// Clones returns a list of clones of the dataset
func (d *Dataset) Clones() {

}

// Clone clones a snapshot and returns a clone dataset
func (d *Dataset) Clone(name string, properties map[string]interface{}) (*Dataset, error) {
	if err := clone(name, d.Name, properties); err != nil {
		return nil, err
	}
	return GetDataset(name)
}

// DestroyOptions are used to determine the behavior when destroying a dataset
type DestroyOptions struct {
	Recursive       bool
	RecursiveClones bool
	ForceUnmount    bool
	Defer           bool
}

// Destroy destroys a zfs dataset, optionally recursive for descendants and
// clones. Note that recursive destroys are not an atomic operation.
func (d *Dataset) Destroy(opts *DestroyOptions) error {
	// Recurse
	if opts.Recursive {
		children, err := d.Children(1)
		if err != nil {
			return err
		}
		for _, child := range children {
			if err := child.Destroy(opts); err != nil {
				return err
			}
		}
	}

	// Recurse Clones
	if opts.RecursiveClones {
		for cloneName := range d.ds.Properties.Clones.Value {
			clone, err := GetDataset(cloneName)
			if err != nil {
				return err
			}
			if err := clone.Destroy(opts); err != nil {
				return err
			}
		}
	}

	// Unmount this dataset
	// TODO: Implement when we have unmount

	// Destroy this dataset
	return destroy(d.Name, opts.Defer)
}

// Diff returns changes between a snapshot and the given dataset.
func (d *Dataset) Diff(name string) {
	// TODO: Implement when we have a zfs diff implementation
}

// GetProperty returns the current value of a property from the dataset
func (d *Dataset) GetProperty(name string) (interface{}, error) {
	dV := reflect.ValueOf(d.ds.Properties)
	propertyIndex, ok := dsPropertyIndexes[strings.ToLower(name)]
	if !ok {
		return nil, errors.New("not a valid property name")
	}
	property := reflect.Indirect(dV).Field(propertyIndex).Interface().(dsProperty)
	return property.value(), nil
}

// SetProperty sets the value of a property of the dataset
func (d *Dataset) SetProperty(name string, value interface{}) error {
	// TODO: Implement when we have a zfs set property implementation
	return errors.New("zfs set property not implemented yet")
}

// Rollback rolls back a dataset to a previous snapshot
func (d *Dataset) Rollback(destroyMoreRecent bool) error {
	if destroyMoreRecent {
		// Get the dataset's snapshots
		dsName := strings.Split(d.Name, "@")[0]
		snapshots, err := getDatasets(dsName, DatasetSnapshot, true, 1)
		if err != nil {
			return err
		}

		// Order from oldest to newest
		creation := func(d1, d2 *Dataset) bool {
			return d1.ds.Properties.Creation.Value < d2.ds.Properties.Creation.Value
		}
		By(creation).Sort(snapshots)

		// Destroy any snapshots newer than the target
		found := false
		for _, snapshot := range snapshots {
			// Ignore this snapshot and all older
			if !found {
				if snapshot.Name == d.Name {
					found = true
					continue
				}
			}

			if err := snapshot.Destroy(&DestroyOptions{}); err != nil {
				return err
			}
		}
	}

	// Rollback to the target snapshot
	_, err := rollback(d.Name)
	return err
}

// SendSnapshot sends a stream of a snapshot to the filedescriptor
// TODO: Decide whether asking for a fd here instead of an io.Writer is ok
func (d *Dataset) SendSnapshot(outputFD uintptr) error {
	return send(d.Name, outputFD, "", false, false)
}

// Snapshot creates a new snapshot of the dataset
func (d *Dataset) Snapshot(name string, recursive bool) error {
	zpool := strings.Split(d.Name, "/")[0]
	snapName := fmt.Sprintf("%s@%s", d.Name, name)
	_, err := snapshot(zpool, []string{snapName}, map[string]string{})
	return err
}

// Snapshots returns a list of snapshots of the dataset
func (d *Dataset) Snapshots() ([]*Dataset, error) {
	return Snapshots(d.Name)
}

func init() {
	dsPropertyIndexes = make(map[string]int)
	dsPropertiesT := reflect.TypeOf(dsProperties{})
	for i := 0; i < dsPropertiesT.NumField(); i++ {
		field := dsPropertiesT.Field(i)
		name := field.Name
		tags := strings.Split(field.Tag.Get("nv"), ",")
		if len(tags) > 0 && tags[0] != "" {
			name = tags[0]
		}
		dsPropertyIndexes[strings.ToLower(name)] = i
	}
}
<|endoftext|>


<commit_before>package dt

import (
	"errors"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/avabot/ava/Godeps/_workspace/src/github.com/sendgrid/sendgrid-go"
)

type MailClient struct {
	sgc *sendgrid.SGClient
}

// TODO add shipping information and purchase identifier (UUID)
func (sg *MailClient) SendPurchaseConfirmation(p *Purchase) error {
	products := p.ProductSels
	if len(products) == 0 {
		return errors.New("empty products slice in purchase confirmation")
	}
	subj := fmt.Sprintf("Order confirmation: #%s", p.DisplayID())
	text := ""
	text += fmt.Sprintf("
Hi %s:
", p.User.Name)
	text += "
Here's a quick order summary for your records. You bought:
"
	text += "
"
	for _, product := range products {
		price := float64(product.Price) / 100
		var size string
		if len(product.Size) > 0 {
			size = fmt.Sprintf(" (%s)", product.Size)
		}
		text += fmt.Sprintf("
%d @ $%.2f - %s%s
", product.Count,
			price, product.Name, size)
	}
	text += "

"
	text += fmt.Sprintf("
Subtotal: 	$%.2f
",
		float64(p.Subtotal())/100)
	text += fmt.Sprintf("
Shipping: 	$%.2f
",
		float64(p.Shipping)/100)
	text += fmt.Sprintf("
Tax: 	$%.2f
",
		float64(p.Tax)/100)
	text += "
My fee: 	$0.00
"
	text += fmt.Sprintf("
Total: 	$%.2f
",
		float64(p.Total)/100)
	text += "
"
	delivery := time.Now().Add(7 * 24 * time.Hour)
	delS := delivery.Format("Monday Jan 2, 2006")
	text += fmt.Sprintf("
Expected delivery before %s. ", delS)
	text += fmt.Sprintf("Your order confirmation number is %s.
", p.ID)
	text += "
Glad I could help! :)

- Ava
"
	text += ""
	return sg.Send(subj, text, p.User)
}

// TODO add shipping information and purchase identifier (UUID)
func (sg *MailClient) SendVendorRequest(p *Purchase) error {
	if len(p.ProductSels) == 0 {
		return errors.New("empty products slice in vendor request")
	}
	var subj string
	if os.Getenv("AVA_ENV") == "production" {
		subj = fmt.Sprintf("Order Request: #%s", p.DisplayID())
	} else {
		subj = fmt.Sprintf("[TEST - PLEASE IGNORE] Order Request: #%s", p.ID)
		(*p.Vendor).ContactName = "Evan"
		(*p.Vendor).ContactEmail = "egtann@gmail.com"
	}
	text := ""
	text += fmt.Sprintf("
Hi %s:
", p.Vendor.ContactName)
	text += fmt.Sprintf("
%s just ordered the following:
",
		p.User.Name)
	text += "
"
	for _, product := range p.ProductSels {
		price := float64(product.Price) / 100
		var size string
		if len(product.Size) > 0 {
			size = fmt.Sprintf(" (%s)", product.Size)
		}
		text += fmt.Sprintf("
%d @ $%.2f - %s%s
", product.Count,
			price, product.Name, size)
	}
	text += "

"
	text += fmt.Sprintf("
Subtotal: 	$%.2f
",
		float64(p.Subtotal())/100)
	text += fmt.Sprintf("
Shipping: 	$%.2f
",
		float64(p.Shipping)/100)
	text += fmt.Sprintf("
Tax: 	$%.2f
",
		float64(p.Tax)/100)
	text += fmt.Sprintf("
Ava's fee: 	($%.2f)
",
		float64(p.AvaFee)/100)
	text += fmt.Sprintf("
Credit card fees: 	($%.2f)
",
		float64(p.CreditCardFee)/100)
	text += fmt.Sprintf("
Total you'll receive: 	$%.2f
",
		float64(p.Total-p.AvaFee-p.CreditCardFee)/100)
	text += "
"
	text += fmt.Sprintf("
%s is expecting delivery before %s. ",
		p.User.Name, p.DeliveryExpectedAt.Format("Monday Jan 2, 2006"))
	text += "The order has been paid for in full and is ready to be shipped.
"
	text += "
If you have any questions or concerns with this order, "
	text += "please respond to this email.
"
	text += "
Best,
"
	text += "
- Ava
"
	text += ""
	return sg.Send(subj, text, p.Vendor)
}

func (sg *MailClient) Send(subj, html string, c Contactable) error {
	msg := sendgrid.NewMail()
	msg.SetFrom("ava@avabot.com")
	msg.SetFromName("Ava")
	msg.AddTo(c.GetEmail())
	msg.AddToName(c.GetName())
	msg.SetSubject(subj)
	msg.SetHTML(html)
	if err := sg.sgc.Send(msg); err != nil {
		return err
	}
	return nil
}

func NewMailClient() *MailClient {
	log.Println("sendgrid", os.Getenv("SENDGRID_KEY"))
	return &MailClient{
		sgc: sendgrid.NewSendGridClientWithApiKey(
			os.Getenv("SENDGRID_KEY"),
		),
	}
}
<commit_msg>Fix mail sender<commit_after>package dt

import (
	"errors"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/avabot/ava/Godeps/_workspace/src/github.com/sendgrid/sendgrid-go"
)

type MailClient struct {
	sgc *sendgrid.SGClient
}

// TODO add shipping information and purchase identifier (UUID)
func (sg *MailClient) SendPurchaseConfirmation(p *Purchase) error {
	products := p.ProductSels
	if len(products) == 0 {
		return errors.New("empty products slice in purchase confirmation")
	}
	subj := fmt.Sprintf("Order confirmation: #%s", p.DisplayID())
	text := ""
	text += fmt.Sprintf("
Hi %s:
", p.User.Name)
	text += "
Here's a quick order summary for your records. You bought:
"
	text += "
"
	for _, product := range products {
		price := float64(product.Price) / 100
		var size string
		if len(product.Size) > 0 {
			size = fmt.Sprintf(" (%s)", product.Size)
		}
		text += fmt.Sprintf("
%d @ $%.2f - %s%s
", product.Count,
			price, product.Name, size)
	}
	text += "

"
	text += fmt.Sprintf("
Subtotal: 	$%.2f
",
		float64(p.Subtotal())/100)
	text += fmt.Sprintf("
Shipping: 	$%.2f
",
		float64(p.Shipping)/100)
	text += fmt.Sprintf("
Tax: 	$%.2f
",
		float64(p.Tax)/100)
	text += "
My fee: 	$0.00
"
	text += fmt.Sprintf("
Total: 	$%.2f
",
		float64(p.Total)/100)
	text += "
"
	delivery := time.Now().Add(7 * 24 * time.Hour)
	delS := delivery.Format("Monday Jan 2, 2006")
	text += fmt.Sprintf("
Expected delivery before %s. ", delS)
	text += fmt.Sprintf("Your order confirmation number is %s.
", p.ID)
	text += "
Glad I could help! :)

- Ava
"
	text += ""
	return sg.Send(subj, text, p.User)
}

// TODO add shipping information and purchase identifier (UUID)
func (sg *MailClient) SendVendorRequest(p *Purchase) error {
	if len(p.ProductSels) == 0 {
		return errors.New("empty products slice in vendor request")
	}
	var subj string
	if os.Getenv("AVA_ENV") == "production" {
		subj = fmt.Sprintf("Order Request: #%s", p.DisplayID())
	} else {
		subj = fmt.Sprintf("[TEST - PLEASE IGNORE] Order Request: #%s", p.ID)
		(*p.Vendor).ContactName = "Evan"
		(*p.Vendor).ContactEmail = "egtann@gmail.com"
	}
	text := ""
	text += fmt.Sprintf("
Hi %s:
", p.Vendor.ContactName)
	text += fmt.Sprintf("
%s just ordered the following:
",
		p.User.Name)
	text += "
"
	for _, product := range p.ProductSels {
		price := float64(product.Price) / 100
		var size string
		if len(product.Size) > 0 {
			size = fmt.Sprintf(" (%s)", product.Size)
		}
		text += fmt.Sprintf("
%d @ $%.2f - %s%s
", product.Count,
			price, product.Name, size)
	}
	text += "

"
	text += fmt.Sprintf("
Subtotal: 	$%.2f
",
		float64(p.Subtotal())/100)
	text += fmt.Sprintf("
Shipping: 	$%.2f
",
		float64(p.Shipping)/100)
	text += fmt.Sprintf("
Tax: 	$%.2f
",
		float64(p.Tax)/100)
	text += fmt.Sprintf("
Ava's fee: 	($%.2f)
",
		float64(p.AvaFee)/100)
	text += fmt.Sprintf("
Credit card fees: 	($%.2f)
",
		float64(p.CreditCardFee)/100)
	text += fmt.Sprintf("
Total you'll receive: 	$%.2f
",
		float64(p.Total-p.AvaFee-p.CreditCardFee)/100)
	text += "
"
	text += fmt.Sprintf("
%s is expecting delivery before %s. ",
		p.User.Name, p.DeliveryExpectedAt.Format("Monday Jan 2, 2006"))
	text += "The order has been paid for in full and is ready to be shipped.
"
	text += "
If you have any questions or concerns with this order, "
	text += "please respond to this email.
"
	text += "
Best,
"
	text += "
- Ava
"
	text += ""
	return sg.Send(subj, text, p.Vendor)
}

func (sg *MailClient) Send(subj, html string, c Contactable) error {
	msg := sendgrid.NewMail()
	msg.SetFrom("ava@avabot.co")
	msg.SetFromName("Ava")
	msg.AddTo(c.GetEmail())
	msg.AddToName(c.GetName())
	msg.SetSubject(subj)
	msg.SetHTML(html)
	if err := sg.sgc.Send(msg); err != nil {
		return err
	}
	return nil
}

func NewMailClient() *MailClient {
	log.Println("sendgrid", os.Getenv("SENDGRID_KEY"))
	return &MailClient{
		sgc: sendgrid.NewSendGridClientWithApiKey(
			os.Getenv("SENDGRID_KEY"),
		),
	}
}
<|endoftext|>


<commit_before>package nodeobservatory

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/go-kit/kit/log"
	core_v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/util/wait"
	listers_core_v1 "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"

	"github.com/sapcc/kubernikus/pkg/api/models"
	v1 "github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
	kube "github.com/sapcc/kubernikus/pkg/client/kubernetes"
	kubernikus_informers_v1 "github.com/sapcc/kubernikus/pkg/generated/informers/externalversions/kubernikus/v1"
)

const (
	BaseDelay              = 5 * time.Second
	MaxDelay               = 300 * time.Second
	KlusterRecheckInterval = 5 * time.Minute
)

type (
	AddFunc    func(kluster *v1.Kluster, node *core_v1.Node)
	UpdateFunc func(kluster *v1.Kluster, nodeCur, nodeOld *core_v1.Node)
	DeleteFunc func(kluster *v1.Kluster, node *core_v1.Node)

	NodeEventHandlerFuncs struct {
		AddFunc    AddFunc
		UpdateFunc UpdateFunc
		DeleteFunc DeleteFunc
	}

	NodeObservatory struct {
		clientFactory       kube.SharedClientFactory
		klusterInformer     kubernikus_informers_v1.KlusterInformer
		namespace           string
		queue               workqueue.RateLimitingInterface
		logger              log.Logger
		nodeInformerMap     sync.Map
		handlersMux         sync.RWMutex
		addEventHandlers    []AddFunc
		updateEventHandlers []UpdateFunc
		deleteEventHandlers []DeleteFunc
		stopCh              <-chan struct{}
		threadiness         int
	}
)

func NewController(informer kubernikus_informers_v1.KlusterInformer, factory kube.SharedClientFactory, logger log.Logger, threadiness int) *NodeObservatory {
	logger = log.With(logger,
		"controller", "nodeobservatory",
		"threadiness", threadiness,
	)

	controller := &NodeObservatory{
		clientFactory:   factory,
		klusterInformer: informer,
		queue:           workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(BaseDelay, MaxDelay), "nodeobservatory"),
		logger:          logger,
		nodeInformerMap: sync.Map{},
		threadiness:     threadiness,
	}

	informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    controller.klusterAddFunc,
		UpdateFunc: controller.klusterUpdateFunc,
		DeleteFunc: controller.klusterDeleteFunc,
	})

	return controller
}

func (n *NodeObservatory) Run(stopCh <-chan struct{}) {
	n.logger.Log(
		"msg", "starting run loop",
		"v", 2,
	)

	n.stopCh = stopCh

	defer n.queue.ShutDown()

	for i := 0; i < n.threadiness; i++ {
		go wait.Until(n.runWorker, time.Second, stopCh)
	}

	ticker := time.NewTicker(KlusterRecheckInterval)
	go func() {
		for {
			select {
			case <-ticker.C:
				n.requeueAllKlusters()
			case <-stopCh:
				ticker.Stop()
				return
			}
		}
	}()
	<-stopCh
}

func (n *NodeObservatory) requeueAllKlusters() (err error) {
	defer func() {
		n.logger.Log(
			"msg", "requeued all",
			"v", 1,
			"err", err,
		)
	}()

	klusters, err := n.klusterInformer.Lister().Klusters(n.namespace).List(labels.Everything())
	if err != nil {
		return err
	}

	for _, kluster := range klusters {
		n.requeueKluster(kluster)
	}

	return nil
}

func (n *NodeObservatory) requeueKluster(kluster *v1.Kluster) {
	n.logger.Log(
		"msg", "queuing",
		"kluster", kluster.Spec.Name,
		"project", kluster.Account(),
		"v", 2,
	)

	key, err := cache.MetaNamespaceKeyFunc(kluster)
	if err == nil {
		n.queue.Add(key)
	}
}

func (n *NodeObservatory) runWorker() {
	for n.processNextWorkItem() {
	}
}

func (n *NodeObservatory) processNextWorkItem() bool {
	key, quit := n.queue.Get()
	if quit {
		return false
	}
	defer n.queue.Done(key)

	var kluster *v1.Kluster

	if obj, exists, _ := n.klusterInformer.Informer().GetIndexer().GetByKey(key.(string)); exists {
		kluster = obj.(*v1.Kluster)
	}

	// Invoke the method containing the business logic
	err := n.reconcile(kluster)

	if err == nil {
		n.queue.Forget(key)
		return true
	}

	if n.queue.NumRequeues(key) < 5 {
		// Re-enqueue the key rate limited. Based on the rate limiter on the
		// queue and the re-enqueue history, the key will be processed later again.
		n.queue.AddRateLimited(key)
		return true
	}

	// Retries exceeded. Forgetting for this reconciliation loop
	n.queue.Forget(key)
	return true
}

func (n *NodeObservatory) reconcile(kluster *v1.Kluster) error {

	n.cleanUpInformers()

	if kluster != nil && (kluster.Status.Phase == models.KlusterPhaseRunning || kluster.Status.Phase == models.KlusterPhaseUpgrading || kluster.Status.Phase == models.KlusterPhaseTerminating) {
		if err := n.createAndWatchNodeInformerForKluster(kluster); err != nil {
			return err
		}
	}

	return nil
}

func (n *NodeObservatory) cleanUpInformers() {
	n.nodeInformerMap.Range(
		func(key, value interface{}) bool {
			if _, exists, _ := n.klusterInformer.Informer().GetIndexer().GetByKey(key.(string)); !exists {
				if i, ok := n.nodeInformerMap.Load(key); ok {
					informer := i.(*NodeInformer)
					informer.close()
				}
				n.nodeInformerMap.Delete(key)
			}
			return true
		},
	)
}

func (n *NodeObservatory) createAndWatchNodeInformerForKluster(kluster *v1.Kluster) error {
	key, err := cache.MetaNamespaceKeyFunc(kluster)
	if err != nil {
		return err
	}

	if _, exists := n.nodeInformerMap.Load(key); !exists {
		n.logger.Log(
			"msg", "creating nodeInformer",
			"kluster", key,
			"v", 2,
		)
		nodeInformer, err := newNodeInformerForKluster(n.clientFactory, kluster)
		if err != nil {
			return err
		}

		nodeInformer.SharedIndexInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
			AddFunc: func(obj interface{}) {
				if kluster, err = n.getKlusterByKey(key); err != nil {
					n.logger.Log(err)
					return
				}
				n.handlersMux.RLock()
				defer n.handlersMux.RUnlock()
				for _, addHandler := range n.addEventHandlers {
					addHandler(kluster, obj.(*core_v1.Node))
				}
			},
			UpdateFunc: func(oldObj, newObj interface{}) {
				if kluster, err = n.getKlusterByKey(key); err != nil {
					n.logger.Log(
						"err", err,
						"v", 2,
					)
					return
				}
				n.handlersMux.RLock()
				defer n.handlersMux.RUnlock()
				for _, updateHandler := range n.updateEventHandlers {
					updateHandler(kluster, oldObj.(*core_v1.Node), newObj.(*core_v1.Node))
				}
			},
			DeleteFunc: func(obj interface{}) {
				if kluster, err = n.getKlusterByKey(key); err != nil {
					n.logger.Log(
						"err", err,
						"v", 2,
					)
					return
				}
				n.handlersMux.RLock()
				defer n.handlersMux.RUnlock()
				for _, deleteHandler := range n.deleteEventHandlers {
					deleteHandler(kluster, obj.(*core_v1.Node))
				}
			},
		})

		n.nodeInformerMap.Store(
			key,
			nodeInformer,
		)

		go func(informer *NodeInformer) {
			ch := make(chan struct{})

			go func() {
				informer.run()
				close(ch)
			}()

			select {
			case <-ch:
			case <-n.stopCh:
				informer.close()
			}

		}(nodeInformer)

	}
	return nil
}

func (n *NodeObservatory) getKlusterByKey(key string) (*v1.Kluster, error) {
	o, exists, err := n.klusterInformer.Informer().GetIndexer().GetByKey(key)
	if err != nil {
		return nil, err
	}
	if !exists {
		return nil, fmt.Errorf("kluster %v was not found", key)
	}
	return o.(*v1.Kluster), err
}

func (n *NodeObservatory) klusterAddFunc(obj interface{}) {
	key, err := cache.MetaNamespaceKeyFunc(obj)
	if err == nil {
		n.queue.Add(key)
	}
}

func (n *NodeObservatory) klusterUpdateFunc(cur, old interface{}) {
	key, err := cache.MetaNamespaceKeyFunc(cur)
	if err == nil {
		n.queue.Add(key)
	}
}

func (n *NodeObservatory) klusterDeleteFunc(obj interface{}) {
	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
	if err == nil {
		n.queue.Add(key)
	}
}

// GetStoreForKluster returns the SharedIndexInformers cache.Store for a given kluster or an error
func (n *NodeObservatory) GetListerForKluster(kluster *v1.Kluster) (listers_core_v1.NodeLister, error) {
	informer, err := n.getNodeInformerForKluster(kluster)
	if err != nil {
		return nil, err
	}

	if err := wait.PollImmediate(100*time.Millisecond, 5*time.Second, func() (bool, error) { return informer.HasSynced(), nil }); err != nil {
		return nil, errors.New("Node cache not synced")
	}
	return listers_core_v1.NewNodeLister(informer.GetIndexer()), nil
}

func (n *NodeObservatory) getNodeInformerForKluster(kluster *v1.Kluster) (*NodeInformer, error) {
	key, err := cache.MetaNamespaceKeyFunc(kluster)
	if err != nil {
		return nil, err
	}
	informer, ok := n.nodeInformerMap.Load(key)
	if ok {
		return informer.(*NodeInformer), nil
	}
	return nil, fmt.Errorf("no informer found for kluster %v", key)
}

// HasSyncedForKluster returns true if the store of the kluster's SharedIndexInformer has synced.
func (n *NodeObservatory) HasSyncedForKluster(kluster *v1.Kluster) bool {
	key, err := cache.MetaNamespaceKeyFunc(kluster)
	if err != nil {
		return false
	}
	informer, ok := n.nodeInformerMap.Load(key)
	if ok {
		return false
	}
	return informer.(*NodeInformer).HasSynced()
}

// AddEventHandlerFuncs adds event handlers to the SharedIndexInformer
func (n *NodeObservatory) AddEventHandlerFuncs(handlers NodeEventHandlerFuncs) {
	n.handlersMux.Lock()
	defer n.handlersMux.Unlock()

	if handlers.AddFunc != nil {
		n.addEventHandlers = append(n.addEventHandlers, handlers.AddFunc)
	}
	if handlers.UpdateFunc != nil {
		n.updateEventHandlers = append(n.updateEventHandlers, handlers.UpdateFunc)
	}
	if handlers.DeleteFunc != nil {
		n.deleteEventHandlers = append(n.deleteEventHandlers, handlers.DeleteFunc)
	}
}
<commit_msg>Fix panic<commit_after>package nodeobservatory

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/go-kit/kit/log"
	core_v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/util/wait"
	listers_core_v1 "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"

	"github.com/sapcc/kubernikus/pkg/api/models"
	v1 "github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
	kube "github.com/sapcc/kubernikus/pkg/client/kubernetes"
	kubernikus_informers_v1 "github.com/sapcc/kubernikus/pkg/generated/informers/externalversions/kubernikus/v1"
)

const (
	BaseDelay              = 5 * time.Second
	MaxDelay               = 300 * time.Second
	KlusterRecheckInterval = 5 * time.Minute
)

type (
	AddFunc    func(kluster *v1.Kluster, node *core_v1.Node)
	UpdateFunc func(kluster *v1.Kluster, nodeCur, nodeOld *core_v1.Node)
	DeleteFunc func(kluster *v1.Kluster, node *core_v1.Node)

	NodeEventHandlerFuncs struct {
		AddFunc    AddFunc
		UpdateFunc UpdateFunc
		DeleteFunc DeleteFunc
	}

	NodeObservatory struct {
		clientFactory       kube.SharedClientFactory
		klusterInformer     kubernikus_informers_v1.KlusterInformer
		namespace           string
		queue               workqueue.RateLimitingInterface
		logger              log.Logger
		nodeInformerMap     sync.Map
		handlersMux         sync.RWMutex
		addEventHandlers    []AddFunc
		updateEventHandlers []UpdateFunc
		deleteEventHandlers []DeleteFunc
		stopCh              <-chan struct{}
		threadiness         int
	}
)

func NewController(informer kubernikus_informers_v1.KlusterInformer, factory kube.SharedClientFactory, logger log.Logger, threadiness int) *NodeObservatory {
	logger = log.With(logger,
		"controller", "nodeobservatory",
		"threadiness", threadiness,
	)

	controller := &NodeObservatory{
		clientFactory:   factory,
		klusterInformer: informer,
		queue:           workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(BaseDelay, MaxDelay), "nodeobservatory"),
		logger:          logger,
		nodeInformerMap: sync.Map{},
		threadiness:     threadiness,
	}

	informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    controller.klusterAddFunc,
		UpdateFunc: controller.klusterUpdateFunc,
		DeleteFunc: controller.klusterDeleteFunc,
	})

	return controller
}

func (n *NodeObservatory) Run(stopCh <-chan struct{}) {
	n.logger.Log(
		"msg", "starting run loop",
		"v", 2,
	)

	n.stopCh = stopCh

	defer n.queue.ShutDown()

	for i := 0; i < n.threadiness; i++ {
		go wait.Until(n.runWorker, time.Second, stopCh)
	}

	ticker := time.NewTicker(KlusterRecheckInterval)
	go func() {
		for {
			select {
			case <-ticker.C:
				n.requeueAllKlusters()
			case <-stopCh:
				ticker.Stop()
				return
			}
		}
	}()
	<-stopCh
}

func (n *NodeObservatory) requeueAllKlusters() (err error) {
	defer func() {
		n.logger.Log(
			"msg", "requeued all",
			"v", 1,
			"err", err,
		)
	}()

	klusters, err := n.klusterInformer.Lister().Klusters(n.namespace).List(labels.Everything())
	if err != nil {
		return err
	}

	for _, kluster := range klusters {
		n.requeueKluster(kluster)
	}

	return nil
}

func (n *NodeObservatory) requeueKluster(kluster *v1.Kluster) {
	n.logger.Log(
		"msg", "queuing",
		"kluster", kluster.Spec.Name,
		"project", kluster.Account(),
		"v", 2,
	)

	key, err := cache.MetaNamespaceKeyFunc(kluster)
	if err == nil {
		n.queue.Add(key)
	}
}

func (n *NodeObservatory) runWorker() {
	for n.processNextWorkItem() {
	}
}

func (n *NodeObservatory) processNextWorkItem() bool {
	key, quit := n.queue.Get()
	if quit {
		return false
	}
	defer n.queue.Done(key)

	var kluster *v1.Kluster

	if obj, exists, _ := n.klusterInformer.Informer().GetIndexer().GetByKey(key.(string)); exists {
		kluster = obj.(*v1.Kluster)
	}

	// Invoke the method containing the business logic
	err := n.reconcile(kluster)

	if err == nil {
		n.queue.Forget(key)
		return true
	}

	if n.queue.NumRequeues(key) < 5 {
		// Re-enqueue the key rate limited. Based on the rate limiter on the
		// queue and the re-enqueue history, the key will be processed later again.
		n.queue.AddRateLimited(key)
		return true
	}

	// Retries exceeded. Forgetting for this reconciliation loop
	n.queue.Forget(key)
	return true
}

func (n *NodeObservatory) reconcile(kluster *v1.Kluster) error {

	n.cleanUpInformers()

	if kluster != nil && (kluster.Status.Phase == models.KlusterPhaseRunning || kluster.Status.Phase == models.KlusterPhaseUpgrading || kluster.Status.Phase == models.KlusterPhaseTerminating) {
		if err := n.createAndWatchNodeInformerForKluster(kluster); err != nil {
			return err
		}
	}

	return nil
}

func (n *NodeObservatory) cleanUpInformers() {
	n.nodeInformerMap.Range(
		func(key, value interface{}) bool {
			if _, exists, _ := n.klusterInformer.Informer().GetIndexer().GetByKey(key.(string)); !exists {
				if i, ok := n.nodeInformerMap.Load(key); ok {
					informer := i.(*NodeInformer)
					informer.close()
				}
				n.nodeInformerMap.Delete(key)
			}
			return true
		},
	)
}

func (n *NodeObservatory) createAndWatchNodeInformerForKluster(kluster *v1.Kluster) error {
	key, err := cache.MetaNamespaceKeyFunc(kluster)
	if err != nil {
		return err
	}

	if _, exists := n.nodeInformerMap.Load(key); !exists {
		n.logger.Log(
			"msg", "creating nodeInformer",
			"kluster", key,
			"v", 2,
		)
		nodeInformer, err := newNodeInformerForKluster(n.clientFactory, kluster)
		if err != nil {
			return err
		}

		nodeInformer.SharedIndexInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
			AddFunc: func(obj interface{}) {
				if kluster, err = n.getKlusterByKey(key); err != nil {
					n.logger.Log(err)
					return
				}
				n.handlersMux.RLock()
				defer n.handlersMux.RUnlock()
				for _, addHandler := range n.addEventHandlers {
					addHandler(kluster, obj.(*core_v1.Node))
				}
			},
			UpdateFunc: func(oldObj, newObj interface{}) {
				if kluster, err = n.getKlusterByKey(key); err != nil {
					n.logger.Log(
						"err", err,
						"v", 2,
					)
					return
				}
				n.handlersMux.RLock()
				defer n.handlersMux.RUnlock()
				for _, updateHandler := range n.updateEventHandlers {
					updateHandler(kluster, oldObj.(*core_v1.Node), newObj.(*core_v1.Node))
				}
			},
			DeleteFunc: func(obj interface{}) {
				if kluster, err = n.getKlusterByKey(key); err != nil {
					n.logger.Log(
						"err", err,
						"v", 2,
					)
					return
				}
				n.handlersMux.RLock()
				defer n.handlersMux.RUnlock()
				for _, deleteHandler := range n.deleteEventHandlers {
					node, ok := obj.(*core_v1.Node)
					if !ok {
						tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
						if !ok {
							n.logger.Log("obj", fmt.Sprintf("%v", obj), "err", "unexpected object type")
							return
						}
						if node, ok = tombstone.Obj.(*core_v1.Node); !ok {
							n.logger.Log("obj", fmt.Sprintf("%v", tombstone.Obj), "err", "unexpected object type in tombstone.Obj")
							return
						}
					}

					deleteHandler(kluster, node)
				}
			},
		})

		n.nodeInformerMap.Store(
			key,
			nodeInformer,
		)

		go func(informer *NodeInformer) {
			ch := make(chan struct{})

			go func() {
				informer.run()
				close(ch)
			}()

			select {
			case <-ch:
			case <-n.stopCh:
				informer.close()
			}

		}(nodeInformer)

	}
	return nil
}

func (n *NodeObservatory) getKlusterByKey(key string) (*v1.Kluster, error) {
	o, exists, err := n.klusterInformer.Informer().GetIndexer().GetByKey(key)
	if err != nil {
		return nil, err
	}
	if !exists {
		return nil, fmt.Errorf("kluster %v was not found", key)
	}
	return o.(*v1.Kluster), err
}

func (n *NodeObservatory) klusterAddFunc(obj interface{}) {
	key, err := cache.MetaNamespaceKeyFunc(obj)
	if err == nil {
		n.queue.Add(key)
	}
}

func (n *NodeObservatory) klusterUpdateFunc(cur, old interface{}) {
	key, err := cache.MetaNamespaceKeyFunc(cur)
	if err == nil {
		n.queue.Add(key)
	}
}

func (n *NodeObservatory) klusterDeleteFunc(obj interface{}) {
	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
	if err == nil {
		n.queue.Add(key)
	}
}

// GetStoreForKluster returns the SharedIndexInformers cache.Store for a given kluster or an error
func (n *NodeObservatory) GetListerForKluster(kluster *v1.Kluster) (listers_core_v1.NodeLister, error) {
	informer, err := n.getNodeInformerForKluster(kluster)
	if err != nil {
		return nil, err
	}

	if err := wait.PollImmediate(100*time.Millisecond, 5*time.Second, func() (bool, error) { return informer.HasSynced(), nil }); err != nil {
		return nil, errors.New("Node cache not synced")
	}
	return listers_core_v1.NewNodeLister(informer.GetIndexer()), nil
}

func (n *NodeObservatory) getNodeInformerForKluster(kluster *v1.Kluster) (*NodeInformer, error) {
	key, err := cache.MetaNamespaceKeyFunc(kluster)
	if err != nil {
		return nil, err
	}
	informer, ok := n.nodeInformerMap.Load(key)
	if ok {
		return informer.(*NodeInformer), nil
	}
	return nil, fmt.Errorf("no informer found for kluster %v", key)
}

// HasSyncedForKluster returns true if the store of the kluster's SharedIndexInformer has synced.
func (n *NodeObservatory) HasSyncedForKluster(kluster *v1.Kluster) bool {
	key, err := cache.MetaNamespaceKeyFunc(kluster)
	if err != nil {
		return false
	}
	informer, ok := n.nodeInformerMap.Load(key)
	if ok {
		return false
	}
	return informer.(*NodeInformer).HasSynced()
}

// AddEventHandlerFuncs adds event handlers to the SharedIndexInformer
func (n *NodeObservatory) AddEventHandlerFuncs(handlers NodeEventHandlerFuncs) {
	n.handlersMux.Lock()
	defer n.handlersMux.Unlock()

	if handlers.AddFunc != nil {
		n.addEventHandlers = append(n.addEventHandlers, handlers.AddFunc)
	}
	if handlers.UpdateFunc != nil {
		n.updateEventHandlers = append(n.updateEventHandlers, handlers.UpdateFunc)
	}
	if handlers.DeleteFunc != nil {
		n.deleteEventHandlers = append(n.deleteEventHandlers, handlers.DeleteFunc)
	}
}
<|endoftext|>


<commit_before>package xorm_ext

import (
	"fmt"
	"github.com/go-xorm/xorm"
	"github.com/gogap/errors"
	"reflect"

	. "github.com/gogap/xorm_ext/errorcode"
)

var (
	errorType = reflect.TypeOf((*error)(nil)).Elem()
)

const (
	Logic       int = 0
	BeforeLogic int = 1
	AfterLogic  int = 2
	OnError     int = 3
	AfterCommit int = 4
)

const (
	REPO_DEFAULT_ENGINE               = "default"
	REPO_ERR_DEFAULT_ENGINE_NOT_FOUND = "`default` xorm engine not found"
)

type logicFuncs struct {
	BeforeLogic interface{}
	AfterLogic  interface{}
	OnError     interface{}
	Logic       interface{}
	AfterCommit interface{}
}

type TXFunc func(repos []interface{}) (err error)

type DBRepo struct {
	isTransaction bool
	engines       map[string]*xorm.Engine
	defaultEngine *xorm.Engine
	txSession     *xorm.Session
}

func (p *DBRepo) SetEngines(ormEngines map[string]*xorm.Engine) {
	if defaultEngine, exist := ormEngines[REPO_DEFAULT_ENGINE]; exist {
		p.engines = ormEngines
		p.defaultEngine = defaultEngine
	} else {
		panic(REPO_ERR_DEFAULT_ENGINE_NOT_FOUND)
	}
}

func (p *DBRepo) Engines() map[string]*xorm.Engine {
	return p.engines
}

func (p *DBRepo) DefaultEngine() *xorm.Engine {
	return p.defaultEngine
}

func (p *DBRepo) IsTransaction() bool {
	return p.isTransaction
}

func (p *DBRepo) beginTransaction(engineName string) (err error) {
	if p.isTransaction == false {
		p.isTransaction = true
		p.txSession = p.SessionUsing(engineName)
		if p.txSession == nil {
			err = ERR_CREATE_ENGINE_FAILED.New(errors.Params{"engineName": engineName})
			return
		}
	} else {
		err = ERR_DB_TX_ALREADY_BEGINED.New()
		return
	}
	return nil
}

func (p *DBRepo) beginNoTransaction(engineName string) error {
	if p.isTransaction {
		return ERR_CAN_NOT_CONV_TO_NO_TX.New()
	}

	p.txSession = p.SessionUsing(engineName)
	if p.txSession == nil {
		return ERR_CREATE_ENGINE_FAILED.New(errors.Params{"engineName": engineName})
	}

	return nil
}

func (p *DBRepo) commitNoTransaction(txFunc interface{}, engineName string, sessions []*xorm.Session, repos ...interface{}) (err error) {
	if p.isTransaction {
		err = ERR_DB_IS_A_TX.New()
		return
	}

	if p.txSession == nil {
		err = ERR_DB_SESSION_IS_NIL.New()
		return
	}

	defer func() {
		for _, session := range sessions {
			session.Close()
		}
	}()

	funcs := getFuncs(txFunc)

	if funcs.BeforeLogic != nil {
		if _, err = callFunc(funcs.BeforeLogic, funcs, repos); err != nil {
			return
		}
	}

	var values []interface{}
	if funcs.Logic != nil {
		if values, err = callFunc(funcs.Logic, funcs, repos); err != nil {
			return
		}
	}

	if funcs.AfterLogic != nil {
		if values, err = callFunc(funcs.AfterLogic, funcs, repos); err != nil {
			return
		}
	}

	if funcs.AfterCommit != nil {
		if _, err = callFunc(funcs.AfterCommit, funcs, values); err != nil {
			return
		}
	}

	return
}

func (p *DBRepo) commitTransaction(txFunc interface{}, repos ...interface{}) (err error) {
	if !p.isTransaction {
		err = ERR_DB_NOT_A_TX.New()
		return
	}

	if p.txSession == nil {
		err = ERR_DB_SESSION_IS_NIL.New()
		return
	}

	defer p.txSession.Close()

	if txFunc == nil {
		err = ERR_DB_TX_NOFUNC.New()
		return
	}

	isNeedRollBack := true

	if e := p.txSession.Begin(); e != nil {
		err = ERR_DB_TX_CANNOT_BEGIN.New().Append(e)
		return
	}

	defer func() {
		if isNeedRollBack == true {
			p.txSession.Rollback()
		}
		return
	}()

	funcs := getFuncs(txFunc)

	if funcs.BeforeLogic != nil {
		if _, err = callFunc(funcs.BeforeLogic, funcs, repos); err != nil {
			return
		}
	}

	var values []interface{}

	if funcs.Logic != nil {
		if values, err = callFunc(funcs.Logic, funcs, repos); err != nil {
			return
		}
	}

	if funcs.AfterLogic != nil {
		if values, err = callFunc(funcs.AfterLogic, funcs, repos); err != nil {
			return
		}
	}

	isNeedRollBack = false
	if err = p.txSession.Commit(); err != nil {
		err = ERR_DB_TX_COMMIT_ERROR.New()
		return
	}

	if funcs.AfterCommit != nil {
		if _, err = callFunc(funcs.AfterCommit, funcs, values); err != nil {
			return
		}
	}

	return
}

func (p *DBRepo) Session() *xorm.Session {
	return p.txSession
}

func (p *DBRepo) NewSession() *xorm.Session {
	return p.defaultEngine.NewSession()
}

func (p *DBRepo) SessionUsing(engineName string) *xorm.Session {
	if engine, exist := p.engines[engineName]; exist {
		return engine.NewSession()
	}
	return nil
}

func getFuncs(fn interface{}) (funcs logicFuncs) {
	switch fn := fn.(type) {
	case TXFunc:
		{
			funcs.Logic = fn
		}
	case map[int]interface{}:
		{
			if hookBeforefn, exist := fn[BeforeLogic]; exist { //hook before
				funcs.BeforeLogic = hookBeforefn
			}

			if logicfn, exist := fn[Logic]; exist {
				funcs.Logic = logicfn
			}

			if hookAfterfn, exist := fn[AfterLogic]; exist { //hook after logic func
				funcs.AfterLogic = hookAfterfn
			}

			if errfn, exist := fn[OnError]; exist { //error callback
				funcs.OnError = errfn
			}

			if afterCommitfn, exist := fn[AfterCommit]; exist { //correct callback
				funcs.AfterCommit = afterCommitfn
			}
		}
	default:
		funcs.Logic = fn
	}

	return
}

func callFunc(fn interface{}, funcs logicFuncs, args []interface{}) (values []interface{}, err error) {
	if fn == nil {
		return
	}

	if values, err = call(fn, args...); err != nil {
		if funcs.OnError != nil {
			call(funcs.OnError, err)
		}
	}

	return
}

func call(fn interface{}, args ...interface{}) ([]interface{}, error) {
	v := reflect.ValueOf(fn)
	if !v.IsValid() {
		return nil, fmt.Errorf("call of nil")
	}
	typ := v.Type()
	if typ.Kind() != reflect.Func {
		return nil, fmt.Errorf("non-function of type %s", typ)
	}
	if !goodFunc(typ) {
		return nil, fmt.Errorf("the last return value should be an error type")
	}
	numIn := typ.NumIn()
	var dddType reflect.Type
	if typ.IsVariadic() {
		if len(args) < numIn-1 {
			return nil, fmt.Errorf("wrong number of args: got %d want at least %d, type: %v", len(args), numIn-1, typ)
		}
		dddType = typ.In(numIn - 1).Elem()
	} else {
		if len(args) != numIn {
			return nil, fmt.Errorf("wrong number of args: got %d want %d, type: %v", len(args), numIn, typ)
		}
	}
	argv := make([]reflect.Value, len(args))
	for i, arg := range args {
		value := reflect.ValueOf(arg)
		// Compute the expected type. Clumsy because of variadics.
		var argType reflect.Type
		if !typ.IsVariadic() || i < numIn-1 {
			argType = typ.In(i)
		} else {
			argType = dddType
		}

		var err error
		if argv[i], err = prepareArg(value, argType); err != nil {
			return nil, fmt.Errorf("arg %d: %s", i, err)
		}
	}

	result := v.Call(argv)
	resultLen := len(result)

	var resultValues []interface{}

	for _, v := range result {
		resultValues = append(resultValues, v.Interface())
	}

	if resultLen == 1 {
		if resultValues[0] != nil {
			return nil, resultValues[0].(error)
		}
	} else if resultLen > 1 {
		if resultValues[resultLen-1] != nil {
			return resultValues[0 : resultLen-1], resultValues[resultLen-1].(error)
		} else {
			return resultValues[0 : resultLen-1], nil
		}
	}

	return nil, nil
}

func goodFunc(typ reflect.Type) bool {
	if typ.NumOut() > 0 && typ.Out(typ.NumOut()-1) == errorType {
		return true
	} else if typ.NumOut() == 0 {
		return true
	}

	return false
}

func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error) {
	if !value.IsValid() {
		if !canBeNil(argType) {
			return reflect.Value{}, fmt.Errorf("value is nil; should be of type %s", argType)
		}
		value = reflect.Zero(argType)
	}
	if !value.Type().AssignableTo(argType) {
		return reflect.Value{}, fmt.Errorf("value has type %s; should be %s", value.Type(), argType)
	}
	return value, nil
}

func canBeNil(typ reflect.Type) bool {
	switch typ.Kind() {
	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
		return true
	}
	return false
}
<commit_msg>fix old version compatible issue<commit_after>package xorm_ext

import (
	"fmt"
	"github.com/go-xorm/xorm"
	"github.com/gogap/errors"
	"reflect"

	. "github.com/gogap/xorm_ext/errorcode"
)

var (
	errorType = reflect.TypeOf((*error)(nil)).Elem()
)

const (
	Logic       int = 0
	BeforeLogic int = 1
	AfterLogic  int = 2
	OnError     int = 3
	AfterCommit int = 4
)

const (
	REPO_DEFAULT_ENGINE               = "default"
	REPO_ERR_DEFAULT_ENGINE_NOT_FOUND = "`default` xorm engine not found"
)

type logicFuncs struct {
	BeforeLogic interface{}
	AfterLogic  interface{}
	OnError     interface{}
	Logic       interface{}
	AfterCommit interface{}
}

type TXFunc func(repos []interface{}) (err error)

type DBRepo struct {
	isTransaction bool
	engines       map[string]*xorm.Engine
	defaultEngine *xorm.Engine
	txSession     *xorm.Session
}

func (p *DBRepo) SetEngines(ormEngines map[string]*xorm.Engine) {
	if defaultEngine, exist := ormEngines[REPO_DEFAULT_ENGINE]; exist {
		p.engines = ormEngines
		p.defaultEngine = defaultEngine
	} else {
		panic(REPO_ERR_DEFAULT_ENGINE_NOT_FOUND)
	}
}

func (p *DBRepo) Engines() map[string]*xorm.Engine {
	return p.engines
}

func (p *DBRepo) DefaultEngine() *xorm.Engine {
	return p.defaultEngine
}

func (p *DBRepo) IsTransaction() bool {
	return p.isTransaction
}

func (p *DBRepo) beginTransaction(engineName string) (err error) {
	if p.isTransaction == false {
		p.isTransaction = true
		p.txSession = p.SessionUsing(engineName)
		if p.txSession == nil {
			err = ERR_CREATE_ENGINE_FAILED.New(errors.Params{"engineName": engineName})
			return
		}
	} else {
		err = ERR_DB_TX_ALREADY_BEGINED.New()
		return
	}
	return nil
}

func (p *DBRepo) beginNoTransaction(engineName string) error {
	if p.isTransaction {
		return ERR_CAN_NOT_CONV_TO_NO_TX.New()
	}

	p.txSession = p.SessionUsing(engineName)
	if p.txSession == nil {
		return ERR_CREATE_ENGINE_FAILED.New(errors.Params{"engineName": engineName})
	}

	return nil
}

func (p *DBRepo) commitNoTransaction(txFunc interface{}, engineName string, sessions []*xorm.Session, repos ...interface{}) (err error) {
	if p.isTransaction {
		err = ERR_DB_IS_A_TX.New()
		return
	}

	if p.txSession == nil {
		err = ERR_DB_SESSION_IS_NIL.New()
		return
	}

	defer func() {
		for _, session := range sessions {
			session.Close()
		}
	}()

	funcs := getFuncs(txFunc)

	if funcs.BeforeLogic != nil {
		if _, err = callFunc(funcs.BeforeLogic, funcs, repos); err != nil {
			return
		}
	}

	var values []interface{}
	if funcs.Logic != nil {
		if values, err = callFunc(funcs.Logic, funcs, repos); err != nil {
			return
		}
	}

	if funcs.AfterLogic != nil {
		if values, err = callFunc(funcs.AfterLogic, funcs, repos); err != nil {
			return
		}
	}

	if funcs.AfterCommit != nil {
		if _, err = callFunc(funcs.AfterCommit, funcs, values); err != nil {
			return
		}
	}

	return
}

func (p *DBRepo) commitTransaction(txFunc interface{}, repos ...interface{}) (err error) {
	if !p.isTransaction {
		err = ERR_DB_NOT_A_TX.New()
		return
	}

	if p.txSession == nil {
		err = ERR_DB_SESSION_IS_NIL.New()
		return
	}

	defer p.txSession.Close()

	if txFunc == nil {
		err = ERR_DB_TX_NOFUNC.New()
		return
	}

	isNeedRollBack := true

	if e := p.txSession.Begin(); e != nil {
		err = ERR_DB_TX_CANNOT_BEGIN.New().Append(e)
		return
	}

	defer func() {
		if isNeedRollBack == true {
			p.txSession.Rollback()
		}
		return
	}()

	funcs := getFuncs(txFunc)

	if funcs.BeforeLogic != nil {
		if _, err = callFunc(funcs.BeforeLogic, funcs, repos); err != nil {
			return
		}
	}

	var values []interface{}

	if funcs.Logic != nil {
		if values, err = callFunc(funcs.Logic, funcs, repos); err != nil {
			return
		}
	}

	if funcs.AfterLogic != nil {
		if values, err = callFunc(funcs.AfterLogic, funcs, repos); err != nil {
			return
		}
	}

	isNeedRollBack = false
	if err = p.txSession.Commit(); err != nil {
		err = ERR_DB_TX_COMMIT_ERROR.New()
		return
	}

	if funcs.AfterCommit != nil {
		if _, err = callFunc(funcs.AfterCommit, funcs, values); err != nil {
			return
		}
	}

	return
}

func (p *DBRepo) Session() *xorm.Session {
	return p.txSession
}

func (p *DBRepo) NewSession() *xorm.Session {
	return p.defaultEngine.NewSession()
}

func (p *DBRepo) SessionUsing(engineName string) *xorm.Session {
	if engine, exist := p.engines[engineName]; exist {
		return engine.NewSession()
	}
	return nil
}

func getFuncs(fn interface{}) (funcs logicFuncs) {
	switch fn := fn.(type) {
	case TXFunc, func(repos []interface{}) (err error):
		{
			funcs.Logic = fn
		}
	case map[int]interface{}:
		{
			if hookBeforefn, exist := fn[BeforeLogic]; exist { //hook before
				funcs.BeforeLogic = hookBeforefn
			}

			if logicfn, exist := fn[Logic]; exist {
				funcs.Logic = logicfn
			}

			if hookAfterfn, exist := fn[AfterLogic]; exist { //hook after logic func
				funcs.AfterLogic = hookAfterfn
			}

			if errfn, exist := fn[OnError]; exist { //error callback
				funcs.OnError = errfn
			}

			if afterCommitfn, exist := fn[AfterCommit]; exist { //correct callback
				funcs.AfterCommit = afterCommitfn
			}
		}
	default:
		funcs.Logic = fn
	}

	return
}

func callFunc(fn interface{}, funcs logicFuncs, args []interface{}) (values []interface{}, err error) {
	if fn == nil {
		return
	}

	switch logicFunc := fn.(type) {
	case TXFunc:
		{
			err = logicFunc(args)
		}
	case func(repos []interface{}) (err error):
		{
			err = logicFunc(args)
		}
	default:
		if values, err = call(fn, args...); err != nil {
			if funcs.OnError != nil {
				call(funcs.OnError, err)
			}
		}

	}

	return
}

func call(fn interface{}, args ...interface{}) ([]interface{}, error) {
	v := reflect.ValueOf(fn)
	if !v.IsValid() {
		return nil, fmt.Errorf("call of nil")
	}
	typ := v.Type()
	if typ.Kind() != reflect.Func {
		return nil, fmt.Errorf("non-function of type %s", typ)
	}
	if !goodFunc(typ) {
		return nil, fmt.Errorf("the last return value should be an error type")
	}
	numIn := typ.NumIn()
	var dddType reflect.Type
	if typ.IsVariadic() {
		if len(args) < numIn-1 {
			return nil, fmt.Errorf("wrong number of args: got %d want at least %d, type: %v", len(args), numIn-1, typ)
		}
		dddType = typ.In(numIn - 1).Elem()
	} else {
		if len(args) != numIn {
			return nil, fmt.Errorf("wrong number of args: got %d want %d, type: %v", len(args), numIn, typ)
		}
	}
	argv := make([]reflect.Value, len(args))
	for i, arg := range args {
		value := reflect.ValueOf(arg)
		// Compute the expected type. Clumsy because of variadics.
		var argType reflect.Type
		if !typ.IsVariadic() || i < numIn-1 {
			argType = typ.In(i)
		} else {
			argType = dddType
		}

		var err error
		if argv[i], err = prepareArg(value, argType); err != nil {
			return nil, fmt.Errorf("arg %d: %s", i, err)
		}
	}

	result := v.Call(argv)
	resultLen := len(result)

	var resultValues []interface{}

	for _, v := range result {
		resultValues = append(resultValues, v.Interface())
	}

	if resultLen == 1 {
		if resultValues[0] != nil {
			return nil, resultValues[0].(error)
		}
	} else if resultLen > 1 {
		if resultValues[resultLen-1] != nil {
			return resultValues[0 : resultLen-1], resultValues[resultLen-1].(error)
		} else {
			return resultValues[0 : resultLen-1], nil
		}
	}

	return nil, nil
}

func goodFunc(typ reflect.Type) bool {
	if typ.NumOut() > 0 && typ.Out(typ.NumOut()-1) == errorType {
		return true
	} else if typ.NumOut() == 0 {
		return true
	}

	return false
}

func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error) {
	if !value.IsValid() {
		if !canBeNil(argType) {
			return reflect.Value{}, fmt.Errorf("value is nil; should be of type %s", argType)
		}
		value = reflect.Zero(argType)
	}
	if !value.Type().AssignableTo(argType) {
		return reflect.Value{}, fmt.Errorf("value has type %s; should be %s", value.Type(), argType)
	}
	return value, nil
}

func canBeNil(typ reflect.Type) bool {
	switch typ.Kind() {
	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package hostdb

import (
	"bytes"
	"errors"
	"net"
	"time"

	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/encoding"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/types"
)

// An Uploader uploads data to a host.
type Uploader interface {
	// Upload revises the underlying contract to store the new data. It
	// returns the offset of the data in the stored file.
	Upload(data []byte) (offset uint64, err error)

	// Address returns the address of the host.
	Address() modules.NetAddress

	// ContractID returns the FileContractID of the contract.
	ContractID() types.FileContractID

	// EndHeight returns the height at which the contract ends.
	EndHeight() types.BlockHeight

	// Close terminates the connection to the uploader.
	Close() error
}

// A hostUploader uploads pieces to a host. It implements the uploader
// interface. hostUploaders are NOT thread-safe; calls to Upload must happen
// in serial.
type hostUploader struct {
	// constants
	price types.Currency

	// updated after each revision
	tree     crypto.MerkleTree
	contract hostContract // only lastTxn is updated

	// resources
	conn net.Conn
	hdb  *HostDB
}

// Address returns the NetAddress of the host.
func (hu *hostUploader) Address() modules.NetAddress { return hu.contract.IP }

// ContractID returns the ID of the contract being revised.
func (hu *hostUploader) ContractID() types.FileContractID { return hu.contract.ID }

// EndHeight returns the height at which the host is no longer obligated to
// store the file.
func (hu *hostUploader) EndHeight() types.BlockHeight { return hu.contract.FileContract.WindowStart }

// Close cleanly ends the revision process with the host, closes the
// connection, and submits the last revision to the transaction pool.
func (hu *hostUploader) Close() error {
	// send an empty revision to indicate that we are finished
	encoding.WriteObject(hu.conn, types.Transaction{})
	hu.conn.Close()
	// submit the most recent revision to the blockchain
	err := hu.hdb.tpool.AcceptTransactionSet([]types.Transaction{hu.contract.LastRevisionTxn})
	if err != nil && err != modules.ErrDuplicateTransactionSet {
		hu.hdb.log.Println("WARN: transaction pool rejected revision transaction:", err)
	}
	return err
}

// Upload revises an existing file contract with a host, and then uploads a
// piece to it.
func (hu *hostUploader) Upload(data []byte) (uint64, error) {
	// offset is old filesize
	offset := hu.contract.LastRevision.NewFileSize

	// calculate price
	hu.hdb.mu.RLock()
	height := hu.hdb.blockHeight
	hu.hdb.mu.RUnlock()
	if height > hu.contract.FileContract.WindowStart {
		return 0, errors.New("contract has already ended")
	}
	piecePrice := types.NewCurrency64(uint64(len(data))).Mul(types.NewCurrency64(uint64(hu.contract.FileContract.WindowStart - height))).Mul(hu.price)
	piecePrice = piecePrice.MulFloat(1.02) // COMPATv0.4.8 -- hosts reject exact prices

	// calculate new merkle root (no error possible with bytes.Reader)
	_ = hu.tree.ReadSegments(bytes.NewReader(data))
	merkleRoot := hu.tree.Root()

	// revise the file contract
	rev := newRevision(hu.contract.LastRevision, uint64(len(data)), merkleRoot, piecePrice)
	signedTxn, err := negotiateRevision(hu.conn, rev, data, hu.contract.SecretKey)
	if err != nil {
		return 0, err
	}

	// update host contract
	hu.contract.LastRevision = rev
	hu.contract.LastRevisionTxn = signedTxn
	hu.hdb.mu.Lock()
	hu.hdb.contracts[hu.contract.ID] = hu.contract
	hu.hdb.save()
	hu.hdb.mu.Unlock()

	return offset, nil
}

// newHostUploader initiates the contract revision process with a host, and
// returns a hostUploader, which satisfies the Uploader interface.
func (hdb *HostDB) newHostUploader(hc hostContract) (*hostUploader, error) {
	hdb.mu.RLock()
	settings, ok := hdb.allHosts[hc.IP] // or activeHosts?
	hdb.mu.RUnlock()
	if !ok {
		return nil, errors.New("no record of that host")
	}
	// TODO: check for excessive price again?

	// initiate revision loop
	conn, err := hdb.dialer.DialTimeout(hc.IP, 15*time.Second)
	if err != nil {
		return nil, err
	}
	if err := encoding.WriteObject(conn, modules.RPCRevise); err != nil {
		return nil, err
	}
	if err := encoding.WriteObject(conn, hc.ID); err != nil {
		return nil, err
	}
	// TODO: some sort of acceptance would be good here, so that we know the
	// uploader will actually work. Maybe send the Merkle root?

	hu := &hostUploader{
		contract: hc,
		price:    settings.Price,

		tree: crypto.NewTree(),

		conn: conn,
		hdb:  hdb,
	}

	return hu, nil
}

// A HostPool is a collection of hosts used to upload a file.
type HostPool interface {
	// UniqueHosts will return up to 'n' unique hosts that are not in 'old'.
	UniqueHosts(n int, old []modules.NetAddress) []Uploader

	// Close terminates all connections in the host pool.
	Close() error
}

// A pool is a collection of hostUploaders that satisfies the HostPool
// interface. New hosts are drawn from a HostDB, and contracts are negotiated
// with them on demand.
type pool struct {
	// details of the contracts to be formed
	filesize uint64
	duration types.BlockHeight

	hosts     []*hostUploader
	blacklist []modules.NetAddress
	hdb       *HostDB
}

// Close closes all of the pool's open host connections, and submits their
// respective contract revisions to the transaction pool.
func (p *pool) Close() error {
	for _, h := range p.hosts {
		h.Close()
	}
	return nil
}

// UniqueHosts will return up to 'n' unique hosts that are not in 'exclude'.
// The pool draws from its set of active connections first, and then negotiates
// new contracts if more hosts are required. Note that this latter case
// requires network I/O, so the caller should always assume that UniqueHosts
// will block.
func (p *pool) UniqueHosts(n int, exclude []modules.NetAddress) (hosts []Uploader) {
	if n == 0 {
		return
	}

	// First reuse existing connections.
outer:
	for _, h := range p.hosts {
		for _, ip := range exclude {
			if h.Address() == ip {
				continue outer
			}
		}
		hosts = append(hosts, h)
		if len(hosts) >= n {
			return hosts
		}
	}

	// Extend the exclude set with the hosts on the pool's blacklist and the
	// hosts we're already connected to.
	exclude = append(exclude, p.blacklist...)
	for _, h := range p.hosts {
		exclude = append(exclude, h.Address())
	}

	// Ask the hostdb for random hosts. We always ask for at least 10, to
	// avoid selecting the same uncooperative hosts over and over.
	ask := n * 2
	if ask < 10 {
		ask = 10
	}
	p.hdb.mu.Lock()
	randHosts := p.hdb.randomHosts(ask, exclude)
	p.hdb.mu.Unlock()

	// Form new contracts with the randomly-picked hosts. If a contract can't
	// be formed, add the host to the pool's blacklist.
	var errs []error
	for _, host := range randHosts {
		contract, err := p.hdb.newContract(host, p.filesize, p.duration)
		if err != nil {
			p.blacklist = append(p.blacklist, host.NetAddress)
			errs = append(errs, err)
			continue
		}
		hu, err := p.hdb.newHostUploader(contract)
		if err != nil {
			p.blacklist = append(p.blacklist, host.NetAddress)
			continue
		}
		hosts = append(hosts, hu)
		p.hosts = append(p.hosts, hu)
		if len(hosts) >= n {
			break
		}
	}
	// If all attempts failed, log the error.
	if len(errs) == len(randHosts) && len(errs) > 0 {
		// Log the last error, since early errors are more likely to be
		// host-specific.
		p.hdb.log.Printf("couldn't form any host contracts: %v", errs[len(errs)-1])
	}
	return hosts
}

// NewPool returns an empty HostPool, unless the HostDB contains no hosts at
// all.
func (hdb *HostDB) NewPool(filesize uint64, duration types.BlockHeight) (HostPool, error) {
	hdb.mu.RLock()
	defer hdb.mu.RUnlock()
	if hdb.isEmpty() {
		return nil, errors.New("HostDB is empty")
	}
	return &pool{
		filesize: filesize,
		duration: duration,
		hdb:      hdb,
	}, nil
}
<commit_msg>switch Printf to Println<commit_after>package hostdb

import (
	"bytes"
	"errors"
	"net"
	"time"

	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/encoding"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/types"
)

// An Uploader uploads data to a host.
type Uploader interface {
	// Upload revises the underlying contract to store the new data. It
	// returns the offset of the data in the stored file.
	Upload(data []byte) (offset uint64, err error)

	// Address returns the address of the host.
	Address() modules.NetAddress

	// ContractID returns the FileContractID of the contract.
	ContractID() types.FileContractID

	// EndHeight returns the height at which the contract ends.
	EndHeight() types.BlockHeight

	// Close terminates the connection to the uploader.
	Close() error
}

// A hostUploader uploads pieces to a host. It implements the uploader
// interface. hostUploaders are NOT thread-safe; calls to Upload must happen
// in serial.
type hostUploader struct {
	// constants
	price types.Currency

	// updated after each revision
	tree     crypto.MerkleTree
	contract hostContract // only lastTxn is updated

	// resources
	conn net.Conn
	hdb  *HostDB
}

// Address returns the NetAddress of the host.
func (hu *hostUploader) Address() modules.NetAddress { return hu.contract.IP }

// ContractID returns the ID of the contract being revised.
func (hu *hostUploader) ContractID() types.FileContractID { return hu.contract.ID }

// EndHeight returns the height at which the host is no longer obligated to
// store the file.
func (hu *hostUploader) EndHeight() types.BlockHeight { return hu.contract.FileContract.WindowStart }

// Close cleanly ends the revision process with the host, closes the
// connection, and submits the last revision to the transaction pool.
func (hu *hostUploader) Close() error {
	// send an empty revision to indicate that we are finished
	encoding.WriteObject(hu.conn, types.Transaction{})
	hu.conn.Close()
	// submit the most recent revision to the blockchain
	err := hu.hdb.tpool.AcceptTransactionSet([]types.Transaction{hu.contract.LastRevisionTxn})
	if err != nil && err != modules.ErrDuplicateTransactionSet {
		hu.hdb.log.Println("WARN: transaction pool rejected revision transaction:", err)
	}
	return err
}

// Upload revises an existing file contract with a host, and then uploads a
// piece to it.
func (hu *hostUploader) Upload(data []byte) (uint64, error) {
	// offset is old filesize
	offset := hu.contract.LastRevision.NewFileSize

	// calculate price
	hu.hdb.mu.RLock()
	height := hu.hdb.blockHeight
	hu.hdb.mu.RUnlock()
	if height > hu.contract.FileContract.WindowStart {
		return 0, errors.New("contract has already ended")
	}
	piecePrice := types.NewCurrency64(uint64(len(data))).Mul(types.NewCurrency64(uint64(hu.contract.FileContract.WindowStart - height))).Mul(hu.price)
	piecePrice = piecePrice.MulFloat(1.02) // COMPATv0.4.8 -- hosts reject exact prices

	// calculate new merkle root (no error possible with bytes.Reader)
	_ = hu.tree.ReadSegments(bytes.NewReader(data))
	merkleRoot := hu.tree.Root()

	// revise the file contract
	rev := newRevision(hu.contract.LastRevision, uint64(len(data)), merkleRoot, piecePrice)
	signedTxn, err := negotiateRevision(hu.conn, rev, data, hu.contract.SecretKey)
	if err != nil {
		return 0, err
	}

	// update host contract
	hu.contract.LastRevision = rev
	hu.contract.LastRevisionTxn = signedTxn
	hu.hdb.mu.Lock()
	hu.hdb.contracts[hu.contract.ID] = hu.contract
	hu.hdb.save()
	hu.hdb.mu.Unlock()

	return offset, nil
}

// newHostUploader initiates the contract revision process with a host, and
// returns a hostUploader, which satisfies the Uploader interface.
func (hdb *HostDB) newHostUploader(hc hostContract) (*hostUploader, error) {
	hdb.mu.RLock()
	settings, ok := hdb.allHosts[hc.IP] // or activeHosts?
	hdb.mu.RUnlock()
	if !ok {
		return nil, errors.New("no record of that host")
	}
	// TODO: check for excessive price again?

	// initiate revision loop
	conn, err := hdb.dialer.DialTimeout(hc.IP, 15*time.Second)
	if err != nil {
		return nil, err
	}
	if err := encoding.WriteObject(conn, modules.RPCRevise); err != nil {
		return nil, err
	}
	if err := encoding.WriteObject(conn, hc.ID); err != nil {
		return nil, err
	}
	// TODO: some sort of acceptance would be good here, so that we know the
	// uploader will actually work. Maybe send the Merkle root?

	hu := &hostUploader{
		contract: hc,
		price:    settings.Price,

		tree: crypto.NewTree(),

		conn: conn,
		hdb:  hdb,
	}

	return hu, nil
}

// A HostPool is a collection of hosts used to upload a file.
type HostPool interface {
	// UniqueHosts will return up to 'n' unique hosts that are not in 'old'.
	UniqueHosts(n int, old []modules.NetAddress) []Uploader

	// Close terminates all connections in the host pool.
	Close() error
}

// A pool is a collection of hostUploaders that satisfies the HostPool
// interface. New hosts are drawn from a HostDB, and contracts are negotiated
// with them on demand.
type pool struct {
	// details of the contracts to be formed
	filesize uint64
	duration types.BlockHeight

	hosts     []*hostUploader
	blacklist []modules.NetAddress
	hdb       *HostDB
}

// Close closes all of the pool's open host connections, and submits their
// respective contract revisions to the transaction pool.
func (p *pool) Close() error {
	for _, h := range p.hosts {
		h.Close()
	}
	return nil
}

// UniqueHosts will return up to 'n' unique hosts that are not in 'exclude'.
// The pool draws from its set of active connections first, and then negotiates
// new contracts if more hosts are required. Note that this latter case
// requires network I/O, so the caller should always assume that UniqueHosts
// will block.
func (p *pool) UniqueHosts(n int, exclude []modules.NetAddress) (hosts []Uploader) {
	if n == 0 {
		return
	}

	// First reuse existing connections.
outer:
	for _, h := range p.hosts {
		for _, ip := range exclude {
			if h.Address() == ip {
				continue outer
			}
		}
		hosts = append(hosts, h)
		if len(hosts) >= n {
			return hosts
		}
	}

	// Extend the exclude set with the hosts on the pool's blacklist and the
	// hosts we're already connected to.
	exclude = append(exclude, p.blacklist...)
	for _, h := range p.hosts {
		exclude = append(exclude, h.Address())
	}

	// Ask the hostdb for random hosts. We always ask for at least 10, to
	// avoid selecting the same uncooperative hosts over and over.
	ask := n * 2
	if ask < 10 {
		ask = 10
	}
	p.hdb.mu.Lock()
	randHosts := p.hdb.randomHosts(ask, exclude)
	p.hdb.mu.Unlock()

	// Form new contracts with the randomly-picked hosts. If a contract can't
	// be formed, add the host to the pool's blacklist.
	var errs []error
	for _, host := range randHosts {
		contract, err := p.hdb.newContract(host, p.filesize, p.duration)
		if err != nil {
			p.blacklist = append(p.blacklist, host.NetAddress)
			errs = append(errs, err)
			continue
		}
		hu, err := p.hdb.newHostUploader(contract)
		if err != nil {
			p.blacklist = append(p.blacklist, host.NetAddress)
			continue
		}
		hosts = append(hosts, hu)
		p.hosts = append(p.hosts, hu)
		if len(hosts) >= n {
			break
		}
	}
	// If all attempts failed, log the error.
	if len(errs) == len(randHosts) && len(errs) > 0 {
		// Log the last error, since early errors are more likely to be
		// host-specific.
		p.hdb.log.Println("couldn't form any host contracts:", errs[len(errs)-1])
	}
	return hosts
}

// NewPool returns an empty HostPool, unless the HostDB contains no hosts at
// all.
func (hdb *HostDB) NewPool(filesize uint64, duration types.BlockHeight) (HostPool, error) {
	hdb.mu.RLock()
	defer hdb.mu.RUnlock()
	if hdb.isEmpty() {
		return nil, errors.New("HostDB is empty")
	}
	return &pool{
		filesize: filesize,
		duration: duration,
		hdb:      hdb,
	}, nil
}
<|endoftext|>


<commit_before>package cassandra

import (
	"github.com/golang/glog"
	apiv1 "k8s.io/api/core/v1"
	"k8s.io/client-go/tools/record"

	v1alpha1 "github.com/jetstack/navigator/pkg/apis/navigator/v1alpha1"
	"github.com/jetstack/navigator/pkg/controllers"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/actions"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/nodepool"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/pilot"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/role"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/rolebinding"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/seedlabeller"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/service"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/serviceaccount"
)

const (
	ErrorSync   = "ErrSync"
	SuccessSync = "SuccessSync"

	PauseField = "spec.paused"

	MessageErrorSyncServiceAccount = "Error syncing service account: %s"
	MessageErrorSyncRole           = "Error syncing role: %s"
	MessageErrorSyncRoleBinding    = "Error syncing role binding: %s"
	MessageErrorSyncConfigMap      = "Error syncing config map: %s"
	MessageErrorSyncService        = "Error syncing service: %s"
	MessageErrorSyncNodePools      = "Error syncing node pools: %s"
	MessageErrorSyncPilots         = "Error syncing pilots: %s"
	MessageErrorSyncSeedLabels     = "Error syncing seed labels: %s"
	MessageErrorSync               = "Error syncing: %s"
	MessageSuccessSync             = "Successfully synced CassandraCluster"
	MessageClusterPaused           = "Cluster paused, not syncing"
)

type ControlInterface interface {
	Sync(*v1alpha1.CassandraCluster) error
}

var _ ControlInterface = &defaultCassandraClusterControl{}

type defaultCassandraClusterControl struct {
	seedProviderServiceControl service.Interface
	nodesServiceControl        service.Interface
	nodepoolControl            nodepool.Interface
	pilotControl               pilot.Interface
	serviceAccountControl      serviceaccount.Interface
	roleControl                role.Interface
	roleBindingControl         rolebinding.Interface
	seedLabellerControl        seedlabeller.Interface
	recorder                   record.EventRecorder
	state                      *controllers.State
}

func NewControl(
	seedProviderServiceControl service.Interface,
	nodesServiceControl service.Interface,
	nodepoolControl nodepool.Interface,
	pilotControl pilot.Interface,
	serviceAccountControl serviceaccount.Interface,
	roleControl role.Interface,
	roleBindingControl rolebinding.Interface,
	seedlabellerControl seedlabeller.Interface,
	recorder record.EventRecorder,
	state *controllers.State,
) ControlInterface {
	return &defaultCassandraClusterControl{
		seedProviderServiceControl: seedProviderServiceControl,
		nodesServiceControl:        nodesServiceControl,
		nodepoolControl:            nodepoolControl,
		pilotControl:               pilotControl,
		serviceAccountControl:      serviceAccountControl,
		roleControl:                roleControl,
		roleBindingControl:         roleBindingControl,
		seedLabellerControl:        seedlabellerControl,
		recorder:                   recorder,
		state:                      state,
	}
}

// checkPausedConditions checks if the given cluster is paused or not and adds an appropriate condition.
func (e *defaultCassandraClusterControl) checkPausedConditions(c *v1alpha1.CassandraCluster) error {
	cond := c.Status.GetStatusCondition(v1alpha1.ClusterConditionProgressing)
	pausedCondExists := cond != nil && cond.Reason == v1alpha1.PausedClusterReason

	needsUpdate := false
	if c.Spec.Paused && !pausedCondExists {
		c.Status.UpdateStatusCondition(
			v1alpha1.ClusterConditionProgressing,
			v1alpha1.ConditionUnknown,
			v1alpha1.PausedClusterReason,
			"Cluster is paused",
		)
		needsUpdate = true
	} else if !c.Spec.Paused && pausedCondExists {
		c.Status.UpdateStatusCondition(
			v1alpha1.ClusterConditionProgressing,
			v1alpha1.ConditionUnknown,
			v1alpha1.ResumedClusterReason,
			"Cluster is resumed",
		)
		needsUpdate = true
	}

	if !needsUpdate {
		return nil
	}

	var err error
	c, err = e.state.NavigatorClientset.NavigatorV1alpha1().CassandraClusters(c.Namespace).UpdateStatus(c)
	return err
}

func (e *defaultCassandraClusterControl) Sync(c *v1alpha1.CassandraCluster) error {
	e.checkPausedConditions(c)

	if c.Spec.Paused == true {
		glog.V(4).Infof("defaultCassandraClusterControl.Sync skipped, since cluster is paused")
		return nil
	}

	glog.V(4).Infof("defaultCassandraClusterControl.Sync")
	err := e.seedProviderServiceControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncService,
			err,
		)
		return err
	}
	err = e.nodesServiceControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncService,
			err,
		)
		return err
	}
	err = e.nodepoolControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncNodePools,
			err,
		)
		return err
	}
	err = e.pilotControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncPilots,
			err,
		)
		return err
	}
	err = e.serviceAccountControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncServiceAccount,
			err,
		)
		return err
	}
	err = e.roleControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncRole,
			err,
		)
		return err
	}
	err = e.roleBindingControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncRoleBinding,
			err,
		)
		return err
	}
	err = e.seedLabellerControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncSeedLabels,
			err,
		)
		return err
	}

	a := NextAction(c)
	if a != nil {
		err = a.Execute(e.state)
		if err != nil {
			e.recorder.Eventf(
				c,
				apiv1.EventTypeWarning,
				ErrorSync,
				MessageErrorSync,
				err,
			)
			return err
		}
	}

	e.recorder.Event(
		c,
		apiv1.EventTypeNormal,
		SuccessSync,
		MessageSuccessSync,
	)
	return nil
}

func NextAction(c *v1alpha1.CassandraCluster) controllers.Action {
	for _, np := range c.Spec.NodePools {
		_, found := c.Status.NodePools[np.Name]
		if !found {
			return &actions.CreateNodePool{
				Cluster:  c,
				NodePool: &np,
			}
		}
	}
	for _, np := range c.Spec.NodePools {
		nps := c.Status.NodePools[np.Name]
		if *np.Replicas > nps.ReadyReplicas {
			return &actions.ScaleOut{
				Cluster:  c,
				NodePool: &np,
			}
		}
	}
	return nil
}
<commit_msg>DeepCopy<commit_after>package cassandra

import (
	"github.com/golang/glog"
	apiv1 "k8s.io/api/core/v1"
	"k8s.io/client-go/tools/record"

	v1alpha1 "github.com/jetstack/navigator/pkg/apis/navigator/v1alpha1"
	"github.com/jetstack/navigator/pkg/controllers"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/actions"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/nodepool"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/pilot"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/role"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/rolebinding"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/seedlabeller"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/service"
	"github.com/jetstack/navigator/pkg/controllers/cassandra/serviceaccount"
)

const (
	ErrorSync   = "ErrSync"
	SuccessSync = "SuccessSync"

	PauseField = "spec.paused"

	MessageErrorSyncServiceAccount = "Error syncing service account: %s"
	MessageErrorSyncRole           = "Error syncing role: %s"
	MessageErrorSyncRoleBinding    = "Error syncing role binding: %s"
	MessageErrorSyncConfigMap      = "Error syncing config map: %s"
	MessageErrorSyncService        = "Error syncing service: %s"
	MessageErrorSyncNodePools      = "Error syncing node pools: %s"
	MessageErrorSyncPilots         = "Error syncing pilots: %s"
	MessageErrorSyncSeedLabels     = "Error syncing seed labels: %s"
	MessageErrorSync               = "Error syncing: %s"
	MessageSuccessSync             = "Successfully synced CassandraCluster"
	MessageClusterPaused           = "Cluster paused, not syncing"
)

type ControlInterface interface {
	Sync(*v1alpha1.CassandraCluster) error
}

var _ ControlInterface = &defaultCassandraClusterControl{}

type defaultCassandraClusterControl struct {
	seedProviderServiceControl service.Interface
	nodesServiceControl        service.Interface
	nodepoolControl            nodepool.Interface
	pilotControl               pilot.Interface
	serviceAccountControl      serviceaccount.Interface
	roleControl                role.Interface
	roleBindingControl         rolebinding.Interface
	seedLabellerControl        seedlabeller.Interface
	recorder                   record.EventRecorder
	state                      *controllers.State
}

func NewControl(
	seedProviderServiceControl service.Interface,
	nodesServiceControl service.Interface,
	nodepoolControl nodepool.Interface,
	pilotControl pilot.Interface,
	serviceAccountControl serviceaccount.Interface,
	roleControl role.Interface,
	roleBindingControl rolebinding.Interface,
	seedlabellerControl seedlabeller.Interface,
	recorder record.EventRecorder,
	state *controllers.State,
) ControlInterface {
	return &defaultCassandraClusterControl{
		seedProviderServiceControl: seedProviderServiceControl,
		nodesServiceControl:        nodesServiceControl,
		nodepoolControl:            nodepoolControl,
		pilotControl:               pilotControl,
		serviceAccountControl:      serviceAccountControl,
		roleControl:                roleControl,
		roleBindingControl:         roleBindingControl,
		seedLabellerControl:        seedlabellerControl,
		recorder:                   recorder,
		state:                      state,
	}
}

// checkPausedConditions checks if the given cluster is paused or not and adds an appropriate condition.
func (e *defaultCassandraClusterControl) checkPausedConditions(c *v1alpha1.CassandraCluster) error {
	cond := c.Status.GetStatusCondition(v1alpha1.ClusterConditionProgressing)
	pausedCondExists := cond != nil && cond.Reason == v1alpha1.PausedClusterReason

	needsUpdate := false
	if c.Spec.Paused && !pausedCondExists {
		c.Status.UpdateStatusCondition(
			v1alpha1.ClusterConditionProgressing,
			v1alpha1.ConditionUnknown,
			v1alpha1.PausedClusterReason,
			"Cluster is paused",
		)
		needsUpdate = true
	} else if !c.Spec.Paused && pausedCondExists {
		c.Status.UpdateStatusCondition(
			v1alpha1.ClusterConditionProgressing,
			v1alpha1.ConditionUnknown,
			v1alpha1.ResumedClusterReason,
			"Cluster is resumed",
		)
		needsUpdate = true
	}

	if !needsUpdate {
		return nil
	}

	var err error
	c, err = e.state.NavigatorClientset.NavigatorV1alpha1().CassandraClusters(c.Namespace).UpdateStatus(c)
	return err
}

func (e *defaultCassandraClusterControl) Sync(c *v1alpha1.CassandraCluster) error {
	c = c.DeepCopy()
	var err error

	e.checkPausedConditions(c)

	if c.Spec.Paused == true {
		glog.V(4).Infof("defaultCassandraClusterControl.Sync skipped, since cluster is paused")
		return nil
	}

	glog.V(4).Infof("defaultCassandraClusterControl.Sync")
	err = e.seedProviderServiceControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncService,
			err,
		)
		return err
	}
	err = e.nodesServiceControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncService,
			err,
		)
		return err
	}
	err = e.nodepoolControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncNodePools,
			err,
		)
		return err
	}
	err = e.pilotControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncPilots,
			err,
		)
		return err
	}
	err = e.serviceAccountControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncServiceAccount,
			err,
		)
		return err
	}
	err = e.roleControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncRole,
			err,
		)
		return err
	}
	err = e.roleBindingControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncRoleBinding,
			err,
		)
		return err
	}
	err = e.seedLabellerControl.Sync(c)
	if err != nil {
		e.recorder.Eventf(
			c,
			apiv1.EventTypeWarning,
			ErrorSync,
			MessageErrorSyncSeedLabels,
			err,
		)
		return err
	}

	a := NextAction(c)
	if a != nil {
		err = a.Execute(e.state)
		if err != nil {
			e.recorder.Eventf(
				c,
				apiv1.EventTypeWarning,
				ErrorSync,
				MessageErrorSync,
				err,
			)
			return err
		}
	}

	e.recorder.Event(
		c,
		apiv1.EventTypeNormal,
		SuccessSync,
		MessageSuccessSync,
	)
	return nil
}

func NextAction(c *v1alpha1.CassandraCluster) controllers.Action {
	for _, np := range c.Spec.NodePools {
		_, found := c.Status.NodePools[np.Name]
		if !found {
			return &actions.CreateNodePool{
				Cluster:  c,
				NodePool: &np,
			}
		}
	}
	for _, np := range c.Spec.NodePools {
		nps := c.Status.NodePools[np.Name]
		if *np.Replicas > nps.ReadyReplicas {
			return &actions.ScaleOut{
				Cluster:  c,
				NodePool: &np,
			}
		}
	}
	return nil
}
<|endoftext|>