<commit_before>// Copyright (c) Alex Ellis 2017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

package handlers

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/openfaas/faas/gateway/requests"
	"github.com/openfaas/faas/gateway/scaling"
)

// MakeAlertHandler handles alerts from Prometheus Alertmanager
func MakeAlertHandler(service scaling.ServiceQuery) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {

		log.Println("Alert received.")

		body, readErr := ioutil.ReadAll(r.Body)

		log.Println(string(body))

		if readErr != nil {
			w.WriteHeader(http.StatusBadRequest)
			w.Write([]byte("Unable to read alert."))

			log.Println(readErr)
			return
		}

		var req requests.PrometheusAlert
		err := json.Unmarshal(body, &req)
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			w.Write([]byte("Unable to parse alert, bad format."))
			log.Println(err)
			return
		}

		errors := handleAlerts(&req, service)
		if len(errors) > 0 {
			log.Println(errors)
			var errorOutput string
			for d, err := range errors {
				errorOutput += fmt.Sprintf("[%d] %s\n", d, err)
			}
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(errorOutput))
			return
		}

		w.WriteHeader(http.StatusOK)
	}
}

func handleAlerts(req *requests.PrometheusAlert, service scaling.ServiceQuery) []error {
	var errors []error
	for _, alert := range req.Alerts {
		if err := scaleService(alert, service); err != nil {
			log.Println(err)
			errors = append(errors, err)
		}
	}

	return errors
}

func scaleService(alert requests.PrometheusInnerAlert, service scaling.ServiceQuery) error {
	var err error
	serviceName := alert.Labels.FunctionName

	if len(serviceName) > 0 {
		queryResponse, getErr := service.GetReplicas(serviceName)
		if getErr == nil {
			status := alert.Status

			newReplicas := CalculateReplicas(status, queryResponse.Replicas, uint64(queryResponse.MaxReplicas), queryResponse.MinReplicas, queryResponse.ScalingFactor)

			log.Printf("[Scale] function=%s %d => %d.\n", serviceName, queryResponse.Replicas, newReplicas)
			if newReplicas == queryResponse.Replicas {
				return nil
			}

			updateErr := service.SetReplicas(serviceName, newReplicas)
			if updateErr != nil {
				err = updateErr
			}
		}
	}
	return err
}

// CalculateReplicas decides what replica count to set depending on current/desired amount
func CalculateReplicas(status string, currentReplicas uint64, maxReplicas uint64, minReplicas uint64, scalingFactor uint64) uint64 {
	newReplicas := currentReplicas
	step := uint64((float64(maxReplicas) / 100) * float64(scalingFactor))

	if status == "firing" && step > 0 {
		if currentReplicas == 1 {
			newReplicas = step
		} else {
			if currentReplicas+step > maxReplicas {
				newReplicas = maxReplicas
			} else {
				newReplicas = currentReplicas + step
			}
		}
	} else { // Resolved event.
		newReplicas = minReplicas
	}

	return newReplicas
}
<commit_msg>Remove the differentiation between currentReplicas==1 and not<commit_after>// Copyright (c) Alex Ellis 2017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

package handlers

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/openfaas/faas/gateway/requests"
	"github.com/openfaas/faas/gateway/scaling"
)

// MakeAlertHandler handles alerts from Prometheus Alertmanager
func MakeAlertHandler(service scaling.ServiceQuery) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {

		log.Println("Alert received.")

		body, readErr := ioutil.ReadAll(r.Body)

		log.Println(string(body))

		if readErr != nil {
			w.WriteHeader(http.StatusBadRequest)
			w.Write([]byte("Unable to read alert."))

			log.Println(readErr)
			return
		}

		var req requests.PrometheusAlert
		err := json.Unmarshal(body, &req)
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			w.Write([]byte("Unable to parse alert, bad format."))
			log.Println(err)
			return
		}

		errors := handleAlerts(&req, service)
		if len(errors) > 0 {
			log.Println(errors)
			var errorOutput string
			for d, err := range errors {
				errorOutput += fmt.Sprintf("[%d] %s\n", d, err)
			}
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(errorOutput))
			return
		}

		w.WriteHeader(http.StatusOK)
	}
}

func handleAlerts(req *requests.PrometheusAlert, service scaling.ServiceQuery) []error {
	var errors []error
	for _, alert := range req.Alerts {
		if err := scaleService(alert, service); err != nil {
			log.Println(err)
			errors = append(errors, err)
		}
	}

	return errors
}

func scaleService(alert requests.PrometheusInnerAlert, service scaling.ServiceQuery) error {
	var err error
	serviceName := alert.Labels.FunctionName

	if len(serviceName) > 0 {
		queryResponse, getErr := service.GetReplicas(serviceName)
		if getErr == nil {
			status := alert.Status

			newReplicas := CalculateReplicas(status, queryResponse.Replicas, uint64(queryResponse.MaxReplicas), queryResponse.MinReplicas, queryResponse.ScalingFactor)

			log.Printf("[Scale] function=%s %d => %d.\n", serviceName, queryResponse.Replicas, newReplicas)
			if newReplicas == queryResponse.Replicas {
				return nil
			}

			updateErr := service.SetReplicas(serviceName, newReplicas)
			if updateErr != nil {
				err = updateErr
			}
		}
	}
	return err
}

// CalculateReplicas decides what replica count to set depending on current/desired amount
func CalculateReplicas(status string, currentReplicas uint64, maxReplicas uint64, minReplicas uint64, scalingFactor uint64) uint64 {
	newReplicas := currentReplicas
	step := uint64((float64(maxReplicas) / 100) * float64(scalingFactor))

	if status == "firing" && step > 0 {
		if currentReplicas+step > maxReplicas {
			newReplicas = maxReplicas
		} else {
			newReplicas = currentReplicas + step
		}
	} else { // Resolved event.
		newReplicas = minReplicas
	}

	return newReplicas
}
<|endoftext|>


<commit_before>package contractor

