<commit_before>package doubleratchet

import (
	"crypto/rand"
	"fmt"

	"crypto/hmac"
	"crypto/sha256"
	"golang.org/x/crypto/curve25519"
	"golang.org/x/crypto/hkdf"
)

// CryptoRecommended is an implementation of Crypto with cryptographic primitives recommended
// by the Double Ratchet Algorithm specification.
// See Crypto interface.
type CryptoRecommended struct{}

func (c CryptoRecommended) GenerateDH() (DHKeyPair, error) {
	var privKey [32]byte
	if l, err := rand.Read(privKey[:]); l != 32 || err != nil {
		return DHKeyPair{}, fmt.Errorf("couldn't generate privKey: %s, %d bytes read", err, l)
	}
	privKey[0] &= 248
	privKey[31] &= 127
	privKey[31] |= 64

	var pubKey [32]byte
	curve25519.ScalarBaseMult(&pubKey, &privKey)
	return DHKeyPair{
		PrivateKey: privKey[:],
		PublicKey:  pubKey[:],
	}, nil
}

func (c CryptoRecommended) DH(dhPair DHKeyPair, dhPub []byte) []byte {
	// TODO: Implement

	return nil
}

func (c CryptoRecommended) KdfRK(rk, dhOut []byte) ([]byte, []byte, error) {
	// TODO: Use sha512? Think about how to switch the implementation later if not.
	var (
		// TODO: Check if HKDF is set up correctly.
		r        = hkdf.New(sha256.New, dhOut, rk, []byte("rsZUpEuXUqqwXBvSy3EcievAh4cMj6QL"))
		chainKey = make([]byte, 32)
		rootKey  = make([]byte, 32)
	)

	if l, err := r.Read(chainKey); l != 32 || err != nil {
		return nil, nil, fmt.Errorf("failed to generate chain key: %s, %d bytes read", err, l)
	}
	if l, err := r.Read(rootKey); l != 32 || err != nil {
		return nil, nil, fmt.Errorf("failed to generate root key: %s, %d bytes read", err, l)
	}

	return chainKey, rootKey, nil
}

func (c CryptoRecommended) KdfCK(ck []byte) ([]byte, []byte) {
	const (
		ckInput = 15
		mkInput = 16
	)

	// TODO: Use sha512? Think about how to switch the implementation later if not.
	h := hmac.New(sha256.New, ck)

	h.Write([]byte(ckInput))
	chainKey := h.Sum(nil)
	h.Reset()

	h.Write([]byte(mkInput))
	msgKey := h.Sum(nil)

	return chainKey, msgKey
}

func (c CryptoRecommended) Encrypt(mk, plaintext, associatedData []byte) (ciphertext []byte) {
	// TODO: Implement.

	return nil
}

func (c CryptoRecommended) Decrypt(mk, ciphertext, associatedData []byte) (plaintext []byte) {
	// TODO: Implement.

	return nil
}
<commit_msg>Implemented DH<commit_after>package doubleratchet

import (
	"crypto/rand"
	"fmt"

	"crypto/hmac"
	"crypto/sha256"
	"golang.org/x/crypto/curve25519"
	"golang.org/x/crypto/hkdf"
)

// CryptoRecommended is an implementation of Crypto with cryptographic primitives recommended
// by the Double Ratchet Algorithm specification.
// See Crypto interface.
type CryptoRecommended struct{}

func (c CryptoRecommended) GenerateDH() (DHKeyPair, error) {
	var privKey [32]byte
	if l, err := rand.Read(privKey[:]); l != 32 || err != nil {
		return DHKeyPair{}, fmt.Errorf("couldn't generate privKey: %s, %d bytes read", err, l)
	}
	privKey[0] &= 248
	privKey[31] &= 127
	privKey[31] |= 64

	var pubKey [32]byte
	curve25519.ScalarBaseMult(&pubKey, &privKey)
	return DHKeyPair{
		PrivateKey: privKey[:],
		PublicKey:  pubKey[:],
	}, nil
}

func (c CryptoRecommended) DH(dhPair DHKeyPair, dhPub []byte) []byte {
	var dhOut [32]byte
	curve25519.ScalarMult(&dhOut, &[32]byte(dhPair.PrivateKey), &[32]byte(dhPub))

	return dhOut[:]
}

func (c CryptoRecommended) KdfRK(rk, dhOut []byte) ([]byte, []byte, error) {
	// TODO: Use sha512? Think about how to switch the implementation later if not.
	var (
		// TODO: Check if HKDF is set up correctly.
		r        = hkdf.New(sha256.New, dhOut, rk, []byte("rsZUpEuXUqqwXBvSy3EcievAh4cMj6QL"))
		chainKey = make([]byte, 32)
		rootKey  = make([]byte, 32)
	)

	if l, err := r.Read(chainKey); l != 32 || err != nil {
		return nil, nil, fmt.Errorf("failed to generate chain key: %s, %d bytes read", err, l)
	}
	if l, err := r.Read(rootKey); l != 32 || err != nil {
		return nil, nil, fmt.Errorf("failed to generate root key: %s, %d bytes read", err, l)
	}

	return chainKey, rootKey, nil
}

func (c CryptoRecommended) KdfCK(ck []byte) ([]byte, []byte) {
	const (
		ckInput = 15
		mkInput = 16
	)

	// TODO: Use sha512? Think about how to switch the implementation later if not.
	h := hmac.New(sha256.New, ck)

	h.Write([]byte(ckInput))
	chainKey := h.Sum(nil)
	h.Reset()

	h.Write([]byte(mkInput))
	msgKey := h.Sum(nil)

	return chainKey, msgKey
}

func (c CryptoRecommended) Encrypt(mk, plaintext, associatedData []byte) (ciphertext []byte) {
	// TODO: Implement.

	return nil
}

func (c CryptoRecommended) Decrypt(mk, ciphertext, associatedData []byte) (plaintext []byte) {
	// TODO: Implement.

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	mp "github.com/mackerelio/go-mackerel-plugin"
)

var cpuNumberPattern = regexp.MustCompile(`^cpu*`)

var graphDef = map[string](mp.Graphs){
	"multicore.cpu.#": mp.Graphs{
		Label: "MultiCore CPU",
		Unit:  "percentage",
		Metrics: [](mp.Metrics){
			mp.Metrics{Name: "user", Label: "user", Diff: false, Stacked: true},
			mp.Metrics{Name: "nice", Label: "nice", Diff: false, Stacked: true},
			mp.Metrics{Name: "system", Label: "system", Diff: false, Stacked: true},
			mp.Metrics{Name: "idle", Label: "idle", Diff: false, Stacked: true},
			mp.Metrics{Name: "iowait", Label: "ioWait", Diff: false, Stacked: true},
			mp.Metrics{Name: "irq", Label: "irq", Diff: false, Stacked: true},
			mp.Metrics{Name: "softirq", Label: "softirq", Diff: false, Stacked: true},
			mp.Metrics{Name: "steal", Label: "steal", Diff: false, Stacked: true},
			mp.Metrics{Name: "guest", Label: "guest", Diff: false, Stacked: true},
		},
	},
	"multicore.average_per_core": mp.Graphs{
		Label: "MultiCore CPU Average per core",
		Unit:  "percentage",
		Metrics: [](mp.Metrics){
			mp.Metrics{Name: "user", Label: "user", Diff: false, Stacked: true},
			mp.Metrics{Name: "nice", Label: "nice", Diff: false, Stacked: true},
			mp.Metrics{Name: "system", Label: "system", Diff: false, Stacked: true},
			mp.Metrics{Name: "idle", Label: "idle", Diff: false, Stacked: true},
			mp.Metrics{Name: "iowait", Label: "ioWait", Diff: false, Stacked: true},
			mp.Metrics{Name: "irq", Label: "irq", Diff: false, Stacked: true},
			mp.Metrics{Name: "softirq", Label: "softirq", Diff: false, Stacked: true},
			mp.Metrics{Name: "steal", Label: "steal", Diff: false, Stacked: true},
			mp.Metrics{Name: "guest", Label: "guest", Diff: false, Stacked: true},
		},
	},
	"multicore.loadavg_per_core": mp.Graphs{
		Label: "MultiCore loadavg5 per core",
		Unit:  "float",
		Metrics: [](mp.Metrics){
			mp.Metrics{Name: "loadavg5", Label: "loadavg5", Diff: false, Stacked: true},
		},
	},
}

type saveItem struct {
	LastTime       time.Time
	ProcStatsByCPU map[string]*procStats
}

type procStats struct {
	User    float64 `json:"user"`
	Nice    float64 `json:"nice"`
	System  float64 `json:"system"`
	Idle    float64 `json:"idle"`
	IoWait  float64 `json:"iowait"`
	Irq     float64 `json:"irq"`
	SoftIrq float64 `json:"softirq"`
	Steal   float64 `json:"steal"`
	Guest   float64 `json:"guest"`
	Total   float64 `json:"total"`
}

type cpuPercentages struct {
	User    float64
	Nice    float64
	System  float64
	Idle    float64
	IoWait  float64
	Irq     float64
	SoftIrq float64
	Steal   float64
	Guest   float64
}

func getProcStat() (string, error) {
	contentbytes, err := ioutil.ReadFile("/proc/stat")
	if err != nil {
		return "", err
	}
	return string(contentbytes), nil
}

func parseFloats(values []string) ([]float64, error) {
	var result []float64
	for _, v := range values {
		f, err := strconv.ParseFloat(v, 64)
		if err != nil {
			return nil, err
		}
		result = append(result, f)
	}
	return result, nil
}

func fill(arr []float64, elementCount int) []float64 {
	arraySize := len(arr)
	if arraySize < elementCount {
		for i := arraySize; i < elementCount; i++ {
			arr = append(arr, 0.0)
		}
	}
	return arr
}

func parseProcStat(str string) (map[string]*procStats, error) {
	var result = make(map[string]*procStats)
	for _, line := range strings.Split(str, "\n") {
		if cpuNumberPattern.MatchString(line) {
			var key = strings.Fields(line)[0]
			var values = strings.Fields(line)[1:]

			floatValues, err := parseFloats(values)
			if err != nil {
				return nil, err
			}
			filledValues := fill(floatValues, 9)

			total := 0.0
			for _, v := range floatValues {
				total += v
			}

			ps := &procStats{
				User:    filledValues[0],
				Nice:    filledValues[1],
				System:  filledValues[2],
				Idle:    filledValues[3],
				IoWait:  filledValues[4],
				Irq:     filledValues[5],
				SoftIrq: filledValues[6],
				Steal:   filledValues[7],
				Guest:   filledValues[8],
				Total:   total,
			}
			result[key] = ps

		} else {
			break
		}
	}
	return result, nil
}

func collectProcStatValues() (map[string]*procStats, error) {
	procStats, err := getProcStat()
	if err != nil {
		return nil, err
	}
	return parseProcStat(procStats)
}

func saveValues(tempFileName string, values map[string]*procStats, now time.Time) error {
	f, err := os.Create(tempFileName)
	if err != nil {
		return err
	}
	defer f.Close()

	s := saveItem{
		LastTime:       time.Now(),
		ProcStatsByCPU: values,
	}

	encoder := json.NewEncoder(f)
	err = encoder.Encode(s)
	if err != nil {
		return err
	}

	return nil
}

func fetchLastValues(tempFileName string) (map[string]*procStats, time.Time, error) {
	f, err := os.Open(tempFileName)
	if err != nil {
		return nil, time.Now(), err
	}
	defer f.Close()

	var stat saveItem
	decoder := json.NewDecoder(f)
	err = decoder.Decode(&stat)
	if err != nil {
		return stat.ProcStatsByCPU, stat.LastTime, err
	}
	return stat.ProcStatsByCPU, stat.LastTime, nil
}

