<commit_before>// 文件数据记录类
// create by gloomy 2017-04-06 10:11:35
package common

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"
)

const maxFileDataRecordingBytes = 1000000 // 默认文件大小

// 文件数据记录对象
// create by gloomy 2017-04-06 10:15:00
type FileDataRecording struct {
	sync.Mutex                         // 锁
	F                         *os.File // 文件对象
	FilePre                   string   // 文件开头字符串
	Fn                        string   // 文件路径
	Bytes                     int      // 文件大小
	Seq                       int      // 第几个
	FileProgram               string   // 文件存放路径
	MaxFileDataRecordingBytes int      // 文件大小
}

// 打开文件数据记录
// create by gloomy 2017-04-06 10:17:38
// 文件存放目录地址 文件开头字符串 文件大小
// 文件数据对象
func OpenLoadFile(fileProgram, filePre string, maxSize int) *FileDataRecording {
	if maxSize == 0 {
		maxSize = maxFileDataRecordingBytes
	}
	lf := &FileDataRecording{
		FilePre:                   filePre,
		FileProgram:               fileProgram,
		MaxFileDataRecordingBytes: maxSize,
	}
	lf.Rotate()
	return lf
}

// 文件退出
// create by gloomy 2017-04-06 10:27:58
func (f *FileDataRecording) Exit() {
	f.Lock()
	f.Close()
	f.F = nil
	f.Unlock()
}

// 文件关闭
// create by gloomy 2017-04-06 10:22:14
func (f *FileDataRecording) Close() {
	if f.F != nil {
		f.F.Close()
		os.Rename(f.Fn, f.Fn[0:len(f.Fn)-4]) //去掉末尾的.tmp
	}
}

// 文件切换
// create by gloomy 2017-04-06 10:30:05
func (f *FileDataRecording) Rotate() {
	f.Lock()
	f.Seq = 0
	f.Close()
	f.CreateNewFile()
	f.Unlock()
}

// 创建新文件
// create by gloomy 2017-04-06 10:33:11
// 错误对象
func (f *FileDataRecording) CreateNewFile() (err error) {
	f.Bytes = 0
	if !strings.HasSuffix(f.FileProgram, "/") {
		f.FileProgram += "/"
	}
	f.Fn = fmt.Sprintf("%s%s-%d-%d.tmp", f.FileProgram, f.FilePre, time.Now().UnixNano(), f.Seq)
	f.Seq++
	f.F, err = os.OpenFile(f.Fn, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("create file %s failed: %s \n", f.Fn, err.Error())
	}
	return
}

// 写入数据
// create by gloomy 2017-04-06 11:40:55
// 需要写入的数据
// 错误对象
func (f *FileDataRecording) WriteData(dataStr string) (err error) {
	f.Lock()
	defer f.Unlock()
	if f.F == nil {
		err = f.CreateNewFile()
		if err != nil {
			return
		}
	}
	dataStrLen := len(dataStr)
	if f.Bytes+dataStrLen > f.MaxFileDataRecordingBytes {
		f.Close()
		if err = f.CreateNewFile(); err != nil {
			return
		}
	}
	f.Bytes += dataStrLen
	_, err = f.F.WriteString(dataStr)
	return
}

// 获取所有完成的文件列表
// create by gloomy 2017-04-06 13:46:51
// 文件列表
func (f *FileDataRecording) FileList() *[]string {
	var (
		fileArray []string
	)
	filepath.Walk(f.FileProgram, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return nil
		}
		if ext := filepath.Ext(path); ext == ".tmp" {
			return nil
		}
		if info.Size() == 0 {
			os.Remove(path)
			return nil
		}
		if strings.HasPrefix(path, f.FilePre) {
			fileArray = append(fileArray, path)
		}
		return nil
	})
	return &fileArray
}

// 删除过期文件
// create by gloomy 2017-04-06 22:53:17
// 几天前
func (f *FileDataRecording) FileListRemoveOld(days int) {
	var (
		timeDate   = time.Now().AddDate(0, 0, days).UnixNano()
		unixNumber int
		fileName   []string
	)
	filepath.Walk(f.FileProgram, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return nil
		}
		if ext := filepath.Ext(path); ext == ".tmp" {
			return nil
		}
		if info.Size() == 0 {
			os.Remove(path)
			return nil
		}
		if strings.HasPrefix(path, f.FilePre) {
			fileName = strings.Split(path, "-")
			if len(fileName) == 3 {
				unixNumber, err = strconv.Atoi(fileName[1])
				if err != nil {
					fmt.Printf("FileListRemoveOld fileProgram: %s path: %s err: %s \n", f.FileProgram, path, err.Error())
					return nil
				}
				if timeDate >= int64(unixNumber) {
					os.Remove(path)
				}
			}
		}
		return nil
	})
}
<commit_msg>修正方法名<commit_after>// 文件数据记录类
// create by gloomy 2017-04-06 10:11:35
package common

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"
)

const maxFileDataRecordingBytes = 1000000 // 默认文件大小

// 文件数据记录对象
// create by gloomy 2017-04-06 10:15:00
type FileDataRecording struct {
	sync.Mutex                         // 锁
	F                         *os.File // 文件对象
	FilePre                   string   // 文件开头字符串
	Fn                        string   // 文件路径
	Bytes                     int      // 文件大小
	Seq                       int      // 第几个
	FileProgram               string   // 文件存放路径
	MaxFileDataRecordingBytes int      // 文件大小
}

// 打开文件数据记录
// create by gloomy 2017-04-06 10:17:38
// 文件存放目录地址 文件开头字符串 文件大小
// 文件数据对象
func OpenLoadFile(fileProgram, filePre string, maxSize int) *FileDataRecording {
	if maxSize == 0 {
		maxSize = maxFileDataRecordingBytes
	}
	lf := &FileDataRecording{
		FilePre:                   filePre,
		FileProgram:               fileProgram,
		MaxFileDataRecordingBytes: maxSize,
	}
	lf.Rotate()
	return lf
}

// 文件退出
// create by gloomy 2017-04-06 10:27:58
func (f *FileDataRecording) Exit() {
	f.Lock()
	f.Close()
	f.F = nil
	f.Unlock()
}

// 文件关闭
// create by gloomy 2017-04-06 10:22:14
func (f *FileDataRecording) Close() {
	if f.F != nil {
		f.F.Close()
		os.Rename(f.Fn, f.Fn[0:len(f.Fn)-4]) //去掉末尾的.tmp
	}
}

// 文件切换
// create by gloomy 2017-04-06 10:30:05
func (f *FileDataRecording) Rotate() {
	f.Lock()
	f.Seq = 0
	f.Close()
	f.CreateNewFile()
	f.Unlock()
}

// 创建新文件
// create by gloomy 2017-04-06 10:33:11
// 错误对象
func (f *FileDataRecording) CreateNewFile() (err error) {
	f.Bytes = 0
	if !strings.HasSuffix(f.FileProgram, "/") {
		f.FileProgram += "/"
	}
	f.Fn = fmt.Sprintf("%s%s-%d-%d.tmp", f.FileProgram, f.FilePre, time.Now().UnixNano(), f.Seq)
	f.Seq++
	f.F, err = os.OpenFile(f.Fn, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("create file %s failed: %s \n", f.Fn, err.Error())
	}
	return
}

// 写入数据
// create by gloomy 2017-04-06 11:40:55
// 需要写入的数据
// 错误对象
func (f *FileDataRecording) WriteData(dataStr string) (err error) {
	f.Lock()
	defer f.Unlock()
	if f.F == nil {
		err = f.CreateNewFile()
		if err != nil {
			return
		}
	}
	dataStrLen := len(dataStr)
	if f.Bytes+dataStrLen > f.MaxFileDataRecordingBytes {
		f.Close()
		if err = f.CreateNewFile(); err != nil {
			return
		}
	}
	f.Bytes += dataStrLen
	_, err = f.F.WriteString(dataStr)
	return
}

// 获取所有完成的文件列表
// create by gloomy 2017-04-06 13:46:51
// 文件列表
func (f *FileDataRecording) FileList() *[]string {
	var (
		fileArray []string
	)
	filepath.Walk(f.FileProgram, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return nil
		}
		if ext := filepath.Ext(path); ext == ".tmp" {
			return nil
		}
		if info.Size() == 0 {
			os.Remove(path)
			return nil
		}
		if strings.HasPrefix(path, f.FilePre) {
			fileArray = append(fileArray, path)
		}
		return nil
	})
	return &fileArray
}

// 删除过期文件
// create by gloomy 2017-04-06 22:53:17
// 几天前
func (f *FileDataRecording) RemoveOldFileList(days int) {
	var (
		timeDate   = time.Now().AddDate(0, 0, days).UnixNano()
		unixNumber int
		fileName   []string
	)
	filepath.Walk(f.FileProgram, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return nil
		}
		if ext := filepath.Ext(path); ext == ".tmp" {
			return nil
		}
		if info.Size() == 0 {
			os.Remove(path)
			return nil
		}
		if strings.HasPrefix(path, f.FilePre) {
			fileName = strings.Split(path, "-")
			if len(fileName) == 3 {
				unixNumber, err = strconv.Atoi(fileName[1])
				if err != nil {
					fmt.Printf("FileListRemoveOld fileProgram: %s path: %s err: %s \n", f.FileProgram, path, err.Error())
					return nil
				}
				if timeDate >= int64(unixNumber) {
					os.Remove(path)
				}
			}
		}
		return nil
	})
}
<|endoftext|>


<commit_before>// Copyright (c) 2010 Go Fightclub Authors
// This source code is released under the terms of the
// MIT license. Please see the file LICENSE for license details.
//
// Mono filter
// Combine all channels into a single channel
// Uses the average of all channels

package mono

import "afp"

type MonoFilter struct {
	ctx *afp.Context
}

func (self *MonoFilter) GetType() int {
	return afp.PIPE_LINK
}

func (self *MonoFilter) Init(ctx *afp.Context, args []string) os.Error {
	self.ctx = ctx
	parser := flags.FlagParser(args)
	parser.Parse()
	if len(parser.Args()) {
		return os.NewError("mono link takes 0 arguments")
	}
	return nil
}

func (self *MonoFilter) Start() {
	header StreamHeader
	ctx := self.ctx
	header <-ctx.HeaderSource
	// Unpack header
	channels := header.Channels

	header.Channels = 1
	// TODO: Is this math correct? Is this guaranteed to be accurate?
	header.ContentLength = header.ContentLength / channels
	ctx.HeaderSink <-header

	if channels == 1 { // Already mono, don't manipulate
		for frame := ctx.Source {
			ctx.Sink <- frame
		}
	} else {
		for frame := ctx.Source {
			ctx.Sink(mergeChannels(frame, channels))
		}
	}
}

func mergeChannels(frame [][]float32, channels int) [][]float32 {
	var monoValue float32
	for sample, sampleValues := range frame {
		monoValue = 0.0
		for _, channelValue := range sampleValues {
			monoValue += channelValue / channels
		}

		newSample = make([]float32, 1)
		newSample[0] = monoValue
		frame[sample] = newSample
	}
	return frame
}
<commit_msg>Fixed misunderstandings of Go & a little sloppiness<commit_after>// Copyright (c) 2010 Go Fightclub Authors
// This source code is released under the terms of the
// MIT license. Please see the file LICENSE for license details.
//
// Mono filter
// Combine all channels into a single channel
// Uses the average of all channels

package mono

import (
	"afp"
	"os"
)

type MonoFilter struct {
	ctx *afp.Context
}

func (self *MonoFilter) GetType() int {
	return afp.PIPE_LINK
}

func (self *MonoFilter) Init(ctx *afp.Context, args []string) os.Error {
	self.ctx = ctx
	if len(os.Args) != 1 {
		return os.NewError("mono link takes 0 arguments")
	}
	return nil
}

func (self *MonoFilter) Start() {
	var header afp.StreamHeader
	ctx := self.ctx
	header = <-ctx.HeaderSource
	// Unpack header
	channels := header.Channels

	header.Channels = 1
	// TODO: Is this math correct? Is this guaranteed to be accurate?
	header.ContentLength = header.ContentLength / int64(channels)
	ctx.HeaderSink <-header

	if channels == 1 { // Already mono, don't manipulate
		for frame := range ctx.Source {
			ctx.Sink <- frame
		}
	} else {
		for frame := range ctx.Source {
			ctx.Sink <- mergeChannels(frame, channels)
		}
	}
}

func mergeChannels(frame [][]float32, channels int8) [][]float32 {
	var monoValue float32
	for sample, sampleValues := range frame {
		monoValue = 0.0
		for _, channelValue := range sampleValues {
			monoValue += channelValue / float32(channels)
		}

		frame[sample] = []float32{monoValue}
	}
	return frame
}
<|endoftext|>