import (
	"errors"
	"net"

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

// verifySettings reads a signed HostSettings object from conn, validates the
// signature, and checks for discrepancies between the known settings and the
// received settings. If there is a discrepancy, the hostDB is notified. The
// received settings are returned.
func verifySettings(conn net.Conn, host modules.HostDBEntry, hdb hostDB) (modules.HostDBEntry, error) {
	// convert host key (types.SiaPublicKey) to a crypto.PublicKey
	if host.PublicKey.Algorithm != types.SignatureEd25519 || len(host.PublicKey.Key) != crypto.PublicKeySize {
		build.Critical("hostdb did not filter out host with wrong signature algorithm:", host.PublicKey.Algorithm)
		return modules.HostDBEntry{}, errors.New("host used unsupported signature algorithm")
	}
	var pk crypto.PublicKey
	copy(pk[:], host.PublicKey.Key)

	// read signed host settings
	var recvSettings modules.HostExternalSettings
	if err := crypto.ReadSignedObject(conn, &recvSettings, modules.NegotiateMaxHostExternalSettingsLen, pk); err != nil {
		return modules.HostDBEntry{}, errors.New("couldn't read host's settings: " + err.Error())
	}
	// TODO: check recvSettings against host.HostExternalSettings. If there is
	// a discrepancy, write the error to conn and update the hostdb
	if recvSettings.NetAddress != host.NetAddress {
		// for now, just overwrite the NetAddress, since we know that
		// host.NetAddress works (it was the one we dialed to get conn)
		recvSettings.NetAddress = host.NetAddress
	}
	host.HostExternalSettings = recvSettings
	return host, nil
}

// startRevision is run at the beginning of each revision iteration. It reads
// the host's settings confirms that the values are acceptable, and writes an acceptance.
func startRevision(conn net.Conn, host modules.HostDBEntry, hdb hostDB) error {
	// verify the host's settings and confirm its identity
	// TODO: return new host, so we can calculate price accurately
	recvSettings, err := verifySettings(conn, host, hdb)
	if err != nil {
		// TODO: doesn't make sense to reject here if the err is an I/O error.
		return modules.WriteNegotiationRejection(conn, err)
	} else if !recvSettings.AcceptingContracts {
		// no need to reject; host will already have disconnected at this point
		return errors.New("host is not accepting contracts")
	}
	return modules.WriteNegotiationAcceptance(conn)
}

// startDownload is run at the beginning of each download iteration. It reads
// the host's settings confirms that the values are acceptable, and writes an acceptance.
func startDownload(conn net.Conn, host modules.HostDBEntry, hdb hostDB) error {
	// verify the host's settings and confirm its identity
	// TODO: return new host, so we can calculate price accurately
	_, err := verifySettings(conn, host, hdb)
	if err != nil {
		// TODO: doesn't make sense to reject here if the err is an I/O error.
		return modules.WriteNegotiationRejection(conn, err)
	}
	return modules.WriteNegotiationAcceptance(conn)
}

// verifyRecentRevision confirms that the host and contractor agree upon the current
// state of the contract being revisde.
func verifyRecentRevision(conn net.Conn, contract Contract) error {
	// send contract ID
	if err := encoding.WriteObject(conn, contract.ID); err != nil {
		return errors.New("couldn't send contract ID: " + err.Error())
	}
	// read challenge
	var challenge crypto.Hash
	if err := encoding.ReadObject(conn, &challenge, 32); err != nil {
		return errors.New("couldn't read challenge: " + err.Error())
	}
	// sign and return
	sig, err := crypto.SignHash(challenge, contract.SecretKey)
	if err != nil {
		return err
	} else if err := encoding.WriteObject(conn, sig); err != nil {
		return errors.New("couldn't send challenge response: " + err.Error())
	}
	// read acceptance
	if err := modules.ReadNegotiationAcceptance(conn); err != nil {
		return errors.New("host did not accept revision request: " + err.Error())
	}
	// read last revision and signatures
	var lastRevision types.FileContractRevision
	var hostSignatures []types.TransactionSignature
	if err := encoding.ReadObject(conn, &lastRevision, 2048); err != nil {
		return errors.New("couldn't read last revision: " + err.Error())
	}
	if err := encoding.ReadObject(conn, &hostSignatures, 2048); err != nil {
		return errors.New("couldn't read host signatures: " + err.Error())
	}
	// verify the revision and signatures
	// NOTE: we can fake the blockheight here because it doesn't affect
	// verification; it just needs to be above the fork height and below the
	// contract expiration (which was checked earlier).
	return modules.VerifyFileContractRevisionTransactionSignatures(lastRevision, hostSignatures, contract.FileContract.WindowStart-1)
}
<commit_msg>don't write rejection if verifySettings fails<commit_after>package contractor

import (
	"errors"
	"net"

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

// verifySettings reads a signed HostSettings object from conn, validates the
// signature, and checks for discrepancies between the known settings and the
// received settings. If there is a discrepancy, the hostDB is notified. The
// received settings are returned.
func verifySettings(conn net.Conn, host modules.HostDBEntry, hdb hostDB) (modules.HostDBEntry, error) {
	// convert host key (types.SiaPublicKey) to a crypto.PublicKey
	if host.PublicKey.Algorithm != types.SignatureEd25519 || len(host.PublicKey.Key) != crypto.PublicKeySize {
		build.Critical("hostdb did not filter out host with wrong signature algorithm:", host.PublicKey.Algorithm)
		return modules.HostDBEntry{}, errors.New("host used unsupported signature algorithm")
	}
	var pk crypto.PublicKey
	copy(pk[:], host.PublicKey.Key)

	// read signed host settings
	var recvSettings modules.HostExternalSettings
	if err := crypto.ReadSignedObject(conn, &recvSettings, modules.NegotiateMaxHostExternalSettingsLen, pk); err != nil {
		return modules.HostDBEntry{}, errors.New("couldn't read host's settings: " + err.Error())
	}
	// TODO: check recvSettings against host.HostExternalSettings. If there is
	// a discrepancy, write the error to conn and update the hostdb
	if recvSettings.NetAddress != host.NetAddress {
		// for now, just overwrite the NetAddress, since we know that
		// host.NetAddress works (it was the one we dialed to get conn)
		recvSettings.NetAddress = host.NetAddress
	}
	host.HostExternalSettings = recvSettings
	return host, nil
}

// startRevision is run at the beginning of each revision iteration. It reads
// the host's settings confirms that the values are acceptable, and writes an acceptance.
func startRevision(conn net.Conn, host modules.HostDBEntry, hdb hostDB) error {
	// verify the host's settings and confirm its identity
	// TODO: return new host, so we can calculate price accurately
	recvSettings, err := verifySettings(conn, host, hdb)
	if err != nil {
		return err
	} else if !recvSettings.AcceptingContracts {
		// no need to reject; host will already have disconnected at this point
		return errors.New("host is not accepting contracts")
	}
	return modules.WriteNegotiationAcceptance(conn)
}

// startDownload is run at the beginning of each download iteration. It reads
// the host's settings confirms that the values are acceptable, and writes an acceptance.
func startDownload(conn net.Conn, host modules.HostDBEntry, hdb hostDB) error {
	// verify the host's settings and confirm its identity
	// TODO: return new host, so we can calculate price accurately
	_, err := verifySettings(conn, host, hdb)
	if err != nil {
		// TODO: doesn't make sense to reject here if the err is an I/O error.
		return modules.WriteNegotiationRejection(conn, err)
	}
	return modules.WriteNegotiationAcceptance(conn)
}

// verifyRecentRevision confirms that the host and contractor agree upon the current
// state of the contract being revisde.
func verifyRecentRevision(conn net.Conn, contract Contract) error {
	// send contract ID
	if err := encoding.WriteObject(conn, contract.ID); err != nil {
		return errors.New("couldn't send contract ID: " + err.Error())
	}
	// read challenge
	var challenge crypto.Hash
	if err := encoding.ReadObject(conn, &challenge, 32); err != nil {
		return errors.New("couldn't read challenge: " + err.Error())
	}
	// sign and return
	sig, err := crypto.SignHash(challenge, contract.SecretKey)
	if err != nil {
		return err
	} else if err := encoding.WriteObject(conn, sig); err != nil {
		return errors.New("couldn't send challenge response: " + err.Error())
	}
	// read acceptance
	if err := modules.ReadNegotiationAcceptance(conn); err != nil {
		return errors.New("host did not accept revision request: " + err.Error())
	}
	// read last revision and signatures
	var lastRevision types.FileContractRevision
	var hostSignatures []types.TransactionSignature
	if err := encoding.ReadObject(conn, &lastRevision, 2048); err != nil {
		return errors.New("couldn't read last revision: " + err.Error())
	}
	if err := encoding.ReadObject(conn, &hostSignatures, 2048); err != nil {
		return errors.New("couldn't read host signatures: " + err.Error())
	}
	// verify the revision and signatures
	// NOTE: we can fake the blockheight here because it doesn't affect
	// verification; it just needs to be above the fork height and below the
	// contract expiration (which was checked earlier).
	return modules.VerifyFileContractRevisionTransactionSignatures(lastRevision, hostSignatures, contract.FileContract.WindowStart-1)
}
<|endoftext|>


<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package engine

import (
	"testing"

	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol"
)

func TestDeviceHistoryBasic(t *testing.T) {
	tc := SetupEngineTest(t, "devhist")
	defer tc.Cleanup()

	CreateAndSignupFakeUserPaper(tc, "dhst")

	ctx := &Context{}
	eng := NewDeviceHistorySelf(tc.G)
	if err := RunEngine(eng, ctx); err != nil {
		t.Fatal(err)
	}
	devs := eng.Devices()
	if len(devs) != 2 {
		t.Errorf("num devices: %d, expected 2", len(devs))
	}

	var desktop keybase1.DeviceDetail
	var paper keybase1.DeviceDetail

	for _, d := range devs {
		switch d.Device.Type {
		case libkb.DeviceTypePaper:
			paper = d
		case libkb.DeviceTypeDesktop:
			desktop = d
		default:
			t.Fatalf("unexpected device type %s", d.Device.Type)
		}
	}

	// paper's provisioner should be desktop
	if paper.Provisioner == nil {
		t.Fatal("paper device has no provisioner")
	}
	if paper.Provisioner.DeviceID != desktop.Device.DeviceID {
		t.Errorf("paper provisioned id: %s, expected %s", paper.Provisioner.DeviceID, desktop.Device.DeviceID)
		t.Logf("desktop: %+v", desktop)
		t.Logf("paper:   %+v", paper)
	}

	// Check that LastUsedTime is set (since we're fetching our own device history)
	for _, d := range devs {
		if d.Device.LastUsedTime == 0 {
			t.Fatal("last used time not set")
		}
	}
}

func TestDeviceHistoryRevoked(t *testing.T) {
	tc := SetupEngineTest(t, "devhist")
	defer tc.Cleanup()

	u := CreateAndSignupFakeUserPaper(tc, "dhst")

	ctx := &Context{}
	eng := NewDeviceHistorySelf(tc.G)
	if err := RunEngine(eng, ctx); err != nil {
		t.Fatal(err)
	}

	var desktop keybase1.DeviceDetail
	var paper keybase1.DeviceDetail

	for _, d := range eng.Devices() {
		switch d.Device.Type {
		case libkb.DeviceTypePaper:
			paper = d
		case libkb.DeviceTypeDesktop:
			desktop = d
		default:
			t.Fatalf("unexpected device type %s", d.Device.Type)
		}
	}

	// paper's provisioner should be desktop
	if paper.Provisioner == nil {
		t.Fatal("paper device has no provisioner")
	}
	if paper.Provisioner.DeviceID != desktop.Device.DeviceID {
		t.Errorf("paper provisioned id: %s, expected %s", paper.Provisioner.DeviceID, desktop.Device.DeviceID)
		t.Logf("desktop: %+v", desktop)
		t.Logf("paper:   %+v", paper)
	}

	// revoke the paper device
	ctx.SecretUI = u.NewSecretUI()
	ctx.LogUI = tc.G.UI.GetLogUI()
	reng := NewRevokeDeviceEngine(RevokeDeviceEngineArgs{ID: paper.Device.DeviceID}, tc.G)
	if err := RunEngine(reng, ctx); err != nil {
		t.Fatal(err)
	}

	// get history after revoke
	eng = NewDeviceHistorySelf(tc.G)
	if err := RunEngine(eng, ctx); err != nil {
		t.Fatal(err)
	}

	var desktop2 keybase1.DeviceDetail
	var paper2 keybase1.DeviceDetail

	for _, d := range eng.Devices() {
		switch d.Device.Type {
		case libkb.DeviceTypePaper:
			paper2 = d
		case libkb.DeviceTypeDesktop:
			desktop2 = d
		default:
			t.Fatalf("unexpected device type %s", d.Device.Type)
		}
	}

	// paper's provisioner should (still) be desktop
	if paper2.Provisioner == nil {
		t.Fatal("paper device has no provisioner")
	}
	if paper2.Provisioner.DeviceID != desktop2.Device.DeviceID {
		t.Errorf("paper provisioned id: %s, expected %s", paper2.Provisioner.DeviceID, desktop2.Device.DeviceID)
		t.Logf("desktop: %+v", desktop2)
		t.Logf("paper:   %+v", paper2)
	}

	if paper2.RevokedAt == nil {
		t.Fatal("paper device RevokedAt is nil")
	}
}

func TestDeviceHistoryPGP(t *testing.T) {
	tc := SetupEngineTest(t, "devhist")
	u1 := createFakeUserWithPGPOnly(t, tc)
	t.Log("Created fake synced pgp user")
	Logout(tc)
	tc.Cleanup()

	// redo SetupEngineTest to get a new home directory...should look like a new device.
	tc = SetupEngineTest(t, "devhist")
	defer tc.Cleanup()

	ctx := &Context{
		ProvisionUI: newTestProvisionUIPassphrase(),
		LoginUI:     &libkb.TestLoginUI{Username: u1.Username},
		LogUI:       tc.G.UI.GetLogUI(),
		SecretUI:    u1.NewSecretUI(),
		GPGUI:       &gpgtestui{},
	}
	eng := NewLogin(tc.G, libkb.DeviceTypeDesktop, "", keybase1.ClientType_CLI)
	if err := RunEngine(eng, ctx); err != nil {
		t.Fatal(err)
	}

	ctx = &Context{}
	heng := NewDeviceHistorySelf(tc.G)
	if err := RunEngine(heng, ctx); err != nil {
		t.Fatal(err)
	}
	devs := heng.Devices()
	if len(devs) != 2 {
		t.Errorf("num devices: %d, expected 2", len(devs))
	}

	var desktop keybase1.DeviceDetail
	var paper keybase1.DeviceDetail

	for _, d := range devs {
		switch d.Device.Type {
		case libkb.DeviceTypePaper:
			paper = d
		case libkb.DeviceTypeDesktop:
			desktop = d
		default:
			t.Fatalf("unexpected device type %s", d.Device.Type)
		}
	}

	// paper's provisioner should be desktop
	if paper.Provisioner == nil {
		t.Fatal("paper device has no provisioner")
	}
	if paper.Provisioner.DeviceID != desktop.Device.DeviceID {
		t.Errorf("paper provisioned id: %s, expected %s", paper.Provisioner.DeviceID, desktop.Device.DeviceID)
		t.Logf("desktop: %+v", desktop)
		t.Logf("paper:   %+v", paper)
	}
}
<commit_msg>expand test<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package engine

import (
	"testing"

	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol"
)

func TestDeviceHistoryBasic(t *testing.T) {
	tc := SetupEngineTest(t, "devhist")
	defer tc.Cleanup()

	CreateAndSignupFakeUserPaper(tc, "dhst")

	ctx := &Context{}
	eng := NewDeviceHistorySelf(tc.G)
	if err := RunEngine(eng, ctx); err != nil {
		t.Fatal(err)
	}
	devs := eng.Devices()
	if len(devs) != 2 {
		t.Errorf("num devices: %d, expected 2", len(devs))
	}

	var desktop keybase1.DeviceDetail
	var paper keybase1.DeviceDetail

	for _, d := range devs {
		switch d.Device.Type {
		case libkb.DeviceTypePaper:
			paper = d
		case libkb.DeviceTypeDesktop:
			desktop = d
		default:
			t.Fatalf("unexpected device type %s", d.Device.Type)
		}
	}

	// paper's provisioner should be desktop
	if paper.Provisioner == nil {
		t.Fatal("paper device has no provisioner")
	}
	if paper.Provisioner.DeviceID != desktop.Device.DeviceID {
		t.Errorf("paper provisioned id: %s, expected %s", paper.Provisioner.DeviceID, desktop.Device.DeviceID)
		t.Logf("desktop: %+v", desktop)
		t.Logf("paper:   %+v", paper)
	}

	// Check that LastUsedTime is set (since we're fetching our own device history)
	for _, d := range devs {
		if d.Device.LastUsedTime == 0 {
			t.Fatal("last used time not set")
		}
	}
}

func TestDeviceHistoryRevoked(t *testing.T) {
	tc := SetupEngineTest(t, "devhist")
	defer tc.Cleanup()

	u := CreateAndSignupFakeUserPaper(tc, "dhst")

	ctx := &Context{}
	eng := NewDeviceHistorySelf(tc.G)
	if err := RunEngine(eng, ctx); err != nil {
		t.Fatal(err)
	}

	var desktop keybase1.DeviceDetail
	var paper keybase1.DeviceDetail

	for _, d := range eng.Devices() {
		switch d.Device.Type {
		case libkb.DeviceTypePaper:
			paper = d
		case libkb.DeviceTypeDesktop:
			desktop = d
		default:
			t.Fatalf("unexpected device type %s", d.Device.Type)
		}
	}

	// paper's provisioner should be desktop
	if paper.Provisioner == nil {
		t.Fatal("paper device has no provisioner")
	}
	if paper.Provisioner.DeviceID != desktop.Device.DeviceID {
		t.Errorf("paper provisioned id: %s, expected %s", paper.Provisioner.DeviceID, desktop.Device.DeviceID)
		t.Logf("desktop: %+v", desktop)
		t.Logf("paper:   %+v", paper)
	}

	// revoke the paper device
	ctx.SecretUI = u.NewSecretUI()
	ctx.LogUI = tc.G.UI.GetLogUI()
	reng := NewRevokeDeviceEngine(RevokeDeviceEngineArgs{ID: paper.Device.DeviceID}, tc.G)
	if err := RunEngine(reng, ctx); err != nil {
		t.Fatal(err)
	}

	// get history after revoke
	eng = NewDeviceHistorySelf(tc.G)
	if err := RunEngine(eng, ctx); err != nil {
		t.Fatal(err)
	}

	var desktop2 keybase1.DeviceDetail
	var paper2 keybase1.DeviceDetail

	for _, d := range eng.Devices() {
		switch d.Device.Type {
		case libkb.DeviceTypePaper:
			paper2 = d
		case libkb.DeviceTypeDesktop:
			desktop2 = d
		default:
			t.Fatalf("unexpected device type %s", d.Device.Type)
		}
	}

	// paper's provisioner should (still) be desktop
	if paper2.Provisioner == nil {
		t.Fatal("paper device has no provisioner")
	}
	if paper2.Provisioner.DeviceID != desktop2.Device.DeviceID {
		t.Errorf("paper provisioned id: %s, expected %s", paper2.Provisioner.DeviceID, desktop2.Device.DeviceID)
		t.Logf("desktop: %+v", desktop2)
		t.Logf("paper:   %+v", paper2)
	}

	if paper2.RevokedAt == nil {
		t.Fatal("paper device RevokedAt is nil")
	}
	if paper2.RevokedBy.IsNil() {
		t.Fatal("paper device RevokedBy is nil")
	}
}

func TestDeviceHistoryPGP(t *testing.T) {
	tc := SetupEngineTest(t, "devhist")
	u1 := createFakeUserWithPGPOnly(t, tc)
	t.Log("Created fake synced pgp user")
	Logout(tc)
	tc.Cleanup()

	// redo SetupEngineTest to get a new home directory...should look like a new device.
	tc = SetupEngineTest(t, "devhist")
	defer tc.Cleanup()

	ctx := &Context{
		ProvisionUI: newTestProvisionUIPassphrase(),
		LoginUI:     &libkb.TestLoginUI{Username: u1.Username},
		LogUI:       tc.G.UI.GetLogUI(),
		SecretUI:    u1.NewSecretUI(),
		GPGUI:       &gpgtestui{},
	}
	eng := NewLogin(tc.G, libkb.DeviceTypeDesktop, "", keybase1.ClientType_CLI)
	if err := RunEngine(eng, ctx); err != nil {
		t.Fatal(err)
	}

	ctx = &Context{}
	heng := NewDeviceHistorySelf(tc.G)
	if err := RunEngine(heng, ctx); err != nil {
		t.Fatal(err)
	}
	devs := heng.Devices()
	if len(devs) != 2 {
		t.Errorf("num devices: %d, expected 2", len(devs))
	}

	var desktop keybase1.DeviceDetail
	var paper keybase1.DeviceDetail

	for _, d := range devs {
		switch d.Device.Type {
		case libkb.DeviceTypePaper:
			paper = d
		case libkb.DeviceTypeDesktop:
			desktop = d
		default:
			t.Fatalf("unexpected device type %s", d.Device.Type)
		}
	}

	// paper's provisioner should be desktop
	if paper.Provisioner == nil {
		t.Fatal("paper device has no provisioner")
	}
	if paper.Provisioner.DeviceID != desktop.Device.DeviceID {
		t.Errorf("paper provisioned id: %s, expected %s", paper.Provisioner.DeviceID, desktop.Device.DeviceID)
		t.Logf("desktop: %+v", desktop)
		t.Logf("paper:   %+v", paper)
	}
}
<|endoftext|>


<commit_before>package p14

import (
	c "common"
	"fmt"
)

// --- Day 11: Chronal Charge ---
// http://adventofcode.com/2018/day/11
func Solve(input string) (string, string) {
	in := c.ToIntOrPanic(input)
	return solveA(in), solveB(in)
}

func solveA(in int) string {
	r := newRecipes()
	for {
		r.iterate()
		if len(r.state) > in+12 {
			return toIntString(r.state[in : in+10])
		}
	}
}

func solveB(in int) string {
	inStr := fmt.Sprintf("%d", in)
	query := make([]int, len(inStr))
	for i, s := range inStr {
		query[i] = int(s - '0')
	}
	r := newRecipes()
	for {
		r.iterate()

		if len(r.state) > len(query) {
			// Two tails since we might have added two items above.
			tail := r.state[len(r.state)-len(query):]
			tail2 := r.state[len(r.state)-len(query)-1 : len(r.state)-1]
			if eq(tail, query) {
				return c.ToString(len(r.state) - len(query))
			}
			if eq(tail2, query) {
				return c.ToString(len(r.state) - len(query) - 1)
			}
		}
	}
}

func newRecipes() *recipes {
	return &recipes{
		elf1:  0,
		elf2:  1,
		state: []int{3, 7},
	}
}

type recipes struct {
	elf1, elf2 int
	state      []int
}

func (r *recipes) iterate() {
	newRecipe := r.state[r.elf1] + r.state[r.elf2]
	if newRecipe < 10 {
		r.state = append(r.state, newRecipe)
	} else {
		p2 := newRecipe % 10
		p1 := (newRecipe - p2) / 10
		r.state = append(r.state, p1, p2)
	}
	r.elf1 = (r.elf1 + 1 + r.state[r.elf1]) % len(r.state)
	r.elf2 = (r.elf2 + 1 + r.state[r.elf2]) % len(r.state)
}

func eq(a []int, b []int) bool {
	if len(a) != len(b) {
		return false
	}
	for i := 0; i < len(a); i++ {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}

func toIntString(state []int) string {
	result := ""
	for _, s := range state {
		result += fmt.Sprintf("%d", s)
	}
	return result
}
<commit_msg>Fix description.<commit_after>package p14

import (
	c "common"
	"fmt"
)

// --- Day 14: Chocolate Charts ---
// http://adventofcode.com/2018/day/14
func Solve(input string) (string, string) {
	in := c.ToIntOrPanic(input)
	return solveA(in), solveB(in)
}

func solveA(in int) string {
	r := newRecipes()
	for {
		r.iterate()
		if len(r.state) > in+12 {
			return toIntString(r.state[in : in+10])
		}
	}
}

func solveB(in int) string {
	inStr := fmt.Sprintf("%d", in)
	query := make([]int, len(inStr))
	for i, s := range inStr {
		query[i] = int(s - '0')
	}
	r := newRecipes()
	for {
		r.iterate()

		if len(r.state) > len(query) {
			// Two tails since we might have added two items above.
			tail := r.state[len(r.state)-len(query):]
			tail2 := r.state[len(r.state)-len(query)-1 : len(r.state)-1]
			if eq(tail, query) {
				return c.ToString(len(r.state) - len(query))
			}
			if eq(tail2, query) {
				return c.ToString(len(r.state) - len(query) - 1)
			}
		}
	}
}

func newRecipes() *recipes {
	return &recipes{
		elf1:  0,
		elf2:  1,
		state: []int{3, 7},
	}
}

type recipes struct {
	elf1, elf2 int
	state      []int
}

func (r *recipes) iterate() {
	newRecipe := r.state[r.elf1] + r.state[r.elf2]
	if newRecipe < 10 {
		r.state = append(r.state, newRecipe)
	} else {
		p2 := newRecipe % 10
		p1 := (newRecipe - p2) / 10
		r.state = append(r.state, p1, p2)
	}
	r.elf1 = (r.elf1 + 1 + r.state[r.elf1]) % len(r.state)
	r.elf2 = (r.elf2 + 1 + r.state[r.elf2]) % len(r.state)
}

func eq(a []int, b []int) bool {
	if len(a) != len(b) {
		return false
	}
	for i := 0; i < len(a); i++ {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}

func toIntString(state []int) string {
	result := ""
	for _, s := range state {
		result += fmt.Sprintf("%d", s)
	}
	return result
}
<|endoftext|>


<commit_before>package redlot

import "encoding/binary"

func uint32ToBytes(v uint32) []byte {
	b := make([]byte, 4)
	binary.BigEndian.PutUint32(b, v)
	return b
}
<commit_msg>add util methods.<commit_after>package redlot

import (
	"encoding/binary"
	"strconv"
)

func uint32ToBytes(v uint32) []byte {
	b := make([]byte, 4)
	binary.BigEndian.PutUint32(b, v)
	return b
}

func uint64ToBytes(v uint64) []byte {
	b := make([]byte, 8)
	binary.BigEndian.PutUint64(b, v)
	return b
}

func bytesToUint32(b []byte) uint32 {
	return binary.BigEndian.Uint32(b)
}

func bytesToUint64(b []byte) uint64 {
	return binary.BigEndian.Uint64(b)
}

func strToInt64(str string) int64 {
	u, err := strconv.ParseInt(str, 10, 64)
	if err != nil {
		u = 0
	}
	return u
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package e2e

import (
	"time"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/apis/extensions"

	. "github.com/onsi/ginkgo"
)

const (
	kindRC         = "replicationController"
	kindDeployment = "deployment"
	subresource    = "scale"
)

// These tests don't seem to be running properly in parallel: issue: #20338.
//
// These tests take ~20 minutes each.
var _ = Describe("Horizontal pod autoscaling (scale resource: CPU) [Serial] [Slow]", func() {
	var rc *ResourceConsumer
	f := NewDefaultFramework("horizontal-pod-autoscaling")

	titleUp := "Should scale from 1 pod to 3 pods and from 3 to 5"
	titleDown := "Should scale from 5 pods to 3 pods and from 3 to 1"

	// TODO(madhusudancs): Fix this when Scale group issues are resolved (see issue #18528).
	// Describe("Deployment", func() {
	// 	// CPU tests via deployments
	// 	It(titleUp, func() {
	// 		scaleUp("deployment", kindDeployment, rc, f)
	// 	})
	// 	It(titleDown, func() {
	// 		scaleDown("deployment", kindDeployment, rc, f)
	// 	})
	// })

	Describe("ReplicationController", func() {
		// CPU tests via replication controllers
		It(titleUp, func() {
			scaleUp("rc", kindRC, rc, f)
		})
		It(titleDown, func() {
			scaleDown("rc", kindRC, rc, f)
		})
	})
})

// HPAScaleTest struct is used by the scale(...) function.
type HPAScaleTest struct {
	initPods          int
	cpuStart          int
	maxCPU            int64
	idealCPU          int
	minPods           int
	maxPods           int
	firstScale        int
	firstScaleStasis  time.Duration
	cpuBurst          int
	secondScale       int
	secondScaleStasis time.Duration
}

// run is a method which runs an HPA lifecycle, from a starting state, to an expected
// The initial state is defined by the initPods parameter.
// The first state change is due to the CPU being consumed initially, which HPA responds to by changing pod counts.
// The second state change is due to the CPU burst parameter, which HPA again responds to.
// TODO The use of 3 states is arbitrary, we could eventually make this test handle "n" states once this test stabilizes.
func (scaleTest *HPAScaleTest) run(name, kind string, rc *ResourceConsumer, f *Framework) {
	rc = NewDynamicResourceConsumer(name, kind, scaleTest.initPods, scaleTest.cpuStart, 0, scaleTest.maxCPU, 100, f)
	defer rc.CleanUp()
	createCPUHorizontalPodAutoscaler(rc, scaleTest.idealCPU, scaleTest.minPods, scaleTest.maxPods)
	rc.WaitForReplicas(scaleTest.firstScale)
	rc.EnsureDesiredReplicas(scaleTest.firstScale, scaleTest.firstScaleStasis)
	rc.ConsumeCPU(scaleTest.cpuBurst)
	rc.WaitForReplicas(scaleTest.secondScale)
}

func scaleUp(name, kind string, rc *ResourceConsumer, f *Framework) {
	scaleTest := &HPAScaleTest{
		initPods:         1,
		cpuStart:         250,
		maxCPU:           500,
		idealCPU:         .2 * 100,
		minPods:          1,
		maxPods:          5,
		firstScale:       3,
		firstScaleStasis: 10 * time.Minute,
		cpuBurst:         700,
		secondScale:      5,
	}
	scaleTest.run(name, kind, rc, f)
}

func scaleDown(name, kind string, rc *ResourceConsumer, f *Framework) {
	scaleTest := &HPAScaleTest{
		initPods:         5,
		cpuStart:         400,
		maxCPU:           500,
		idealCPU:         .3 * 100,
		minPods:          1,
		maxPods:          5,
		firstScale:       3,
		firstScaleStasis: 10 * time.Minute,
		cpuBurst:         100,
		secondScale:      1,
	}
	scaleTest.run(name, kind, rc, f)
}

func createCPUHorizontalPodAutoscaler(rc *ResourceConsumer, cpu, minReplicas, maxRepl int) {
	hpa := &extensions.HorizontalPodAutoscaler{
		ObjectMeta: api.ObjectMeta{
			Name:      rc.name,
			Namespace: rc.framework.Namespace.Name,
		},
		Spec: extensions.HorizontalPodAutoscalerSpec{
			ScaleRef: extensions.SubresourceReference{
				Kind:        rc.kind,
				Name:        rc.name,
				Subresource: subresource,
			},
			MinReplicas:    &minReplicas,
			MaxReplicas:    maxRepl,
			CPUUtilization: &extensions.CPUTargetUtilization{TargetPercentage: cpu},
		},
	}
	_, errHPA := rc.framework.Client.Extensions().HorizontalPodAutoscalers(rc.framework.Namespace.Name).Create(hpa)
	expectNoError(errHPA)
}
<commit_msg>Added HPA lightweight e2e test<commit_after>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package e2e

import (
	"time"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/apis/extensions"

	. "github.com/onsi/ginkgo"
)

const (
	kindRC         = "replicationController"
	kindDeployment = "deployment"
	subresource    = "scale"
)

// These tests don't seem to be running properly in parallel: issue: #20338.
//

var _ = Describe("Horizontal pod autoscaling (scale resource: CPU)", func() {
	var rc *ResourceConsumer
	f := NewDefaultFramework("horizontal-pod-autoscaling")

	titleUp := "Should scale from 1 pod to 3 pods and from 3 to 5 and verify decision stability"
	titleDown := "Should scale from 5 pods to 3 pods and from 3 to 1 and verify decision stability"

	// TODO(madhusudancs): Fix this when Scale group issues are resolved (see issue #18528).
	// These tests take ~20 minutes each.
	// Describe("[Serial] [Slow] Deployment", func() {
	// 	// CPU tests via deployments
	// 	It(titleUp, func() {
	// 		scaleUp("deployment", kindDeployment, rc, f)
	// 	})
	// 	It(titleDown, func() {
	// 		scaleDown("deployment", kindDeployment, rc, f)
	// 	})
	// })

	// These tests take ~20 minutes each.
	Describe("[Serial] [Slow] ReplicationController", func() {
		// CPU tests via replication controllers
		It(titleUp, func() {
			scaleUp("rc", kindRC, rc, f)
		})
		It(titleDown, func() {
			scaleDown("rc", kindRC, rc, f)
		})
	})

	Describe("ReplicationController light", func() {
		It("Should scale from 1 pod to 2 pods", func() {
			scaleTest := &HPAScaleTest{
				initPods:                    1,
				totalInitialCPUUsage:        150,
				perPodCPURequest:            200,
				targetCPUUtilizationPercent: 50,
				minPods:                     1,
				maxPods:                     2,
				firstScale:                  2,
			}
			scaleTest.run("rc-light", kindRC, rc, f)
		})
		It("Should scale from 2 pods to 1 pod using HPA version v1", func() {
			scaleTest := &HPAScaleTest{
				initPods:                    2,
				totalInitialCPUUsage:        50,
				perPodCPURequest:            200,
				targetCPUUtilizationPercent: 50,
				minPods:                     1,
				maxPods:                     2,
				firstScale:                  1,
				useV1:                       true,
			}
			scaleTest.run("rc-light", kindRC, rc, f)
		})
	})
})

// HPAScaleTest struct is used by the scale(...) function.
type HPAScaleTest struct {
	initPods                    int
	totalInitialCPUUsage        int
	perPodCPURequest            int64
	targetCPUUtilizationPercent int
	minPods                     int
	maxPods                     int
	firstScale                  int
	firstScaleStasis            time.Duration
	cpuBurst                    int
	secondScale                 int
	secondScaleStasis           time.Duration
	useV1                       bool
}

// run is a method which runs an HPA lifecycle, from a starting state, to an expected
// The initial state is defined by the initPods parameter.
// The first state change is due to the CPU being consumed initially, which HPA responds to by changing pod counts.
// The second state change (optional) is due to the CPU burst parameter, which HPA again responds to.
// TODO The use of 3 states is arbitrary, we could eventually make this test handle "n" states once this test stabilizes.
func (scaleTest *HPAScaleTest) run(name, kind string, rc *ResourceConsumer, f *Framework) {
	rc = NewDynamicResourceConsumer(name, kind, scaleTest.initPods, scaleTest.totalInitialCPUUsage, 0, scaleTest.perPodCPURequest, 100, f)
	defer rc.CleanUp()
	createCPUHorizontalPodAutoscaler(rc, scaleTest.targetCPUUtilizationPercent, scaleTest.minPods, scaleTest.maxPods, scaleTest.useV1)
	rc.WaitForReplicas(scaleTest.firstScale)
	if scaleTest.firstScaleStasis > 0 {
		rc.EnsureDesiredReplicas(scaleTest.firstScale, scaleTest.firstScaleStasis)
	}
	if scaleTest.cpuBurst > 0 && scaleTest.secondScale > 0 {
		rc.ConsumeCPU(scaleTest.cpuBurst)
		rc.WaitForReplicas(scaleTest.secondScale)
	}
}

func scaleUp(name, kind string, rc *ResourceConsumer, f *Framework) {
	scaleTest := &HPAScaleTest{
		initPods:                    1,
		totalInitialCPUUsage:        250,
		perPodCPURequest:            500,
		targetCPUUtilizationPercent: 20,
		minPods:                     1,
		maxPods:                     5,
		firstScale:                  3,
		firstScaleStasis:            10 * time.Minute,
		cpuBurst:                    700,
		secondScale:                 5,
	}
	scaleTest.run(name, kind, rc, f)
}

func scaleDown(name, kind string, rc *ResourceConsumer, f *Framework) {
	scaleTest := &HPAScaleTest{
		initPods:                    5,
		totalInitialCPUUsage:        400,
		perPodCPURequest:            500,
		targetCPUUtilizationPercent: 30,
		minPods:                     1,
		maxPods:                     5,
		firstScale:                  3,
		firstScaleStasis:            10 * time.Minute,
		cpuBurst:                    100,
		secondScale:                 1,
	}
	scaleTest.run(name, kind, rc, f)
}

func createCPUHorizontalPodAutoscaler(rc *ResourceConsumer, cpu, minReplicas, maxRepl int, useV1 bool) {
	hpa := &extensions.HorizontalPodAutoscaler{
		ObjectMeta: api.ObjectMeta{
			Name:      rc.name,
			Namespace: rc.framework.Namespace.Name,
		},
		Spec: extensions.HorizontalPodAutoscalerSpec{
			ScaleRef: extensions.SubresourceReference{
				Kind:        rc.kind,
				Name:        rc.name,
				Subresource: subresource,
			},
			MinReplicas:    &minReplicas,
			MaxReplicas:    maxRepl,
			CPUUtilization: &extensions.CPUTargetUtilization{TargetPercentage: cpu},
		},
	}
	var errHPA error
	if useV1 {
		_, errHPA = rc.framework.Client.Autoscaling().HorizontalPodAutoscalers(rc.framework.Namespace.Name).Create(hpa)
	} else {
		_, errHPA = rc.framework.Client.Extensions().HorizontalPodAutoscalers(rc.framework.Namespace.Name).Create(hpa)
	}
	expectNoError(errHPA)
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/* This test check that setHostnameAsFQDN PodSpec field works as
 * expected.
 */

package e2enode

import (
	"crypto/rand"
	"fmt"
	"math/big"
	"time"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/kubernetes/pkg/kubelet/events"

	"k8s.io/kubernetes/test/e2e/framework"
	e2eevents "k8s.io/kubernetes/test/e2e/framework/events"
	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
	imageutils "k8s.io/kubernetes/test/utils/image"

	"github.com/onsi/ginkgo"
)

func generatePodName(base string) string {
	id, err := rand.Int(rand.Reader, big.NewInt(214748))
	if err != nil {
		return base
	}
	return fmt.Sprintf("%s-%d", base, id)
}

func testPod(podnamebase string) *v1.Pod {
	podName := generatePodName(podnamebase)
	pod := &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:        podName,
			Labels:      map[string]string{"name": podName},
			Annotations: map[string]string{},
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{
				{
					Name:  "test-container",
					Image: imageutils.GetE2EImage(imageutils.BusyBox),
				},
			},
			RestartPolicy: v1.RestartPolicyNever,
		},
	}

	return pod
}

var _ = SIGDescribe("Hostname of Pod [Feature:SetHostnameAsFQDN][NodeFeature:SetHostnameAsFQDN]", func() {
	f := framework.NewDefaultFramework("hostfqdn")
	dnsDomain := "cluster.local"
	if cdn := framework.TestContext.ClusterDNSDomain; cdn != "" {
		dnsDomain = cdn
	}

	/*
	   Release: v1.19
	   Testname: Create Pod without fully qualified domain name (FQDN)
	   Description: A Pod that does not define the subdomain field in it spec, does not have FQDN.
	*/
	ginkgo.It("a pod without subdomain field does not have FQDN", func() {
		pod := testPod("hostfqdn")
		pod.Spec.Containers[0].Command = []string{"sh", "-c", "echo $(hostname)';'$(hostname -f)';'"}
		output := []string{fmt.Sprintf("%s;%s;", pod.ObjectMeta.Name, pod.ObjectMeta.Name)}
		// Create Pod
		f.TestContainerOutput("shortname only", pod, 0, output)
	})

	/*
	   Release: v1.19
	   Testname: Create Pod without FQDN, setHostnameAsFQDN field set to true
	   Description: A Pod that does not define the subdomain field in it spec, does not have FQDN.
	                Hence, SetHostnameAsFQDN feature has no effect.
	*/
	ginkgo.It("a pod without FQDN is not affected by SetHostnameAsFQDN field", func() {
		pod := testPod("hostfqdn")
		// Setting setHostnameAsFQDN field to true should have no effect.
		setHostnameAsFQDN := true
		pod.Spec.SetHostnameAsFQDN = &setHostnameAsFQDN
		pod.Spec.Containers[0].Command = []string{"sh", "-c", "echo $(hostname)';'$(hostname -f)';'"}
		output := []string{fmt.Sprintf("%s;%s;", pod.ObjectMeta.Name, pod.ObjectMeta.Name)}
		// Create Pod
		f.TestContainerOutput("shortname only", pod, 0, output)
	})

	/*
	   Release: v1.19
	   Testname: Create Pod with FQDN, setHostnameAsFQDN field not defined.
	   Description: A Pod that defines the subdomain field in it spec has FQDN.
	                hostname command returns shortname (pod name in this case), and hostname -f returns FQDN.
	*/
	ginkgo.It("a pod with subdomain field has FQDN, hostname is shortname", func() {
		pod := testPod("hostfqdn")
		pod.Spec.Containers[0].Command = []string{"sh", "-c", "echo $(hostname)';'$(hostname -f)';'"}
		subdomain := "t"
		// Set PodSpec subdomain field to generate FQDN for pod
		pod.Spec.Subdomain = subdomain
		// Expected Pod FQDN
		hostFQDN := fmt.Sprintf("%s.%s.%s.svc.%s", pod.ObjectMeta.Name, subdomain, f.Namespace.Name, dnsDomain)
		output := []string{fmt.Sprintf("%s;%s;", pod.ObjectMeta.Name, hostFQDN)}
		// Create Pod
		f.TestContainerOutput("shortname and fqdn", pod, 0, output)
	})

	/*
	   Release: v1.19
	   Testname: Create Pod with FQDN, setHostnameAsFQDN field set to true.
	   Description: A Pod that defines the subdomain field in it spec has FQDN. When setHostnameAsFQDN: true, the
	                hostname is set to be the FQDN. In this case, both commands hostname and hostname -f return the FQDN of the Pod.
	*/
	ginkgo.It("a pod with subdomain field has FQDN, when setHostnameAsFQDN is set to true, the FQDN is set as hostname", func() {
		pod := testPod("hostfqdn")
		pod.Spec.Containers[0].Command = []string{"sh", "-c", "echo $(hostname)';'$(hostname -f)';'"}
		subdomain := "t"
		// Set PodSpec subdomain field to generate FQDN for pod
		pod.Spec.Subdomain = subdomain
		// Set PodSpec setHostnameAsFQDN to set FQDN as hostname
		setHostnameAsFQDN := true
		pod.Spec.SetHostnameAsFQDN = &setHostnameAsFQDN
		// Expected Pod FQDN
		hostFQDN := fmt.Sprintf("%s.%s.%s.svc.%s", pod.ObjectMeta.Name, subdomain, f.Namespace.Name, dnsDomain)
		// Fail if FQDN is longer than 64 characters, otherwise the Pod will remain pending until test timeout.
		// In Linux, 64 characters is the limit of the hostname kernel field, which this test sets to the pod FQDN.
		framework.ExpectEqual(len(hostFQDN) < 65, true, fmt.Sprintf("The FQDN of the Pod cannot be longer than 64 characters, requested %s which is %d characters long.", hostFQDN, len(hostFQDN)))
		output := []string{fmt.Sprintf("%s;%s;", hostFQDN, hostFQDN)}
		// Create Pod
		f.TestContainerOutput("fqdn and fqdn", pod, 0, output)
	})

	/*
	   Release: v1.20
	   Testname: Fail to Create Pod with longer than 64 bytes FQDN when setHostnameAsFQDN field set to true.
	   Description: A Pod that defines the subdomain field in it spec has FQDN.
	                 When setHostnameAsFQDN: true, the hostname is set to be
	                 the FQDN. Since kernel limit is 64 bytes for hostname field,
	                 if pod FQDN is longer than 64 bytes it will generate events
	                 regarding FailedCreatePodSandBox.
	*/

	ginkgo.It("a pod configured to set FQDN as hostname will remain in Pending "+
		"state generating FailedCreatePodSandBox events when the FQDN is "+
		"longer than 64 bytes", func() {
		// 55 characters for name plus -<int>.t.svc.cluster.local is way more than 64 bytes
		pod := testPod("hostfqdnveryveryveryverylongforfqdntobemorethan64bytes")
		pod.Spec.Containers[0].Command = []string{"sh", "-c", "echo $(hostname)';'$(hostname -f)';'"}
		subdomain := "t"
		// Set PodSpec subdomain field to generate FQDN for pod
		pod.Spec.Subdomain = subdomain
		// Set PodSpec setHostnameAsFQDN to set FQDN as hostname
		setHostnameAsFQDN := true
		pod.Spec.SetHostnameAsFQDN = &setHostnameAsFQDN
		// Create Pod
		launchedPod := f.PodClient().Create(pod)
		// Ensure we delete pod
		defer f.PodClient().DeleteSync(launchedPod.Name, metav1.DeleteOptions{}, framework.DefaultPodDeletionTimeout)

		// Pod should remain in the pending state generating events with reason FailedCreatePodSandBox
		// Expected Message Error Event
		expectedMessage := "Failed to create pod sandbox: failed " +
			"to construct FQDN from pod hostname and cluster domain, FQDN "
		framework.Logf("Waiting for Pod to generate FailedCreatePodSandBox event.")
		// Wait for event with reason FailedCreatePodSandBox
		expectSandboxFailureEvent(f, launchedPod, expectedMessage)
		// Check Pod is in Pending Phase
		err := checkPodIsPending(f, launchedPod.ObjectMeta.Name, launchedPod.ObjectMeta.Namespace)
		framework.ExpectNoError(err)

	})
})

// expectSandboxFailureEvent polls for an event with reason "FailedCreatePodSandBox" containing the
// expected message string.
func expectSandboxFailureEvent(f *framework.Framework, pod *v1.Pod, msg string) {
	eventSelector := fields.Set{
		"involvedObject.kind":      "Pod",
		"involvedObject.name":      pod.Name,
		"involvedObject.namespace": f.Namespace.Name,
		"reason":                   events.FailedCreatePodSandBox,
	}.AsSelector().String()
	framework.ExpectNoError(e2eevents.WaitTimeoutForEvent(
		f.ClientSet, f.Namespace.Name, eventSelector, msg, framework.PodEventTimeout))
}

func checkPodIsPending(f *framework.Framework, podName, namespace string) error {
	c := f.ClientSet
	// we call this functoin after we saw event failing to create Pod, hence
	// pod has already been created and it should be in Pending status. Giving
	// 30 seconds to fetch the pod to avoid failing for transient issues getting
	// pods.
	fetchPodTimeout := 30 * time.Second
	return e2epod.WaitForPodCondition(c, namespace, podName, "Failed to Create Pod", fetchPodTimeout, func(pod *v1.Pod) (bool, error) {
		// We are looking for the pod to be scheduled and in Pending state
		if pod.Status.Phase == v1.PodPending {
			for _, cond := range pod.Status.Conditions {
				if cond.Type == v1.PodScheduled && cond.Status == v1.ConditionTrue {
					return true, nil
				}
			}
		}
		// If pod gets to this status, either FQDN is shorter than 64bytes
		// or setHostnameAsFQDN feature is not enable/in use.
		if pod.Status.Phase == v1.PodRunning || pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodFailed {
			return true, fmt.Errorf("Expected pod %q in namespace %q to be in phase Pending, but got phase: %v", podName, namespace, pod.Status.Phase)
		}
		return false, nil
	})
}
<commit_msg>Fixing expected pod subdomain to match framework.TestContext.ClusterDNSDomain<commit_after>/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/* This test check that setHostnameAsFQDN PodSpec field works as
 * expected.
 */

package e2enode

import (
	"crypto/rand"
	"fmt"
	"math/big"
	"time"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/kubernetes/pkg/kubelet/events"

	"k8s.io/kubernetes/test/e2e/framework"
	e2eevents "k8s.io/kubernetes/test/e2e/framework/events"
	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
	imageutils "k8s.io/kubernetes/test/utils/image"

	"github.com/onsi/ginkgo"
)

func generatePodName(base string) string {
	id, err := rand.Int(rand.Reader, big.NewInt(214748))
	if err != nil {
		return base
	}
	return fmt.Sprintf("%s-%d", base, id)
}

func testPod(podnamebase string) *v1.Pod {
	podName := generatePodName(podnamebase)
	pod := &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:        podName,
			Labels:      map[string]string{"name": podName},
			Annotations: map[string]string{},
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{
				{
					Name:  "test-container",
					Image: imageutils.GetE2EImage(imageutils.BusyBox),
				},
			},
			RestartPolicy: v1.RestartPolicyNever,
		},
	}

	return pod
}

var _ = SIGDescribe("Hostname of Pod [Feature:SetHostnameAsFQDN][NodeFeature:SetHostnameAsFQDN]", func() {
	f := framework.NewDefaultFramework("hostfqdn")
	/*
	   Release: v1.19
	   Testname: Create Pod without fully qualified domain name (FQDN)
	   Description: A Pod that does not define the subdomain field in it spec, does not have FQDN.
	*/
	ginkgo.It("a pod without subdomain field does not have FQDN", func() {
		pod := testPod("hostfqdn")
		pod.Spec.Containers[0].Command = []string{"sh", "-c", "echo $(hostname)';'$(hostname -f)';'"}
		output := []string{fmt.Sprintf("%s;%s;", pod.ObjectMeta.Name, pod.ObjectMeta.Name)}
		// Create Pod
		f.TestContainerOutput("shortname only", pod, 0, output)
	})

	/*
	   Release: v1.19
	   Testname: Create Pod without FQDN, setHostnameAsFQDN field set to true
	   Description: A Pod that does not define the subdomain field in it spec, does not have FQDN.
	                Hence, SetHostnameAsFQDN feature has no effect.
	*/
	ginkgo.It("a pod without FQDN is not affected by SetHostnameAsFQDN field", func() {
		pod := testPod("hostfqdn")
		// Setting setHostnameAsFQDN field to true should have no effect.
		setHostnameAsFQDN := true
		pod.Spec.SetHostnameAsFQDN = &setHostnameAsFQDN
		pod.Spec.Containers[0].Command = []string{"sh", "-c", "echo $(hostname)';'$(hostname -f)';'"}
		output := []string{fmt.Sprintf("%s;%s;", pod.ObjectMeta.Name, pod.ObjectMeta.Name)}
		// Create Pod
		f.TestContainerOutput("shortname only", pod, 0, output)
	})

	/*
	   Release: v1.19
	   Testname: Create Pod with FQDN, setHostnameAsFQDN field not defined.
	   Description: A Pod that defines the subdomain field in it spec has FQDN.
	                hostname command returns shortname (pod name in this case), and hostname -f returns FQDN.
	*/
	ginkgo.It("a pod with subdomain field has FQDN, hostname is shortname", func() {
		pod := testPod("hostfqdn")
		pod.Spec.Containers[0].Command = []string{"sh", "-c", "echo $(hostname)';'$(hostname -f)';'"}
		subdomain := "t"
		// Set PodSpec subdomain field to generate FQDN for pod
		pod.Spec.Subdomain = subdomain
		// Expected Pod FQDN
		hostFQDN := fmt.Sprintf("%s.%s.%s.svc.%s", pod.ObjectMeta.Name, subdomain, f.Namespace.Name, framework.TestContext.ClusterDNSDomain)
		output := []string{fmt.Sprintf("%s;%s;", pod.ObjectMeta.Name, hostFQDN)}
		// Create Pod
		f.TestContainerOutput("shortname and fqdn", pod, 0, output)
	})

	/*
	   Release: v1.19
	   Testname: Create Pod with FQDN, setHostnameAsFQDN field set to true.
	   Description: A Pod that defines the subdomain field in it spec has FQDN. When setHostnameAsFQDN: true, the
	                hostname is set to be the FQDN. In this case, both commands hostname and hostname -f return the FQDN of the Pod.
	*/
	ginkgo.It("a pod with subdomain field has FQDN, when setHostnameAsFQDN is set to true, the FQDN is set as hostname", func() {
		pod := testPod("hostfqdn")
		pod.Spec.Containers[0].Command = []string{"sh", "-c", "echo $(hostname)';'$(hostname -f)';'"}
		subdomain := "t"
		// Set PodSpec subdomain field to generate FQDN for pod
		pod.Spec.Subdomain = subdomain
		// Set PodSpec setHostnameAsFQDN to set FQDN as hostname
		setHostnameAsFQDN := true
		pod.Spec.SetHostnameAsFQDN = &setHostnameAsFQDN
		// Expected Pod FQDN
		hostFQDN := fmt.Sprintf("%s.%s.%s.svc.%s", pod.ObjectMeta.Name, subdomain, f.Namespace.Name, framework.TestContext.ClusterDNSDomain)
		// Fail if FQDN is longer than 64 characters, otherwise the Pod will remain pending until test timeout.
		// In Linux, 64 characters is the limit of the hostname kernel field, which this test sets to the pod FQDN.
		framework.ExpectEqual(len(hostFQDN) < 65, true, fmt.Sprintf("The FQDN of the Pod cannot be longer than 64 characters, requested %s which is %d characters long.", hostFQDN, len(hostFQDN)))
		output := []string{fmt.Sprintf("%s;%s;", hostFQDN, hostFQDN)}
		// Create Pod
		f.TestContainerOutput("fqdn and fqdn", pod, 0, output)
	})

	/*
	   Release: v1.20
	   Testname: Fail to Create Pod with longer than 64 bytes FQDN when setHostnameAsFQDN field set to true.
	   Description: A Pod that defines the subdomain field in it spec has FQDN.
	                 When setHostnameAsFQDN: true, the hostname is set to be
	                 the FQDN. Since kernel limit is 64 bytes for hostname field,
	                 if pod FQDN is longer than 64 bytes it will generate events
	                 regarding FailedCreatePodSandBox.
	*/

	ginkgo.It("a pod configured to set FQDN as hostname will remain in Pending "+
		"state generating FailedCreatePodSandBox events when the FQDN is "+
		"longer than 64 bytes", func() {
		// 55 characters for name plus -<int>.t.svc.cluster.local is way more than 64 bytes
		pod := testPod("hostfqdnveryveryveryverylongforfqdntobemorethan64bytes")
		pod.Spec.Containers[0].Command = []string{"sh", "-c", "echo $(hostname)';'$(hostname -f)';'"}
		subdomain := "t"
		// Set PodSpec subdomain field to generate FQDN for pod
		pod.Spec.Subdomain = subdomain
		// Set PodSpec setHostnameAsFQDN to set FQDN as hostname
		setHostnameAsFQDN := true
		pod.Spec.SetHostnameAsFQDN = &setHostnameAsFQDN
		// Create Pod
		launchedPod := f.PodClient().Create(pod)
		// Ensure we delete pod
		defer f.PodClient().DeleteSync(launchedPod.Name, metav1.DeleteOptions{}, framework.DefaultPodDeletionTimeout)

		// Pod should remain in the pending state generating events with reason FailedCreatePodSandBox
		// Expected Message Error Event
		expectedMessage := "Failed to create pod sandbox: failed " +
			"to construct FQDN from pod hostname and cluster domain, FQDN "
		framework.Logf("Waiting for Pod to generate FailedCreatePodSandBox event.")
		// Wait for event with reason FailedCreatePodSandBox
		expectSandboxFailureEvent(f, launchedPod, expectedMessage)
		// Check Pod is in Pending Phase
		err := checkPodIsPending(f, launchedPod.ObjectMeta.Name, launchedPod.ObjectMeta.Namespace)
		framework.ExpectNoError(err)

	})
})

// expectSandboxFailureEvent polls for an event with reason "FailedCreatePodSandBox" containing the
// expected message string.
func expectSandboxFailureEvent(f *framework.Framework, pod *v1.Pod, msg string) {
	eventSelector := fields.Set{
		"involvedObject.kind":      "Pod",
		"involvedObject.name":      pod.Name,
		"involvedObject.namespace": f.Namespace.Name,
		"reason":                   events.FailedCreatePodSandBox,
	}.AsSelector().String()
	framework.ExpectNoError(e2eevents.WaitTimeoutForEvent(
		f.ClientSet, f.Namespace.Name, eventSelector, msg, framework.PodEventTimeout))
}

func checkPodIsPending(f *framework.Framework, podName, namespace string) error {
	c := f.ClientSet
	// we call this functoin after we saw event failing to create Pod, hence
	// pod has already been created and it should be in Pending status. Giving
	// 30 seconds to fetch the pod to avoid failing for transient issues getting
	// pods.
	fetchPodTimeout := 30 * time.Second
	return e2epod.WaitForPodCondition(c, namespace, podName, "Failed to Create Pod", fetchPodTimeout, func(pod *v1.Pod) (bool, error) {
		// We are looking for the pod to be scheduled and in Pending state
		if pod.Status.Phase == v1.PodPending {
			for _, cond := range pod.Status.Conditions {
				if cond.Type == v1.PodScheduled && cond.Status == v1.ConditionTrue {
					return true, nil
				}
			}
		}
		// If pod gets to this status, either FQDN is shorter than 64bytes
		// or setHostnameAsFQDN feature is not enable/in use.
		if pod.Status.Phase == v1.PodRunning || pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodFailed {
			return true, fmt.Errorf("Expected pod %q in namespace %q to be in phase Pending, but got phase: %v", podName, namespace, pod.Status.Phase)
		}
		return false, nil
	})
}
<|endoftext|>


<commit_before>package google

import (
	"fmt"
	"io"
	"log"
	"net"
	"net/url"
	"strings"

	"google.golang.org/api/googleapi"
	sqladmin "google.golang.org/api/sqladmin/v1beta4"
)

type RetryErrorPredicateFunc func(error) (bool, string)

/** ADD GLOBAL ERROR RETRY PREDICATES HERE **/
// Retry predicates that shoud apply to all requests should be added here.
var defaultErrorRetryPredicates = []RetryErrorPredicateFunc{
	// Common network errors (usually wrapped by URL error)
	isNetworkTemporaryError,
	isNetworkTimeoutError,
	isIoEOFError,
	isConnectionResetNetworkError,

	// Common GCP error codes
	isCommonRetryableErrorCode,

	//While this might apply only to Cloud SQL, historically,
	// we had this in our global default error retries.
	// Keeping it as a default for now.
	is409OperationInProgressError,
}

/** END GLOBAL ERROR RETRY PREDICATES HERE **/

func isNetworkTemporaryError(err error) (bool, string) {
	if netErr, ok := err.(*net.OpError); ok && netErr.Temporary() {
		return true, "marked as timeout"
	}
	if urlerr, ok := err.(*url.Error); ok && urlerr.Temporary() {
		return true, "marked as timeout"
	}
	return false, ""
}

func isNetworkTimeoutError(err error) (bool, string) {
	if netErr, ok := err.(*net.OpError); ok && netErr.Timeout() {
		return true, "marked as timeout"
	}
	if urlerr, ok := err.(*url.Error); ok && urlerr.Timeout() {
		return true, "marked as timeout"
	}
	return false, ""
}

func isIoEOFError(err error) (bool, string) {
	if err == io.ErrUnexpectedEOF {
		return true, "Got unexpected EOF"
	}

	if urlerr, urlok := err.(*url.Error); urlok {
		wrappedErr := urlerr.Unwrap()
		if wrappedErr == io.ErrUnexpectedEOF {
			return true, "Got unexpected EOF"
		}
	}
	return false, ""
}

const connectionResetByPeerErr = ": connection reset by peer"

func isConnectionResetNetworkError(err error) (bool, string) {
	if strings.HasSuffix(err.Error(), connectionResetByPeerErr) {
		return true, fmt.Sprintf("reset connection error: %v", err)
	}
	return false, ""
}

// Retry 409s because some APIs like Cloud SQL throw a 409 if concurrent calls
// are being made.
//
//The only way right now to determine it is a retryable 409 due to
// concurrent calls is to look at the contents of the error message.
// See https://github.com/hashicorp/terraform-provider-google/issues/3279
func is409OperationInProgressError(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code == 409 && strings.Contains(gerr.Body, "operationInProgress") {
		log.Printf("[DEBUG] Dismissed an error as retryable based on error code 409 and error reason 'operationInProgress': %s", err)
		return true, "Operation still in progress"
	}
	return false, ""
}

// Retry on comon googleapi error codes for retryable errors.
// TODO(#5609): This may not need to be applied globally - figure out
// what retryable error codes apply to which API.
func isCommonRetryableErrorCode(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code == 429 || gerr.Code == 500 || gerr.Code == 502 || gerr.Code == 503 {
		log.Printf("[DEBUG] Dismissed an error as retryable based on error code: %s", err)
		return true, fmt.Sprintf("Retryable error code %d", gerr.Code)
	}
	return false, ""
}

// We've encountered a few common fingerprint-related strings; if this is one of
// them, we're confident this is an error due to fingerprints.
var FINGERPRINT_FAIL_ERRORS = []string{"Invalid fingerprint.", "Supplied fingerprint does not match current metadata fingerprint."}

// Retry the operation if it looks like a fingerprint mismatch.
func isFingerprintError(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code != 412 {
		return false, ""
	}

	for _, msg := range FINGERPRINT_FAIL_ERRORS {
		if strings.Contains(err.Error(), msg) {
			return true, "fingerprint mismatch"
		}
	}

	return false, ""
}

// If a permission necessary to provision a resource is created in the same config
// as the resource itself, the permission may not have propagated by the time terraform
// attempts to create the resource. This allows those errors to be retried until the timeout expires
func iamMemberMissing(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "permission") {
			return true, "Waiting for IAM member permissions to propagate."
		}
	}
	return false, ""
}