func calcCPUUsage(currentValues map[string]*procStats, now time.Time, lastValues map[string]*procStats, lastTime time.Time) (map[string]*cpuPercentages, error) {

	result := make(map[string]*cpuPercentages)
	for key, current := range currentValues {
		last := lastValues[key]

		user, err := calcPercentage(current.User, last.User, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		nice, err := calcPercentage(current.Nice, last.Nice, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		system, err := calcPercentage(current.System, last.System, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		idle, err := calcPercentage(current.Idle, last.Idle, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		iowait, err := calcPercentage(current.IoWait, last.IoWait, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		irq, err := calcPercentage(current.Irq, last.Irq, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		softirq, err := calcPercentage(current.SoftIrq, last.SoftIrq, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		steal, err := calcPercentage(current.Steal, last.Steal, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		guest, err := calcPercentage(current.Guest, last.Guest, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}

		p := &cpuPercentages{
			User:    user,
			Nice:    nice,
			System:  system,
			Idle:    idle,
			IoWait:  iowait,
			Irq:     irq,
			SoftIrq: softirq,
			Steal:   steal,
			Guest:   guest,
		}
		result[key] = p
	}

	return result, nil
}

func calcPercentage(currentValue float64, lastValue float64, currentTotal float64, lastTotal float64, now time.Time, lastTime time.Time) (float64, error) {
	value, err := calcDiff(currentValue, now, lastValue, lastTime)
	if err != nil {
		return 0.0, err
	}

	total, err := calcDiff(currentTotal, now, lastTotal, lastTime)
	if err != nil {
		return 0.0, err
	}

	return (value / total * 100.0), nil
}

func calcDiff(value float64, now time.Time, lastValue float64, lastTime time.Time) (float64, error) {
	diffTime := now.Unix() - lastTime.Unix()
	if diffTime > 600 {
		return 0.0, errors.New("Too long duration")
	}

	diff := (value - lastValue) * 60 / float64(diffTime)

	if lastValue <= value {
		return diff, nil
	}
	return 0.0, nil
}

func fetchLoadavg5() (float64, error) {
	contentbytes, err := ioutil.ReadFile("/proc/loadavg")
	if err != nil {
		return 0.0, err
	}
	content := string(contentbytes)
	cols := strings.Split(content, " ")

	f, err := strconv.ParseFloat(cols[1], 64)
	if err != nil {
		return 0.0, err
	}

	return f, nil
}

func printValue(key string, value float64, time time.Time) {
	fmt.Printf("%s\t%f\t%d\n", key, value, time.Unix())
}

func outputCPUUsage(cpuUsage map[string]*cpuPercentages, now time.Time) {
	if cpuUsage != nil {
		for key, values := range cpuUsage {
			if key != "cpu" {
				printValue(fmt.Sprintf("multicore.cpu.%s.user", key), values.User, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.nice", key), values.Nice, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.system", key), values.System, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.idle", key), values.Idle, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.iowait", key), values.IoWait, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.irq", key), values.Irq, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.softirq", key), values.SoftIrq, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.steal", key), values.Steal, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.guest", key), values.Guest, now)
			}
		}
	}
}

func outputAverageCPUUsage(cpuUsage map[string]*cpuPercentages, now time.Time) {
	if cpuUsage != nil {
		values := cpuUsage["cpu"]
		printValue("multicore.average_per_core.user", values.User, now)
		printValue("multicore.average_per_core.nice", values.Nice, now)
		printValue("multicore.average_per_core.system", values.System, now)
		printValue("multicore.average_per_core.idle", values.Idle, now)
		printValue("multicore.average_per_core.iowait", values.IoWait, now)
		printValue("multicore.average_per_core.irq", values.Irq, now)
		printValue("multicore.average_per_core.softirq", values.SoftIrq, now)
		printValue("multicore.average_per_core.steal", values.Steal, now)
		printValue("multicore.average_per_core.guest", values.Guest, now)
	}
}

func outputLoadavgPerCore(loadavgPerCore float64, now time.Time) {
	printValue("multicore.loadavg_per_core.loadavg5", loadavgPerCore, now)
}

func outputDefinitions() {
	fmt.Println("# mackerel-agent-plugin")
	var graphs mp.GraphDef
	graphs.Graphs = graphDef

	b, err := json.Marshal(graphs)
	if err != nil {
		log.Fatalln("OutputDefinitions: ", err)
	}
	fmt.Println(string(b))
}

// main function
func main() {
	optTempfile := flag.String("tempfile", "", "Temp file name")
	var tempFileName string
	if *optTempfile != "" {
		tempFileName = *optTempfile
	} else {
		tempFileName = "/tmp/mackerel-plugin-multicore"
	}
	now := time.Now()

	currentValues, _ := collectProcStatValues()
	lastValues, lastTime, err := fetchLastValues(tempFileName)
	saveValues(tempFileName, currentValues, now)
	if err != nil {
		log.Fatalln("fetchLastValues: ", err)
	}

	cpuUsage := make(map[string]*cpuPercentages)
	if lastValues != nil {
		var err error
		cpuUsage, err = calcCPUUsage(currentValues, now, lastValues, lastTime)
		if err != nil {
			log.Fatalln("calcCPUUsage: ", err)
		}
	}

	loadavg5, err := fetchLoadavg5()
	if err != nil {
		log.Fatalln("fetchLoadavg5: ", err)
	}
	loadPerCPUCount := loadavg5 / (float64(len(cpuUsage) - 1))

	if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" {
		outputDefinitions()
	} else {
		outputCPUUsage(cpuUsage, now)
		outputAverageCPUUsage(cpuUsage, now)
		outputLoadavgPerCore(loadPerCPUCount, now)
	}
}
<commit_msg>use `strings.HasPrefix`<commit_after>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"strconv"
	"strings"
	"time"

	mp "github.com/mackerelio/go-mackerel-plugin"
)

var graphDef = map[string](mp.Graphs){
	"multicore.cpu.#": mp.Graphs{
		Label: "MultiCore CPU",
		Unit:  "percentage",
		Metrics: [](mp.Metrics){
			mp.Metrics{Name: "user", Label: "user", Diff: false, Stacked: true},
			mp.Metrics{Name: "nice", Label: "nice", Diff: false, Stacked: true},
			mp.Metrics{Name: "system", Label: "system", Diff: false, Stacked: true},
			mp.Metrics{Name: "idle", Label: "idle", Diff: false, Stacked: true},
			mp.Metrics{Name: "iowait", Label: "ioWait", Diff: false, Stacked: true},
			mp.Metrics{Name: "irq", Label: "irq", Diff: false, Stacked: true},
			mp.Metrics{Name: "softirq", Label: "softirq", Diff: false, Stacked: true},
			mp.Metrics{Name: "steal", Label: "steal", Diff: false, Stacked: true},
			mp.Metrics{Name: "guest", Label: "guest", Diff: false, Stacked: true},
		},
	},
	"multicore.average_per_core": mp.Graphs{
		Label: "MultiCore CPU Average per core",
		Unit:  "percentage",
		Metrics: [](mp.Metrics){
			mp.Metrics{Name: "user", Label: "user", Diff: false, Stacked: true},
			mp.Metrics{Name: "nice", Label: "nice", Diff: false, Stacked: true},
			mp.Metrics{Name: "system", Label: "system", Diff: false, Stacked: true},
			mp.Metrics{Name: "idle", Label: "idle", Diff: false, Stacked: true},
			mp.Metrics{Name: "iowait", Label: "ioWait", Diff: false, Stacked: true},
			mp.Metrics{Name: "irq", Label: "irq", Diff: false, Stacked: true},
			mp.Metrics{Name: "softirq", Label: "softirq", Diff: false, Stacked: true},
			mp.Metrics{Name: "steal", Label: "steal", Diff: false, Stacked: true},
			mp.Metrics{Name: "guest", Label: "guest", Diff: false, Stacked: true},
		},
	},
	"multicore.loadavg_per_core": mp.Graphs{
		Label: "MultiCore loadavg5 per core",
		Unit:  "float",
		Metrics: [](mp.Metrics){
			mp.Metrics{Name: "loadavg5", Label: "loadavg5", Diff: false, Stacked: true},
		},
	},
}

type saveItem struct {
	LastTime       time.Time
	ProcStatsByCPU map[string]*procStats
}

type procStats struct {
	User    float64 `json:"user"`
	Nice    float64 `json:"nice"`
	System  float64 `json:"system"`
	Idle    float64 `json:"idle"`
	IoWait  float64 `json:"iowait"`
	Irq     float64 `json:"irq"`
	SoftIrq float64 `json:"softirq"`
	Steal   float64 `json:"steal"`
	Guest   float64 `json:"guest"`
	Total   float64 `json:"total"`
}

type cpuPercentages struct {
	User    float64
	Nice    float64
	System  float64
	Idle    float64
	IoWait  float64
	Irq     float64
	SoftIrq float64
	Steal   float64
	Guest   float64
}

func getProcStat() (string, error) {
	contentbytes, err := ioutil.ReadFile("/proc/stat")
	if err != nil {
		return "", err
	}
	return string(contentbytes), nil
}

func parseFloats(values []string) ([]float64, error) {
	var result []float64
	for _, v := range values {
		f, err := strconv.ParseFloat(v, 64)
		if err != nil {
			return nil, err
		}
		result = append(result, f)
	}
	return result, nil
}

func fill(arr []float64, elementCount int) []float64 {
	arraySize := len(arr)
	if arraySize < elementCount {
		for i := arraySize; i < elementCount; i++ {
			arr = append(arr, 0.0)
		}
	}
	return arr
}

func parseProcStat(str string) (map[string]*procStats, error) {
	var result = make(map[string]*procStats)
	for _, line := range strings.Split(str, "\n") {
		if strings.HasPrefix(line, "cpu") {
			var key = strings.Fields(line)[0]
			var values = strings.Fields(line)[1:]

			floatValues, err := parseFloats(values)
			if err != nil {
				return nil, err
			}
			filledValues := fill(floatValues, 9)

			total := 0.0
			for _, v := range floatValues {
				total += v
			}

			ps := &procStats{
				User:    filledValues[0],
				Nice:    filledValues[1],
				System:  filledValues[2],
				Idle:    filledValues[3],
				IoWait:  filledValues[4],
				Irq:     filledValues[5],
				SoftIrq: filledValues[6],
				Steal:   filledValues[7],
				Guest:   filledValues[8],
				Total:   total,
			}
			result[key] = ps

		} else {
			break
		}
	}
	return result, nil
}

func collectProcStatValues() (map[string]*procStats, error) {
	procStats, err := getProcStat()
	if err != nil {
		return nil, err
	}
	return parseProcStat(procStats)
}

func saveValues(tempFileName string, values map[string]*procStats, now time.Time) error {
	f, err := os.Create(tempFileName)
	if err != nil {
		return err
	}
	defer f.Close()

	s := saveItem{
		LastTime:       time.Now(),
		ProcStatsByCPU: values,
	}

	encoder := json.NewEncoder(f)
	err = encoder.Encode(s)
	if err != nil {
		return err
	}

	return nil
}

func fetchLastValues(tempFileName string) (map[string]*procStats, time.Time, error) {
	f, err := os.Open(tempFileName)
	if err != nil {
		return nil, time.Now(), err
	}
	defer f.Close()

	var stat saveItem
	decoder := json.NewDecoder(f)
	err = decoder.Decode(&stat)
	if err != nil {
		return stat.ProcStatsByCPU, stat.LastTime, err
	}
	return stat.ProcStatsByCPU, stat.LastTime, nil
}

func calcCPUUsage(currentValues map[string]*procStats, now time.Time, lastValues map[string]*procStats, lastTime time.Time) (map[string]*cpuPercentages, error) {

	result := make(map[string]*cpuPercentages)
	for key, current := range currentValues {
		last := lastValues[key]

		user, err := calcPercentage(current.User, last.User, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		nice, err := calcPercentage(current.Nice, last.Nice, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		system, err := calcPercentage(current.System, last.System, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		idle, err := calcPercentage(current.Idle, last.Idle, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		iowait, err := calcPercentage(current.IoWait, last.IoWait, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		irq, err := calcPercentage(current.Irq, last.Irq, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		softirq, err := calcPercentage(current.SoftIrq, last.SoftIrq, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		steal, err := calcPercentage(current.Steal, last.Steal, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}
		guest, err := calcPercentage(current.Guest, last.Guest, current.Total, last.Total, now, lastTime)
		if err != nil {
			return nil, err
		}

		p := &cpuPercentages{
			User:    user,
			Nice:    nice,
			System:  system,
			Idle:    idle,
			IoWait:  iowait,
			Irq:     irq,
			SoftIrq: softirq,
			Steal:   steal,
			Guest:   guest,
		}
		result[key] = p
	}

	return result, nil
}

func calcPercentage(currentValue float64, lastValue float64, currentTotal float64, lastTotal float64, now time.Time, lastTime time.Time) (float64, error) {
	value, err := calcDiff(currentValue, now, lastValue, lastTime)
	if err != nil {
		return 0.0, err
	}

	total, err := calcDiff(currentTotal, now, lastTotal, lastTime)
	if err != nil {
		return 0.0, err
	}

	return (value / total * 100.0), nil
}

func calcDiff(value float64, now time.Time, lastValue float64, lastTime time.Time) (float64, error) {
	diffTime := now.Unix() - lastTime.Unix()
	if diffTime > 600 {
		return 0.0, errors.New("Too long duration")
	}

	diff := (value - lastValue) * 60 / float64(diffTime)

	if lastValue <= value {
		return diff, nil
	}
	return 0.0, nil
}

func fetchLoadavg5() (float64, error) {
	contentbytes, err := ioutil.ReadFile("/proc/loadavg")
	if err != nil {
		return 0.0, err
	}
	content := string(contentbytes)
	cols := strings.Split(content, " ")

	f, err := strconv.ParseFloat(cols[1], 64)
	if err != nil {
		return 0.0, err
	}

	return f, nil
}

func printValue(key string, value float64, time time.Time) {
	fmt.Printf("%s\t%f\t%d\n", key, value, time.Unix())
}

func outputCPUUsage(cpuUsage map[string]*cpuPercentages, now time.Time) {
	if cpuUsage != nil {
		for key, values := range cpuUsage {
			if key != "cpu" {
				printValue(fmt.Sprintf("multicore.cpu.%s.user", key), values.User, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.nice", key), values.Nice, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.system", key), values.System, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.idle", key), values.Idle, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.iowait", key), values.IoWait, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.irq", key), values.Irq, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.softirq", key), values.SoftIrq, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.steal", key), values.Steal, now)
				printValue(fmt.Sprintf("multicore.cpu.%s.guest", key), values.Guest, now)
			}
		}
	}
}

func outputAverageCPUUsage(cpuUsage map[string]*cpuPercentages, now time.Time) {
	if cpuUsage != nil {
		values := cpuUsage["cpu"]
		printValue("multicore.average_per_core.user", values.User, now)
		printValue("multicore.average_per_core.nice", values.Nice, now)
		printValue("multicore.average_per_core.system", values.System, now)
		printValue("multicore.average_per_core.idle", values.Idle, now)
		printValue("multicore.average_per_core.iowait", values.IoWait, now)
		printValue("multicore.average_per_core.irq", values.Irq, now)
		printValue("multicore.average_per_core.softirq", values.SoftIrq, now)
		printValue("multicore.average_per_core.steal", values.Steal, now)
		printValue("multicore.average_per_core.guest", values.Guest, now)
	}
}

func outputLoadavgPerCore(loadavgPerCore float64, now time.Time) {
	printValue("multicore.loadavg_per_core.loadavg5", loadavgPerCore, now)
}

func outputDefinitions() {
	fmt.Println("# mackerel-agent-plugin")
	var graphs mp.GraphDef
	graphs.Graphs = graphDef

	b, err := json.Marshal(graphs)
	if err != nil {
		log.Fatalln("OutputDefinitions: ", err)
	}
	fmt.Println(string(b))
}

// main function
func main() {
	optTempfile := flag.String("tempfile", "", "Temp file name")
	var tempFileName string
	if *optTempfile != "" {
		tempFileName = *optTempfile
	} else {
		tempFileName = "/tmp/mackerel-plugin-multicore"
	}
	now := time.Now()

	currentValues, _ := collectProcStatValues()
	lastValues, lastTime, err := fetchLastValues(tempFileName)
	saveValues(tempFileName, currentValues, now)
	if err != nil {
		log.Fatalln("fetchLastValues: ", err)
	}

	cpuUsage := make(map[string]*cpuPercentages)
	if lastValues != nil {
		var err error
		cpuUsage, err = calcCPUUsage(currentValues, now, lastValues, lastTime)
		if err != nil {
			log.Fatalln("calcCPUUsage: ", err)
		}
	}

	loadavg5, err := fetchLoadavg5()
	if err != nil {
		log.Fatalln("fetchLoadavg5: ", err)
	}
	loadPerCPUCount := loadavg5 / (float64(len(cpuUsage) - 1))

	if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" {
		outputDefinitions()
	} else {
		outputCPUUsage(cpuUsage, now)
		outputAverageCPUUsage(cpuUsage, now)
		outputLoadavgPerCore(loadPerCPUCount, now)
	}
}
<|endoftext|>


<commit_before>package builtins

import (
	"fmt"
	"github.com/SaviorPhoenix/gosh/cmd"
	"github.com/SaviorPhoenix/gosh/sh"
	"os"
)

type builtinFunc func(c cmd.GoshCmd) int

var builtins = map[string]builtinFunc{
	"exit": builtinFunc(
		func(c cmd.GoshCmd) int {
			fmt.Println("exit")
			os.Exit(0)

			//Not reached, obviously
			return 1
		}),

	"cd": builtinFunc(
		func(c cmd.GoshCmd) int {
			name := c.GetNameStr()
			if c.GetElements() == 1 && *name == "cd" {
				env := shell.Sh.GetEnv()
				os.Chdir(env.GetEnvVar("home"))
				return 1
			} else {
				tokens := c.GetTokens()
				os.Chdir(tokens[1])
			}
			return 1
		}),

	"add-var": builtinFunc(
		func(c cmd.GoshCmd) int {
			if c.GetElements() != 3 {
				fmt.Println("Usage: add-var <variable name> <variable value>")
			} else {
				env := shell.Sh.GetEnv()
				tokens := c.GetTokens()
				env.AddEnvVar(tokens[1], tokens[2])
			}
			return 1
		}),
	"set-var": builtinFunc(
		func(c cmd.GoshCmd) int {
			if c.GetElements() != 3 {
				fmt.Println("Usage: set-var <variable name> <new variable value>")
			} else {
				env := shell.Sh.GetEnv()
				tokens := c.GetTokens()
				env.SetEnvVar(tokens[1], tokens[2])
			}
			return 1
		}),

	"print-var": builtinFunc(
		func(c cmd.GoshCmd) int {
			if c.GetElements() != 2 {
				fmt.Println("Usage: print-var <variable name>")
			} else {
				env := shell.Sh.GetEnv()
				tokens := c.GetTokens()
				printVar := env.GetEnvVar(tokens[1])
				fmt.Println(printVar)
			}
			return 1
		}),

	"delete-var": builtinFunc(
		func(c cmd.GoshCmd) int {
			if c.GetElements() != 2 {
				fmt.Println("Usage: delete-var <variable name>")
			} else {
				env := shell.Sh.GetEnv()
				tokens := c.GetTokens()
				delVar := env.GetEnvVar(tokens[1])
				env.DeleteEnvVar(delVar)
			}
			return 1
		}),
}

func CheckBuiltin(c cmd.GoshCmd) int {
	do := builtins[*c.NameStr]
	if do != nil {
		return do(c)
	} else {
		return 0
	}
}
<commit_msg>Refactored delete-var/print-var/cd builtins<commit_after>package builtins

import (
	"fmt"
	"github.com/SaviorPhoenix/gosh/cmd"
	"github.com/SaviorPhoenix/gosh/sh"
	"os"
)

type builtinFunc func(c cmd.GoshCmd) int

var builtins = map[string]builtinFunc{
	"exit": builtinFunc(
		func(c cmd.GoshCmd) int {
			fmt.Println("exit")
			os.Exit(0)

			//Not reached, obviously
			return 1
		}),

	"cd": builtinFunc(
		func(c cmd.GoshCmd) int {
			if c.GetElements() == 1 {
				env := shell.Sh.GetEnv()
				os.Chdir(env.GetEnvVar("home"))
				return 1
			} else {
				tokens := c.GetTokens()
				os.Chdir(tokens[1])
			}
			return 1
		}),

	"add-var": builtinFunc(
		func(c cmd.GoshCmd) int {
			if c.GetElements() != 3 {
				fmt.Println("Usage: add-var <variable name> <variable value>")
			} else {
				env := shell.Sh.GetEnv()
				tokens := c.GetTokens()
				env.AddEnvVar(tokens[1], tokens[2])
			}
			return 1
		}),

	"set-var": builtinFunc(
		func(c cmd.GoshCmd) int {
			if c.GetElements() != 3 {
				fmt.Println("Usage: set-var <variable name> <new variable value>")
			} else {
				env := shell.Sh.GetEnv()
				tokens := c.GetTokens()
				env.SetEnvVar(tokens[1], tokens[2])
			}
			return 1
		}),

	"print-var": builtinFunc(
		func(c cmd.GoshCmd) int {
			if c.GetElements() != 2 {
				fmt.Println("Usage: print-var <variable name>")
			} else {
				env := shell.Sh.GetEnv()
				tokens := c.GetTokens()

				printVar := env.GetEnvVar(tokens[1])
				if printVar == "" {
					fmt.Println("No such variable:", tokens[1])
					return 1
				}

				fmt.Println(printVar)
			}
			return 1
		}),

	"delete-var": builtinFunc(
		func(c cmd.GoshCmd) int {
			if c.GetElements() != 2 {
				fmt.Println("Usage: delete-var <variable name>")
			} else {
				env := shell.Sh.GetEnv()
				tokens := c.GetTokens()
				delVar := env.GetEnvVar(tokens[1])

				if env.DeleteEnvVar(delVar) == false {
					fmt.Println("No such variable:", tokens[1])
				}
			}
			return 1
		}),
}

func CheckBuiltin(c cmd.GoshCmd) int {
	do := builtins[*c.NameStr]
	if do != nil {
		return do(c)
	} else {
		return 0
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"os"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestDump_Dump(t *testing.T) {
	c := NewClickhouse(-1, 10)
	dumpDir := "dumptest"
	dumper := NewDumper(dumpDir)
	c.Dumper = dumper
	c.AddServer("")
	c.Dump("eee", "eee", "error", "", 502)
	assert.True(t, c.Empty())
	buf, _, err := dumper.GetDumpData(dumper.dumpName(1, "", 502))
	assert.Nil(t, err)
	assert.Equal(t, "eee\neee", string(buf))

	sender := &fakeSender{}
	err = dumper.ProcessNextDump(sender)
	assert.Nil(t, err)
	assert.Len(t, sender.sendQueryHistory, 1)
	err = dumper.ProcessNextDump(sender)
	assert.True(t, errors.Is(err, ErrNoDumps))
	assert.Len(t, sender.sendQueryHistory, 1)

	dumper.Listen(sender, 1)
	c.Dump("eee", "eee", "", "", 502)
	time.Sleep(time.Second)
	err = dumper.ProcessNextDump(sender)
	assert.Equal(t, ErrNoDumps, err)

	err = os.Remove(dumpDir)
	assert.Nil(t, err)
}
<commit_msg>fix test<commit_after>package main

import (
	"errors"
	"os"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestDump_Dump(t *testing.T) {
	c := NewClickhouse(-1, 10)
	dumpDir := "dumptest"
	dumper := NewDumper(dumpDir)
	c.Dumper = dumper
	c.AddServer("")
	c.Dump("eee", "eee", "error", "", 502)
	assert.True(t, c.Empty())
	buf, _, err := dumper.GetDumpData(dumper.dumpName(1, "", 502))
	assert.Nil(t, err)
	assert.Equal(t, "eee\neee", string(buf))

	sender := &fakeSender{}
	err = dumper.ProcessNextDump(sender)
	assert.Nil(t, err)
	assert.Len(t, sender.sendQueryHistory, 1)
	err = dumper.ProcessNextDump(sender)
	assert.True(t, errors.Is(err, ErrNoDumps))
	assert.Len(t, sender.sendQueryHistory, 1)

	dumper.Listen(sender, 1)
	c.Dump("eee", "eee", "", "", 502)
	time.Sleep(time.Second * 2)
	err = dumper.ProcessNextDump(sender)
	assert.Equal(t, ErrNoDumps, err)

	err = os.Remove(dumpDir)
	assert.Nil(t, err)
}
<|endoftext|>


<commit_before>// Package https facilitates the management of TLS assets and integrates
// Let's Encrypt functionality into Caddy with first-class support for
// creating and renewing certificates automatically. It is designed to
// configure sites for HTTPS by default.
package https

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"strings"

	"github.com/mholt/caddy/middleware"
	"github.com/mholt/caddy/middleware/redirect"
	"github.com/mholt/caddy/server"
	"github.com/xenolf/lego/acme"
)

// Activate sets up TLS for each server config in configs
// as needed; this consists of acquiring and maintaining
// certificates and keys for qualifying configs and enabling
// OCSP stapling for all TLS-enabled configs.
//
// This function may prompt the user to provide an email
// address if none is available through other means. It
// prefers the email address specified in the config, but
// if that is not available it will check the command line
// argument. If absent, it will use the most recent email
// address from last time. If there isn't one, the user
// will be prompted and shown SA link.
//
// Also note that calling this function activates asset
// management automatically, which keeps certificates
// renewed and OCSP stapling updated.
//
// Activate returns the updated list of configs, since
// some may have been appended, for example, to redirect
// plaintext HTTP requests to their HTTPS counterpart.
// This function only appends; it does not splice.
func Activate(configs []server.Config) ([]server.Config, error) {
	// just in case previous caller forgot...
	Deactivate()

	// pre-screen each config and earmark the ones that qualify for managed TLS
	MarkQualified(configs)

	// place certificates and keys on disk
	err := ObtainCerts(configs, true, false)
	if err != nil {
		return configs, err
	}

	// update TLS configurations
	err = EnableTLS(configs, true)
	if err != nil {
		return configs, err
	}

	// set up redirects
	configs = MakePlaintextRedirects(configs)

	// renew all relevant certificates that need renewal. this is important
	// to do right away for a couple reasons, mainly because each restart,
	// the renewal ticker is reset, so if restarts happen more often than
	// the ticker interval, renewals would never happen. but doing
	// it right away at start guarantees that renewals aren't missed.
	err = renewManagedCertificates(true)
	if err != nil {
		return configs, err
	}

	// keep certificates renewed and OCSP stapling updated
	go maintainAssets(stopChan)

	return configs, nil
}

// Deactivate cleans up long-term, in-memory resources
// allocated by calling Activate(). Essentially, it stops
// the asset maintainer from running, meaning that certificates
// will not be renewed, OCSP staples will not be updated, etc.
func Deactivate() (err error) {
	defer func() {
		if rec := recover(); rec != nil {
			err = errors.New("already deactivated")
		}
	}()
	close(stopChan)
	stopChan = make(chan struct{})
	return
}

// MarkQualified scans each config and, if it qualifies for managed
// TLS, it sets the Managed field of the TLSConfig to true.
func MarkQualified(configs []server.Config) {
	for i := 0; i < len(configs); i++ {
		if ConfigQualifies(configs[i]) {
			configs[i].TLS.Managed = true
		}
	}
}

// ObtainCerts obtains certificates for all these configs as long as a
// certificate does not already exist on disk. It does not modify the
// configs at all; it only obtains and stores certificates and keys to
// the disk. If allowPrompts is true, the user may be shown a prompt.
// If proxyACME is true, the ACME challenges will be proxied to our alt port.
func ObtainCerts(configs []server.Config, allowPrompts, proxyACME bool) error {
	// We group configs by email so we don't make the same clients over and
	// over. This has the potential to prompt the user for an email, but we
	// prevent that by assuming that if we already have a listener that can
	// proxy ACME challenge requests, then the server is already running and
	// the operator is no longer present.
	groupedConfigs := groupConfigsByEmail(configs, allowPrompts)

	for email, group := range groupedConfigs {
		// Wait as long as we can before creating the client, because it
		// may not be needed, for example, if we already have what we
		// need on disk. Creating a client involves the network and
		// potentially prompting the user, etc., so only do if necessary.
		var client *ACMEClient

		for _, cfg := range group {
			if cfg.Host == "" || existingCertAndKey(cfg.Host) {
				continue
			}

			// Now we definitely do need a client
			if client == nil {
				var err error
				client, err = NewACMEClient(email, allowPrompts)
				if err != nil {
					return errors.New("error creating client: " + err.Error())
				}
			}

			// c.Configure assumes that allowPrompts == !proxyACME,
			// but that's not always true. For example, a restart where
			// the user isn't present and we're not listening on port 80.
			// TODO: This could probably be refactored better.
			if proxyACME {
				client.SetHTTPAddress(net.JoinHostPort(cfg.BindHost, AlternatePort))
				client.SetTLSAddress(net.JoinHostPort(cfg.BindHost, AlternatePort))
				client.ExcludeChallenges([]acme.Challenge{acme.TLSSNI01, acme.DNS01})
			} else {
				client.SetHTTPAddress(net.JoinHostPort(cfg.BindHost, ""))
				client.SetTLSAddress(net.JoinHostPort(cfg.BindHost, ""))
				client.ExcludeChallenges([]acme.Challenge{acme.DNS01})
			}

			err := client.Obtain([]string{cfg.Host})
			if err != nil {
				return err
			}
		}
	}

	return nil
}

// groupConfigsByEmail groups configs by the email address to be used by an
// ACME client. It only groups configs that have TLS enabled and that are
// marked as Managed. If userPresent is true, the operator MAY be prompted
// for an email address.
func groupConfigsByEmail(configs []server.Config, userPresent bool) map[string][]server.Config {
	initMap := make(map[string][]server.Config)
	for _, cfg := range configs {
		if !cfg.TLS.Managed {
			continue
		}
		leEmail := getEmail(cfg, userPresent)
		initMap[leEmail] = append(initMap[leEmail], cfg)
	}
	return initMap
}

// EnableTLS configures each config to use TLS according to default settings.
// It will only change configs that are marked as managed, and assumes that
// certificates and keys are already on disk. If loadCertificates is true,
// the certificates will be loaded from disk into the cache for this process
// to use. If false, TLS will still be enabled and configured with default
// settings, but no certificates will be parsed loaded into the cache, and
// the returned error value will always be nil.
func EnableTLS(configs []server.Config, loadCertificates bool) error {
	for i := 0; i < len(configs); i++ {
		if !configs[i].TLS.Managed {
			continue
		}
		configs[i].TLS.Enabled = true
		if loadCertificates && configs[i].Host != "" {
			_, err := cacheManagedCertificate(configs[i].Host, false)
			if err != nil {
				return err
			}
		}
		setDefaultTLSParams(&configs[i])
	}
	return nil
}

// hostHasOtherPort returns true if there is another config in the list with the same
// hostname that has port otherPort, or false otherwise. All the configs are checked
// against the hostname of allConfigs[thisConfigIdx].
func hostHasOtherPort(allConfigs []server.Config, thisConfigIdx int, otherPort string) bool {
	for i, otherCfg := range allConfigs {
		if i == thisConfigIdx {
			continue // has to be a config OTHER than the one we're comparing against
		}
		if otherCfg.Host == allConfigs[thisConfigIdx].Host && otherCfg.Port == otherPort {
			return true
		}
	}
	return false
}

// MakePlaintextRedirects sets up redirects from port 80 to the relevant HTTPS
// hosts. You must pass in all configs, not just configs that qualify, since
// we must know whether the same host already exists on port 80, and those would
// not be in a list of configs that qualify for automatic HTTPS. This function will
// only set up redirects for configs that qualify. It returns the updated list of
// all configs.
func MakePlaintextRedirects(allConfigs []server.Config) []server.Config {
	for i, cfg := range allConfigs {
		if cfg.TLS.Managed &&
			!hostHasOtherPort(allConfigs, i, "80") &&
			(cfg.Port == "443" || !hostHasOtherPort(allConfigs, i, "443")) {
			allConfigs = append(allConfigs, redirPlaintextHost(cfg))
		}
	}
	return allConfigs
}

// ConfigQualifies returns true if cfg qualifies for
// fully managed TLS (but not on-demand TLS, which is
// not considered here). It does NOT check to see if a
// cert and key already exist for the config. If the
// config does qualify, you should set cfg.TLS.Managed
// to true and check that instead, because the process of
// setting up the config may make it look like it
// doesn't qualify even though it originally did.
func ConfigQualifies(cfg server.Config) bool {
	return (!cfg.TLS.Manual || cfg.TLS.OnDemand) && // user might provide own cert and key

		// user can force-disable automatic HTTPS for this host
		cfg.Scheme != "http" &&
		cfg.Port != "80" &&
		cfg.TLS.LetsEncryptEmail != "off" &&

		// we get can't certs for some kinds of hostnames, but
		// on-demand TLS allows empty hostnames at startup
		(HostQualifies(cfg.Host) || cfg.TLS.OnDemand)
}

// HostQualifies returns true if the hostname alone
// appears eligible for automatic HTTPS. For example,
// localhost, empty hostname, and IP addresses are
// not eligible because we cannot obtain certificates
// for those names.
func HostQualifies(hostname string) bool {
	return hostname != "localhost" && // localhost is ineligible

		// hostname must not be empty
		strings.TrimSpace(hostname) != "" &&

		// cannot be an IP address, see
		// https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt
		// (also trim [] from either end, since that special case can sneak through
		// for IPv6 addresses using the -host flag and with empty/no Caddyfile)
		net.ParseIP(strings.Trim(hostname, "[]")) == nil
}

// existingCertAndKey returns true if the host has a certificate
// and private key in storage already, false otherwise.
func existingCertAndKey(host string) bool {
	_, err := os.Stat(storage.SiteCertFile(host))
	if err != nil {
		return false
	}
	_, err = os.Stat(storage.SiteKeyFile(host))
	if err != nil {
		return false
	}
	return true
}

// saveCertResource saves the certificate resource to disk. This
// includes the certificate file itself, the private key, and the
// metadata file.
func saveCertResource(cert acme.CertificateResource) error {
	err := os.MkdirAll(storage.Site(cert.Domain), 0700)
	if err != nil {
		return err
	}

	// Save cert
	err = ioutil.WriteFile(storage.SiteCertFile(cert.Domain), cert.Certificate, 0600)
	if err != nil {
		return err
	}

	// Save private key
	err = ioutil.WriteFile(storage.SiteKeyFile(cert.Domain), cert.PrivateKey, 0600)
	if err != nil {
		return err
	}

	// Save cert metadata
	jsonBytes, err := json.MarshalIndent(&cert, "", "\t")
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(storage.SiteMetaFile(cert.Domain), jsonBytes, 0600)
	if err != nil {
		return err
	}

	return nil
}

// redirPlaintextHost returns a new plaintext HTTP configuration for
// a virtualHost that simply redirects to cfg, which is assumed to
// be the HTTPS configuration. The returned configuration is set
// to listen on port 80.
func redirPlaintextHost(cfg server.Config) server.Config {
	toURL := "https://{host}" // serve any host, since cfg.Host could be empty
	if cfg.Port != "443" && cfg.Port != "80" {
		toURL += ":" + cfg.Port
	}

	redirMidware := func(next middleware.Handler) middleware.Handler {
		return redirect.Redirect{Next: next, Rules: []redirect.Rule{
			{
				FromScheme: "http",
				FromPath:   "/",
				To:         toURL + "{uri}",
				Code:       http.StatusMovedPermanently,
			},
		}}
	}

	return server.Config{
		Host:       cfg.Host,
		BindHost:   cfg.BindHost,
		Port:       "80",
		Middleware: []middleware.Middleware{redirMidware},
	}
}

// Revoke revokes the certificate for host via ACME protocol.
func Revoke(host string) error {
	if !existingCertAndKey(host) {
		return errors.New("no certificate and key for " + host)
	}

	email := getEmail(server.Config{Host: host}, true)
	if email == "" {
		return errors.New("email is required to revoke")
	}

	client, err := NewACMEClient(email, true)
	if err != nil {
		return err
	}

	certFile := storage.SiteCertFile(host)
	certBytes, err := ioutil.ReadFile(certFile)
	if err != nil {
		return err
	}

	err = client.RevokeCertificate(certBytes)
	if err != nil {
		return err
	}

	err = os.Remove(certFile)
	if err != nil {
		return errors.New("certificate revoked, but unable to delete certificate file: " + err.Error())
	}

	return nil
}

var (
	// DefaultEmail represents the Let's Encrypt account email to use if none provided
	DefaultEmail string

	// Agreed indicates whether user has agreed to the Let's Encrypt SA
	Agreed bool

	// CAUrl represents the base URL to the CA's ACME endpoint
	CAUrl string
)

// AlternatePort is the port on which the acme client will open a
// listener and solve the CA's challenges. If this alternate port
// is used instead of the default port (80 or 443), then the
// default port for the challenge must be forwarded to this one.
const AlternatePort = "5033"

// KeySize represents the length of a key in bits.
type KeySize int

// Key sizes are used to determine the strength of a key.
const (
	Ecc224  KeySize = 224
	Ecc256          = 256
	Rsa2048         = 2048
	Rsa4096         = 4096
)

// rsaKeySizeToUse is the size to use for new RSA keys.
// This shouldn't need to change except for in tests;
// the size can be drastically reduced for speed.
var rsaKeySizeToUse = Rsa2048

// stopChan is used to signal the maintenance goroutine
// to terminate.
var stopChan chan struct{}
<commit_msg>Only obtain certificate and enable TLS if host qualifies (fixes #638)<commit_after>// Package https facilitates the management of TLS assets and integrates
// Let's Encrypt functionality into Caddy with first-class support for
// creating and renewing certificates automatically. It is designed to
// configure sites for HTTPS by default.
package https

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"strings"

	"github.com/mholt/caddy/middleware"
	"github.com/mholt/caddy/middleware/redirect"
	"github.com/mholt/caddy/server"
	"github.com/xenolf/lego/acme"
)

// Activate sets up TLS for each server config in configs
// as needed; this consists of acquiring and maintaining
// certificates and keys for qualifying configs and enabling
// OCSP stapling for all TLS-enabled configs.
//
// This function may prompt the user to provide an email
// address if none is available through other means. It
// prefers the email address specified in the config, but
// if that is not available it will check the command line
// argument. If absent, it will use the most recent email
// address from last time. If there isn't one, the user
// will be prompted and shown SA link.
//
// Also note that calling this function activates asset
// management automatically, which keeps certificates
// renewed and OCSP stapling updated.
//
// Activate returns the updated list of configs, since
// some may have been appended, for example, to redirect
// plaintext HTTP requests to their HTTPS counterpart.
// This function only appends; it does not splice.
func Activate(configs []server.Config) ([]server.Config, error) {
	// just in case previous caller forgot...
	Deactivate()

	// pre-screen each config and earmark the ones that qualify for managed TLS
	MarkQualified(configs)

	// place certificates and keys on disk
	err := ObtainCerts(configs, true, false)
	if err != nil {
		return configs, err
	}

	// update TLS configurations
	err = EnableTLS(configs, true)
	if err != nil {
		return configs, err
	}

	// set up redirects
	configs = MakePlaintextRedirects(configs)

	// renew all relevant certificates that need renewal. this is important
	// to do right away for a couple reasons, mainly because each restart,
	// the renewal ticker is reset, so if restarts happen more often than
	// the ticker interval, renewals would never happen. but doing
	// it right away at start guarantees that renewals aren't missed.
	err = renewManagedCertificates(true)
	if err != nil {
		return configs, err
	}

	// keep certificates renewed and OCSP stapling updated
	go maintainAssets(stopChan)

	return configs, nil
}

// Deactivate cleans up long-term, in-memory resources
// allocated by calling Activate(). Essentially, it stops
// the asset maintainer from running, meaning that certificates
// will not be renewed, OCSP staples will not be updated, etc.
func Deactivate() (err error) {
	defer func() {
		if rec := recover(); rec != nil {
			err = errors.New("already deactivated")
		}
	}()
	close(stopChan)
	stopChan = make(chan struct{})
	return
}

// MarkQualified scans each config and, if it qualifies for managed
// TLS, it sets the Managed field of the TLSConfig to true.
func MarkQualified(configs []server.Config) {
	for i := 0; i < len(configs); i++ {
		if ConfigQualifies(configs[i]) {
			configs[i].TLS.Managed = true
		}
	}
}

// ObtainCerts obtains certificates for all these configs as long as a
// certificate does not already exist on disk. It does not modify the
// configs at all; it only obtains and stores certificates and keys to
// the disk. If allowPrompts is true, the user may be shown a prompt.
// If proxyACME is true, the ACME challenges will be proxied to our alt port.
func ObtainCerts(configs []server.Config, allowPrompts, proxyACME bool) error {
	// We group configs by email so we don't make the same clients over and
	// over. This has the potential to prompt the user for an email, but we
	// prevent that by assuming that if we already have a listener that can
	// proxy ACME challenge requests, then the server is already running and
	// the operator is no longer present.
	groupedConfigs := groupConfigsByEmail(configs, allowPrompts)

	for email, group := range groupedConfigs {
		// Wait as long as we can before creating the client, because it
		// may not be needed, for example, if we already have what we
		// need on disk. Creating a client involves the network and
		// potentially prompting the user, etc., so only do if necessary.
		var client *ACMEClient

		for _, cfg := range group {
			if !HostQualifies(cfg.Host) || existingCertAndKey(cfg.Host) {
				continue
			}

			// Now we definitely do need a client
			if client == nil {
				var err error
				client, err = NewACMEClient(email, allowPrompts)
				if err != nil {
					return errors.New("error creating client: " + err.Error())
				}
			}

			// c.Configure assumes that allowPrompts == !proxyACME,
			// but that's not always true. For example, a restart where
			// the user isn't present and we're not listening on port 80.
			// TODO: This could probably be refactored better.
			if proxyACME {
				client.SetHTTPAddress(net.JoinHostPort(cfg.BindHost, AlternatePort))
				client.SetTLSAddress(net.JoinHostPort(cfg.BindHost, AlternatePort))
				client.ExcludeChallenges([]acme.Challenge{acme.TLSSNI01, acme.DNS01})
			} else {
				client.SetHTTPAddress(net.JoinHostPort(cfg.BindHost, ""))
				client.SetTLSAddress(net.JoinHostPort(cfg.BindHost, ""))
				client.ExcludeChallenges([]acme.Challenge{acme.DNS01})
			}

			err := client.Obtain([]string{cfg.Host})
			if err != nil {
				return err
			}
		}
	}

	return nil
}

// groupConfigsByEmail groups configs by the email address to be used by an
// ACME client. It only groups configs that have TLS enabled and that are
// marked as Managed. If userPresent is true, the operator MAY be prompted
// for an email address.
func groupConfigsByEmail(configs []server.Config, userPresent bool) map[string][]server.Config {
	initMap := make(map[string][]server.Config)
	for _, cfg := range configs {
		if !cfg.TLS.Managed {
			continue
		}
		leEmail := getEmail(cfg, userPresent)
		initMap[leEmail] = append(initMap[leEmail], cfg)
	}
	return initMap
}

// EnableTLS configures each config to use TLS according to default settings.
// It will only change configs that are marked as managed, and assumes that
// certificates and keys are already on disk. If loadCertificates is true,
// the certificates will be loaded from disk into the cache for this process
// to use. If false, TLS will still be enabled and configured with default
// settings, but no certificates will be parsed loaded into the cache, and
// the returned error value will always be nil.
func EnableTLS(configs []server.Config, loadCertificates bool) error {
	for i := 0; i < len(configs); i++ {
		if !configs[i].TLS.Managed {
			continue
		}
		configs[i].TLS.Enabled = true
		if loadCertificates && HostQualifies(configs[i].Host) {
			_, err := cacheManagedCertificate(configs[i].Host, false)
			if err != nil {
				return err
			}
		}
		setDefaultTLSParams(&configs[i])
	}
	return nil
}

// hostHasOtherPort returns true if there is another config in the list with the same
// hostname that has port otherPort, or false otherwise. All the configs are checked
// against the hostname of allConfigs[thisConfigIdx].
func hostHasOtherPort(allConfigs []server.Config, thisConfigIdx int, otherPort string) bool {
	for i, otherCfg := range allConfigs {
		if i == thisConfigIdx {
			continue // has to be a config OTHER than the one we're comparing against
		}
		if otherCfg.Host == allConfigs[thisConfigIdx].Host && otherCfg.Port == otherPort {
			return true
		}
	}
	return false
}

// MakePlaintextRedirects sets up redirects from port 80 to the relevant HTTPS
// hosts. You must pass in all configs, not just configs that qualify, since
// we must know whether the same host already exists on port 80, and those would
// not be in a list of configs that qualify for automatic HTTPS. This function will
// only set up redirects for configs that qualify. It returns the updated list of
// all configs.
func MakePlaintextRedirects(allConfigs []server.Config) []server.Config {
	for i, cfg := range allConfigs {
		if cfg.TLS.Managed &&
			!hostHasOtherPort(allConfigs, i, "80") &&
			(cfg.Port == "443" || !hostHasOtherPort(allConfigs, i, "443")) {
			allConfigs = append(allConfigs, redirPlaintextHost(cfg))
		}
	}
	return allConfigs
}

// ConfigQualifies returns true if cfg qualifies for
// fully managed TLS (but not on-demand TLS, which is
// not considered here). It does NOT check to see if a
// cert and key already exist for the config. If the
// config does qualify, you should set cfg.TLS.Managed
// to true and check that instead, because the process of
// setting up the config may make it look like it
// doesn't qualify even though it originally did.
func ConfigQualifies(cfg server.Config) bool {
	return (!cfg.TLS.Manual || cfg.TLS.OnDemand) && // user might provide own cert and key

		// user can force-disable automatic HTTPS for this host
		cfg.Scheme != "http" &&
		cfg.Port != "80" &&
		cfg.TLS.LetsEncryptEmail != "off" &&

		// we get can't certs for some kinds of hostnames, but
		// on-demand TLS allows empty hostnames at startup
		(HostQualifies(cfg.Host) || cfg.TLS.OnDemand)
}

// HostQualifies returns true if the hostname alone
// appears eligible for automatic HTTPS. For example,
// localhost, empty hostname, and IP addresses are
// not eligible because we cannot obtain certificates
// for those names.
func HostQualifies(hostname string) bool {
	return hostname != "localhost" && // localhost is ineligible

		// hostname must not be empty
		strings.TrimSpace(hostname) != "" &&

		// cannot be an IP address, see
		// https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt
		// (also trim [] from either end, since that special case can sneak through
		// for IPv6 addresses using the -host flag and with empty/no Caddyfile)
		net.ParseIP(strings.Trim(hostname, "[]")) == nil
}

// existingCertAndKey returns true if the host has a certificate
// and private key in storage already, false otherwise.
func existingCertAndKey(host string) bool {
	_, err := os.Stat(storage.SiteCertFile(host))
	if err != nil {
		return false
	}
	_, err = os.Stat(storage.SiteKeyFile(host))
	if err != nil {
		return false
	}
	return true
}

// saveCertResource saves the certificate resource to disk. This
// includes the certificate file itself, the private key, and the
// metadata file.
func saveCertResource(cert acme.CertificateResource) error {
	err := os.MkdirAll(storage.Site(cert.Domain), 0700)
	if err != nil {
		return err
	}

	// Save cert
	err = ioutil.WriteFile(storage.SiteCertFile(cert.Domain), cert.Certificate, 0600)
	if err != nil {
		return err
	}

	// Save private key
	err = ioutil.WriteFile(storage.SiteKeyFile(cert.Domain), cert.PrivateKey, 0600)
	if err != nil {
		return err
	}

	// Save cert metadata
	jsonBytes, err := json.MarshalIndent(&cert, "", "\t")
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(storage.SiteMetaFile(cert.Domain), jsonBytes, 0600)
	if err != nil {
		return err
	}

	return nil
}

// redirPlaintextHost returns a new plaintext HTTP configuration for
// a virtualHost that simply redirects to cfg, which is assumed to
// be the HTTPS configuration. The returned configuration is set
// to listen on port 80.
func redirPlaintextHost(cfg server.Config) server.Config {
	toURL := "https://{host}" // serve any host, since cfg.Host could be empty
	if cfg.Port != "443" && cfg.Port != "80" {
		toURL += ":" + cfg.Port
	}

	redirMidware := func(next middleware.Handler) middleware.Handler {
		return redirect.Redirect{Next: next, Rules: []redirect.Rule{
			{
				FromScheme: "http",
				FromPath:   "/",
				To:         toURL + "{uri}",
				Code:       http.StatusMovedPermanently,
			},
		}}
	}

	return server.Config{
		Host:       cfg.Host,
		BindHost:   cfg.BindHost,
		Port:       "80",
		Middleware: []middleware.Middleware{redirMidware},
	}
}

// Revoke revokes the certificate for host via ACME protocol.
func Revoke(host string) error {
	if !existingCertAndKey(host) {
		return errors.New("no certificate and key for " + host)
	}

	email := getEmail(server.Config{Host: host}, true)
	if email == "" {
		return errors.New("email is required to revoke")
	}

	client, err := NewACMEClient(email, true)
	if err != nil {
		return err
	}

	certFile := storage.SiteCertFile(host)
	certBytes, err := ioutil.ReadFile(certFile)
	if err != nil {
		return err
	}

	err = client.RevokeCertificate(certBytes)
	if err != nil {
		return err
	}

	err = os.Remove(certFile)
	if err != nil {
		return errors.New("certificate revoked, but unable to delete certificate file: " + err.Error())
	}

	return nil
}

var (
	// DefaultEmail represents the Let's Encrypt account email to use if none provided
	DefaultEmail string

	// Agreed indicates whether user has agreed to the Let's Encrypt SA
	Agreed bool

	// CAUrl represents the base URL to the CA's ACME endpoint
	CAUrl string
)

// AlternatePort is the port on which the acme client will open a
// listener and solve the CA's challenges. If this alternate port
// is used instead of the default port (80 or 443), then the
// default port for the challenge must be forwarded to this one.
const AlternatePort = "5033"

// KeySize represents the length of a key in bits.
type KeySize int

// Key sizes are used to determine the strength of a key.
const (
	Ecc224  KeySize = 224
	Ecc256          = 256
	Rsa2048         = 2048
	Rsa4096         = 4096
)

// rsaKeySizeToUse is the size to use for new RSA keys.
// This shouldn't need to change except for in tests;
// the size can be drastically reduced for speed.
var rsaKeySizeToUse = Rsa2048

// stopChan is used to signal the maintenance goroutine
// to terminate.
var stopChan chan struct{}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
)

var (
	dataFile = flag.String("file", "store.json", "data store file name")
	status   = flag.String("status", "stopped|pending|terminated", "the status you would like to poll")
)

var store *StatusStore

type Conn struct {
	aw2  *ec2.EC2
	data map[string]string
}

// create a struct to map toml config file
// TODO: add this as an switch in init()
type AwsConfig struct {
	AwsSecretKey string `toml:"AWS_ACCESS_KEY_ID"`
	AwsAccessKey string `toml:"AWS_SECRET_ACCESS_KEY"`
	Region       string `toml:"AWS_REGION"`
}

func recieveStatus(dataMap map[string]string) <-chan string {
	c := make(chan string)
	go func() {
		for _, v := range dataMap {
			c <- fmt.Sprintf("%s", v)
			//			time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
		}
	}()
	return c

}

func (c *Conn) iterateResToMap(resp *ec2.DescribeInstancesOutput) {
	insMap := make(map[string]string)
	for idx, _ := range resp.Reservations {
		for _, inst := range resp.Reservations[idx].Instances {
			// fmt.Printf("   Instance State: %v InstanceID: %v \n", *inst.State.Name, *inst.InstanceID)
			// dereference pointer
			var id, state string
			id = *inst.PrivateDNSName
			state = *inst.State.Name
			insMap[id] = state
		}
	}
	c.data = insMap
}

func (c *Conn) GetEc2Data() {

	resp, err := c.aw2.DescribeInstances(nil)
	if err != nil {
		log.Fatal(err)
	}

	c.iterateResToMap(resp)

}

func NewEc2() *Conn {
	c := new(Conn)
	c.aw2 = ec2.New(&aws.Config{Region: "us-west-2"})

	return c
}

func (d *StatusStore) AddDataToFile(status string, c *Conn) {
	for k, v := range c.data {
		if v == status {
			if _, ok := d.status[k]; ok {
				fmt.Printf("we already have that key %s", d.status[k])
			} else {
				err := d.save(k, v)
				if err != nil {
					log.Printf("something went wrong save %s", k)
				}
			}
		}
	}
}

func Add(value string) {
	key := store.Put(value)
	fmt.Println(key)
}

func main() {
	// Create an EC2 service object in the "us-west-2" region
	// Note that you can also configure your region globally by
	// exporting the AWS_REGION environment variable
	flag.Parse()

	// instantiate new ec2 "object"
	c := NewEc2()

	// Get new Status store
	d := NewStatusStore(*dataFile)

	// Get a data set to work with
	c.GetEc2Data()

	// lets save some data
	d.AddDataToFile(*status, c)

}
<commit_msg>removing some debug messages<commit_after>package main

import (
	"flag"
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
)

var (
	dataFile = flag.String("file", "store.json", "data store file name")
	status   = flag.String("status", "stopped|pending|terminated", "the status you would like to poll")
)

var store *StatusStore

type Conn struct {
	aw2  *ec2.EC2
	data map[string]string
}

// create a struct to map toml config file
// TODO: add this as an switch in init()
type AwsConfig struct {
	AwsSecretKey string `toml:"AWS_ACCESS_KEY_ID"`
	AwsAccessKey string `toml:"AWS_SECRET_ACCESS_KEY"`
	Region       string `toml:"AWS_REGION"`
}

func recieveStatus(dataMap map[string]string) <-chan string {
	c := make(chan string)
	go func() {
		for _, v := range dataMap {
			c <- fmt.Sprintf("%s", v)
			//			time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
		}
	}()
	return c

}

func (c *Conn) iterateResToMap(resp *ec2.DescribeInstancesOutput) {
	insMap := make(map[string]string)
	for idx, _ := range resp.Reservations {
		for _, inst := range resp.Reservations[idx].Instances {
			// fmt.Printf("   Instance State: %v InstanceID: %v \n", *inst.State.Name, *inst.InstanceID)
			// dereference pointer
			var id, state string
			id = *inst.PrivateDNSName
			state = *inst.State.Name
			insMap[id] = state
		}
	}
	c.data = insMap
}

func (c *Conn) GetEc2Data() {

	resp, err := c.aw2.DescribeInstances(nil)
	if err != nil {
		log.Fatal(err)
	}

	c.iterateResToMap(resp)

}

func NewEc2() *Conn {
	c := new(Conn)
	c.aw2 = ec2.New(&aws.Config{Region: "us-west-2"})

	return c
}

func (d *StatusStore) AddDataToFile(status string, c *Conn) {
	for k, v := range c.data {
		if v == status {
			if _, ok := d.status[k]; ok {
				fmt.Printf("we already have that key %s\n", d.status[k])
			} else {
				err := d.save(k, v)
				if err != nil {
					log.Printf("something went wrong save %s", k)
				}
			}
		}
	}
}

func Add(value string) {
	key := store.Put(value)
	fmt.Println(key)
}

func main() {
	// Create an EC2 service object in the "us-west-2" region
	// Note that you can also configure your region globally by
	// exporting the AWS_REGION environment variable
	flag.Parse()

	// instantiate new ec2 "object"
	c := NewEc2()

	// Get new Status store
	d := NewStatusStore(*dataFile)

	// Get a data set to work with
	c.GetEc2Data()

	// lets save some data
	d.AddDataToFile(*status, c)

}
<|endoftext|>


<commit_before>package job

import (
	"context"
	"fmt"
	"sort"
	"sync"

	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"

	"github.com/zrepl/zrepl/daemon/logging/trace"
	"github.com/zrepl/zrepl/util/nodefault"

	"github.com/zrepl/zrepl/config"
	"github.com/zrepl/zrepl/daemon/filters"
	"github.com/zrepl/zrepl/daemon/job/wakeup"
	"github.com/zrepl/zrepl/daemon/pruner"
	"github.com/zrepl/zrepl/daemon/snapper"
	"github.com/zrepl/zrepl/endpoint"
	"github.com/zrepl/zrepl/replication/logic/pdu"
	"github.com/zrepl/zrepl/zfs"
)

type SnapJob struct {
	name     endpoint.JobID
	fsfilter zfs.DatasetFilter
	snapper  *snapper.PeriodicOrManual

	prunerFactory *pruner.LocalPrunerFactory

	promPruneSecs *prometheus.HistogramVec // labels: prune_side

	prunerMtx sync.Mutex
	pruner    *pruner.Pruner
}

func (j *SnapJob) Name() string { return j.name.String() }

func (j *SnapJob) Type() Type { return TypeSnap }

func snapJobFromConfig(g *config.Global, in *config.SnapJob) (j *SnapJob, err error) {
	j = &SnapJob{}
	fsf, err := filters.DatasetMapFilterFromConfig(in.Filesystems)
	if err != nil {
		return nil, errors.Wrap(err, "cannot build filesystem filter")
	}
	j.fsfilter = fsf

	if j.snapper, err = snapper.FromConfig(g, fsf, in.Snapshotting); err != nil {
		return nil, errors.Wrap(err, "cannot build snapper")
	}
	j.name, err = endpoint.MakeJobID(in.Name)
	if err != nil {
		return nil, errors.Wrap(err, "invalid job name")
	}
	j.promPruneSecs = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Namespace:   "zrepl",
		Subsystem:   "pruning",
		Name:        "time",
		Help:        "seconds spent in pruner",
		ConstLabels: prometheus.Labels{"zrepl_job": j.name.String()},
	}, []string{"prune_side"})
	j.prunerFactory, err = pruner.NewLocalPrunerFactory(in.Pruning, j.promPruneSecs)
	if err != nil {
		return nil, errors.Wrap(err, "cannot build snapjob pruning rules")
	}
	return j, nil
}

func (j *SnapJob) RegisterMetrics(registerer prometheus.Registerer) {
	registerer.MustRegister(j.promPruneSecs)
}

type SnapJobStatus struct {
	Pruning      *pruner.Report
	Snapshotting *snapper.Report // may be nil
}

func (j *SnapJob) Status() *Status {
	s := &SnapJobStatus{}
	t := j.Type()
	j.prunerMtx.Lock()
	if j.pruner != nil {
		s.Pruning = j.pruner.Report()
	}
	j.prunerMtx.Unlock()
	s.Snapshotting = j.snapper.Report()
	return &Status{Type: t, JobSpecific: s}
}

func (j *SnapJob) OwnedDatasetSubtreeRoot() (rfs *zfs.DatasetPath, ok bool) {
	return nil, false
}

func (j *SnapJob) SenderConfig() *endpoint.SenderConfig { return nil }

func (j *SnapJob) Run(ctx context.Context) {
	ctx, endTask := trace.WithTaskAndSpan(ctx, "snap-job", j.Name())
	defer endTask()
	log := GetLogger(ctx)

	defer log.Info("job exiting")

	periodicDone := make(chan struct{})
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	periodicCtx, endTask := trace.WithTask(ctx, "snapshotting")
	defer endTask()
	go j.snapper.Run(periodicCtx, periodicDone)

	invocationCount := 0
outer:
	for {
		log.Info("wait for wakeups")
		select {
		case <-ctx.Done():
			log.WithError(ctx.Err()).Info("context")
			break outer

		case <-wakeup.Wait(ctx):
		case <-periodicDone:
		}
		invocationCount++

		invocationCtx, endSpan := trace.WithSpan(ctx, fmt.Sprintf("invocation-%d", invocationCount))
		j.doPrune(invocationCtx)
		endSpan()
	}
}

// Adaptor that implements pruner.History around a pruner.Target.
// The ReplicationCursor method is Get-op only and always returns
// the filesystem's most recent version's GUID.
//
// TODO:
// This is a work-around for the current package daemon/pruner
// and package pruning.Snapshot limitation: they require the
//  `Replicated` getter method be present, but obviously,
// a local job like SnapJob can't deliver on that.
// But the pruner.Pruner gives up on an FS if no replication
// cursor is present, which is why this pruner returns the
// most recent filesystem version.
type alwaysUpToDateReplicationCursorHistory struct {
	// the Target passed as Target to BuildLocalPruner
	target pruner.Target
}

var _ pruner.History = (*alwaysUpToDateReplicationCursorHistory)(nil)

func (h alwaysUpToDateReplicationCursorHistory) ReplicationCursor(ctx context.Context, req *pdu.ReplicationCursorReq) (*pdu.ReplicationCursorRes, error) {
	fsvReq := &pdu.ListFilesystemVersionsReq{
		Filesystem: req.GetFilesystem(),
	}
	res, err := h.target.ListFilesystemVersions(ctx, fsvReq)
	if err != nil {
		return nil, err
	}
	fsvs := res.GetVersions()
	if len(fsvs) <= 0 {
		return &pdu.ReplicationCursorRes{Result: &pdu.ReplicationCursorRes_Notexist{Notexist: true}}, nil
	}
	// always return must recent version
	sort.Slice(fsvs, func(i, j int) bool {
		return fsvs[i].CreateTXG < fsvs[j].CreateTXG
	})
	mostRecent := fsvs[len(fsvs)-1]
	return &pdu.ReplicationCursorRes{Result: &pdu.ReplicationCursorRes_Guid{Guid: mostRecent.GetGuid()}}, nil
}

func (h alwaysUpToDateReplicationCursorHistory) ListFilesystems(ctx context.Context, req *pdu.ListFilesystemReq) (*pdu.ListFilesystemRes, error) {
	return h.target.ListFilesystems(ctx, req)
}

func (j *SnapJob) doPrune(ctx context.Context) {
	ctx, endSpan := trace.WithSpan(ctx, "snap-job-do-prune")
	defer endSpan()
	log := GetLogger(ctx)
	sender := endpoint.NewSender(endpoint.SenderConfig{
		JobID: j.name,
		FSF:   j.fsfilter,
		// FIXME encryption setting is irrelevant for SnapJob because the endpoint is only used as pruner.Target
		Encrypt: &nodefault.Bool{B: true},
	})
	j.prunerMtx.Lock()
	j.pruner = j.prunerFactory.BuildLocalPruner(ctx, sender, alwaysUpToDateReplicationCursorHistory{sender})
	j.prunerMtx.Unlock()
	log.Info("start pruning")
	j.pruner.Prune()
	log.Info("finished pruning")
}
<commit_msg>bandwidth limiting: fix crash with SnapJob<commit_after>package job

import (
	"context"
	"fmt"
	"sort"
	"sync"

	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"

	"github.com/zrepl/zrepl/daemon/logging/trace"
	"github.com/zrepl/zrepl/util/bandwidthlimit"
	"github.com/zrepl/zrepl/util/nodefault"

	"github.com/zrepl/zrepl/config"
	"github.com/zrepl/zrepl/daemon/filters"
	"github.com/zrepl/zrepl/daemon/job/wakeup"
	"github.com/zrepl/zrepl/daemon/pruner"
	"github.com/zrepl/zrepl/daemon/snapper"
	"github.com/zrepl/zrepl/endpoint"
	"github.com/zrepl/zrepl/replication/logic/pdu"
	"github.com/zrepl/zrepl/zfs"
)

type SnapJob struct {
	name     endpoint.JobID
	fsfilter zfs.DatasetFilter
	snapper  *snapper.PeriodicOrManual

	prunerFactory *pruner.LocalPrunerFactory

	promPruneSecs *prometheus.HistogramVec // labels: prune_side

	prunerMtx sync.Mutex
	pruner    *pruner.Pruner
}

func (j *SnapJob) Name() string { return j.name.String() }

func (j *SnapJob) Type() Type { return TypeSnap }

func snapJobFromConfig(g *config.Global, in *config.SnapJob) (j *SnapJob, err error) {
	j = &SnapJob{}
	fsf, err := filters.DatasetMapFilterFromConfig(in.Filesystems)
	if err != nil {
		return nil, errors.Wrap(err, "cannot build filesystem filter")
	}
	j.fsfilter = fsf

	if j.snapper, err = snapper.FromConfig(g, fsf, in.Snapshotting); err != nil {
		return nil, errors.Wrap(err, "cannot build snapper")
	}
	j.name, err = endpoint.MakeJobID(in.Name)
	if err != nil {
		return nil, errors.Wrap(err, "invalid job name")
	}
	j.promPruneSecs = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Namespace:   "zrepl",
		Subsystem:   "pruning",
		Name:        "time",
		Help:        "seconds spent in pruner",
		ConstLabels: prometheus.Labels{"zrepl_job": j.name.String()},
	}, []string{"prune_side"})
	j.prunerFactory, err = pruner.NewLocalPrunerFactory(in.Pruning, j.promPruneSecs)
	if err != nil {
		return nil, errors.Wrap(err, "cannot build snapjob pruning rules")
	}
	return j, nil
}

func (j *SnapJob) RegisterMetrics(registerer prometheus.Registerer) {
	registerer.MustRegister(j.promPruneSecs)
}

type SnapJobStatus struct {
	Pruning      *pruner.Report
	Snapshotting *snapper.Report // may be nil
}

func (j *SnapJob) Status() *Status {
	s := &SnapJobStatus{}
	t := j.Type()
	j.prunerMtx.Lock()
	if j.pruner != nil {
		s.Pruning = j.pruner.Report()
	}
	j.prunerMtx.Unlock()
	s.Snapshotting = j.snapper.Report()
	return &Status{Type: t, JobSpecific: s}
}

func (j *SnapJob) OwnedDatasetSubtreeRoot() (rfs *zfs.DatasetPath, ok bool) {
	return nil, false
}

func (j *SnapJob) SenderConfig() *endpoint.SenderConfig { return nil }

func (j *SnapJob) Run(ctx context.Context) {
	ctx, endTask := trace.WithTaskAndSpan(ctx, "snap-job", j.Name())
	defer endTask()
	log := GetLogger(ctx)

	defer log.Info("job exiting")

	periodicDone := make(chan struct{})
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	periodicCtx, endTask := trace.WithTask(ctx, "snapshotting")
	defer endTask()
	go j.snapper.Run(periodicCtx, periodicDone)

	invocationCount := 0
outer:
	for {
		log.Info("wait for wakeups")
		select {
		case <-ctx.Done():
			log.WithError(ctx.Err()).Info("context")
			break outer

		case <-wakeup.Wait(ctx):
		case <-periodicDone:
		}
		invocationCount++

		invocationCtx, endSpan := trace.WithSpan(ctx, fmt.Sprintf("invocation-%d", invocationCount))
		j.doPrune(invocationCtx)
		endSpan()
	}
}

// Adaptor that implements pruner.History around a pruner.Target.
// The ReplicationCursor method is Get-op only and always returns
// the filesystem's most recent version's GUID.
//
// TODO:
// This is a work-around for the current package daemon/pruner
// and package pruning.Snapshot limitation: they require the
//  `Replicated` getter method be present, but obviously,
// a local job like SnapJob can't deliver on that.
// But the pruner.Pruner gives up on an FS if no replication
// cursor is present, which is why this pruner returns the
// most recent filesystem version.
type alwaysUpToDateReplicationCursorHistory struct {
	// the Target passed as Target to BuildLocalPruner
	target pruner.Target
}

var _ pruner.History = (*alwaysUpToDateReplicationCursorHistory)(nil)

func (h alwaysUpToDateReplicationCursorHistory) ReplicationCursor(ctx context.Context, req *pdu.ReplicationCursorReq) (*pdu.ReplicationCursorRes, error) {
	fsvReq := &pdu.ListFilesystemVersionsReq{
		Filesystem: req.GetFilesystem(),
	}
	res, err := h.target.ListFilesystemVersions(ctx, fsvReq)
	if err != nil {
		return nil, err
	}
	fsvs := res.GetVersions()
	if len(fsvs) <= 0 {
		return &pdu.ReplicationCursorRes{Result: &pdu.ReplicationCursorRes_Notexist{Notexist: true}}, nil
	}
	// always return must recent version
	sort.Slice(fsvs, func(i, j int) bool {
		return fsvs[i].CreateTXG < fsvs[j].CreateTXG
	})
	mostRecent := fsvs[len(fsvs)-1]
	return &pdu.ReplicationCursorRes{Result: &pdu.ReplicationCursorRes_Guid{Guid: mostRecent.GetGuid()}}, nil
}

func (h alwaysUpToDateReplicationCursorHistory) ListFilesystems(ctx context.Context, req *pdu.ListFilesystemReq) (*pdu.ListFilesystemRes, error) {
	return h.target.ListFilesystems(ctx, req)
}

func (j *SnapJob) doPrune(ctx context.Context) {
	ctx, endSpan := trace.WithSpan(ctx, "snap-job-do-prune")
	defer endSpan()
	log := GetLogger(ctx)
	sender := endpoint.NewSender(endpoint.SenderConfig{
		JobID: j.name,
		FSF:   j.fsfilter,
		// FIXME the following config fields are irrelevant for SnapJob
		// because the endpoint is only used as pruner.Target.
		// However, the implementation requires them to be set.
		Encrypt:        &nodefault.Bool{B: true},
		BandwidthLimit: bandwidthlimit.NoLimitConfig(),
	})
	j.prunerMtx.Lock()
	j.pruner = j.prunerFactory.BuildLocalPruner(ctx, sender, alwaysUpToDateReplicationCursorHistory{sender})
	j.prunerMtx.Unlock()
	log.Info("start pruning")
	j.pruner.Prune()
	log.Info("finished pruning")
}
<|endoftext|>


<commit_before>package ecs

import "github.com/jiangshengwu/aliyun-sdk-for-go/util"

type InstanceService interface {
	/**
	 * RegionId(required):
	 * ZoneId(optional):
	 * ImageId(required):
	 * InstanceType(required): OtherService.DescribeInstanceTypes
	 * SecurityGroupId(required):
	 * InstanceName(optional):
	 * Description(optional):
	 * InternetChargeType(optional): 网络计费类型，按流量计费还是按固定带宽计费: PayByBandwidth|PayByTraffic，默认PayByBandwidth
	 * InternetMaxBandwidthIn(optional):
	 * InternetMaxBandwidthOut(optional):
	 * HostName(optional):
	 * Password(optional):
	 * IoOptimized(optional): IO优化, none|optimized, 默认none
	 * SystemDisk.Category(optional):
	 * SystemDisk.DiskName(optional):
	 * SystemDisk.Description(optional):
	 * DataDisk.n.Size(optional):
	 * DataDisk.n.Category(optional):
	 * DataDisk.n.SnapshotId(optional):
	 * DataDisk.n.DiskName(optional):
	 * DataDisk.n.Description(optional):
	 * DataDisk.n.Device(optional):
	 * DataDisk.n.DeleteWithInstance(optional):
	 * VSwitchId(optional): 如果是创建 VPC 类型的实例，需要指定交换机的 ID
	 * PrivateIpAddress(optional):
	 */
	CreateInstance(params map[string]string) (CreateInstanceResponse, error)
	StartInstance(params map[string]string) (StartInstanceResponse, error)
	StopInstance(params map[string]string) (StopInstanceResponse, error)
	RebootInstance(params map[string]string) (RebootInstanceResponse, error)
	ModifyInstanceAttribute(params map[string]string) (ModifyInstanceAttributeResponse, error)
	ModifyInstanceVpcAttribute(params map[string]string) (ModifyInstanceVpcAttributeResponse, error)
	DescribeInstanceStatus(params map[string]string) (DescribeInstanceStatusResponse, error)
	DescribeInstanceAttribute(params map[string]string) (DescribeInstanceAttributeResponse, error)
	DescribeInstances(params map[string]string) (DescribeInstancesResponse, error)
	DeleteInstance(params map[string]string) (DeleteInstanceResponse, error)
	JoinSecurityGroup(params map[string]string) (JoinSecurityGroupResponse, error)
	LeaveSecurityGroup(params map[string]string) (LeaveSecurityGroupResponse, error)
	DescribeInstanceVncUrl(params map[string]string) (DescribeInstanceVncUrlResponse, error)
	ModifyInstanceVncPasswd(params map[string]string) (ModifyInstanceVncPasswdResponse, error)
}

type InstanceOperator struct {
	Common *CommonParam
}

// Response struct for CreateInstance
type CreateInstanceResponse struct {
	util.ErrorResponse
	InstanceId string `json:"InstanceId"`
}

// Response struct for StartInstance
type StartInstanceResponse struct {
	util.ErrorResponse
}

// Response struct for StopInstance
type StopInstanceResponse struct {
	util.ErrorResponse
}

// Response struct for RebootInstance
type RebootInstanceResponse struct {
	util.ErrorResponse
}

// Response struct for ModifyInstanceAttribute
type ModifyInstanceAttributeResponse struct {
	util.ErrorResponse
}

// Response struct for ModifyInstanceVpcAttribute
type ModifyInstanceVpcAttributeResponse struct {
	util.ErrorResponse
}

// Response struct for DescribeInstanceStatus
type DescribeInstanceStatusResponse struct {
	util.ErrorResponse
	util.PageResponse
	AllInstanceStatuses InstanceStatusSetType `json:"InstanceStatuses"`
}

type InstanceStatusSetType struct {
	AllInstanceStatus []InstanceStatusItemType `json:"InstanceStatus"`
}

// See http://docs.aliyun.com/?spm=5176.775974174.2.4.BYfRJ2#/ecs/open-api/datatype&instancestatusitemtype
type InstanceStatusItemType struct {
	InstanceId string `json:"InstanceId"`
	Status     string `json:"Status"`
}

// See http://docs.aliyun.com/?spm=5176.775974174.2.4.BYfRJ2#/ecs/open-api/datatype&instanceattributestype
type InstanceAttributesType struct {
	InstanceId              string                  `json:"InstanceId"`
	InstanceName            string                  `json:"InstanceName"`
	Description             string                  `json:"Description"`
	ImageId                 string                  `json:"ImageId"`
	RegionId                string                  `json:"RegionId"`
	ZoneId                  string                  `json:"ZoneId"`
	ClusterId               string                  `json:"ClusterId"`
	InstanceType            string                  `json:"InstanceType"`
	HostName                string                  `json:"HostName"`
	Status                  string                  `json:"Status"`
	AllOperationLocks       OperationLocks          `json:"OperationLocks"`
	AllSecurityGroupIds     SecurityGroupIdSetType  `json:"SecurityGroupIds"`
	PublicIpAddress         IpAddressSetType        `json:"PublicIpAddress"`
	InnerIpAddress          IpAddressSetType        `json:"InnerIpAddress"`
	InternetMaxBandwidthIn  int                     `json:"InternetMaxBandwidthIn"`
	InternetMaxBandwidthOut int                     `json:"InternetMaxBandwidthOut"`
	InternetChargeType      string                  `json:"InternetChargeType"`
	CreationTime            string                  `json:"CreationTime"`
	InstanceNetworkType     string                  `json:"InstanceNetworkType"`
	VpcAttributes           VpcAttributesType       `json:"VpcAttributes"`
	EipAddress              EipAddressAssociateType `json:"EipAddress"`
	InstanceChargeType      string                  `json:"InstanceChargeType"` // PrePaid：预付费，即包年包月; PostPaid：后付费，即按量付费
	DeviceAvailable         bool                    `json:"DeviceAvailable"`    // 实例是否还可以挂载磁盘
	IoOptimized             bool                    `json:"IoOptimized"`        // 是否是 IO 优化型实例
	ExpiredTime             string                  `json:"ExpiredTime"`        // 过期时间，按照ISO8601标准表示，并需要使用UTC时间。格式为：YYYY-MM-DDThh:mmZ
}

// See http://docs.aliyun.com/?spm=5176.775974174.2.4.BYfRJ2#/ecs/open-api/datatype&vpcattributestype
type VpcAttributesType struct {
	VpcId            string           `json:"VpcId"`
	VSwitchId        string           `json:"VSwitchId"`
	PrivateIpAddress IpAddressSetType `json:"PrivateIpAddress"`
	NatIpAddress     string           `json:"NatIpAddress"`
}

// See http://docs.aliyun.com/?spm=5176.775974174.2.4.BYfRJ2#/ecs/open-api/datatype&eipaddressassociatetype
type EipAddressAssociateType struct {
	AllocationId       string `json:"AllocationId"`
	IpAddress          string `json:"IpAddress"`
	Bandwidth          int    `json:"Bandwidth"`
	InternetChargeType string `json:"InternetChargeType"`
}

// Response struct for DescribeInstanceAttribute
type DescribeInstanceAttributeResponse struct {
	util.ErrorResponse
	InstanceAttributesType
}

// See http://docs.aliyun.com/?spm=5176.775974174.2.4.BYfRJ2#/ecs/open-api/datatype&securitygroupidsettype
type SecurityGroupIdSetType struct {
	AllSecurityGroupId []string `json:"SecurityGroupId"`
}

// See http://docs.aliyun.com/?spm=5176.775974174.2.4.BYfRJ2#/ecs/open-api/datatype&ipaddresssettype
type IpAddressSetType struct {
	AllIpAddress []string `json:"IpAddress"`
}

// Response struct for DescribeInstances
type DescribeInstancesResponse struct {
	util.ErrorResponse
	util.PageResponse
	AllInstances Instances `json:"Instances"`
}

type Instances struct {
	AllInstance []InstanceAttributesType `json:"Instance"`
}

// Response struct for DeleteInstance
type DeleteInstanceResponse struct {
	util.ErrorResponse
}

// Response struct for JoinSecurityGroup
type JoinSecurityGroupResponse struct {
	util.ErrorResponse
}

// Response struct for LeaveSecurityGroup
type LeaveSecurityGroupResponse struct {
	util.ErrorResponse
}

// Response struct for DescribeInstanceVncUrl
type DescribeInstanceVncUrlResponse struct {
	util.ErrorResponse
	util.PageResponse
	AllInstances Instances `json:"Instances"`
}

// Response struct for ModifyInstanceVncPasswd
type ModifyInstanceVncPasswdResponse struct {
	util.ErrorResponse
}

func (op *InstanceOperator) CreateInstance(params map[string]string) (CreateInstanceResponse, error) {
	var resp CreateInstanceResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) StartInstance(params map[string]string) (StartInstanceResponse, error) {
	var resp StartInstanceResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) StopInstance(params map[string]string) (StopInstanceResponse, error) {
	var resp StopInstanceResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) RebootInstance(params map[string]string) (RebootInstanceResponse, error) {
	var resp RebootInstanceResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) ModifyInstanceAttribute(params map[string]string) (ModifyInstanceAttributeResponse, error) {
	var resp ModifyInstanceAttributeResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) ModifyInstanceVpcAttribute(params map[string]string) (ModifyInstanceVpcAttributeResponse, error) {
	var resp ModifyInstanceVpcAttributeResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) DescribeInstanceStatus(params map[string]string) (DescribeInstanceStatusResponse, error) {
	var resp DescribeInstanceStatusResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) DescribeInstanceAttribute(params map[string]string) (DescribeInstanceAttributeResponse, error) {
	var resp DescribeInstanceAttributeResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) DescribeInstances(params map[string]string) (DescribeInstancesResponse, error) {
	var resp DescribeInstancesResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) DeleteInstance(params map[string]string) (DeleteInstanceResponse, error) {
	var resp DeleteInstanceResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) JoinSecurityGroup(params map[string]string) (JoinSecurityGroupResponse, error) {
	var resp JoinSecurityGroupResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) LeaveSecurityGroup(params map[string]string) (LeaveSecurityGroupResponse, error) {
	var resp LeaveSecurityGroupResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) DescribeInstanceVncUrl(params map[string]string) (DescribeInstanceVncUrlResponse, error) {
	var resp DescribeInstanceVncUrlResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) ModifyInstanceVncPasswd(params map[string]string) (ModifyInstanceVncPasswdResponse, error){
	var resp ModifyInstanceVncPasswdResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}
<commit_msg>Update instance.go<commit_after>package ecs

import "github.com/jiangshengwu/aliyun-sdk-for-go/util"

type InstanceService interface {
	/**
	 * RegionId(required):
	 * ZoneId(optional):
	 * ImageId(required):
	 * InstanceType(required): OtherService.DescribeInstanceTypes
	 * SecurityGroupId(required):
	 * InstanceName(optional):
	 * Description(optional):
	 * InternetChargeType(optional): 网络计费类型，按流量计费还是按固定带宽计费: PayByBandwidth|PayByTraffic，默认PayByBandwidth
	 * InternetMaxBandwidthIn(optional):
	 * InternetMaxBandwidthOut(optional):
	 * HostName(optional):
	 * Password(optional):
	 * IoOptimized(optional): IO优化, none|optimized, 默认none
	 * SystemDisk.Category(optional):
	 * SystemDisk.DiskName(optional):
	 * SystemDisk.Description(optional):
	 * DataDisk.n.Size(optional):
	 * DataDisk.n.Category(optional):
	 * DataDisk.n.SnapshotId(optional):
	 * DataDisk.n.DiskName(optional):
	 * DataDisk.n.Description(optional):
	 * DataDisk.n.Device(optional):
	 * DataDisk.n.DeleteWithInstance(optional):
	 * VSwitchId(optional): 如果是创建 VPC 类型的实例，需要指定交换机的 ID
	 * PrivateIpAddress(optional):
	 */
	CreateInstance(params map[string]string) (CreateInstanceResponse, error)
	StartInstance(params map[string]string) (StartInstanceResponse, error)
	StopInstance(params map[string]string) (StopInstanceResponse, error)
	RebootInstance(params map[string]string) (RebootInstanceResponse, error)
	ModifyInstanceAttribute(params map[string]string) (ModifyInstanceAttributeResponse, error)
	ModifyInstanceVpcAttribute(params map[string]string) (ModifyInstanceVpcAttributeResponse, error)
	DescribeInstanceStatus(params map[string]string) (DescribeInstanceStatusResponse, error)
	DescribeInstanceAttribute(params map[string]string) (DescribeInstanceAttributeResponse, error)
	DescribeInstances(params map[string]string) (DescribeInstancesResponse, error)
	DeleteInstance(params map[string]string) (DeleteInstanceResponse, error)
	JoinSecurityGroup(params map[string]string) (JoinSecurityGroupResponse, error)
	LeaveSecurityGroup(params map[string]string) (LeaveSecurityGroupResponse, error)
	DescribeInstanceVncUrl(params map[string]string) (DescribeInstanceVncUrlResponse, error)
	ModifyInstanceVncPasswd(params map[string]string) (ModifyInstanceVncPasswdResponse, error)
}

type InstanceOperator struct {
	Common *CommonParam
}

// Response struct for CreateInstance
type CreateInstanceResponse struct {
	util.ErrorResponse
	InstanceId string `json:"InstanceId"`
}

// Response struct for StartInstance
type StartInstanceResponse struct {
	util.ErrorResponse
}

// Response struct for StopInstance
type StopInstanceResponse struct {
	util.ErrorResponse
}

// Response struct for RebootInstance
type RebootInstanceResponse struct {
	util.ErrorResponse
}

// Response struct for ModifyInstanceAttribute
type ModifyInstanceAttributeResponse struct {
	util.ErrorResponse
}

// Response struct for ModifyInstanceVpcAttribute
type ModifyInstanceVpcAttributeResponse struct {
	util.ErrorResponse
}

// Response struct for DescribeInstanceStatus
type DescribeInstanceStatusResponse struct {
	util.ErrorResponse
	util.PageResponse
	AllInstanceStatuses InstanceStatusSetType `json:"InstanceStatuses"`
}

type InstanceStatusSetType struct {
	AllInstanceStatus []InstanceStatusItemType `json:"InstanceStatus"`
}

// See http://docs.aliyun.com/?spm=5176.775974174.2.4.BYfRJ2#/ecs/open-api/datatype&instancestatusitemtype
type InstanceStatusItemType struct {
	InstanceId string `json:"InstanceId"`
	Status     string `json:"Status"`
}

// See http://docs.aliyun.com/?spm=5176.775974174.2.4.BYfRJ2#/ecs/open-api/datatype&instanceattributestype
type InstanceAttributesType struct {
	InstanceId              string                  `json:"InstanceId"`
	InstanceName            string                  `json:"InstanceName"`
	Description             string                  `json:"Description"`
	ImageId                 string                  `json:"ImageId"`
	RegionId                string                  `json:"RegionId"`
	ZoneId                  string                  `json:"ZoneId"`
	ClusterId               string                  `json:"ClusterId"`
	InstanceType            string                  `json:"InstanceType"`
	HostName                string                  `json:"HostName"`
	Status                  string                  `json:"Status"`
	AllOperationLocks       OperationLocks          `json:"OperationLocks"`
	AllSecurityGroupIds     SecurityGroupIdSetType  `json:"SecurityGroupIds"`
	PublicIpAddress         IpAddressSetType        `json:"PublicIpAddress"`
	InnerIpAddress          IpAddressSetType        `json:"InnerIpAddress"`
	InternetMaxBandwidthIn  int                     `json:"InternetMaxBandwidthIn"`
	InternetMaxBandwidthOut int                     `json:"InternetMaxBandwidthOut"`
	InternetChargeType      string                  `json:"InternetChargeType"`
	CreationTime            string                  `json:"CreationTime"`
	InstanceNetworkType     string                  `json:"InstanceNetworkType"`
	VpcAttributes           VpcAttributesType       `json:"VpcAttributes"`
	EipAddress              EipAddressAssociateType `json:"EipAddress"`
	InstanceChargeType      string                  `json:"InstanceChargeType"` // PrePaid：预付费，即包年包月; PostPaid：后付费，即按量付费
	DeviceAvailable         bool                    `json:"DeviceAvailable"`    // 实例是否还可以挂载磁盘
	IoOptimized             bool                    `json:"IoOptimized"`        // 是否是 IO 优化型实例
	ExpiredTime             string                  `json:"ExpiredTime"`        // 过期时间，按照ISO8601标准表示，并需要使用UTC时间。格式为：YYYY-MM-DDThh:mmZ
}

// See http://docs.aliyun.com/?spm=5176.775974174.2.4.BYfRJ2#/ecs/open-api/datatype&vpcattributestype
type VpcAttributesType struct {
	VpcId            string           `json:"VpcId"`
	VSwitchId        string           `json:"VSwitchId"`
	PrivateIpAddress IpAddressSetType `json:"PrivateIpAddress"`
	NatIpAddress     string           `json:"NatIpAddress"`
}

// See http://docs.aliyun.com/?spm=5176.775974174.2.4.BYfRJ2#/ecs/open-api/datatype&eipaddressassociatetype
type EipAddressAssociateType struct {
	AllocationId       string `json:"AllocationId"`
	IpAddress          string `json:"IpAddress"`
	Bandwidth          int    `json:"Bandwidth"`
	InternetChargeType string `json:"InternetChargeType"`
}

// Response struct for DescribeInstanceAttribute
type DescribeInstanceAttributeResponse struct {
	util.ErrorResponse
	InstanceAttributesType
}

// See http://docs.aliyun.com/?spm=5176.775974174.2.4.BYfRJ2#/ecs/open-api/datatype&securitygroupidsettype
type SecurityGroupIdSetType struct {
	AllSecurityGroupId []string `json:"SecurityGroupId"`
}

// See http://docs.aliyun.com/?spm=5176.775974174.2.4.BYfRJ2#/ecs/open-api/datatype&ipaddresssettype
type IpAddressSetType struct {
	AllIpAddress []string `json:"IpAddress"`
}

// Response struct for DescribeInstances
type DescribeInstancesResponse struct {
	util.ErrorResponse
	util.PageResponse
	AllInstances Instances `json:"Instances"`
}

type Instances struct {
	AllInstance []InstanceAttributesType `json:"Instance"`
}

// Response struct for DeleteInstance
type DeleteInstanceResponse struct {
	util.ErrorResponse
}

// Response struct for JoinSecurityGroup
type JoinSecurityGroupResponse struct {
	util.ErrorResponse
}

// Response struct for LeaveSecurityGroup
type LeaveSecurityGroupResponse struct {
	util.ErrorResponse
}

// Response struct for DescribeInstanceVncUrl
type DescribeInstanceVncUrlResponse struct {
	util.ErrorResponse
	util.PageResponse
	AllInstances Instances `json:"Instances"`
}

// Response struct for ModifyInstanceVncPasswd
type ModifyInstanceVncPasswdResponse struct {
	util.ErrorResponse
}

func (op *InstanceOperator) CreateInstance(params map[string]string) (CreateInstanceResponse, error) {
	var resp CreateInstanceResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) StartInstance(params map[string]string) (StartInstanceResponse, error) {
	var resp StartInstanceResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) StopInstance(params map[string]string) (StopInstanceResponse, error) {
	var resp StopInstanceResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) RebootInstance(params map[string]string) (RebootInstanceResponse, error) {
	var resp RebootInstanceResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) ModifyInstanceAttribute(params map[string]string) (ModifyInstanceAttributeResponse, error) {
	var resp ModifyInstanceAttributeResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) ModifyInstanceVpcAttribute(params map[string]string) (ModifyInstanceVpcAttributeResponse, error) {
	var resp ModifyInstanceVpcAttributeResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) DescribeInstanceStatus(params map[string]string) (DescribeInstanceStatusResponse, error) {
	var resp DescribeInstanceStatusResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) DescribeInstanceAttribute(params map[string]string) (DescribeInstanceAttributeResponse, error) {
	var resp DescribeInstanceAttributeResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) DescribeInstances(params map[string]string) (DescribeInstancesResponse, error) {
	var resp DescribeInstancesResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) DeleteInstance(params map[string]string) (DeleteInstanceResponse, error) {
	var resp DeleteInstanceResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) JoinSecurityGroup(params map[string]string) (JoinSecurityGroupResponse, error) {
	var resp JoinSecurityGroupResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) LeaveSecurityGroup(params map[string]string) (LeaveSecurityGroupResponse, error) {
	var resp LeaveSecurityGroupResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) DescribeInstanceVncUrl(params map[string]string) (DescribeInstanceVncUrlResponse, error) {
	var resp DescribeInstanceVncUrlResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}

func (op *InstanceOperator) ModifyInstanceVncPasswd(params map[string]string) (ModifyInstanceVncPasswdResponse, error) {
	var resp ModifyInstanceVncPasswdResponse
	err := op.Common.Request(GetFuncName(1), params, &resp)
	return resp, err
}
<|endoftext|>