<commit_before>package pty

import (
	"errors"
	"os"
	"syscall"
	"unsafe"
)

func open() (pty, tty *os.File, err error) {
	pFD, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC, 0)
	if err != nil {
		return nil, nil, err
	}
	p := os.NewFile(uintptr(pFD), "/dev/ptmx")
	// In case of error after this point, make sure we close the ptmx fd.
	defer func() {
		if err != nil {
			_ = p.Close() // Best effort.
		}
	}()

	sname, err := ptsname(p)
	if err != nil {
		return nil, nil, err
	}

	if err := grantpt(p); err != nil {
		return nil, nil, err
	}

	if err := unlockpt(p); err != nil {
		return nil, nil, err
	}

	t, err := os.OpenFile(sname, os.O_RDWR, 0)
	if err != nil {
		return nil, nil, err
	}
	return p, t, nil
}

func ptsname(f *os.File) (string, error) {
	n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME))

	err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0])))
	if err != nil {
		return "", err
	}

	for i, c := range n {
		if c == 0 {
			return string(n[:i]), nil
		}
	}
	return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
}

func grantpt(f *os.File) error {
	return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0)
}

func unlockpt(f *os.File) error {
	return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0)
}
<commit_msg>Add O_NOCTTY for darwin when opening slave (#93)<commit_after>package pty

import (
	"errors"
	"os"
	"syscall"
	"unsafe"
)

func open() (pty, tty *os.File, err error) {
	pFD, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC, 0)
	if err != nil {
		return nil, nil, err
	}
	p := os.NewFile(uintptr(pFD), "/dev/ptmx")
	// In case of error after this point, make sure we close the ptmx fd.
	defer func() {
		if err != nil {
			_ = p.Close() // Best effort.
		}
	}()

	sname, err := ptsname(p)
	if err != nil {
		return nil, nil, err
	}

	if err := grantpt(p); err != nil {
		return nil, nil, err
	}

	if err := unlockpt(p); err != nil {
		return nil, nil, err
	}

	t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0)
	if err != nil {
		return nil, nil, err
	}
	return p, t, nil
}

func ptsname(f *os.File) (string, error) {
	n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME))

	err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0])))
	if err != nil {
		return "", err
	}

	for i, c := range n {
		if c == 0 {
			return string(n[:i]), nil
		}
	}
	return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
}

func grantpt(f *os.File) error {
	return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0)
}

func unlockpt(f *os.File) error {
	return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0)
}
<|endoftext|>


<commit_before>// Copyright 2019 The Berglas 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.

package berglas

import (
	"context"
	"net/http"
	"strings"

	"cloud.google.com/go/iam"
	"github.com/pkg/errors"
	storagev1 "google.golang.org/api/storage/v1"
	iampb "google.golang.org/genproto/googleapis/iam/v1"
)

const (
	iamObjectReader = "roles/storage.legacyObjectReader"
	iamKMSDecrypt   = "roles/cloudkms.cryptoKeyDecrypter"
)

// storageIAM returns an IAM storage handle to the given object since one does
// not exist in the storage libray.
func (c *Client) storageIAM(bucket, object string) (*iam.Handle, error) {
	return iam.InternalNewHandleClient(&iamClient{
		raw: c.storageIAMClient,
	}, bucket+"/"+object), nil
}

// iamClient implements the iam.client interface.
type iamClient struct {
	raw *storagev1.Service
}

func (c *iamClient) Get(ctx context.Context, resource string) (*iampb.Policy, error) {
	bucket, object, err := parseBucketObj(resource)
	if err != nil {
		return nil, err
	}

	call := c.raw.Objects.GetIamPolicy(bucket, object)
	setClientHeader(call.Header())

	rp, err := call.Context(ctx).Do()
	if err != nil {
		return nil, err
	}
	return iamFromStoragePolicy(rp), nil
}

func (c *iamClient) Set(ctx context.Context, resource string, p *iampb.Policy) error {
	bucket, object, err := parseBucketObj(resource)
	if err != nil {
		return err
	}

	rp := iamToStoragePolicy(p)
	call := c.raw.Objects.SetIamPolicy(bucket, object, rp)
	setClientHeader(call.Header())

	if _, err := call.Context(ctx).Do(); err != nil {
		return err
	}
	return nil
}

func (c *iamClient) Test(ctx context.Context, resource string, perms []string) ([]string, error) {
	bucket, object, err := parseBucketObj(resource)
	if err != nil {
		return nil, err
	}

	call := c.raw.Objects.TestIamPermissions(bucket, object, perms)
	setClientHeader(call.Header())

	res, err := call.Context(ctx).Do()
	if err != nil {
		return nil, err
	}
	return res.Permissions, nil
}

// parseBucketObj parses a bucket, object tuple
func parseBucketObj(s string) (string, string, error) {
	s = strings.TrimPrefix(s, "gs://")

	ss := strings.SplitN(s, "/", 2)
	if len(ss) < 2 {
		return "", "", errors.Errorf("does not match bucket/object format: %s", s)
	}

	return ss[0], ss[1], nil
}

func iamToStoragePolicy(ip *iampb.Policy) *storagev1.Policy {
	return &storagev1.Policy{
		Bindings: iamToStorageBindings(ip.Bindings),
		Etag:     string(ip.Etag),
	}
}

func iamToStorageBindings(ibs []*iampb.Binding) []*storagev1.PolicyBindings {
	var rbs []*storagev1.PolicyBindings
	for _, ib := range ibs {
		rbs = append(rbs, &storagev1.PolicyBindings{
			Role:    ib.Role,
			Members: ib.Members,
		})
	}
	return rbs
}

func iamFromStoragePolicy(rp *storagev1.Policy) *iampb.Policy {
	return &iampb.Policy{
		Bindings: iamFromStorageBindings(rp.Bindings),
		Etag:     []byte(rp.Etag),
	}
}

func iamFromStorageBindings(rbs []*storagev1.PolicyBindings) []*iampb.Binding {
	var ibs []*iampb.Binding
	for _, rb := range rbs {
		ibs = append(ibs, &iampb.Binding{
			Role:    rb.Role,
			Members: rb.Members,
		})
	}
	return ibs
}

func setClientHeader(h http.Header) {
	h.Set("User-Agent", UserAgent)
}
<commit_msg>Add retryable IAM functions<commit_after>// Copyright 2019 The Berglas 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.

package berglas

import (
	"context"
	"net/http"
	"strings"
	"time"

	"cloud.google.com/go/iam"
	"github.com/GoogleCloudPlatform/berglas/pkg/retry"
	"github.com/pkg/errors"
	"google.golang.org/api/googleapi"
	storagev1 "google.golang.org/api/storage/v1"
	iampb "google.golang.org/genproto/googleapis/iam/v1"
)

const (
	iamObjectReader = "roles/storage.legacyObjectReader"
	iamKMSDecrypt   = "roles/cloudkms.cryptoKeyDecrypter"
)

// storageIAM returns an IAM storage handle to the given object since one does
// not exist in the storage libray.
func (c *Client) storageIAM(bucket, object string) (*iam.Handle, error) {
	return iam.InternalNewHandleClient(&iamClient{
		raw: c.storageIAMClient,
	}, bucket+"/"+object), nil
}

// iamClient implements the iam.client interface.
type iamClient struct {
	raw *storagev1.Service
}

func (c *iamClient) Get(ctx context.Context, resource string) (*iampb.Policy, error) {
	bucket, object, err := parseBucketObj(resource)
	if err != nil {
		return nil, err
	}

	call := c.raw.Objects.GetIamPolicy(bucket, object)
	setClientHeader(call.Header())

	rp, err := call.Context(ctx).Do()
	if err != nil {
		return nil, err
	}
	return iamFromStoragePolicy(rp), nil
}

func (c *iamClient) Set(ctx context.Context, resource string, p *iampb.Policy) error {
	bucket, object, err := parseBucketObj(resource)
	if err != nil {
		return err
	}

	rp := iamToStoragePolicy(p)
	call := c.raw.Objects.SetIamPolicy(bucket, object, rp)
	setClientHeader(call.Header())

	if _, err := call.Context(ctx).Do(); err != nil {
		return err
	}
	return nil
}

func (c *iamClient) Test(ctx context.Context, resource string, perms []string) ([]string, error) {
	bucket, object, err := parseBucketObj(resource)
	if err != nil {
		return nil, err
	}

	call := c.raw.Objects.TestIamPermissions(bucket, object, perms)
	setClientHeader(call.Header())

	res, err := call.Context(ctx).Do()
	if err != nil {
		return nil, err
	}
	return res.Permissions, nil
}

// parseBucketObj parses a bucket, object tuple
func parseBucketObj(s string) (string, string, error) {
	s = strings.TrimPrefix(s, "gs://")

	ss := strings.SplitN(s, "/", 2)
	if len(ss) < 2 {
		return "", "", errors.Errorf("does not match bucket/object format: %s", s)
	}

	return ss[0], ss[1], nil
}

func iamToStoragePolicy(ip *iampb.Policy) *storagev1.Policy {
	return &storagev1.Policy{
		Bindings: iamToStorageBindings(ip.Bindings),
		Etag:     string(ip.Etag),
	}
}

func iamToStorageBindings(ibs []*iampb.Binding) []*storagev1.PolicyBindings {
	var rbs []*storagev1.PolicyBindings
	for _, ib := range ibs {
		rbs = append(rbs, &storagev1.PolicyBindings{
			Role:    ib.Role,
			Members: ib.Members,
		})
	}
	return rbs
}

func iamFromStoragePolicy(rp *storagev1.Policy) *iampb.Policy {
	return &iampb.Policy{
		Bindings: iamFromStorageBindings(rp.Bindings),
		Etag:     []byte(rp.Etag),
	}
}

func iamFromStorageBindings(rbs []*storagev1.PolicyBindings) []*iampb.Binding {
	var ibs []*iampb.Binding
	for _, rb := range rbs {
		ibs = append(ibs, &iampb.Binding{
			Role:    rb.Role,
			Members: rb.Members,
		})
	}
	return ibs
}

func setClientHeader(h http.Header) {
	h.Set("User-Agent", UserAgent)
}

func getIAMPolicyWithRetries(ctx context.Context, h *iam.Handle) (*iam.Policy, error) {
	var policy *iam.Policy
	var err error

	if err := retry.RetryFib(ctx, 500*time.Millisecond, 5, func() error {
		policy, err = h.Policy(ctx)
		if err != nil {
			if terr, ok := errors.Cause(err).(*googleapi.Error); ok {
				switch {
				case terr.Code == 412:
					// IAM returns 412 while propagating
					return retry.RetryableError(terr)
				case terr.Code >= 400 && terr.Code <= 499:
					return terr
				}
			}
			return retry.RetryableError(err)
		}
		return nil
	}); err != nil {
		return nil, err
	}

	return policy, nil
}