// Cloud PubSub returns a 400 error if a topic's parent project was recently created and an
// organization policy has not propagated.
// See https://github.com/hashicorp/terraform-provider-google/issues/4349
func pubsubTopicProjectNotReady(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "retry this operation") {
			log.Printf("[DEBUG] Dismissed error as a retryable operation: %s", err)
			return true, "Waiting for Pubsub topic's project to properly initialize with organiation policy"
		}
	}
	return false, ""
}

// Retry if Cloud SQL operation returns a 429 with a specific message for
// concurrent operations.
func isSqlInternalError(err error) (bool, string) {
	if gerr, ok := err.(*SqlAdminOperationError); ok {
		// SqlAdminOperationError is a non-interface type so we need to cast it through
		// a layer of interface{}.  :)
		var ierr interface{}
		ierr = gerr
		if serr, ok := ierr.(*sqladmin.OperationErrors); ok && serr.Errors[0].Code == "INTERNAL_ERROR" {
			return true, "Received an internal error, which is sometimes retryable for some SQL resources.  Optimistically retrying."
		}

	}
	return false, ""
}

// Retry if Cloud SQL operation returns a 429 with a specific message for
// concurrent operations.
func isSqlOperationInProgressError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 409 {
		if strings.Contains(gerr.Body, "you cannot reuse the name of the deleted instance until one week from the deletion date.") {
			return false, ""
		}

		return true, "Waiting for other concurrent Cloud SQL operations to finish"
	}
	return false, ""
}

// Retry if service usage decides you're activating the same service multiple
// times. This can happen if a service and a dependent service aren't batched
// together- eg container.googleapis.com in one request followed by compute.g.c
// in the next (container relies on compute and implicitly activates it)
func serviceUsageServiceBeingActivated(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 400 {
		if strings.Contains(gerr.Body, "Another activation or deactivation is in progress") {
			return false, ""
		}

		return true, "Waiting for same service activation/deactivation to finish"
	}
	return false, ""
}

// Retry if Monitoring operation returns a 429 with a specific message for
// concurrent operations.
func isMonitoringConcurrentEditError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && strings.Contains(strings.ToLower(gerr.Body), "too many concurrent edits") {
			return true, "Waiting for other Monitoring changes to finish"
		}
	}
	return false, ""
}

// Retry if App Engine operation returns a 429 with a specific message for
// concurrent operations.
func isAppEngineRetryableError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && strings.Contains(strings.ToLower(gerr.Body), "operation is already in progress") {
			return true, "Waiting for other concurrent App Engine changes to finish"
		}
	}
	return false, ""
}

// Retry if KMS CryptoKeyVersions returns a 400 for PENDING_GENERATION
func isCryptoKeyVersionsPendingGeneration(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 400 {
		if strings.Contains(gerr.Body, "PENDING_GENERATION") {
			return true, "Waiting for pending key generation"
		}
	}
	return false, ""
}