func setIAMPolicyWithRetries(ctx context.Context, h *iam.Handle, newP *iam.Policy) error {
	if err := retry.RetryFib(ctx, 500*time.Millisecond, 5, func() error {
		// Get the current policy - this is required because IAM validates etags
		latestP, err := h.Policy(ctx)
		if err != nil {
			if terr, ok := errors.Cause(err).(*googleapi.Error); ok {
				switch {
				case terr.Code == 412:
					// IAM returns 412 while propagating
					return retry.RetryableError(terr)
				case terr.Code >= 400 && terr.Code <= 499:
					return terr
				}
			}
			return retry.RetryableError(err)
		}

		// Overwrite the existing policy with ours
		for _, r := range newP.Roles() {
			for _, m := range newP.Members(r) {
				latestP.Add(m, r)
			}
		}

		// Update the policy
		if err := h.SetPolicy(ctx, latestP); err != nil {
			if terr, ok := errors.Cause(err).(*googleapi.Error); ok {
				switch {
				case terr.Code == 412:
					// IAM returns 412 while propagating
					return retry.RetryableError(terr)
				case terr.Code >= 400 && terr.Code <= 499:
					return terr
				}
			}
			return retry.RetryableError(err)
		}
		return nil
	}); err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package envoy

import (
	"context"
	"fmt"
	envoyv2 "github.com/envoyproxy/go-control-plane/envoy/api/v2"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/listener"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
	accesslogv2 "github.com/envoyproxy/go-control-plane/envoy/config/filter/accesslog/v2"
	"github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2"
	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2"
	"github.com/envoyproxy/go-control-plane/pkg/cache"
	xds "github.com/envoyproxy/go-control-plane/pkg/server"
	"github.com/envoyproxy/go-control-plane/pkg/util"
	"github.com/gogo/protobuf/types"
	"github.com/google/uuid"
	log "github.com/sirupsen/logrus"
	"google.golang.org/grpc"
	kubev1 "k8s.io/api/core/v1"
	v1alpha12 "knative.dev/serving/pkg/apis/networking/v1alpha1"
	"kourier/pkg/kubernetes"
	"net"
	"net/http"
	"strconv"
	"time"
)

const (
	grpcMaxConcurrentStreams = 1000000
)

type EnvoyXdsServer struct {
	gatewayPort    uint
	managementPort uint
	kubeClient     kubernetes.KubernetesClient // TODO: let's try to remove this coupling later
	ctx            context.Context
	server         xds.Server
	snapshotCache  cache.SnapshotCache
}

// Hasher returns node ID as an ID
type Hasher struct {
}

func (h Hasher) ID(node *core.Node) string {
	if node == nil {
		return "unknown"
	}
	return node.Id
}

func NewEnvoyXdsServer(gatewayPort uint, managementPort uint, kubeClient kubernetes.KubernetesClient) EnvoyXdsServer {
	ctx := context.Background()
	snapshotCache := cache.NewSnapshotCache(true, Hasher{}, nil)
	srv := xds.NewServer(snapshotCache, nil)

	return EnvoyXdsServer{
		gatewayPort:    gatewayPort,
		managementPort: managementPort,
		kubeClient:     kubeClient,
		ctx:            ctx,
		server:         srv,
		snapshotCache:  snapshotCache,
	}
}

// RunManagementServer starts an xDS server at the given Port.
func (envoyXdsServer *EnvoyXdsServer) RunManagementServer() {
	port := envoyXdsServer.managementPort
	server := envoyXdsServer.server

	var grpcOptions []grpc.ServerOption
	grpcOptions = append(grpcOptions, grpc.MaxConcurrentStreams(grpcMaxConcurrentStreams))
	grpcServer := grpc.NewServer(grpcOptions...)
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		log.Error("Failed to listen")
	}

	// register services
	discovery.RegisterAggregatedDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterEndpointDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterClusterDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterRouteDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterListenerDiscoveryServiceServer(grpcServer, server)

	log.Printf("Starting Management Server on Port %d\n", port)
	go func() {
		if err = grpcServer.Serve(lis); err != nil {
			log.Errorf("%s", err)
		}
	}()
	<-envoyXdsServer.ctx.Done()
	grpcServer.GracefulStop()
}

// RunManagementGateway starts an HTTP gateway to an xDS server.
func (envoyXdsServer *EnvoyXdsServer) RunGateway() {
	port := envoyXdsServer.gatewayPort
	server := envoyXdsServer.server
	ctx := envoyXdsServer.ctx

	log.Printf("Starting HTTP/1.1 gateway on Port %d\n", port)
	httpServer := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: &xds.HTTPGateway{Server: server}}
	go func() {
		if err := httpServer.ListenAndServe(); err != nil {
			panic(err)
		}
	}()

	<-ctx.Done()
	if err := httpServer.Shutdown(ctx); err != nil {
		panic(err)
	}
}

func (envoyXdsServer *EnvoyXdsServer) SetSnapshotForClusterIngresses(nodeId string, Ingresses []v1alpha12.IngressAccessor) {
	var virtualHosts []*route.VirtualHost
	var routeCache []cache.Resource
	var clusterCache []cache.Resource

	for i, ingress := range Ingresses {

		routeName := getRouteName(ingress)
		routeNamespace := getRouteNamespace(ingress)

		log.WithFields(log.Fields{"name": routeName, "namespace": routeNamespace}).Info("Knative Ingress found")

		for _, rule := range ingress.GetSpec().Rules {

			var ruleRoute []*route.Route
			domains := rule.Hosts

			for _, httpPath := range rule.HTTP.Paths {

				path := "/"
				if httpPath.Path != "" {
					path = httpPath.Path
				}

				headersRev := httpPath.AppendHeaders

				var wrs []*route.WeightedCluster_ClusterWeight

				for _, split := range httpPath.Splits {

					headersSplit := split.AppendHeaders

					endpointList, err := envoyXdsServer.kubeClient.EndpointsForRevision(split.ServiceNamespace, split.ServiceName)

					if err != nil {
						log.Errorf("%s", err)
						break
					}

					service, err := envoyXdsServer.kubeClient.ServiceForRevision(split.ServiceNamespace, split.ServiceName)

					if err != nil {
						log.Errorf("%s", err)
						break
					}

					var targetPort int32
					http2 := false
					for _, port := range service.Spec.Ports {
						if port.Port == split.ServicePort.IntVal || port.Name == split.ServicePort.StrVal {
							targetPort = port.TargetPort.IntVal
							http2 = port.Name == "http2" || port.Name == "h2c"
						}
					}

					lbEndpoints := lbEndpointsForKubeEndpoints(endpointList, targetPort)

					connectTimeout := 5 * time.Second
					cluster := clusterForRevision(split.ServiceName, connectTimeout, lbEndpoints, http2, path)
					clusterCache = append(clusterCache, &cluster)

					weightedCluster := weightedCluster(split.ServiceName, uint32(split.Percent), path, headersSplit)

					wrs = append(wrs, &weightedCluster)

				}

				attempts := 0
				var perTryTimeout time.Duration
				if httpPath.Retries != nil {
					attempts = httpPath.Retries.Attempts

					if httpPath.Retries.PerTryTimeout != nil {
						perTryTimeout = httpPath.Retries.PerTryTimeout.Duration
					}
				}

				var routeTimeout time.Duration
				if httpPath.Timeout != nil {
					routeTimeout = httpPath.Timeout.Duration
				}

				r := createRouteForRevision(routeName, i, path, wrs, attempts, perTryTimeout, routeTimeout, headersRev)

				ruleRoute = append(ruleRoute, &r)
				routeCache = append(routeCache, &r)

			}

			virtualHost := route.VirtualHost{
				Name:    routeName,
				Domains: domains,
				Routes:  ruleRoute,
			}

			virtualHosts = append(virtualHosts, &virtualHost)
		}

	}

	manager := httpConnectionManager(virtualHosts)
	l := envoyListener(&manager)
	listenerCache := []cache.Resource{&l}

	snapshotVersion, errUUID := uuid.NewUUID()
	if errUUID != nil {
		log.Error(errUUID)
		return
	}

	snapshot := cache.NewSnapshot(snapshotVersion.String(), nil, clusterCache, routeCache, listenerCache)

	err := envoyXdsServer.snapshotCache.SetSnapshot(nodeId, snapshot)
	if err != nil {
		log.Error(err)
	}
}

func createRouteForRevision(routeName string, i int, path string, wrs []*route.WeightedCluster_ClusterWeight, attempts int, perTryTimeout time.Duration, timeout time.Duration, headersToAdd map[string]string) route.Route {

	// TODO: extract header generation
	var headers []*core.HeaderValueOption

	for k, v := range headersToAdd {

		header := core.HeaderValueOption{
			Header: &core.HeaderValue{
				Key:   k,
				Value: v,
			},
			Append: &types.BoolValue{
				Value: true,
			},
		}

		headers = append(headers, &header)

	}

	r := route.Route{
		Name: routeName + "_" + strconv.Itoa(i),
		Match: &route.RouteMatch{
			PathSpecifier: &route.RouteMatch_Prefix{
				Prefix: path,
			},
		},
		Action: &route.Route_Route{Route: &route.RouteAction{
			ClusterSpecifier: &route.RouteAction_WeightedClusters{
				WeightedClusters: &route.WeightedCluster{
					Clusters: wrs,
				},
			},
			Timeout:     &timeout,
			RetryPolicy: createRetryPolicy(attempts, perTryTimeout),
		}},
		RequestHeadersToAdd: headers,
	}

	return r
}

func createRetryPolicy(attempts int, perTryTimeout time.Duration) *route.RetryPolicy {
	if attempts > 0 {
		return &route.RetryPolicy{
			RetryOn: "5xx",
			NumRetries: &types.UInt32Value{
				Value: uint32(attempts),
			},
			PerTryTimeout: &perTryTimeout,
		}
	} else {
		return nil
	}
}

func getRouteNamespace(ingress v1alpha12.IngressAccessor) string {
	return ingress.GetLabels()["serving.knative.dev/routeNamespace"]
}

func getRouteName(ingress v1alpha12.IngressAccessor) string {
	return ingress.GetLabels()["serving.knative.dev/route"]
}

func lbEndpointsForKubeEndpoints(kubeEndpoints *kubev1.EndpointsList, targetPort int32) []*endpoint.LbEndpoint {
	var result []*endpoint.LbEndpoint

	for _, kubeEndpoint := range kubeEndpoints.Items {
		for _, subset := range kubeEndpoint.Subsets {

			for _, address := range subset.Addresses {

				serviceEndpoint := &core.Address{
					Address: &core.Address_SocketAddress{
						SocketAddress: &core.SocketAddress{
							Protocol: core.TCP,
							Address:  address.IP,
							PortSpecifier: &core.SocketAddress_PortValue{
								PortValue: uint32(targetPort),
							},
							Ipv4Compat: true,
						},
					},
				}

				lbEndpoint := endpoint.LbEndpoint{
					HostIdentifier: &endpoint.LbEndpoint_Endpoint{
						Endpoint: &endpoint.Endpoint{
							Address: serviceEndpoint,
						},
					},
				}
				result = append(result, &lbEndpoint)
			}
		}
	}

	return result
}

func clusterForRevision(revisionName string, connectTimeout time.Duration, lbEndpoints []*endpoint.LbEndpoint, http2 bool, path string) envoyv2.Cluster {

	cluster := envoyv2.Cluster{
		Name: revisionName + path,
		ClusterDiscoveryType: &envoyv2.Cluster_Type{
			Type: envoyv2.Cluster_STRICT_DNS,
		},
		ConnectTimeout: &connectTimeout,
		LoadAssignment: &envoyv2.ClusterLoadAssignment{
			ClusterName: revisionName + path,
			Endpoints: []*endpoint.LocalityLbEndpoints{
				{
					LbEndpoints: lbEndpoints,
				},
			},
		},
	}

	if http2 {
		cluster.Http2ProtocolOptions = &core.Http2ProtocolOptions{}
	}

	return cluster
}

func weightedCluster(revisionName string, trafficPerc uint32, path string, headersToAdd map[string]string) route.WeightedCluster_ClusterWeight {

	var headers []*core.HeaderValueOption

	for k, v := range headersToAdd {

		header := core.HeaderValueOption{
			Header: &core.HeaderValue{
				Key:   k,
				Value: v,
			},
			Append: &types.BoolValue{
				Value: true,
			},
		}

		headers = append(headers, &header)

	}

	return route.WeightedCluster_ClusterWeight{
		Name: revisionName + path,
		Weight: &types.UInt32Value{
			Value: trafficPerc,
		},
		RequestHeadersToAdd: headers,
	}
}

func httpConnectionManager(virtualHosts []*route.VirtualHost) v2.HttpConnectionManager {
	return v2.HttpConnectionManager{
		CodecType:  v2.AUTO,
		StatPrefix: "ingress_http",
		RouteSpecifier: &v2.HttpConnectionManager_RouteConfig{
			RouteConfig: &envoyv2.RouteConfiguration{
				Name:         "local_route",
				VirtualHosts: virtualHosts,
			},
		},
		HttpFilters: []*v2.HttpFilter{
			{
				Name: util.Router,
			},
		},

		AccessLog: accessLogs(),
	}
}

// Outputs to /dev/stdout using the default format
func accessLogs() []*accesslogv2.AccessLog {
	accessLogConfigFields := make(map[string]*types.Value)
	accessLogConfigFields["path"] = &types.Value{
		Kind: &types.Value_StringValue{
			StringValue: "/dev/stdout",
		},
	}

	return []*accesslogv2.AccessLog{
		{
			Name: "envoy.file_access_log",
			ConfigType: &accesslogv2.AccessLog_Config{
				Config: &types.Struct{Fields: accessLogConfigFields},
			},
		},
	}
}

func envoyListener(httpConnectionManager *v2.HttpConnectionManager) envoyv2.Listener {
	pbst, err := util.MessageToStruct(httpConnectionManager)
	if err != nil {
		panic(err)
	}

	return envoyv2.Listener{
		Name: "listener_0",
		Address: &core.Address{
			Address: &core.Address_SocketAddress{
				SocketAddress: &core.SocketAddress{
					Protocol: core.TCP,
					Address:  "0.0.0.0",
					PortSpecifier: &core.SocketAddress_PortValue{
						PortValue: uint32(8080),
					},
				},
			},
		},
		FilterChains: []*listener.FilterChain{{
			Filters: []*listener.Filter{{
				Name:       util.HTTPConnectionManager,
				ConfigType: &listener.Filter_Config{Config: pbst},
			}},
		}},
	}
}
<commit_msg>envoy: organize funcs to instantiate routes<commit_after>package envoy

import (
	"context"
	"fmt"
	envoyv2 "github.com/envoyproxy/go-control-plane/envoy/api/v2"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/listener"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
	accesslogv2 "github.com/envoyproxy/go-control-plane/envoy/config/filter/accesslog/v2"
	"github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2"
	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2"
	"github.com/envoyproxy/go-control-plane/pkg/cache"
	xds "github.com/envoyproxy/go-control-plane/pkg/server"
	"github.com/envoyproxy/go-control-plane/pkg/util"
	"github.com/gogo/protobuf/types"
	"github.com/google/uuid"
	log "github.com/sirupsen/logrus"
	"google.golang.org/grpc"
	kubev1 "k8s.io/api/core/v1"
	v1alpha12 "knative.dev/serving/pkg/apis/networking/v1alpha1"
	"kourier/pkg/kubernetes"
	"net"
	"net/http"
	"strconv"
	"time"
)

const (
	grpcMaxConcurrentStreams = 1000000
)

type EnvoyXdsServer struct {
	gatewayPort    uint
	managementPort uint
	kubeClient     kubernetes.KubernetesClient // TODO: let's try to remove this coupling later
	ctx            context.Context
	server         xds.Server
	snapshotCache  cache.SnapshotCache
}

// Hasher returns node ID as an ID
type Hasher struct {
}

func (h Hasher) ID(node *core.Node) string {
	if node == nil {
		return "unknown"
	}
	return node.Id
}

func NewEnvoyXdsServer(gatewayPort uint, managementPort uint, kubeClient kubernetes.KubernetesClient) EnvoyXdsServer {
	ctx := context.Background()
	snapshotCache := cache.NewSnapshotCache(true, Hasher{}, nil)
	srv := xds.NewServer(snapshotCache, nil)

	return EnvoyXdsServer{
		gatewayPort:    gatewayPort,
		managementPort: managementPort,
		kubeClient:     kubeClient,
		ctx:            ctx,
		server:         srv,
		snapshotCache:  snapshotCache,
	}
}

// RunManagementServer starts an xDS server at the given Port.
func (envoyXdsServer *EnvoyXdsServer) RunManagementServer() {
	port := envoyXdsServer.managementPort
	server := envoyXdsServer.server

	var grpcOptions []grpc.ServerOption
	grpcOptions = append(grpcOptions, grpc.MaxConcurrentStreams(grpcMaxConcurrentStreams))
	grpcServer := grpc.NewServer(grpcOptions...)
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		log.Error("Failed to listen")
	}

	// register services
	discovery.RegisterAggregatedDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterEndpointDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterClusterDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterRouteDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterListenerDiscoveryServiceServer(grpcServer, server)

	log.Printf("Starting Management Server on Port %d\n", port)
	go func() {
		if err = grpcServer.Serve(lis); err != nil {
			log.Errorf("%s", err)
		}
	}()
	<-envoyXdsServer.ctx.Done()
	grpcServer.GracefulStop()
}

// RunManagementGateway starts an HTTP gateway to an xDS server.
func (envoyXdsServer *EnvoyXdsServer) RunGateway() {
	port := envoyXdsServer.gatewayPort
	server := envoyXdsServer.server
	ctx := envoyXdsServer.ctx

	log.Printf("Starting HTTP/1.1 gateway on Port %d\n", port)
	httpServer := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: &xds.HTTPGateway{Server: server}}
	go func() {
		if err := httpServer.ListenAndServe(); err != nil {
			panic(err)
		}
	}()

	<-ctx.Done()
	if err := httpServer.Shutdown(ctx); err != nil {
		panic(err)
	}
}

func (envoyXdsServer *EnvoyXdsServer) SetSnapshotForClusterIngresses(nodeId string, Ingresses []v1alpha12.IngressAccessor) {
	var virtualHosts []*route.VirtualHost
	var routeCache []cache.Resource
	var clusterCache []cache.Resource

	for i, ingress := range Ingresses {

		routeName := getRouteName(ingress)
		routeNamespace := getRouteNamespace(ingress)

		log.WithFields(log.Fields{"name": routeName, "namespace": routeNamespace}).Info("Knative Ingress found")

		for _, rule := range ingress.GetSpec().Rules {

			var ruleRoute []*route.Route
			domains := rule.Hosts

			for _, httpPath := range rule.HTTP.Paths {

				path := "/"
				if httpPath.Path != "" {
					path = httpPath.Path
				}

				var wrs []*route.WeightedCluster_ClusterWeight

				for _, split := range httpPath.Splits {

					headersSplit := split.AppendHeaders

					endpointList, err := envoyXdsServer.kubeClient.EndpointsForRevision(split.ServiceNamespace, split.ServiceName)

					if err != nil {
						log.Errorf("%s", err)
						break
					}

					service, err := envoyXdsServer.kubeClient.ServiceForRevision(split.ServiceNamespace, split.ServiceName)

					if err != nil {
						log.Errorf("%s", err)
						break
					}

					var targetPort int32
					http2 := false
					for _, port := range service.Spec.Ports {
						if port.Port == split.ServicePort.IntVal || port.Name == split.ServicePort.StrVal {
							targetPort = port.TargetPort.IntVal
							http2 = port.Name == "http2" || port.Name == "h2c"
						}
					}

					lbEndpoints := lbEndpointsForKubeEndpoints(endpointList, targetPort)

					connectTimeout := 5 * time.Second
					cluster := clusterForRevision(split.ServiceName, connectTimeout, lbEndpoints, http2, path)
					clusterCache = append(clusterCache, &cluster)

					weightedCluster := weightedCluster(split.ServiceName, uint32(split.Percent), path, headersSplit)

					wrs = append(wrs, &weightedCluster)

				}

				r := createRouteForRevision(routeName, i, &httpPath, wrs)

				ruleRoute = append(ruleRoute, &r)
				routeCache = append(routeCache, &r)

			}

			virtualHost := route.VirtualHost{
				Name:    routeName,
				Domains: domains,
				Routes:  ruleRoute,
			}

			virtualHosts = append(virtualHosts, &virtualHost)
		}

	}

	manager := httpConnectionManager(virtualHosts)
	l := envoyListener(&manager)
	listenerCache := []cache.Resource{&l}

	snapshotVersion, errUUID := uuid.NewUUID()
	if errUUID != nil {
		log.Error(errUUID)
		return
	}

	snapshot := cache.NewSnapshot(snapshotVersion.String(), nil, clusterCache, routeCache, listenerCache)

	err := envoyXdsServer.snapshotCache.SetSnapshot(nodeId, snapshot)
	if err != nil {
		log.Error(err)
	}
}

func createRouteForRevision(routeName string, i int, httpPath *v1alpha12.HTTPIngressPath, wrs []*route.WeightedCluster_ClusterWeight) route.Route {
	path := "/"
	if httpPath.Path != "" {
		path = httpPath.Path
	}

	var routeTimeout time.Duration
	if httpPath.Timeout != nil {
		routeTimeout = httpPath.Timeout.Duration
	}

	r := route.Route{
		Name: routeName + "_" + strconv.Itoa(i),
		Match: &route.RouteMatch{
			PathSpecifier: &route.RouteMatch_Prefix{
				Prefix: path,
			},
		},
		Action: &route.Route_Route{Route: &route.RouteAction{
			ClusterSpecifier: &route.RouteAction_WeightedClusters{
				WeightedClusters: &route.WeightedCluster{
					Clusters: wrs,
				},
			},
			Timeout:     &routeTimeout,
			RetryPolicy: createRetryPolicyForRoute(httpPath),
		}},
		RequestHeadersToAdd: headersToAdd(httpPath.AppendHeaders),
	}

	return r
}

func headersToAdd(headers map[string]string) []*core.HeaderValueOption {
	var res []*core.HeaderValueOption

	for headerName, headerVal := range headers {
		header := core.HeaderValueOption{
			Header: &core.HeaderValue{
				Key:   headerName,
				Value: headerVal,
			},
			Append: &types.BoolValue{
				Value: true,
			},
		}

		res = append(res, &header)

	}

	return res
}

func createRetryPolicyForRoute(httpPath *v1alpha12.HTTPIngressPath) *route.RetryPolicy {
	attempts := 0
	var perTryTimeout time.Duration
	if httpPath.Retries != nil {
		attempts = httpPath.Retries.Attempts

		if httpPath.Retries.PerTryTimeout != nil {
			perTryTimeout = httpPath.Retries.PerTryTimeout.Duration
		}
	}

	if attempts > 0 {
		return &route.RetryPolicy{
			RetryOn: "5xx",
			NumRetries: &types.UInt32Value{
				Value: uint32(attempts),
			},
			PerTryTimeout: &perTryTimeout,
		}
	} else {
		return nil
	}
}

func getRouteNamespace(ingress v1alpha12.IngressAccessor) string {
	return ingress.GetLabels()["serving.knative.dev/routeNamespace"]
}

func getRouteName(ingress v1alpha12.IngressAccessor) string {
	return ingress.GetLabels()["serving.knative.dev/route"]
}

func lbEndpointsForKubeEndpoints(kubeEndpoints *kubev1.EndpointsList, targetPort int32) []*endpoint.LbEndpoint {
	var result []*endpoint.LbEndpoint

	for _, kubeEndpoint := range kubeEndpoints.Items {
		for _, subset := range kubeEndpoint.Subsets {

			for _, address := range subset.Addresses {

				serviceEndpoint := &core.Address{
					Address: &core.Address_SocketAddress{
						SocketAddress: &core.SocketAddress{
							Protocol: core.TCP,
							Address:  address.IP,
							PortSpecifier: &core.SocketAddress_PortValue{
								PortValue: uint32(targetPort),
							},
							Ipv4Compat: true,
						},
					},
				}

				lbEndpoint := endpoint.LbEndpoint{
					HostIdentifier: &endpoint.LbEndpoint_Endpoint{
						Endpoint: &endpoint.Endpoint{
							Address: serviceEndpoint,
						},
					},
				}
				result = append(result, &lbEndpoint)
			}
		}
	}

	return result
}

func clusterForRevision(revisionName string, connectTimeout time.Duration, lbEndpoints []*endpoint.LbEndpoint, http2 bool, path string) envoyv2.Cluster {

	cluster := envoyv2.Cluster{
		Name: revisionName + path,
		ClusterDiscoveryType: &envoyv2.Cluster_Type{
			Type: envoyv2.Cluster_STRICT_DNS,
		},
		ConnectTimeout: &connectTimeout,
		LoadAssignment: &envoyv2.ClusterLoadAssignment{
			ClusterName: revisionName + path,
			Endpoints: []*endpoint.LocalityLbEndpoints{
				{
					LbEndpoints: lbEndpoints,
				},
			},
		},
	}

	if http2 {
		cluster.Http2ProtocolOptions = &core.Http2ProtocolOptions{}
	}

	return cluster
}

func weightedCluster(revisionName string, trafficPerc uint32, path string, headers map[string]string) route.WeightedCluster_ClusterWeight {
	return route.WeightedCluster_ClusterWeight{
		Name: revisionName + path,
		Weight: &types.UInt32Value{
			Value: trafficPerc,
		},
		RequestHeadersToAdd: headersToAdd(headers),
	}
}

func httpConnectionManager(virtualHosts []*route.VirtualHost) v2.HttpConnectionManager {
	return v2.HttpConnectionManager{
		CodecType:  v2.AUTO,
		StatPrefix: "ingress_http",
		RouteSpecifier: &v2.HttpConnectionManager_RouteConfig{
			RouteConfig: &envoyv2.RouteConfiguration{
				Name:         "local_route",
				VirtualHosts: virtualHosts,
			},
		},
		HttpFilters: []*v2.HttpFilter{
			{
				Name: util.Router,
			},
		},

		AccessLog: accessLogs(),
	}
}

// Outputs to /dev/stdout using the default format
func accessLogs() []*accesslogv2.AccessLog {
	accessLogConfigFields := make(map[string]*types.Value)
	accessLogConfigFields["path"] = &types.Value{
		Kind: &types.Value_StringValue{
			StringValue: "/dev/stdout",
		},
	}

	return []*accesslogv2.AccessLog{
		{
			Name: "envoy.file_access_log",
			ConfigType: &accesslogv2.AccessLog_Config{
				Config: &types.Struct{Fields: accessLogConfigFields},
			},
		},
	}
}