// Retry if getting a resource/operation returns a 404 for specific operations.
// opType should describe the operation for which 404 can be retryable.
func isNotFoundRetryableError(opType string) RetryErrorPredicateFunc {
	return func(err error) (bool, string) {
		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
			return true, fmt.Sprintf("Retry 404s for %s", opType)
		}
		return false, ""
	}
}

func isStoragePreconditionError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 412 {
		return true, fmt.Sprintf("Retry on storage precondition not met")
	}
	return false, ""
}

func isDataflowJobUpdateRetryableError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 404 && strings.Contains(gerr.Body, "in RUNNING OR DRAINING state") {
			return true, "Waiting for job to be in a valid state"
		}
	}
	return false, ""
}

func isPeeringOperationInProgress(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "There is a peering operation in progress") {
			return true, "Waiting peering operation to complete"
		}
	}
	return false, ""
}

func isCloudFunctionsSourceCodeError(err error) (bool, string) {
	if operr, ok := err.(*CommonOpError); ok {
		if operr.Code == 3 && operr.Message == "Failed to retrieve function source code" {
			return true, fmt.Sprintf("Retry on Function failing to pull code from GCS")
		}
	}
	return false, ""
}

func datastoreIndex409Contention(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && strings.Contains(gerr.Body, "too much contention") {
			return true, "too much contention - waiting for less activity"
		}
	}
	return false, ""
}
<commit_msg>Address bigquery's low-but-fast-refreshing quota by accepting and retrying quota errors. (#4094) (#547)<commit_after>package google

import (
	"fmt"
	"io"
	"log"
	"net"
	"net/url"
	"strings"

	"google.golang.org/api/googleapi"
	sqladmin "google.golang.org/api/sqladmin/v1beta4"
)

type RetryErrorPredicateFunc func(error) (bool, string)

/** ADD GLOBAL ERROR RETRY PREDICATES HERE **/
// Retry predicates that shoud apply to all requests should be added here.
var defaultErrorRetryPredicates = []RetryErrorPredicateFunc{
	// Common network errors (usually wrapped by URL error)
	isNetworkTemporaryError,
	isNetworkTimeoutError,
	isIoEOFError,
	isConnectionResetNetworkError,

	// Common GCP error codes
	isCommonRetryableErrorCode,

	//While this might apply only to Cloud SQL, historically,
	// we had this in our global default error retries.
	// Keeping it as a default for now.
	is409OperationInProgressError,
}

/** END GLOBAL ERROR RETRY PREDICATES HERE **/

func isNetworkTemporaryError(err error) (bool, string) {
	if netErr, ok := err.(*net.OpError); ok && netErr.Temporary() {
		return true, "marked as timeout"
	}
	if urlerr, ok := err.(*url.Error); ok && urlerr.Temporary() {
		return true, "marked as timeout"
	}
	return false, ""
}

func isNetworkTimeoutError(err error) (bool, string) {
	if netErr, ok := err.(*net.OpError); ok && netErr.Timeout() {
		return true, "marked as timeout"
	}
	if urlerr, ok := err.(*url.Error); ok && urlerr.Timeout() {
		return true, "marked as timeout"
	}
	return false, ""
}

func isIoEOFError(err error) (bool, string) {
	if err == io.ErrUnexpectedEOF {
		return true, "Got unexpected EOF"
	}

	if urlerr, urlok := err.(*url.Error); urlok {
		wrappedErr := urlerr.Unwrap()
		if wrappedErr == io.ErrUnexpectedEOF {
			return true, "Got unexpected EOF"
		}
	}
	return false, ""
}

const connectionResetByPeerErr = ": connection reset by peer"

func isConnectionResetNetworkError(err error) (bool, string) {
	if strings.HasSuffix(err.Error(), connectionResetByPeerErr) {
		return true, fmt.Sprintf("reset connection error: %v", err)
	}
	return false, ""
}

// Retry 409s because some APIs like Cloud SQL throw a 409 if concurrent calls
// are being made.
//
//The only way right now to determine it is a retryable 409 due to
// concurrent calls is to look at the contents of the error message.
// See https://github.com/hashicorp/terraform-provider-google/issues/3279
func is409OperationInProgressError(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code == 409 && strings.Contains(gerr.Body, "operationInProgress") {
		log.Printf("[DEBUG] Dismissed an error as retryable based on error code 409 and error reason 'operationInProgress': %s", err)
		return true, "Operation still in progress"
	}
	return false, ""
}

// Retry on comon googleapi error codes for retryable errors.
// TODO(#5609): This may not need to be applied globally - figure out
// what retryable error codes apply to which API.
func isCommonRetryableErrorCode(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code == 429 || gerr.Code == 500 || gerr.Code == 502 || gerr.Code == 503 {
		log.Printf("[DEBUG] Dismissed an error as retryable based on error code: %s", err)
		return true, fmt.Sprintf("Retryable error code %d", gerr.Code)
	}
	return false, ""
}

// We've encountered a few common fingerprint-related strings; if this is one of
// them, we're confident this is an error due to fingerprints.
var FINGERPRINT_FAIL_ERRORS = []string{"Invalid fingerprint.", "Supplied fingerprint does not match current metadata fingerprint."}

// Retry the operation if it looks like a fingerprint mismatch.
func isFingerprintError(err error) (bool, string) {
	gerr, ok := err.(*googleapi.Error)
	if !ok {
		return false, ""
	}

	if gerr.Code != 412 {
		return false, ""
	}

	for _, msg := range FINGERPRINT_FAIL_ERRORS {
		if strings.Contains(err.Error(), msg) {
			return true, "fingerprint mismatch"
		}
	}

	return false, ""
}

// If a permission necessary to provision a resource is created in the same config
// as the resource itself, the permission may not have propagated by the time terraform
// attempts to create the resource. This allows those errors to be retried until the timeout expires
func iamMemberMissing(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "permission") {
			return true, "Waiting for IAM member permissions to propagate."
		}
	}
	return false, ""
}