func envoyListener(httpConnectionManager *v2.HttpConnectionManager) envoyv2.Listener {
	pbst, err := util.MessageToStruct(httpConnectionManager)
	if err != nil {
		panic(err)
	}

	return envoyv2.Listener{
		Name: "listener_0",
		Address: &core.Address{
			Address: &core.Address_SocketAddress{
				SocketAddress: &core.SocketAddress{
					Protocol: core.TCP,
					Address:  "0.0.0.0",
					PortSpecifier: &core.SocketAddress_PortValue{
						PortValue: uint32(8080),
					},
				},
			},
		},
		FilterChains: []*listener.FilterChain{{
			Filters: []*listener.Filter{{
				Name:       util.HTTPConnectionManager,
				ConfigType: &listener.Filter_Config{Config: pbst},
			}},
		}},
	}
}
<|endoftext|>


<commit_before>/*
 * Bitcoin RPC return types.
 *
 * (c) 2011-2013 Bernd Fix   >Y<
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package rpc

///////////////////////////////////////////////////////////////////////
// public types

//---------------------------------------------------------------------
/*
 * Generic infomation about running Bitcoin server.
 */
type Info struct {
	Version         int
	ProtocolVersion int
	WalletVersion   int
	Proxy           string
	TestNet         bool
	Connections     int
	KeyPoolSize     int
	TimeOffset      int
	KeyPoolOldest   int
	Balance         float64
	Errors          string
	PayTxFee        float64
	Difficulty      float64
	Blocks          int
}

//---------------------------------------------------------------------
/*
 * Block (element of the Bitcoin blockchain)
 */
type Block struct {
	TxIDs             []string
	Time              int
	Height            int
	Nonce             int
	Confirmations     int
	Hash              string
	PreviousBlockHash string
	NextBlockHash     string
	Bits              string
	Difficulty        int
	MerkleRoot        string
	Version           int
	Size              int
}

//---------------------------------------------------------------------
/*
 * Bitcoin transaction
 */
type Transaction struct {
	Amount        float64
	Fee           float64
	BlockIndex    int
	Confirmations int
	TxID          string
	BlockHash     string
	Time int
	BlockTime int
	TimeReceived int
}

//---------------------------------------------------------------------
/*
 * Unspent transactions for accounts
 */
type Unspent struct {
	Id            string
	Vout          int
	ScriptPubkey  string
	Amount        float64
	Confirmations int
}

//---------------------------------------------------------------------
/*
 * Received transactions for account/address (accumulated)
 */
type Received struct {
	Account       string
	Label         string
	Address       string
	Amount        float64
	Confirmations int
}

//---------------------------------------------------------------------
/*
 * Validity check on address
 */
type Validity struct {
	Address      string
	IsCompressed bool
	Account      string
	PubKey       string
	IsMine       bool
	IsValid      bool
}

//---------------------------------------------------------------------
/*
 * Balance of Bitcoin address (used for outgoing transactions as well)
 */
type Balance struct {
	Address string
	Amount float64
}
<commit_msg>Output type added. Transaction IDs named consistently as 'Id'.<commit_after>/*
 * Bitcoin RPC return types.
 *
 * (c) 2011-2013 Bernd Fix   >Y<
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package rpc

///////////////////////////////////////////////////////////////////////
// public types

//---------------------------------------------------------------------
/*
 * Generic infomation about running Bitcoin server.
 */
type Info struct {
	Version         int
	ProtocolVersion int
	WalletVersion   int
	Proxy           string
	TestNet         bool
	Connections     int
	KeyPoolSize     int
	TimeOffset      int
	KeyPoolOldest   int
	Balance         float64
	Errors          string
	PayTxFee        float64
	Difficulty      float64
	Blocks          int
}

//---------------------------------------------------------------------
/*
 * Block (element of the Bitcoin blockchain)
 */
type Block struct {
	IdList            []string
	Time              int
	Height            int
	Nonce             int
	Confirmations     int
	Hash              string
	PreviousBlockHash string
	NextBlockHash     string
	Bits              string
	Difficulty        int
	MerkleRoot        string
	Version           int
	Size              int
}

//---------------------------------------------------------------------
/*
 * Bitcoin transaction
 */
type Transaction struct {
	Amount        float64
	Fee           float64
	BlockIndex    int
	Confirmations int
	Id            string
	BlockHash     string
	Time          int
	BlockTime     int
	TimeReceived  int
}

//---------------------------------------------------------------------
/*
 * Transaction output
 */
type Output struct {
	Id   string
	Vout int
}

//---------------------------------------------------------------------
/*
 * Unspent transactions for accounts
 */
type Unspent struct {
	Output
	ScriptPubkey  string
	Amount        float64
	Confirmations int
}

//---------------------------------------------------------------------
/*
 * Received transactions for account/address (accumulated)
 */
type Received struct {
	Account       string
	Label         string
	Address       string
	Amount        float64
	Confirmations int
}

//---------------------------------------------------------------------
/*
 * Validity check on address
 */
type Validity struct {
	Address      string
	IsCompressed bool
	Account      string
	PubKey       string
	IsMine       bool
	IsValid      bool
}

//---------------------------------------------------------------------
/*
 * Balance of Bitcoin address (used for outgoing transactions as well)
 */
type Balance struct {
	Address string
	Amount  float64
}
<|endoftext|>


<commit_before>package irc

import (
	"errors"
	"strings"
)

type Mask struct {
	Nick     string
	Username string
	Hostname string
	Mask     string
}
type Message struct {
	Raw     string
	Tags    map[string]string
	Prefix  *Mask
	Command string
	Params  []string
}

// ToLine - Convert the Message struct to its raw IRC line
func (m *Message) ToLine() string {
	line := ""

	if len(m.Tags) > 0 {
		line += "@"

		for tagName, tagVal := range m.Tags {
			line += tagName
			if tagVal != "" {
				line += ":" + tagVal
			}
			line += ";"
		}
	}

	if m.Prefix != nil {
		prefix := ""

		if m.Prefix.Nick != "" {
			prefix += m.Prefix.Nick
		}

		if m.Prefix.Username != "" && m.Prefix.Nick != "" {
			prefix += "!" + m.Prefix.Username
		} else if m.Prefix.Username != "" {
			prefix += m.Prefix.Username
		}

		if m.Prefix.Hostname != "" && prefix != "" {
			prefix += "@" + m.Prefix.Username
		} else if m.Prefix.Hostname != "" {
			prefix += m.Prefix.Hostname
		}

		if line != "" {
			line += " :" + prefix
		} else {
			line += ":" + prefix
		}
	}

	if line != "" {
		line += " " + m.Command
	} else {
		line += m.Command
	}

	paramLen := len(m.Params)
	for idx, param := range m.Params {
		if idx == paramLen-1 && strings.Contains(param, " ") {
			line += " :" + param
		} else {
			line += " " + param
		}
	}

	return line
}

func createMask(maskStr string) *Mask {
	mask := &Mask{
		Mask: maskStr,
	}

	usernameStart := strings.Index(maskStr, "!")
	hostStart := strings.Index(maskStr, "@")

	if usernameStart == -1 && hostStart == -1 {
		mask.Nick = maskStr
	} else if usernameStart > -1 && hostStart > -1 {
		mask.Nick = maskStr[0:usernameStart]
		mask.Username = maskStr[usernameStart+1 : hostStart]
		mask.Hostname = maskStr[hostStart+1:]
	} else if usernameStart > -1 && hostStart == -1 {
		mask.Nick = maskStr[0:usernameStart]
		mask.Username = maskStr[usernameStart+1:]
	} else if usernameStart == -1 && hostStart > -1 {
		mask.Username = maskStr[0:hostStart]
		mask.Hostname = maskStr[hostStart+1:]
	}

	return mask
}

// ParseLine - Turn a raw IRC line into a message
func ParseLine(line string) (*Message, error) {
	message := &Message{
		Raw:  line,
		Tags: make(map[string]string),
	}

	token := ""
	rest := ""

	token, rest = nextToken(line, false)
	if token == "" {
		return nil, errors.New("Empty line")
	}

	// Tags. Starts with "@"
	if token[0] == 64 {
		tagsRaw := token[1:]
		tags := strings.Split(tagsRaw, ";")
		for _, tag := range tags {
			parts := strings.Split(tag, "=")
			if len(parts) == 1 {
				message.Tags[parts[0]] = ""
			} else {
				message.Tags[parts[0]] = parts[1]
			}
		}

		token, rest = nextToken(rest, false)
	}

	// Prefix. Starts with ":"
	if token != "" && token[0] == 58 {
		message.Prefix = createMask(token[1:])
		token, rest = nextToken(rest, false)
	}

	// Command
	if token == "" {
		return nil, errors.New("Missing command")
	}

	message.Command = token

	// Params
	for {
		token, rest = nextToken(rest, true)
		if token == "" {
			break
		}

		message.Params = append(message.Params, token)
	}

	return message, nil
}

func nextToken(s string, allowTrailing bool) (string, string) {
	s = strings.TrimLeft(s, " ")

	if len(s) == 0 {
		return "", ""
	}

	// The last token (trailing) start with :
	if allowTrailing && s[0] == 58 {
		return s[1:], ""
	}

	token := ""
	spaceIdx := strings.Index(s, " ")
	if spaceIdx > -1 {
		token = s[:spaceIdx]
		s = s[spaceIdx+1:]
	} else {
		token = s
		s = ""
	}

	return token, s
}
<commit_msg>Trimming newlines from IRC lines before parsing<commit_after>package irc

import (
	"errors"
	"strings"
)

type Mask struct {
	Nick     string
	Username string
	Hostname string
	Mask     string
}
type Message struct {
	Raw     string
	Tags    map[string]string
	Prefix  *Mask
	Command string
	Params  []string
}

// ToLine - Convert the Message struct to its raw IRC line
func (m *Message) ToLine() string {
	line := ""

	if len(m.Tags) > 0 {
		line += "@"

		for tagName, tagVal := range m.Tags {
			line += tagName
			if tagVal != "" {
				line += ":" + tagVal
			}
			line += ";"
		}
	}

	if m.Prefix != nil {
		prefix := ""

		if m.Prefix.Nick != "" {
			prefix += m.Prefix.Nick
		}

		if m.Prefix.Username != "" && m.Prefix.Nick != "" {
			prefix += "!" + m.Prefix.Username
		} else if m.Prefix.Username != "" {
			prefix += m.Prefix.Username
		}

		if m.Prefix.Hostname != "" && prefix != "" {
			prefix += "@" + m.Prefix.Username
		} else if m.Prefix.Hostname != "" {
			prefix += m.Prefix.Hostname
		}

		if line != "" {
			line += " :" + prefix
		} else {
			line += ":" + prefix
		}
	}

	if line != "" {
		line += " " + m.Command
	} else {
		line += m.Command
	}

	paramLen := len(m.Params)
	for idx, param := range m.Params {
		if idx == paramLen-1 && strings.Contains(param, " ") {
			line += " :" + param
		} else {
			line += " " + param
		}
	}

	return line
}

func createMask(maskStr string) *Mask {
	mask := &Mask{
		Mask: maskStr,
	}

	usernameStart := strings.Index(maskStr, "!")
	hostStart := strings.Index(maskStr, "@")

	if usernameStart == -1 && hostStart == -1 {
		mask.Nick = maskStr
	} else if usernameStart > -1 && hostStart > -1 {
		mask.Nick = maskStr[0:usernameStart]
		mask.Username = maskStr[usernameStart+1 : hostStart]
		mask.Hostname = maskStr[hostStart+1:]
	} else if usernameStart > -1 && hostStart == -1 {
		mask.Nick = maskStr[0:usernameStart]
		mask.Username = maskStr[usernameStart+1:]
	} else if usernameStart == -1 && hostStart > -1 {
		mask.Username = maskStr[0:hostStart]
		mask.Hostname = maskStr[hostStart+1:]
	}

	return mask
}

// ParseLine - Turn a raw IRC line into a message
func ParseLine(input string) (*Message, error) {
	line := strings.Trim(input, "\r\n")

	message := &Message{
		Raw:  line,
		Tags: make(map[string]string),
	}

	token := ""
	rest := ""

	token, rest = nextToken(line, false)
	if token == "" {
		return nil, errors.New("Empty line")
	}

	// Tags. Starts with "@"
	if token[0] == 64 {
		tagsRaw := token[1:]
		tags := strings.Split(tagsRaw, ";")
		for _, tag := range tags {
			parts := strings.Split(tag, "=")
			if len(parts) == 1 {
				message.Tags[parts[0]] = ""
			} else {
				message.Tags[parts[0]] = parts[1]
			}
		}

		token, rest = nextToken(rest, false)
	}

	// Prefix. Starts with ":"
	if token != "" && token[0] == 58 {
		message.Prefix = createMask(token[1:])
		token, rest = nextToken(rest, false)
	}

	// Command
	if token == "" {
		return nil, errors.New("Missing command")
	}

	message.Command = token

	// Params
	for {
		token, rest = nextToken(rest, true)
		if token == "" {
			break
		}

		message.Params = append(message.Params, token)
	}

	return message, nil
}

func nextToken(s string, allowTrailing bool) (string, string) {
	s = strings.TrimLeft(s, " ")

	if len(s) == 0 {
		return "", ""
	}

	// The last token (trailing) start with :
	if allowTrailing && s[0] == 58 {
		return s[1:], ""
	}

	token := ""
	spaceIdx := strings.Index(s, " ")
	if spaceIdx > -1 {
		token = s[:spaceIdx]
		s = s[spaceIdx+1:]
	} else {
		token = s
		s = ""
	}

	return token, s
}
<|endoftext|>


<commit_before>package mount

import (
	"sort"
	"strings"

	"github.com/sirupsen/logrus"
)

// GetMounts retrieves a list of mounts for the current running process.
func GetMounts() ([]*Info, error) {
	return parseMountTable()
}

// Mounted determines if a specified mountpoint has been mounted.
// On Linux it looks at /proc/self/mountinfo.
func Mounted(mountpoint string) (bool, error) {
	entries, err := parseMountTable()
	if err != nil {
		return false, err
	}

	// Search the table for the mountpoint
	for _, e := range entries {
		if e.Mountpoint == mountpoint {
			return true, nil
		}
	}
	return false, nil
}

// Mount will mount filesystem according to the specified configuration, on the
// condition that the target path is *not* already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func Mount(device, target, mType, options string) error {
	flag, _ := parseOptions(options)
	if flag&REMOUNT != REMOUNT {
		if mounted, err := Mounted(target); err != nil || mounted {
			return err
		}
	}
	return ForceMount(device, target, mType, options)
}

// ForceMount will mount a filesystem according to the specified configuration,
// *regardless* if the target path is not already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func ForceMount(device, target, mType, options string) error {
	flag, data := parseOptions(options)
	return mount(device, target, mType, uintptr(flag), data)
}

// Unmount lazily unmounts a filesystem on supported platforms, otherwise
// does a normal unmount.
func Unmount(target string) error {
	if mounted, err := Mounted(target); err != nil || !mounted {
		return err
	}
	return unmount(target, mntDetach)
}

// RecursiveUnmount unmounts the target and all mounts underneath, starting with
// the deepsest mount first.
func RecursiveUnmount(target string) error {
	mounts, err := GetMounts()
	if err != nil {
		return err
	}

	// Make the deepest mount be first
	sort.Sort(sort.Reverse(byMountpoint(mounts)))

	for i, m := range mounts {
		if !strings.HasPrefix(m.Mountpoint, target) {
			continue
		}
		logrus.Debugf("Trying to unmount %s", m.Mountpoint)
		err = Unmount(m.Mountpoint)
		if err != nil && i == len(mounts)-1 {
			if mounted, err := Mounted(m.Mountpoint); err != nil || mounted {
				return err
			}
			// Ignore errors for submounts and continue trying to unmount others
			// The final unmount should fail if there ane any submounts remaining
		} else if err != nil {
			logrus.Errorf("Failed to unmount %s: %v", m.Mountpoint, err)
		} else if err == nil {
			logrus.Debugf("Unmounted %s", m.Mountpoint)
		}
	}
	return nil
}
<commit_msg>Optimizations for recurrsive unmount<commit_after>package mount

import (
	"sort"
	"strings"

	"syscall"

	"github.com/sirupsen/logrus"
)

// GetMounts retrieves a list of mounts for the current running process.
func GetMounts() ([]*Info, error) {
	return parseMountTable()
}

// Mounted determines if a specified mountpoint has been mounted.
// On Linux it looks at /proc/self/mountinfo.
func Mounted(mountpoint string) (bool, error) {
	entries, err := parseMountTable()
	if err != nil {
		return false, err
	}

	// Search the table for the mountpoint
	for _, e := range entries {
		if e.Mountpoint == mountpoint {
			return true, nil
		}
	}
	return false, nil
}

// Mount will mount filesystem according to the specified configuration, on the
// condition that the target path is *not* already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func Mount(device, target, mType, options string) error {
	flag, _ := parseOptions(options)
	if flag&REMOUNT != REMOUNT {
		if mounted, err := Mounted(target); err != nil || mounted {
			return err
		}
	}
	return ForceMount(device, target, mType, options)
}

// ForceMount will mount a filesystem according to the specified configuration,
// *regardless* if the target path is not already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func ForceMount(device, target, mType, options string) error {
	flag, data := parseOptions(options)
	return mount(device, target, mType, uintptr(flag), data)
}

// Unmount lazily unmounts a filesystem on supported platforms, otherwise
// does a normal unmount.
func Unmount(target string) error {
	if mounted, err := Mounted(target); err != nil || !mounted {
		return err
	}
	return unmount(target, mntDetach)
}

// RecursiveUnmount unmounts the target and all mounts underneath, starting with
// the deepsest mount first.
func RecursiveUnmount(target string) error {
	mounts, err := GetMounts()
	if err != nil {
		return err
	}

	// Make the deepest mount be first
	sort.Sort(sort.Reverse(byMountpoint(mounts)))

	for i, m := range mounts {
		if !strings.HasPrefix(m.Mountpoint, target) {
			continue
		}
		logrus.Debugf("Trying to unmount %s", m.Mountpoint)
		err = unmount(m.Mountpoint, mntDetach)
		if err != nil {
			// If the error is EINVAL either this whole package is wrong (invalid flags passed to unmount(2)) or this is
			// not a mountpoint (which is ok in this case).
			// Meanwhile calling `Mounted()` is very expensive.
			//
			// We've purposefully used `syscall.EINVAL` here instead of `unix.EINVAL` to avoid platform branching
			// Since `EINVAL` is defined for both Windows and Linux in the `syscall` package (and other platforms),
			//   this is nicer than defining a custom value that we can refer to in each platform file.
			if err == syscall.EINVAL {
				continue
			}
			if i == len(mounts)-1 {
				if mounted, e := Mounted(m.Mountpoint); e != nil || mounted {
					return err
				}
				continue
			}
			// This is some submount, we can ignore this error for now, the final unmount will fail if this is a real problem
			logrus.WithError(err).Warnf("Failed to unmount submount %s", m.Mountpoint)
			continue
		}

		logrus.Debugf("Unmounted %s", m.Mountpoint)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2015 CoreOS, Inc.
//
// 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 plugin

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/appc/cni/pkg/ip"
	"github.com/vishvananda/netlink"
)

// Find returns the full path of the plugin by searching in CNI_PATH
func Find(plugin string) string {
	paths := strings.Split(os.Getenv("CNI_PATH"), ":")

	for _, p := range paths {
		fullname := filepath.Join(p, plugin)
		if fi, err := os.Stat(fullname); err == nil && fi.Mode().IsRegular() {
			return fullname
		}
	}

	return ""
}

func pluginErr(err error, output []byte) error {
	if _, ok := err.(*exec.ExitError); ok {
		emsg := Error{}
		if perr := json.Unmarshal(output, &emsg); perr != nil {
			return fmt.Errorf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr)
		}
		details := ""
		if emsg.Details != "" {
			details = fmt.Sprintf("; %v", emsg.Details)
		}
		return fmt.Errorf("%v%v", emsg.Msg, details)
	}

	return err
}

// ExecAdd executes IPAM plugin, assuming CNI_COMMAND == ADD.
// Parses and returns resulting IPConfig
func ExecAdd(plugin string, netconf []byte) (*Result, error) {
	if os.Getenv("CNI_COMMAND") != "ADD" {
		return nil, fmt.Errorf("CNI_COMMAND is not ADD")
	}

	pluginPath := Find(plugin)
	if pluginPath == "" {
		return nil, fmt.Errorf("could not find %q plugin", plugin)
	}

	stdout := &bytes.Buffer{}

	c := exec.Cmd{
		Path:   pluginPath,
		Args:   []string{pluginPath},
		Stdin:  bytes.NewBuffer(netconf),
		Stdout: stdout,
		Stderr: os.Stderr,
	}
	if err := c.Run(); err != nil {
		return nil, pluginErr(err, stdout.Bytes())
	}

	res := &Result{}
	err := json.Unmarshal(stdout.Bytes(), res)
	return res, err
}

// ExecDel executes IPAM plugin, assuming CNI_COMMAND == DEL.
func ExecDel(plugin string, netconf []byte) error {
	if os.Getenv("CNI_COMMAND") != "DEL" {
		return fmt.Errorf("CNI_COMMAND is not DEL")
	}

	pluginPath := Find(plugin)
	if pluginPath == "" {
		return fmt.Errorf("could not find %q plugin", plugin)
	}

	stdout := &bytes.Buffer{}

	c := exec.Cmd{
		Path:   pluginPath,
		Args:   []string{pluginPath},
		Stdin:  bytes.NewBuffer(netconf),
		Stdout: stdout,
		Stderr: os.Stderr,
	}
	if err := c.Run(); err != nil {
		return pluginErr(err, stdout.Bytes())
	}
	return nil
}

// ConfigureIface takes the result of IPAM plugin and
// applies to the ifName interface
func ConfigureIface(ifName string, res *Result) error {
	link, err := netlink.LinkByName(ifName)
	if err != nil {
		return fmt.Errorf("failed to lookup %q: %v", ifName, err)
	}

	if err := netlink.LinkSetUp(link); err != nil {
		return fmt.Errorf("failed to set %q UP: %v", ifName, err)
	}

	// TODO(eyakubovich): IPv6
	addr := &netlink.Addr{IPNet: &res.IP4.IP, Label: ""}
	if err = netlink.AddrAdd(link, addr); err != nil {
		return fmt.Errorf("failed to add IP addr to %q: %v", ifName, err)
	}

	for _, r := range res.IP4.Routes {
		gw := r.GW
		if gw == nil {
			gw = res.IP4.Gateway
		}
		if err = ip.AddRoute(&r.Dst, gw, link); err != nil {
			// we skip over duplicate routes as we assume the first one wins
			if !os.IsExist(err) {
				return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, ifName, err)
			}
		}
	}

	return nil
}
<commit_msg>IPAM plugin: improve error messages<commit_after>// Copyright 2015 CoreOS, Inc.
//
// 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 plugin

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/appc/cni/pkg/ip"
	"github.com/vishvananda/netlink"
)

// Find returns the full path of the plugin by searching in CNI_PATH
func Find(plugin string) string {
	paths := strings.Split(os.Getenv("CNI_PATH"), ":")

	for _, p := range paths {
		fullname := filepath.Join(p, plugin)
		if fi, err := os.Stat(fullname); err == nil && fi.Mode().IsRegular() {
			return fullname
		}
	}

	return ""
}

func pluginErr(err error, output []byte) error {
	if _, ok := err.(*exec.ExitError); ok {
		emsg := Error{}
		if perr := json.Unmarshal(output, &emsg); perr != nil {
			return fmt.Errorf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr)
		}
		details := ""
		if emsg.Details != "" {
			details = fmt.Sprintf("; %v", emsg.Details)
		}
		return fmt.Errorf("%v%v", emsg.Msg, details)
	}

	return err
}

// ExecAdd executes IPAM plugin, assuming CNI_COMMAND == ADD.
// Parses and returns resulting IPConfig
func ExecAdd(plugin string, netconf []byte) (*Result, error) {
	if os.Getenv("CNI_COMMAND") != "ADD" {
		return nil, fmt.Errorf("CNI_COMMAND is not ADD")
	}
	if plugin == "" {
		return nil, fmt.Errorf(`Name of IPAM plugin is missing. Specify a "type" field in the "ipam" section`)
        }

	pluginPath := Find(plugin)
	if pluginPath == "" {
		return nil, fmt.Errorf("could not find %q IPAM plugin", plugin)
	}

	stdout := &bytes.Buffer{}

	c := exec.Cmd{
		Path:   pluginPath,
		Args:   []string{pluginPath},
		Stdin:  bytes.NewBuffer(netconf),
		Stdout: stdout,
		Stderr: os.Stderr,
	}
	if err := c.Run(); err != nil {
		return nil, pluginErr(err, stdout.Bytes())
	}

	res := &Result{}
	err := json.Unmarshal(stdout.Bytes(), res)
	return res, err
}

// ExecDel executes IPAM plugin, assuming CNI_COMMAND == DEL.
func ExecDel(plugin string, netconf []byte) error {
	if os.Getenv("CNI_COMMAND") != "DEL" {
		return fmt.Errorf("CNI_COMMAND is not DEL")
	}

	pluginPath := Find(plugin)
	if pluginPath == "" {
		return fmt.Errorf("could not find %q plugin", plugin)
	}

	stdout := &bytes.Buffer{}

	c := exec.Cmd{
		Path:   pluginPath,
		Args:   []string{pluginPath},
		Stdin:  bytes.NewBuffer(netconf),
		Stdout: stdout,
		Stderr: os.Stderr,
	}
	if err := c.Run(); err != nil {
		return pluginErr(err, stdout.Bytes())
	}
	return nil
}