// Cloud PubSub returns a 400 error if a topic's parent project was recently created and an
// organization policy has not propagated.
// See https://github.com/hashicorp/terraform-provider-google/issues/4349
func pubsubTopicProjectNotReady(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "retry this operation") {
			log.Printf("[DEBUG] Dismissed error as a retryable operation: %s", err)
			return true, "Waiting for Pubsub topic's project to properly initialize with organiation policy"
		}
	}
	return false, ""
}

// Retry if Cloud SQL operation returns a 429 with a specific message for
// concurrent operations.
func isSqlInternalError(err error) (bool, string) {
	if gerr, ok := err.(*SqlAdminOperationError); ok {
		// SqlAdminOperationError is a non-interface type so we need to cast it through
		// a layer of interface{}.  :)
		var ierr interface{}
		ierr = gerr
		if serr, ok := ierr.(*sqladmin.OperationErrors); ok && serr.Errors[0].Code == "INTERNAL_ERROR" {
			return true, "Received an internal error, which is sometimes retryable for some SQL resources.  Optimistically retrying."
		}

	}
	return false, ""
}

// Retry if Cloud SQL operation returns a 429 with a specific message for
// concurrent operations.
func isSqlOperationInProgressError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 409 {
		if strings.Contains(gerr.Body, "you cannot reuse the name of the deleted instance until one week from the deletion date.") {
			return false, ""
		}

		return true, "Waiting for other concurrent Cloud SQL operations to finish"
	}
	return false, ""
}

// Retry if service usage decides you're activating the same service multiple
// times. This can happen if a service and a dependent service aren't batched
// together- eg container.googleapis.com in one request followed by compute.g.c
// in the next (container relies on compute and implicitly activates it)
func serviceUsageServiceBeingActivated(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 400 {
		if strings.Contains(gerr.Body, "Another activation or deactivation is in progress") {
			return false, ""
		}

		return true, "Waiting for same service activation/deactivation to finish"
	}
	return false, ""
}

// Retry if Bigquery operation returns a 403 with a specific message for
// concurrent operations (which are implemented in terms of 'edit quota').
func isBigqueryIAMQuotaError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 403 && strings.Contains(strings.ToLower(gerr.Body), "exceeded rate limits") {
			return true, "Waiting for Bigquery edit quota to refresh"
		}
	}
	return false, ""
}

// Retry if Monitoring operation returns a 429 with a specific message for
// concurrent operations.
func isMonitoringConcurrentEditError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && strings.Contains(strings.ToLower(gerr.Body), "too many concurrent edits") {
			return true, "Waiting for other Monitoring changes to finish"
		}
	}
	return false, ""
}

// Retry if App Engine operation returns a 429 with a specific message for
// concurrent operations.
func isAppEngineRetryableError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && strings.Contains(strings.ToLower(gerr.Body), "operation is already in progress") {
			return true, "Waiting for other concurrent App Engine changes to finish"
		}
	}
	return false, ""
}

// Retry if KMS CryptoKeyVersions returns a 400 for PENDING_GENERATION
func isCryptoKeyVersionsPendingGeneration(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 400 {
		if strings.Contains(gerr.Body, "PENDING_GENERATION") {
			return true, "Waiting for pending key generation"
		}
	}
	return false, ""
}

// Retry if getting a resource/operation returns a 404 for specific operations.
// opType should describe the operation for which 404 can be retryable.
func isNotFoundRetryableError(opType string) RetryErrorPredicateFunc {
	return func(err error) (bool, string) {
		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
			return true, fmt.Sprintf("Retry 404s for %s", opType)
		}
		return false, ""
	}
}

func isStoragePreconditionError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 412 {
		return true, fmt.Sprintf("Retry on storage precondition not met")
	}
	return false, ""
}

func isDataflowJobUpdateRetryableError(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 404 && strings.Contains(gerr.Body, "in RUNNING OR DRAINING state") {
			return true, "Waiting for job to be in a valid state"
		}
	}
	return false, ""
}

func isPeeringOperationInProgress(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 400 && strings.Contains(gerr.Body, "There is a peering operation in progress") {
			return true, "Waiting peering operation to complete"
		}
	}
	return false, ""
}

func isCloudFunctionsSourceCodeError(err error) (bool, string) {
	if operr, ok := err.(*CommonOpError); ok {
		if operr.Code == 3 && operr.Message == "Failed to retrieve function source code" {
			return true, fmt.Sprintf("Retry on Function failing to pull code from GCS")
		}
	}
	return false, ""
}

func datastoreIndex409Contention(err error) (bool, string) {
	if gerr, ok := err.(*googleapi.Error); ok {
		if gerr.Code == 409 && strings.Contains(gerr.Body, "too much contention") {
			return true, "too much contention - waiting for less activity"
		}
	}
	return false, ""
}
<|endoftext|>


<commit_before>// +build integrationtest

package integrationtest

import (
	"testing"
	"time"

	"github.com/portworx/sched-ops/k8s"
	"github.com/portworx/torpedo/drivers/scheduler"
	"github.com/stretchr/testify/require"
	apps_api "k8s.io/api/apps/v1beta2"
	"k8s.io/api/core/v1"
	storage_api "k8s.io/api/storage/v1"
)

const (
	annotationStorageProvisioner = "volume.beta.kubernetes.io/storage-provisioner"
)

func testExtender(t *testing.T) {
	t.Run("pvcOwnershipTest", pvcOwnershipTest)
	t.Run("noPVCTest", noPVCTest)
	t.Run("singlePVCTest", singlePVCTest)
	t.Run("statefulsetTest", statefulsetTest)
	t.Run("multiplePVCTest", multiplePVCTest)
	t.Run("driverNodeErrorTest", driverNodeErrorTest)
}

func noPVCTest(t *testing.T) {
	ctxs, err := schedulerDriver.Schedule(generateInstanceID(t, "nopvctest"),
		scheduler.ScheduleOptions{AppKeys: []string{"mysql-nopvc"}})
	require.NoError(t, err, "Error scheduling task")
	require.Equal(t, 1, len(ctxs), "Only one task should have started")

	err = schedulerDriver.WaitForRunning(ctxs[0], defaultWaitTimeout, defaultWaitInterval)
	require.NoError(t, err, "Error waiting for pod to get to running state")

	destroyAndWait(t, ctxs)
}

func singlePVCTest(t *testing.T) {
	ctxs, err := schedulerDriver.Schedule(generateInstanceID(t, "singlepvctest"),
		scheduler.ScheduleOptions{AppKeys: []string{"mysql-1-pvc"}})
	require.NoError(t, err, "Error scheduling task")
	require.Equal(t, 1, len(ctxs), "Only one task should have started")

	err = schedulerDriver.WaitForRunning(ctxs[0], defaultWaitTimeout, defaultWaitInterval)
	require.NoError(t, err, "Error waiting for pod to get to running state")

	scheduledNodes, err := schedulerDriver.GetNodesForApp(ctxs[0])
	require.NoError(t, err, "Error getting node for app")
	require.Equal(t, 1, len(scheduledNodes), "App should be scheduled on one node")

	volumeNames := getVolumeNames(t, ctxs[0])
	require.Equal(t, 1, len(volumeNames), "Should only have one volume")

	verifyScheduledNode(t, scheduledNodes[0], volumeNames)

	destroyAndWait(t, ctxs)
}

func statefulsetTest(t *testing.T) {
	ctxs, err := schedulerDriver.Schedule(generateInstanceID(t, "sstest"),
		scheduler.ScheduleOptions{AppKeys: []string{"elasticsearch"}})
	require.NoError(t, err, "Error scheduling task")
	require.Equal(t, 1, len(ctxs), "Only one task should have started")

	err = schedulerDriver.WaitForRunning(ctxs[0], defaultWaitTimeout, defaultWaitInterval)
	require.NoError(t, err, "Error waiting for elasticsearch statefulset to get to running state")

	scheduledNodes, err := schedulerDriver.GetNodesForApp(ctxs[0])
	require.NoError(t, err, "Error getting node for app")
	require.Equal(t, 3, len(scheduledNodes), "App should be scheduled on one node")

	// TODO: torpedo doesn't return correct volumes here
	//volumeNames := getVolumeNames(t, ctxs[0])
	//require.Equal(t, 3, len(volumeNames), "Should have 3 volumes")

	// TODO: Add verification for node where it was scheduled
	// torpedo doesn't return the pod->pvc mapping, so we can't validate that it
	// got scheduled on a prioritized node
	//verifyScheduledNode(t, scheduledNodes[0], volumeNames)

	destroyAndWait(t, ctxs)
}

func multiplePVCTest(t *testing.T) {
	ctxs, err := schedulerDriver.Schedule(generateInstanceID(t, "multipvctest"),
		scheduler.ScheduleOptions{AppKeys: []string{"mysql-2-pvc"}})
	require.NoError(t, err, "Error scheduling task")
	require.Equal(t, 1, len(ctxs), "Only one task should have started")

	err = schedulerDriver.WaitForRunning(ctxs[0], defaultWaitTimeout, defaultWaitInterval)
	require.NoError(t, err, "Error waiting for pod to get to running state")

	scheduledNodes, err := schedulerDriver.GetNodesForApp(ctxs[0])
	require.NoError(t, err, "Error getting node for app")
	require.Equal(t, 1, len(scheduledNodes), "App should be scheduled on one node")

	volumeNames := getVolumeNames(t, ctxs[0])
	require.Equal(t, 2, len(volumeNames), "Should have two volumes")

	verifyScheduledNode(t, scheduledNodes[0], volumeNames)
	destroyAndWait(t, ctxs)
}

func driverNodeErrorTest(t *testing.T) {
	ctxs, err := schedulerDriver.Schedule(generateInstanceID(t, "drivererrtest"),
		scheduler.ScheduleOptions{AppKeys: []string{"mysql-1-pvc"}})
	require.NoError(t, err, "Error scheduling task")
	require.Equal(t, 1, len(ctxs), "Only one task should have started")

	err = schedulerDriver.WaitForRunning(ctxs[0], defaultWaitTimeout, defaultWaitInterval)
	require.NoError(t, err, "Error waiting for pod to get to running state")

	scheduledNodes, err := schedulerDriver.GetNodesForApp(ctxs[0])
	require.NoError(t, err, "Error getting node for app")
	require.Equal(t, 1, len(scheduledNodes), "App should be scheduled on one node")

	volumeNames := getVolumeNames(t, ctxs[0])
	require.Equal(t, 1, len(volumeNames), "Should have only one volume")

	verifyScheduledNode(t, scheduledNodes[0], volumeNames)

	time.Sleep(1 * time.Minute)

	err = volumeDriver.StopDriver(scheduledNodes, false)
	require.NoError(t, err, "Error stopping driver on scheduled Node %+v", scheduledNodes[0])
	stoppedNode := scheduledNodes[0]

	time.Sleep(1 * time.Minute)
	err = schedulerDriver.DeleteTasks(ctxs[0])
	require.NoError(t, err, "Error deleting pod")
	time.Sleep(10 * time.Second)

	err = schedulerDriver.WaitForRunning(ctxs[0], defaultWaitTimeout, defaultWaitInterval)
	require.NoError(t, err, "Error waiting for pod to get to running state after deletion")

	scheduledNodes, err = schedulerDriver.GetNodesForApp(ctxs[0])
	require.NoError(t, err, "Error getting node for app")
	require.Equal(t, 1, len(scheduledNodes), "App should be scheduled on one node")
	require.NotEqual(t, stoppedNode.Name, scheduledNodes[0].Name, "Task restarted on stopped node")

	volumeNames = getVolumeNames(t, ctxs[0])
	require.Equal(t, 1, len(volumeNames), "Should have only one volume")

	verifyScheduledNode(t, scheduledNodes[0], volumeNames)

	err = volumeDriver.StartDriver(stoppedNode)
	require.NoError(t, err, "Error starting driver on Node %+v", scheduledNodes[0])

	err = volumeDriver.WaitDriverUpOnNode(stoppedNode)
	require.NoError(t, err, "Error waiting for Node to start %+v", scheduledNodes[0])

	destroyAndWait(t, ctxs)
}