// ConfigureIface takes the result of IPAM plugin and
// applies to the ifName interface
func ConfigureIface(ifName string, res *Result) error {
	link, err := netlink.LinkByName(ifName)
	if err != nil {
		return fmt.Errorf("failed to lookup %q: %v", ifName, err)
	}

	if err := netlink.LinkSetUp(link); err != nil {
		return fmt.Errorf("failed to set %q UP: %v", ifName, err)
	}

	// TODO(eyakubovich): IPv6
	addr := &netlink.Addr{IPNet: &res.IP4.IP, Label: ""}
	if err = netlink.AddrAdd(link, addr); err != nil {
		return fmt.Errorf("failed to add IP addr to %q: %v", ifName, err)
	}

	for _, r := range res.IP4.Routes {
		gw := r.GW
		if gw == nil {
			gw = res.IP4.Gateway
		}
		if err = ip.AddRoute(&r.Dst, gw, link); err != nil {
			// we skip over duplicate routes as we assume the first one wins
			if !os.IsExist(err) {
				return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, ifName, err)
			}
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package service

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strconv"
	"time"

	"github.com/onsi/ginkgo"
	configv1 "github.com/openshift/api/config/v1"
	configclient "github.com/openshift/client-go/config/clientset/versioned"
	"github.com/openshift/origin/pkg/monitor"
	"github.com/openshift/origin/test/extended/util/disruption"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/tools/events"
	"k8s.io/kubernetes/test/e2e/framework"
	e2enetwork "k8s.io/kubernetes/test/e2e/framework/network"
	"k8s.io/kubernetes/test/e2e/framework/service"
	"k8s.io/kubernetes/test/e2e/upgrades"
)

// UpgradeTest tests that a service is available before, during, and
// after a cluster upgrade.
type UpgradeTest struct {
	jig        *service.TestJig
	tcpService *v1.Service

	unsupportedPlatform bool
}

func (UpgradeTest) Name() string { return "k8s-service-lb-available" }
func (UpgradeTest) DisplayName() string {
	return "[sig-network-edge] Application behind service load balancer with PDB is not disrupted"
}

func shouldTestPDBs() bool { return true }

// Setup creates a service with a load balancer and makes sure it's reachable.
func (t *UpgradeTest) Setup(f *framework.Framework) {
	configClient, err := configclient.NewForConfig(f.ClientConfig())
	framework.ExpectNoError(err)
	infra, err := configClient.ConfigV1().Infrastructures().Get(context.Background(), "cluster", metav1.GetOptions{})
	framework.ExpectNoError(err)
	// ovirt does not support service type loadbalancer because it doesn't program a cloud.
	if infra.Status.PlatformStatus.Type == configv1.OvirtPlatformType || infra.Status.PlatformStatus.Type == configv1.KubevirtPlatformType || infra.Status.PlatformStatus.Type == configv1.LibvirtPlatformType || infra.Status.PlatformStatus.Type == configv1.VSpherePlatformType {
		t.unsupportedPlatform = true
	}
	if t.unsupportedPlatform {
		return
	}

	serviceName := "service-test"
	jig := service.NewTestJig(f.ClientSet, f.Namespace.Name, serviceName)

	ns := f.Namespace
	cs := f.ClientSet

	ginkgo.By("creating a TCP service " + serviceName + " with type=LoadBalancer in namespace " + ns.Name)
	tcpService, err := jig.CreateTCPService(func(s *v1.Service) {
		s.Spec.Type = v1.ServiceTypeLoadBalancer
		s.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal
		if s.Annotations == nil {
			s.Annotations = make(map[string]string)
		}
		// We tune the LB checks to match the longest intervals available so that interactions between
		// upgrading components and the service are more obvious.
		// - AWS allows configuration, default is 70s (6 failed with 10s interval in 1.17) set to match GCP
		s.Annotations["service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval"] = "8"
		s.Annotations["service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold"] = "3"
		s.Annotations["service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold"] = "2"
		// - Azure is hardcoded to 15s (2 failed with 5s interval in 1.17) and is sufficient
		// - GCP has a non-configurable interval of 32s (3 failed health checks with 8s interval in 1.17)
		//   - thus pods need to stay up for > 32s, so pod shutdown period will will be 45s
	})
	framework.ExpectNoError(err)
	tcpService, err = jig.WaitForLoadBalancer(service.GetServiceLoadBalancerCreationTimeout(cs))
	framework.ExpectNoError(err)

	// Get info to hit it with
	tcpIngressIP := service.GetIngressPoint(&tcpService.Status.LoadBalancer.Ingress[0])
	svcPort := int(tcpService.Spec.Ports[0].Port)

	ginkgo.By("creating RC to be part of service " + serviceName)
	rc, err := jig.Run(func(rc *v1.ReplicationController) {
		// ensure the pod waits long enough for most LBs to take it out of rotation, which has to be
		// longer than the LB failed health check interval
		rc.Spec.Template.Spec.Containers[0].Lifecycle = &v1.Lifecycle{
			PreStop: &v1.Handler{
				Exec: &v1.ExecAction{Command: []string{"sleep", "45"}},
			},
		}
		// ensure the pod is not forcibly deleted at 30s, but waits longer than the graceful sleep
		minute := int64(60)
		rc.Spec.Template.Spec.TerminationGracePeriodSeconds = &minute

		jig.AddRCAntiAffinity(rc)
	})
	framework.ExpectNoError(err)

	if shouldTestPDBs() {
		ginkgo.By("creating a PodDisruptionBudget to cover the ReplicationController")
		_, err = jig.CreatePDB(rc)
		framework.ExpectNoError(err)
	}

	// Hit it once before considering ourselves ready
	ginkgo.By("hitting pods through the service's LoadBalancer")
	timeout := 10 * time.Minute
	// require thirty seconds of passing requests to continue (in case the SLB becomes available and then degrades)
	TestReachableHTTPWithMinSuccessCount(tcpIngressIP, svcPort, 30, timeout)

	t.jig = jig
	t.tcpService = tcpService
}

// Test runs a connectivity check to the service.
func (t *UpgradeTest) Test(f *framework.Framework, done <-chan struct{}, upgrade upgrades.UpgradeType) {
	if t.unsupportedPlatform {
		return
	}

	client, err := framework.LoadClientset()
	framework.ExpectNoError(err)

	stopCh := make(chan struct{})
	defer close(stopCh)
	newBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: client.EventsV1()})
	r := newBroadcaster.NewRecorder(scheme.Scheme, "openshift.io/upgrade-test-service")
	newBroadcaster.StartRecordingToSink(stopCh)

	ginkgo.By("continuously hitting pods through the service's LoadBalancer")

	ctx, cancel := context.WithCancel(context.Background())
	m := monitor.NewMonitorWithInterval(1 * time.Second)
	err = startEndpointMonitoring(ctx, m, t.tcpService, r)
	framework.ExpectNoError(err, "unable to monitor API")

	start := time.Now()
	m.StartSampling(ctx)

	// wait to ensure API is still up after the test ends
	<-done
	ginkgo.By("waiting for any post disruption failures")
	time.Sleep(15 * time.Second)
	cancel()
	end := time.Now()

	disruption.ExpectNoDisruption(f, 0.02, end.Sub(start), m.Events(time.Time{}, time.Time{}), "Service was unreachable during disruption")

	// verify finalizer behavior
	defer func() {
		ginkgo.By("Check that service can be deleted with finalizer")
		service.WaitForServiceDeletedWithFinalizer(t.jig.Client, t.tcpService.Namespace, t.tcpService.Name)
	}()
	ginkgo.By("Check that finalizer is present on loadBalancer type service")
	service.WaitForServiceUpdatedWithFinalizer(t.jig.Client, t.tcpService.Namespace, t.tcpService.Name, true)
}

// Teardown cleans up any remaining resources.
func (t *UpgradeTest) Teardown(f *framework.Framework) {
	// rely on the namespace deletion to clean up everything
}

func startEndpointMonitoring(ctx context.Context, m *monitor.Monitor, svc *v1.Service, r events.EventRecorder) error {
	tcpIngressIP := service.GetIngressPoint(&svc.Status.LoadBalancer.Ingress[0])
	svcPort := int(svc.Spec.Ports[0].Port)
	url := fmt.Sprintf("http://%s/echo?msg=Hello", net.JoinHostPort(tcpIngressIP, strconv.Itoa(svcPort)))
	// this client reuses connections and detects abrupt breaks
	continuousClient := &http.Client{
		Timeout: 10 * time.Second,
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout: 15 * time.Second,
			}).Dial,
			TLSHandshakeTimeout: 15 * time.Second,
		},
	}

	m.AddSampler(
		monitor.StartSampling(ctx, m, time.Second, func(previous bool) (condition *monitor.Condition, next bool) {
			resp, err := continuousClient.Get(url)
			if err == nil {
				defer resp.Body.Close()
				body, err := ioutil.ReadAll(resp.Body)
				if err == nil && !bytes.Contains(body, []byte("Hello")) {
					err = fmt.Errorf("service returned success but did not contain the correct body contents: %q", string(body))
				}
			}
			switch {
			case err == nil && !previous:
				condition = &monitor.Condition{
					Level:   monitor.Info,
					Locator: locateService(svc),
					Message: "Service started responding to GET requests on reused connections",
				}
			case err != nil && previous:
				framework.Logf("Service %s is unreachable on reused connections: %v", svc.Name, err)
				r.Eventf(&v1.ObjectReference{Kind: "Service", Namespace: "kube-system", Name: "service-upgrade-test"}, nil, v1.EventTypeWarning, "Unreachable", "detected", "on reused connections")
				condition = &monitor.Condition{
					Level:   monitor.Error,
					Locator: locateService(svc),
					Message: "Service stopped responding to GET requests on reused connections",
				}
			case err != nil:
				framework.Logf("Service %s is unreachable on reused connections: %v", svc.Name, err)
			}
			return condition, err == nil
		}).ConditionWhenFailing(&monitor.Condition{
			Level:   monitor.Error,
			Locator: locateService(svc),
			Message: "Service is not responding to GET requests on reused connections",
		}),
	)

	// this client creates fresh connections and detects failure to establish connections
	client := &http.Client{
		Timeout: 10 * time.Second,
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout:   15 * time.Second,
				KeepAlive: -1,
			}).Dial,
			TLSHandshakeTimeout: 15 * time.Second,
			IdleConnTimeout:     15 * time.Second,
			DisableKeepAlives:   true,
		},
	}
	m.AddSampler(
		monitor.StartSampling(ctx, m, time.Second, func(previous bool) (condition *monitor.Condition, next bool) {
			resp, err := client.Get(url)
			if err == nil {
				defer resp.Body.Close()
				body, err := ioutil.ReadAll(resp.Body)
				if err == nil && !bytes.Contains(body, []byte("Hello")) {
					err = fmt.Errorf("service returned success but did not contain the correct body contents: %q", string(body))
				}
			}
			switch {
			case err == nil && !previous:
				condition = &monitor.Condition{
					Level:   monitor.Info,
					Locator: locateService(svc),
					Message: "Service started responding to GET requests over new connections",
				}
			case err != nil && previous:
				framework.Logf("Service %s is unreachable on new connections: %v", svc.Name, err)
				r.Eventf(&v1.ObjectReference{Kind: "Service", Namespace: "kube-system", Name: "service-upgrade-test"}, nil, v1.EventTypeWarning, "Unreachable", "detected", "on new connections")
				condition = &monitor.Condition{
					Level:   monitor.Error,
					Locator: locateService(svc),
					Message: "Service stopped responding to GET requests over new connections",
				}
			case err != nil:
				framework.Logf("Service %s is unreachable on new connections: %v", svc.Name, err)
			}
			return condition, err == nil
		}).ConditionWhenFailing(&monitor.Condition{
			Level:   monitor.Error,
			Locator: locateService(svc),
			Message: "Service is not responding to GET requests over new connections",
		}),
	)
	return nil
}

func locateService(svc *v1.Service) string {
	return fmt.Sprintf("ns/%s svc/%s", svc.Namespace, svc.Name)
}

// TestReachableHTTPWithMinSuccessCount tests that the given host serves HTTP on the given port for a minimum of successCount number of
// counts at a given interval. If the service reachability fails, the counter gets reset
func TestReachableHTTPWithMinSuccessCount(host string, port int, successCount int, timeout time.Duration) {
	consecutiveSuccessCnt := 0
	err := wait.PollImmediate(framework.Poll, timeout, func() (bool, error) {
		result := e2enetwork.PokeHTTP(host, port, "/echo?msg=hello",
			&e2enetwork.HTTPPokeParams{
				BodyContains:   "hello",
				RetriableCodes: []int{},
			})
		if result.Status == e2enetwork.HTTPSuccess {
			consecutiveSuccessCnt++
			return consecutiveSuccessCnt >= successCount, nil
		}
		consecutiveSuccessCnt = 0
		return false, nil // caller can retry
	})
	framework.ExpectNoError(err)
}
<commit_msg>Add BareMetalPlatformType into e2e upgrade service unsupported list<commit_after>package service

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strconv"
	"time"

	"github.com/onsi/ginkgo"
	configv1 "github.com/openshift/api/config/v1"
	configclient "github.com/openshift/client-go/config/clientset/versioned"
	"github.com/openshift/origin/pkg/monitor"
	"github.com/openshift/origin/test/extended/util/disruption"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/tools/events"
	"k8s.io/kubernetes/test/e2e/framework"
	e2enetwork "k8s.io/kubernetes/test/e2e/framework/network"
	"k8s.io/kubernetes/test/e2e/framework/service"
	"k8s.io/kubernetes/test/e2e/upgrades"
)

// UpgradeTest tests that a service is available before, during, and
// after a cluster upgrade.
type UpgradeTest struct {
	jig        *service.TestJig
	tcpService *v1.Service

	unsupportedPlatform bool
}

func (UpgradeTest) Name() string { return "k8s-service-lb-available" }
func (UpgradeTest) DisplayName() string {
	return "[sig-network-edge] Application behind service load balancer with PDB is not disrupted"
}

func shouldTestPDBs() bool { return true }

// Setup creates a service with a load balancer and makes sure it's reachable.
func (t *UpgradeTest) Setup(f *framework.Framework) {
	configClient, err := configclient.NewForConfig(f.ClientConfig())
	framework.ExpectNoError(err)
	infra, err := configClient.ConfigV1().Infrastructures().Get(context.Background(), "cluster", metav1.GetOptions{})
	framework.ExpectNoError(err)
	// ovirt does not support service type loadbalancer because it doesn't program a cloud.
	if infra.Status.PlatformStatus.Type == configv1.OvirtPlatformType || infra.Status.PlatformStatus.Type == configv1.KubevirtPlatformType || infra.Status.PlatformStatus.Type == configv1.LibvirtPlatformType || infra.Status.PlatformStatus.Type == configv1.VSpherePlatformType || infra.Status.PlatformStatus.Type == configv1.BareMetalPlatformType {
		t.unsupportedPlatform = true
	}
	if t.unsupportedPlatform {
		return
	}

	serviceName := "service-test"
	jig := service.NewTestJig(f.ClientSet, f.Namespace.Name, serviceName)

	ns := f.Namespace
	cs := f.ClientSet

	ginkgo.By("creating a TCP service " + serviceName + " with type=LoadBalancer in namespace " + ns.Name)
	tcpService, err := jig.CreateTCPService(func(s *v1.Service) {
		s.Spec.Type = v1.ServiceTypeLoadBalancer
		s.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal
		if s.Annotations == nil {
			s.Annotations = make(map[string]string)
		}
		// We tune the LB checks to match the longest intervals available so that interactions between
		// upgrading components and the service are more obvious.
		// - AWS allows configuration, default is 70s (6 failed with 10s interval in 1.17) set to match GCP
		s.Annotations["service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval"] = "8"
		s.Annotations["service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold"] = "3"
		s.Annotations["service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold"] = "2"
		// - Azure is hardcoded to 15s (2 failed with 5s interval in 1.17) and is sufficient
		// - GCP has a non-configurable interval of 32s (3 failed health checks with 8s interval in 1.17)
		//   - thus pods need to stay up for > 32s, so pod shutdown period will will be 45s
	})
	framework.ExpectNoError(err)
	tcpService, err = jig.WaitForLoadBalancer(service.GetServiceLoadBalancerCreationTimeout(cs))
	framework.ExpectNoError(err)

	// Get info to hit it with
	tcpIngressIP := service.GetIngressPoint(&tcpService.Status.LoadBalancer.Ingress[0])
	svcPort := int(tcpService.Spec.Ports[0].Port)

	ginkgo.By("creating RC to be part of service " + serviceName)
	rc, err := jig.Run(func(rc *v1.ReplicationController) {
		// ensure the pod waits long enough for most LBs to take it out of rotation, which has to be
		// longer than the LB failed health check interval
		rc.Spec.Template.Spec.Containers[0].Lifecycle = &v1.Lifecycle{
			PreStop: &v1.Handler{
				Exec: &v1.ExecAction{Command: []string{"sleep", "45"}},
			},
		}
		// ensure the pod is not forcibly deleted at 30s, but waits longer than the graceful sleep
		minute := int64(60)
		rc.Spec.Template.Spec.TerminationGracePeriodSeconds = &minute

		jig.AddRCAntiAffinity(rc)
	})
	framework.ExpectNoError(err)

	if shouldTestPDBs() {
		ginkgo.By("creating a PodDisruptionBudget to cover the ReplicationController")
		_, err = jig.CreatePDB(rc)
		framework.ExpectNoError(err)
	}

	// Hit it once before considering ourselves ready
	ginkgo.By("hitting pods through the service's LoadBalancer")
	timeout := 10 * time.Minute
	// require thirty seconds of passing requests to continue (in case the SLB becomes available and then degrades)
	TestReachableHTTPWithMinSuccessCount(tcpIngressIP, svcPort, 30, timeout)

	t.jig = jig
	t.tcpService = tcpService
}

// Test runs a connectivity check to the service.
func (t *UpgradeTest) Test(f *framework.Framework, done <-chan struct{}, upgrade upgrades.UpgradeType) {
	if t.unsupportedPlatform {
		return
	}

	client, err := framework.LoadClientset()
	framework.ExpectNoError(err)

	stopCh := make(chan struct{})
	defer close(stopCh)
	newBroadcaster := events.NewBroadcaster(&events.EventSinkImpl{Interface: client.EventsV1()})
	r := newBroadcaster.NewRecorder(scheme.Scheme, "openshift.io/upgrade-test-service")
	newBroadcaster.StartRecordingToSink(stopCh)

	ginkgo.By("continuously hitting pods through the service's LoadBalancer")

	ctx, cancel := context.WithCancel(context.Background())
	m := monitor.NewMonitorWithInterval(1 * time.Second)
	err = startEndpointMonitoring(ctx, m, t.tcpService, r)
	framework.ExpectNoError(err, "unable to monitor API")

	start := time.Now()
	m.StartSampling(ctx)

	// wait to ensure API is still up after the test ends
	<-done
	ginkgo.By("waiting for any post disruption failures")
	time.Sleep(15 * time.Second)
	cancel()
	end := time.Now()

	disruption.ExpectNoDisruption(f, 0.02, end.Sub(start), m.Events(time.Time{}, time.Time{}), "Service was unreachable during disruption")

	// verify finalizer behavior
	defer func() {
		ginkgo.By("Check that service can be deleted with finalizer")
		service.WaitForServiceDeletedWithFinalizer(t.jig.Client, t.tcpService.Namespace, t.tcpService.Name)
	}()
	ginkgo.By("Check that finalizer is present on loadBalancer type service")
	service.WaitForServiceUpdatedWithFinalizer(t.jig.Client, t.tcpService.Namespace, t.tcpService.Name, true)
}

// Teardown cleans up any remaining resources.
func (t *UpgradeTest) Teardown(f *framework.Framework) {
	// rely on the namespace deletion to clean up everything
}

func startEndpointMonitoring(ctx context.Context, m *monitor.Monitor, svc *v1.Service, r events.EventRecorder) error {
	tcpIngressIP := service.GetIngressPoint(&svc.Status.LoadBalancer.Ingress[0])
	svcPort := int(svc.Spec.Ports[0].Port)
	url := fmt.Sprintf("http://%s/echo?msg=Hello", net.JoinHostPort(tcpIngressIP, strconv.Itoa(svcPort)))
	// this client reuses connections and detects abrupt breaks
	continuousClient := &http.Client{
		Timeout: 10 * time.Second,
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout: 15 * time.Second,
			}).Dial,
			TLSHandshakeTimeout: 15 * time.Second,
		},
	}

	m.AddSampler(
		monitor.StartSampling(ctx, m, time.Second, func(previous bool) (condition *monitor.Condition, next bool) {
			resp, err := continuousClient.Get(url)
			if err == nil {
				defer resp.Body.Close()
				body, err := ioutil.ReadAll(resp.Body)
				if err == nil && !bytes.Contains(body, []byte("Hello")) {
					err = fmt.Errorf("service returned success but did not contain the correct body contents: %q", string(body))
				}
			}
			switch {
			case err == nil && !previous:
				condition = &monitor.Condition{
					Level:   monitor.Info,
					Locator: locateService(svc),
					Message: "Service started responding to GET requests on reused connections",
				}
			case err != nil && previous:
				framework.Logf("Service %s is unreachable on reused connections: %v", svc.Name, err)
				r.Eventf(&v1.ObjectReference{Kind: "Service", Namespace: "kube-system", Name: "service-upgrade-test"}, nil, v1.EventTypeWarning, "Unreachable", "detected", "on reused connections")
				condition = &monitor.Condition{
					Level:   monitor.Error,
					Locator: locateService(svc),
					Message: "Service stopped responding to GET requests on reused connections",
				}
			case err != nil:
				framework.Logf("Service %s is unreachable on reused connections: %v", svc.Name, err)
			}
			return condition, err == nil
		}).ConditionWhenFailing(&monitor.Condition{
			Level:   monitor.Error,
			Locator: locateService(svc),
			Message: "Service is not responding to GET requests on reused connections",
		}),
	)

	// this client creates fresh connections and detects failure to establish connections
	client := &http.Client{
		Timeout: 10 * time.Second,
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout:   15 * time.Second,
				KeepAlive: -1,
			}).Dial,
			TLSHandshakeTimeout: 15 * time.Second,
			IdleConnTimeout:     15 * time.Second,
			DisableKeepAlives:   true,
		},
	}
	m.AddSampler(
		monitor.StartSampling(ctx, m, time.Second, func(previous bool) (condition *monitor.Condition, next bool) {
			resp, err := client.Get(url)
			if err == nil {
				defer resp.Body.Close()
				body, err := ioutil.ReadAll(resp.Body)
				if err == nil && !bytes.Contains(body, []byte("Hello")) {
					err = fmt.Errorf("service returned success but did not contain the correct body contents: %q", string(body))
				}
			}
			switch {
			case err == nil && !previous:
				condition = &monitor.Condition{
					Level:   monitor.Info,
					Locator: locateService(svc),
					Message: "Service started responding to GET requests over new connections",
				}
			case err != nil && previous:
				framework.Logf("Service %s is unreachable on new connections: %v", svc.Name, err)
				r.Eventf(&v1.ObjectReference{Kind: "Service", Namespace: "kube-system", Name: "service-upgrade-test"}, nil, v1.EventTypeWarning, "Unreachable", "detected", "on new connections")
				condition = &monitor.Condition{
					Level:   monitor.Error,
					Locator: locateService(svc),
					Message: "Service stopped responding to GET requests over new connections",
				}
			case err != nil:
				framework.Logf("Service %s is unreachable on new connections: %v", svc.Name, err)
			}
			return condition, err == nil
		}).ConditionWhenFailing(&monitor.Condition{
			Level:   monitor.Error,
			Locator: locateService(svc),
			Message: "Service is not responding to GET requests over new connections",
		}),
	)
	return nil
}

func locateService(svc *v1.Service) string {
	return fmt.Sprintf("ns/%s svc/%s", svc.Namespace, svc.Name)
}

// TestReachableHTTPWithMinSuccessCount tests that the given host serves HTTP on the given port for a minimum of successCount number of
// counts at a given interval. If the service reachability fails, the counter gets reset
func TestReachableHTTPWithMinSuccessCount(host string, port int, successCount int, timeout time.Duration) {
	consecutiveSuccessCnt := 0
	err := wait.PollImmediate(framework.Poll, timeout, func() (bool, error) {
		result := e2enetwork.PokeHTTP(host, port, "/echo?msg=hello",
			&e2enetwork.HTTPPokeParams{
				BodyContains:   "hello",
				RetriableCodes: []int{},
			})
		if result.Status == e2enetwork.HTTPSuccess {
			consecutiveSuccessCnt++
			return consecutiveSuccessCnt >= successCount, nil
		}
		consecutiveSuccessCnt = 0
		return false, nil // caller can retry
	})
	framework.ExpectNoError(err)
}
<|endoftext|>