func pvcOwnershipTest(t *testing.T) {
	ctxs, err := schedulerDriver.Schedule(generateInstanceID(t, "ownershiptest"),
		scheduler.ScheduleOptions{AppKeys: []string{"mysql-repl-1"}})
	require.NoError(t, err, "Error scheduling task")
	require.Equal(t, 1, len(ctxs), "Only one task should have started")

	err = schedulerDriver.WaitForRunning(ctxs[0], defaultWaitTimeout, defaultWaitInterval)
	require.NoError(t, err, "Error waiting for pod to get to running state")

	scheduledNodes, err := schedulerDriver.GetNodesForApp(ctxs[0])
	require.NoError(t, err, "Error getting node for app")
	require.Equal(t, 1, len(scheduledNodes), "App should be scheduled on one node")

	volumeNames := getVolumeNames(t, ctxs[0])
	require.Equal(t, 1, len(volumeNames), "Should have only one volume")

	verifyScheduledNode(t, scheduledNodes[0], volumeNames)

	for _, spec := range ctxs[0].App.SpecList {
		if obj, ok := spec.(*storage_api.StorageClass); ok {
			err := k8s.Instance().DeleteStorageClass(obj.Name)
			require.NoError(t, err, "Error deleting storage class for mysql.")
		}
		if obj, ok := spec.(*v1.PersistentVolumeClaim); ok {
			updatePVC, err := k8s.Instance().GetPersistentVolumeClaim(obj.Name, obj.Namespace)
			require.NoError(t, err, "Error getting persistent volume claim.")
			if _, hasKey := updatePVC.Annotations[annotationStorageProvisioner]; hasKey {
				delete(updatePVC.Annotations, "storage-provisioner")
			}
			_, err = k8s.Instance().UpdatePersistentVolumeClaim(updatePVC)
			require.NoError(t, err, "Error updating annotations in PVC.")
		}
	}

	err = volumeDriver.StopDriver(scheduledNodes, false)
	require.NoError(t, err, "Error stopping driver on scheduled Node %+v", scheduledNodes[0])

	time.Sleep(3 * time.Minute)

	var errUnscheduledPod bool
	for _, spec := range ctxs[0].App.SpecList {
		if obj, ok := spec.(*apps_api.Deployment); ok {
			if obj.Name == "mysql" {
				depPods, err := k8s.Instance().GetDeploymentPods(obj)
				require.NoError(t, err, "Error getting pods for deployment ,mysql.")
				for _, pod := range depPods {
					for _, cond := range pod.Status.Conditions {
						if cond.Type == v1.PodScheduled && cond.Status == v1.ConditionFalse {
							errUnscheduledPod = true
						}
					}
				}
			}
		}
	}
	require.Equal(t, true, errUnscheduledPod, "Pod should not have been schedule.")

	err = volumeDriver.StartDriver(scheduledNodes[0])
	require.NoError(t, err, "Error starting driver on scheduled Node %+v", scheduledNodes[0])

	err = volumeDriver.WaitDriverUpOnNode(scheduledNodes[0])
	require.NoError(t, err, "Volume driver is not up on Node %+v", scheduledNodes[0])

	destroyAndWait(t, ctxs)
}
<commit_msg>Use correct spec for extender integration test<commit_after>// +build integrationtest

package integrationtest

import (
	"testing"
	"time"

	"github.com/portworx/sched-ops/k8s"
	"github.com/portworx/torpedo/drivers/scheduler"
	"github.com/stretchr/testify/require"
	apps_api "k8s.io/api/apps/v1beta2"
	"k8s.io/api/core/v1"
	storage_api "k8s.io/api/storage/v1"
)

const (
	annotationStorageProvisioner = "volume.beta.kubernetes.io/storage-provisioner"
)

func testExtender(t *testing.T) {
	t.Run("pvcOwnershipTest", pvcOwnershipTest)
	t.Run("noPVCTest", noPVCTest)
	t.Run("singlePVCTest", singlePVCTest)
	t.Run("statefulsetTest", statefulsetTest)
	t.Run("multiplePVCTest", multiplePVCTest)
	t.Run("driverNodeErrorTest", driverNodeErrorTest)
}

func noPVCTest(t *testing.T) {
	ctxs, err := schedulerDriver.Schedule(generateInstanceID(t, "nopvctest"),
		scheduler.ScheduleOptions{AppKeys: []string{"mysql-nopvc"}})
	require.NoError(t, err, "Error scheduling task")
	require.Equal(t, 1, len(ctxs), "Only one task should have started")

	err = schedulerDriver.WaitForRunning(ctxs[0], defaultWaitTimeout, defaultWaitInterval)
	require.NoError(t, err, "Error waiting for pod to get to running state")

	destroyAndWait(t, ctxs)
}

func singlePVCTest(t *testing.T) {
	ctxs, err := schedulerDriver.Schedule(generateInstanceID(t, "singlepvctest"),
		scheduler.ScheduleOptions{AppKeys: []string{"mysql-1-pvc"}})
	require.NoError(t, err, "Error scheduling task")
	require.Equal(t, 1, len(ctxs), "Only one task should have started")

	err = schedulerDriver.WaitForRunning(ctxs[0], defaultWaitTimeout, defaultWaitInterval)
	require.NoError(t, err, "Error waiting for pod to get to running state")

	scheduledNodes, err := schedulerDriver.GetNodesForApp(ctxs[0])
	require.NoError(t, err, "Error getting node for app")
	require.Equal(t, 1, len(scheduledNodes), "App should be scheduled on one node")

	volumeNames := getVolumeNames(t, ctxs[0])
	require.Equal(t, 1, len(volumeNames), "Should only have one volume")

	verifyScheduledNode(t, scheduledNodes[0], volumeNames)

	destroyAndWait(t, ctxs)
}

func statefulsetTest(t *testing.T) {
	ctxs, err := schedulerDriver.Schedule(generateInstanceID(t, "sstest"),
		scheduler.ScheduleOptions{AppKeys: []string{"elasticsearch"}})
	require.NoError(t, err, "Error scheduling task")
	require.Equal(t, 1, len(ctxs), "Only one task should have started")

	err = schedulerDriver.WaitForRunning(ctxs[0], defaultWaitTimeout, defaultWaitInterval)
	require.NoError(t, err, "Error waiting for elasticsearch statefulset to get to running state")

	scheduledNodes, err := schedulerDriver.GetNodesForApp(ctxs[0])
	require.NoError(t, err, "Error getting node for app")
	require.Equal(t, 3, len(scheduledNodes), "App should be scheduled on one node")

	// TODO: torpedo doesn't return correct volumes here
	//volumeNames := getVolumeNames(t, ctxs[0])
	//require.Equal(t, 3, len(volumeNames), "Should have 3 volumes")

	// TODO: Add verification for node where it was scheduled
	// torpedo doesn't return the pod->pvc mapping, so we can't validate that it
	// got scheduled on a prioritized node
	//verifyScheduledNode(t, scheduledNodes[0], volumeNames)

	destroyAndWait(t, ctxs)
}

func multiplePVCTest(t *testing.T) {
	ctxs, err := schedulerDriver.Schedule(generateInstanceID(t, "multipvctest"),
		scheduler.ScheduleOptions{AppKeys: []string{"mysql-2-pvc"}})
	require.NoError(t, err, "Error scheduling task")
	require.Equal(t, 1, len(ctxs), "Only one task should have started")

	err = schedulerDriver.WaitForRunning(ctxs[0], defaultWaitTimeout, defaultWaitInterval)
	require.NoError(t, err, "Error waiting for pod to get to running state")

	scheduledNodes, err := schedulerDriver.GetNodesForApp(ctxs[0])
	require.NoError(t, err, "Error getting node for app")
	require.Equal(t, 1, len(scheduledNodes), "App should be scheduled on one node")

	volumeNames := getVolumeNames(t, ctxs[0])
	require.Equal(t, 2, len(volumeNames), "Should have two volumes")

	verifyScheduledNode(t, scheduledNodes[0], volumeNames)
	destroyAndWait(t, ctxs)
}

func driverNodeErrorTest(t *testing.T) {
	ctxs, err := schedulerDriver.Schedule(generateInstanceID(t, "drivererrtest"),
		scheduler.ScheduleOptions{AppKeys: []string{"mysql-1-pvc"}})
	require.NoError(t, err, "Error scheduling task")
	require.Equal(t, 1, len(ctxs), "Only one task should have started")

	err = schedulerDriver.WaitForRunning(ctxs[0], defaultWaitTimeout, defaultWaitInterval)
	require.NoError(t, err, "Error waiting for pod to get to running state")

	scheduledNodes, err := schedulerDriver.GetNodesForApp(ctxs[0])
	require.NoError(t, err, "Error getting node for app")
	require.Equal(t, 1, len(scheduledNodes), "App should be scheduled on one node")

	volumeNames := getVolumeNames(t, ctxs[0])
	require.Equal(t, 1, len(volumeNames), "Should have only one volume")

	verifyScheduledNode(t, scheduledNodes[0], volumeNames)

	time.Sleep(1 * time.Minute)

	err = volumeDriver.StopDriver(scheduledNodes, false)
	require.NoError(t, err, "Error stopping driver on scheduled Node %+v", scheduledNodes[0])
	stoppedNode := scheduledNodes[0]

	time.Sleep(1 * time.Minute)
	err = schedulerDriver.DeleteTasks(ctxs[0])
	require.NoError(t, err, "Error deleting pod")
	time.Sleep(10 * time.Second)

	err = schedulerDriver.WaitForRunning(ctxs[0], defaultWaitTimeout, defaultWaitInterval)
	require.NoError(t, err, "Error waiting for pod to get to running state after deletion")

	scheduledNodes, err = schedulerDriver.GetNodesForApp(ctxs[0])
	require.NoError(t, err, "Error getting node for app")
	require.Equal(t, 1, len(scheduledNodes), "App should be scheduled on one node")
	require.NotEqual(t, stoppedNode.Name, scheduledNodes[0].Name, "Task restarted on stopped node")

	volumeNames = getVolumeNames(t, ctxs[0])
	require.Equal(t, 1, len(volumeNames), "Should have only one volume")

	verifyScheduledNode(t, scheduledNodes[0], volumeNames)

	err = volumeDriver.StartDriver(stoppedNode)
	require.NoError(t, err, "Error starting driver on Node %+v", scheduledNodes[0])

	err = volumeDriver.WaitDriverUpOnNode(stoppedNode)
	require.NoError(t, err, "Error waiting for Node to start %+v", scheduledNodes[0])

	destroyAndWait(t, ctxs)
}

func pvcOwnershipTest(t *testing.T) {
	ctxs, err := schedulerDriver.Schedule(generateInstanceID(t, "ownershiptest"),
		scheduler.ScheduleOptions{AppKeys: []string{"mysql-1-pvc"}})
	require.NoError(t, err, "Error scheduling task")
	require.Equal(t, 1, len(ctxs), "Only one task should have started")

	err = schedulerDriver.WaitForRunning(ctxs[0], defaultWaitTimeout, defaultWaitInterval)
	require.NoError(t, err, "Error waiting for pod to get to running state")

	scheduledNodes, err := schedulerDriver.GetNodesForApp(ctxs[0])
	require.NoError(t, err, "Error getting node for app")
	require.Equal(t, 1, len(scheduledNodes), "App should be scheduled on one node")

	volumeNames := getVolumeNames(t, ctxs[0])
	require.Equal(t, 1, len(volumeNames), "Should have only one volume")

	verifyScheduledNode(t, scheduledNodes[0], volumeNames)

	for _, spec := range ctxs[0].App.SpecList {
		if obj, ok := spec.(*storage_api.StorageClass); ok {
			err := k8s.Instance().DeleteStorageClass(obj.Name)
			require.NoError(t, err, "Error deleting storage class for mysql.")
		}
		if obj, ok := spec.(*v1.PersistentVolumeClaim); ok {
			updatePVC, err := k8s.Instance().GetPersistentVolumeClaim(obj.Name, obj.Namespace)
			require.NoError(t, err, "Error getting persistent volume claim.")
			if _, hasKey := updatePVC.Annotations[annotationStorageProvisioner]; hasKey {
				delete(updatePVC.Annotations, "storage-provisioner")
			}
			_, err = k8s.Instance().UpdatePersistentVolumeClaim(updatePVC)
			require.NoError(t, err, "Error updating annotations in PVC.")
		}
	}

	err = volumeDriver.StopDriver(scheduledNodes, false)
	require.NoError(t, err, "Error stopping driver on scheduled Node %+v", scheduledNodes[0])

	time.Sleep(3 * time.Minute)

	var errUnscheduledPod bool
	for _, spec := range ctxs[0].App.SpecList {
		if obj, ok := spec.(*apps_api.Deployment); ok {
			if obj.Name == "mysql" {
				depPods, err := k8s.Instance().GetDeploymentPods(obj)
				require.NoError(t, err, "Error getting pods for deployment ,mysql.")
				for _, pod := range depPods {
					for _, cond := range pod.Status.Conditions {
						if cond.Type == v1.PodScheduled && cond.Status == v1.ConditionFalse {
							errUnscheduledPod = true
						}
					}
				}
			}
		}
	}
	require.Equal(t, true, errUnscheduledPod, "Pod should not have been schedule.")

	err = volumeDriver.StartDriver(scheduledNodes[0])
	require.NoError(t, err, "Error starting driver on scheduled Node %+v", scheduledNodes[0])

	err = volumeDriver.WaitDriverUpOnNode(scheduledNodes[0])
	require.NoError(t, err, "Volume driver is not up on Node %+v", scheduledNodes[0])

	destroyAndWait(t, ctxs)
}
<|endoftext|>