<commit_before>package metrics

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/alecthomas/units"
	"github.com/matt-deboer/kuill/pkg/auth"
	"github.com/matt-deboer/kuill/pkg/clients"
	log "github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Provider gathers metrics from the underlying kubernetes cluster
type Provider struct {
	summary     *Summaries
	mutex       sync.RWMutex
	kubeClients *clients.KubeClients
	killSwitch  chan struct{}
}

// NewMetricsProvider returns a Provider capable of returning metrics for the cluster
func NewMetricsProvider(kubeClients *clients.KubeClients) (*Provider, error) {

	m := &Provider{
		kubeClients: kubeClients,
		killSwitch:  make(chan struct{}, 1),
	}

	m.summary = m.summarize()

	go func() {
		for {
			select {
			case _ = <-m.killSwitch:
				return
			default:
				time.Sleep(15 * time.Second)

				summary := m.summarize()

				m.mutex.Lock()
				m.summary = summary
				m.mutex.Unlock()
			}
		}
	}()

	return m, nil
}

// Close shuts down the metrics provider's background worker(s)
func (m *Provider) Close() {
	m.killSwitch <- struct{}{}
}

// GetMetrics returns a summarized metrics bundle for the entire cluster
func (m *Provider) GetMetrics(w http.ResponseWriter, r *http.Request, context auth.Context) {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	data, err := json.Marshal(m.summary)
	if err != nil {
		log.Error(err)
		http.Error(w, "Failed to marshall metrics", http.StatusInternalServerError)
	} else {
		w.Header().Set("Content-Type", "application/json")
		w.Write(data)
	}
}

func (m *Provider) summarize() *Summaries {

	summary := &Summaries{
		Namespace: make(map[string]*Summary),
		Node:      make(map[string]*Summary),
		Pod:       make(map[string]*Summary),
	}
	aggregates := make(map[string]uint64)
	namespaces := make(map[string]bool)

	for _, node := range m.listNodes() {
		nodeName := node.ObjectMeta.Name

		nodeSummary := m.readNodeSummary(fmt.Sprintf("%s/api/v1/proxy/nodes/%s:10255/stats/summary",
			m.kubeClients.BaseURL, nodeName))
		if nodeSummary != nil {

			convertedSummary, convertedByNs := convertSummary(nodeSummary, node, summary)
			// aggregate at the cluster level
			aggregates["Cluster:totalMillicores"] += convertedSummary.CPU.Total
			aggregates["Cluster:memTotalBytes"] += convertedSummary.Memory.Total
			aggregates["Cluster:usageNanoCores"] += nodeSummary.Node.CPU.UsageNanoCores
			aggregates["Cluster:memUsageBytes"] += nodeSummary.Node.Memory.UsageBytes
			aggregates["Cluster:networkTxBytes"] += nodeSummary.Node.Network.TxBytes
			aggregates["Cluster:networkRxBytes"] += nodeSummary.Node.Network.RxBytes
			aggregates["Cluster:networkSeconds"] += uint64(nodeSummary.Node.Network.Time.Sub(nodeSummary.Node.StartTime).Seconds())
			aggregates["Cluster:fsCapacityBytes"] += nodeSummary.Node.Fs.CapacityBytes
			aggregates["Cluster:fsUsedBytes"] += nodeSummary.Node.Fs.UsedBytes

			// aggregate at the namespace level
			for ns, nsStat := range convertedByNs {
				aggregates["Namespace:"+ns+":fsUsedBytes"] += nsStat.Disk.Usage
				aggregates["Namespace:"+ns+":fsCapacityBytes"] += nsStat.Disk.Total
			}

			for _, podStats := range nodeSummary.Pods {
				ns := podStats.PodRef.Namespace
				aggregates["Namespace:"+ns+":pods"]++
				aggregates["Cluster:pods"]++
				convertedSummary.Pods.Usage++
				namespaces[ns] = true
				for _, volStats := range podStats.VolumeStats {
					aggregates["Cluster:volCapacityBytes"] += volStats.CapacityBytes
					aggregates["Cluster:volUsedBytes"] += volStats.UsedBytes
					aggregates["Namespace:"+ns+":volCapacityBytes"] += volStats.CapacityBytes
					aggregates["Namespace:"+ns+":volUsedBytes"] += volStats.UsedBytes
				}
				for _, c := range podStats.Containers {
					aggregates["Namespace:"+ns+":containers"]++
					aggregates["Cluster:containers"]++
					convertedSummary.Containers.Usage++
					if c.CPU != nil {
						aggregates["Namespace:"+ns+":usageCoreNanoSeconds"] += c.CPU.UsageCoreNanoSeconds
						aggregates["Namespace:"+ns+":usageNanoCores"] += c.CPU.UsageNanoCores
					}
					if c.Memory != nil {
						aggregates["Namespace:"+ns+":memAvailableBytes"] += c.Memory.AvailableBytes
						aggregates["Namespace:"+ns+":memUsageBytes"] += c.Memory.UsageBytes
						aggregates["Namespace:"+ns+":memRSSBytes"] += c.Memory.RSSBytes
						aggregates["Namespace:"+ns+":memWorkingSetBytes"] += c.Memory.WorkingSetBytes
					}
				}
				if podStats.Network != nil {
					aggregates["Namespace:"+ns+":networkTxBytes"] += podStats.Network.TxBytes
					aggregates["Namespace:"+ns+":networkRxBytes"] += podStats.Network.RxBytes
					aggregates["Namespace:"+ns+":networkSeconds"] += uint64(podStats.Network.Time.Unix() - podStats.StartTime.Unix())
				}
			}

			summary.Node[node.ObjectMeta.Name] = convertedSummary
		}
	}

	summary.Cluster = makeSummary("Cluster", aggregates)
	for ns := range namespaces {
		prefix := "Namespace:" + ns
		nsSummary := makeSummary(prefix, aggregates)

		// To be replaced by resource quota for the NS, should one exist
		nsSummary.CPU.Total = summary.Cluster.CPU.Total
		nsSummary.CPU.Ratio = safeDivideFloat(float64(nsSummary.CPU.Usage), float64(nsSummary.CPU.Total))
		nsSummary.Memory.Total = summary.Cluster.Memory.Total
		nsSummary.Memory.Ratio = safeDivideFloat(float64(nsSummary.Memory.Usage), float64(nsSummary.Memory.Total))

		summary.Namespace[ns] = nsSummary
	}

	return summary
}

func safeGet(ref *uint64) uint64 {
	if ref != nil {
		return *ref
	}
	return 0
}

func safeDivideInt(dividend, divisor uint64) uint64 {
	if divisor == 0 {
		return 0
	}
	return dividend / divisor
}

func safeDivideFloat(dividend, divisor float64) float64 {
	if divisor == 0.0 {
		return 0.0
	}
	return dividend / divisor
}

func convertSummary(summary *KubeletStatsSummary, node v1.Node, summaries *Summaries) (*Summary, map[string]*Summary) {

	summaryByNs := make(map[string]*Summary)
	networkSeconds := uint64(0)
	if summary.Node.Network != nil {
		networkSeconds = uint64(summary.Node.Network.Time.Unix() - summary.Node.StartTime.Unix())
	}
	volCapacityBytes := uint64(0)
	volUsageBytes := uint64(0)
	for _, podStats := range summary.Pods {

		podSummary := convertPodSummary(podStats)
		podKey := fmt.Sprintf("%s/%s", podStats.PodRef.Namespace, podStats.PodRef.Name)
		summaries.Pod[podKey] = podSummary
		nsSummary, ok := summaryByNs[podStats.PodRef.Namespace]
		if !ok {
			nsSummary = &Summary{Disk: &SummaryStat{Units: "bytes"}}
			summaryByNs[podStats.PodRef.Namespace] = nsSummary
		}
		nsSummary.Disk.Usage += podSummary.Disk.Usage
		nsSummary.Disk.Total += podSummary.Disk.Total

		volUsageBytes += podSummary.Volumes.Usage
		volCapacityBytes += podSummary.Volumes.Total
	}

	totalCPUCores, err := strconv.Atoi(node.Status.Allocatable.Cpu().String())
	if err != nil {
		log.Errorf("Failed to parse allocatable.cpu; %v", err)
	}
	allocatableBytesString := node.Status.Allocatable.Memory().String()
	if strings.HasSuffix(allocatableBytesString, "i") {
		allocatableBytesString += "B"
	}
	totalMemoryBytes, err := units.ParseStrictBytes(allocatableBytesString)
	if err != nil {
		log.Errorf("Failed to parse allocatable.memory; %v", err)
	}

	if summary.Node.CPU == nil {
		summary.Node.CPU = &CPUStats{}
	}

	if summary.Node.Memory == nil {
		summary.Node.Memory = &MemoryStats{}
	}

	if summary.Node.Fs == nil {
		summary.Node.Fs = &FsStats{}
	}

	return &Summary{
		CPU: newSummaryStat(
			summary.Node.CPU.UsageNanoCores/1000000,
			uint64(totalCPUCores*1000),
			"millicores"),
		Memory: newSummaryStat(
			summary.Node.Memory.UsageBytes,
			uint64(totalMemoryBytes),
			"bytes"),
		Disk: newSummaryStat(
			summary.Node.Fs.UsedBytes,
			summary.Node.Fs.CapacityBytes,
			"bytes"),
		Volumes: newSummaryStat(
			volUsageBytes,
			volCapacityBytes,
			"bytes"),
		NetRx: newSummaryStat(
			safeDivideInt(summary.Node.Network.RxBytes, networkSeconds),
			1,
			"bytes/sec"),
		NetTx: newSummaryStat(
			safeDivideInt(summary.Node.Network.TxBytes, networkSeconds),
			1,
			"bytes/sec"),
		Pods: &SummaryStat{
			Usage: 0,
		},
		Containers: &SummaryStat{
			Usage: 0,
		},
	}, summaryByNs
}

func safeDivide(dividend, divisor uint64) uint64 {
	if divisor == 0 {
		return 0
	}
	return dividend / divisor
}

func convertPodSummary(pod PodStats) *Summary {

	networkSeconds := uint64(pod.Network.Time.Unix() - pod.StartTime.Unix())
	summary := &Summary{
		CPU:     &SummaryStat{Units: "millicores"},
		Memory:  &SummaryStat{Units: "bytes"},
		Volumes: &SummaryStat{Units: "bytes"},
		Disk:    &SummaryStat{Units: "bytes"},
		NetRx: newSummaryStat(
			safeDivideInt(pod.Network.RxBytes, networkSeconds),
			1,
			"bytes/sec"),
		NetTx: newSummaryStat(
			safeDivideInt(pod.Network.TxBytes, networkSeconds),
			1,
			"bytes/sec"),
		Pods: &SummaryStat{
			Usage: 1,
		},
		Containers: &SummaryStat{
			Usage: uint64(len(pod.Containers)),
		},
	}

	for _, volStats := range pod.VolumeStats {
		summary.Volumes.Usage += volStats.CapacityBytes
		summary.Volumes.Total += volStats.UsedBytes
	}

	for _, container := range pod.Containers {
		if container.CPU != nil {
			summary.CPU.Usage += container.CPU.UsageNanoCores / 1000000
		}
		if container.Memory != nil {
			summary.Memory.Usage += container.Memory.UsageBytes
		}
		if container.Rootfs != nil {
			summary.Disk.Usage += container.Rootfs.UsedBytes
		}
	}
	return summary
}

func makeSummary(prefix string, aggregates map[string]uint64) *Summary {

	usageNanoCores := aggregates[prefix+":usageNanoCores"]
	totalMillicores := aggregates[prefix+":totalMillicores"]
	memUsageBytes := aggregates[prefix+":memUsageBytes"]
	memTotalBytes := aggregates[prefix+":memTotalBytes"]
	networkTxBytes := aggregates[prefix+":networkTxBytes"]
	networkRxBytes := aggregates[prefix+":networkRxBytes"]
	// We create a fake timespan here with the current time as the endpoint;
	// the important part is that the duration is correct
	networkSeconds := aggregates[prefix+":networkSeconds"]
	if networkSeconds == 0 {
		networkSeconds = math.MaxUint64
	}

	fsCapacityBytes := aggregates[prefix+":fsCapacityBytes"]
	fsUsedBytes := aggregates[prefix+":fsUsedBytes"]
	volCapacityBytes := aggregates[prefix+":volCapacityBytes"]
	volUsedBytes := aggregates[prefix+":volUsedBytes"]
	podCount := aggregates[prefix+":pods"]
	containerCount := aggregates[prefix+":containers"]

	return &Summary{

		CPU: newSummaryStat(
			usageNanoCores/1000000,
			totalMillicores,
			"millicores"),
		Memory: newSummaryStat(
			memUsageBytes,
			memTotalBytes,
			"bytes"),
		Disk: newSummaryStat(
			fsUsedBytes,
			fsCapacityBytes,
			"bytes"),
		Volumes: newSummaryStat(
			volUsedBytes,
			volCapacityBytes,
			"bytes"),
		NetRx: newSummaryStat(
			safeDivideInt(networkRxBytes, networkSeconds),
			1,
			"bytes/sec"),
		NetTx: newSummaryStat(
			safeDivideInt(networkTxBytes, networkSeconds),
			1,
			"bytes/sec"),
		Pods: &SummaryStat{
			Usage: podCount,
		},
		Containers: &SummaryStat{
			Usage: containerCount,
		},
	}
}

func (m *Provider) readNodeSummary(path string) *KubeletStatsSummary {

	req, err := http.NewRequest("GET", fmt.Sprintf("%s", path), nil)
	if err != nil {
		log.Errorf("Failed to create request for path %s: %v", path, err)
		return nil
	}
	if len(m.kubeClients.BearerToken) > 0 {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", string(m.kubeClients.BearerToken)))
	}

	resp, err := m.kubeClients.HTTP.Do(req)
	if err == nil {
		if resp.StatusCode != http.StatusOK {
			log.Errorf("Failed to read node summary response from '%s'; %s: %v", path, resp.Status, err)
		}
		b, err := ioutil.ReadAll(resp.Body)
		if err == nil {
			// var data stats.Summary
			var data KubeletStatsSummary
			if err = json.Unmarshal(b, &data); err == nil {
				return &data
			}
			log.Errorf("Failed to unmarshal node summary response from '%s'; %v", path, string(b))
		} else {
			log.Errorf("Failed to read node summary response from '%s'; %v", path, err)
		}
	} else {
		log.Errorf("Failed to read node summary from '%s': %v", path, err)
	}
	return nil
}

func (m *Provider) listNodes() []v1.Node {
	nodes, err := m.kubeClients.Standard.Core().Nodes().List(meta_v1.ListOptions{})
	if err == nil {
		return nodes.Items
	}
	log.Error(err)
	return []v1.Node{}
}
<commit_msg>fix for missing network info in metrics<commit_after>package metrics

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/alecthomas/units"
	"github.com/matt-deboer/kuill/pkg/auth"
	"github.com/matt-deboer/kuill/pkg/clients"
	log "github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Provider gathers metrics from the underlying kubernetes cluster
type Provider struct {
	summary     *Summaries
	mutex       sync.RWMutex
	kubeClients *clients.KubeClients
	killSwitch  chan struct{}
}

// NewMetricsProvider returns a Provider capable of returning metrics for the cluster
func NewMetricsProvider(kubeClients *clients.KubeClients) (*Provider, error) {

	m := &Provider{
		kubeClients: kubeClients,
		killSwitch:  make(chan struct{}, 1),
	}

	m.summary = m.summarize()

	go func() {
		for {
			select {
			case _ = <-m.killSwitch:
				return
			default:
				time.Sleep(15 * time.Second)

				summary := m.summarize()

				m.mutex.Lock()
				m.summary = summary
				m.mutex.Unlock()
			}
		}
	}()

	return m, nil
}

// Close shuts down the metrics provider's background worker(s)
func (m *Provider) Close() {
	m.killSwitch <- struct{}{}
}

// GetMetrics returns a summarized metrics bundle for the entire cluster
func (m *Provider) GetMetrics(w http.ResponseWriter, r *http.Request, context auth.Context) {
	m.mutex.RLock()
	defer m.mutex.RUnlock()

	data, err := json.Marshal(m.summary)
	if err != nil {
		log.Error(err)
		http.Error(w, "Failed to marshall metrics", http.StatusInternalServerError)
	} else {
		w.Header().Set("Content-Type", "application/json")
		w.Write(data)
	}
}

func (m *Provider) summarize() *Summaries {

	summary := &Summaries{
		Namespace: make(map[string]*Summary),
		Node:      make(map[string]*Summary),
		Pod:       make(map[string]*Summary),
	}
	aggregates := make(map[string]uint64)
	namespaces := make(map[string]bool)

	for _, node := range m.listNodes() {
		nodeName := node.ObjectMeta.Name

		nodeSummary := m.readNodeSummary(fmt.Sprintf("%s/api/v1/proxy/nodes/%s:10255/stats/summary",
			m.kubeClients.BaseURL, nodeName))
		if nodeSummary != nil {

			convertedSummary, convertedByNs := convertSummary(nodeSummary, node, summary)
			// aggregate at the cluster level
			aggregates["Cluster:totalMillicores"] += convertedSummary.CPU.Total
			aggregates["Cluster:memTotalBytes"] += convertedSummary.Memory.Total
			aggregates["Cluster:usageNanoCores"] += nodeSummary.Node.CPU.UsageNanoCores
			aggregates["Cluster:memUsageBytes"] += nodeSummary.Node.Memory.UsageBytes
			if nodeSummary.Node.Network != nil {
				aggregates["Cluster:networkTxBytes"] += nodeSummary.Node.Network.TxBytes
				aggregates["Cluster:networkRxBytes"] += nodeSummary.Node.Network.RxBytes
				aggregates["Cluster:networkSeconds"] += uint64(nodeSummary.Node.Network.Time.Sub(nodeSummary.Node.StartTime).Seconds())
			}
			aggregates["Cluster:fsCapacityBytes"] += nodeSummary.Node.Fs.CapacityBytes
			aggregates["Cluster:fsUsedBytes"] += nodeSummary.Node.Fs.UsedBytes

			// aggregate at the namespace level
			for ns, nsStat := range convertedByNs {
				aggregates["Namespace:"+ns+":fsUsedBytes"] += nsStat.Disk.Usage
				aggregates["Namespace:"+ns+":fsCapacityBytes"] += nsStat.Disk.Total
			}

			for _, podStats := range nodeSummary.Pods {
				ns := podStats.PodRef.Namespace
				aggregates["Namespace:"+ns+":pods"]++
				aggregates["Cluster:pods"]++
				convertedSummary.Pods.Usage++
				namespaces[ns] = true
				for _, volStats := range podStats.VolumeStats {
					aggregates["Cluster:volCapacityBytes"] += volStats.CapacityBytes
					aggregates["Cluster:volUsedBytes"] += volStats.UsedBytes
					aggregates["Namespace:"+ns+":volCapacityBytes"] += volStats.CapacityBytes
					aggregates["Namespace:"+ns+":volUsedBytes"] += volStats.UsedBytes
				}
				for _, c := range podStats.Containers {
					aggregates["Namespace:"+ns+":containers"]++
					aggregates["Cluster:containers"]++
					convertedSummary.Containers.Usage++
					if c.CPU != nil {
						aggregates["Namespace:"+ns+":usageCoreNanoSeconds"] += c.CPU.UsageCoreNanoSeconds
						aggregates["Namespace:"+ns+":usageNanoCores"] += c.CPU.UsageNanoCores
					}
					if c.Memory != nil {
						aggregates["Namespace:"+ns+":memAvailableBytes"] += c.Memory.AvailableBytes
						aggregates["Namespace:"+ns+":memUsageBytes"] += c.Memory.UsageBytes
						aggregates["Namespace:"+ns+":memRSSBytes"] += c.Memory.RSSBytes
						aggregates["Namespace:"+ns+":memWorkingSetBytes"] += c.Memory.WorkingSetBytes
					}
				}
				if podStats.Network != nil {
					aggregates["Namespace:"+ns+":networkTxBytes"] += podStats.Network.TxBytes
					aggregates["Namespace:"+ns+":networkRxBytes"] += podStats.Network.RxBytes
					aggregates["Namespace:"+ns+":networkSeconds"] += uint64(podStats.Network.Time.Unix() - podStats.StartTime.Unix())
				}
			}

			summary.Node[node.ObjectMeta.Name] = convertedSummary
		}
	}

	summary.Cluster = makeSummary("Cluster", aggregates)
	for ns := range namespaces {
		prefix := "Namespace:" + ns
		nsSummary := makeSummary(prefix, aggregates)

		// To be replaced by resource quota for the NS, should one exist
		nsSummary.CPU.Total = summary.Cluster.CPU.Total
		nsSummary.CPU.Ratio = safeDivideFloat(float64(nsSummary.CPU.Usage), float64(nsSummary.CPU.Total))
		nsSummary.Memory.Total = summary.Cluster.Memory.Total
		nsSummary.Memory.Ratio = safeDivideFloat(float64(nsSummary.Memory.Usage), float64(nsSummary.Memory.Total))

		summary.Namespace[ns] = nsSummary
	}

	return summary
}

func safeGet(ref *uint64) uint64 {
	if ref != nil {
		return *ref
	}
	return 0
}

func safeDivideInt(dividend, divisor uint64) uint64 {
	if divisor == 0 {
		return 0
	}
	return dividend / divisor
}

func safeDivideFloat(dividend, divisor float64) float64 {
	if divisor == 0.0 {
		return 0.0
	}
	return dividend / divisor
}

func convertSummary(summary *KubeletStatsSummary, node v1.Node, summaries *Summaries) (*Summary, map[string]*Summary) {

	summaryByNs := make(map[string]*Summary)
	networkSeconds := uint64(0)
	if summary.Node.Network != nil {
		networkSeconds = uint64(summary.Node.Network.Time.Unix() - summary.Node.StartTime.Unix())
	}
	volCapacityBytes := uint64(0)
	volUsageBytes := uint64(0)
	for _, podStats := range summary.Pods {

		podSummary := convertPodSummary(podStats)
		podKey := fmt.Sprintf("%s/%s", podStats.PodRef.Namespace, podStats.PodRef.Name)
		summaries.Pod[podKey] = podSummary
		nsSummary, ok := summaryByNs[podStats.PodRef.Namespace]
		if !ok {
			nsSummary = &Summary{Disk: &SummaryStat{Units: "bytes"}}
			summaryByNs[podStats.PodRef.Namespace] = nsSummary
		}
		nsSummary.Disk.Usage += podSummary.Disk.Usage
		nsSummary.Disk.Total += podSummary.Disk.Total

		volUsageBytes += podSummary.Volumes.Usage
		volCapacityBytes += podSummary.Volumes.Total
	}

	totalCPUCores, err := strconv.Atoi(node.Status.Allocatable.Cpu().String())
	if err != nil {
		log.Errorf("Failed to parse allocatable.cpu; %v", err)
	}
	allocatableBytesString := node.Status.Allocatable.Memory().String()
	if strings.HasSuffix(allocatableBytesString, "i") {
		allocatableBytesString += "B"
	}
	totalMemoryBytes, err := units.ParseStrictBytes(allocatableBytesString)
	if err != nil {
		log.Errorf("Failed to parse allocatable.memory; %v", err)
	}

	if summary.Node.CPU == nil {
		summary.Node.CPU = &CPUStats{}
	}

	if summary.Node.Memory == nil {
		summary.Node.Memory = &MemoryStats{}
	}

	if summary.Node.Fs == nil {
		summary.Node.Fs = &FsStats{}
	}

	result := &Summary{
		CPU: newSummaryStat(
			summary.Node.CPU.UsageNanoCores/1000000,
			uint64(totalCPUCores*1000),
			"millicores"),
		Memory: newSummaryStat(
			summary.Node.Memory.UsageBytes,
			uint64(totalMemoryBytes),
			"bytes"),
		Disk: newSummaryStat(
			summary.Node.Fs.UsedBytes,
			summary.Node.Fs.CapacityBytes,
			"bytes"),
		Volumes: newSummaryStat(
			volUsageBytes,
			volCapacityBytes,
			"bytes"),
		Pods: &SummaryStat{
			Usage: 0,
		},
		Containers: &SummaryStat{
			Usage: 0,
		},
	}
	if summary.Node.Network != nil {
		result.NetRx = newSummaryStat(
			safeDivideInt(summary.Node.Network.RxBytes, networkSeconds),
			1,
			"bytes/sec")
		result.NetTx = newSummaryStat(
			safeDivideInt(summary.Node.Network.TxBytes, networkSeconds),
			1,
			"bytes/sec")
	}

	return result, summaryByNs

}

func safeDivide(dividend, divisor uint64) uint64 {
	if divisor == 0 {
		return 0
	}
	return dividend / divisor
}

func convertPodSummary(pod PodStats) *Summary {

	networkSeconds := uint64(pod.Network.Time.Unix() - pod.StartTime.Unix())
	summary := &Summary{
		CPU:     &SummaryStat{Units: "millicores"},
		Memory:  &SummaryStat{Units: "bytes"},
		Volumes: &SummaryStat{Units: "bytes"},
		Disk:    &SummaryStat{Units: "bytes"},
		NetRx: newSummaryStat(
			safeDivideInt(pod.Network.RxBytes, networkSeconds),
			1,
			"bytes/sec"),
		NetTx: newSummaryStat(
			safeDivideInt(pod.Network.TxBytes, networkSeconds),
			1,
			"bytes/sec"),
		Pods: &SummaryStat{
			Usage: 1,
		},
		Containers: &SummaryStat{
			Usage: uint64(len(pod.Containers)),
		},
	}

	for _, volStats := range pod.VolumeStats {
		summary.Volumes.Usage += volStats.CapacityBytes
		summary.Volumes.Total += volStats.UsedBytes
	}

	for _, container := range pod.Containers {
		if container.CPU != nil {
			summary.CPU.Usage += container.CPU.UsageNanoCores / 1000000
		}
		if container.Memory != nil {
			summary.Memory.Usage += container.Memory.UsageBytes
		}
		if container.Rootfs != nil {
			summary.Disk.Usage += container.Rootfs.UsedBytes
		}
	}
	return summary
}

func makeSummary(prefix string, aggregates map[string]uint64) *Summary {

	usageNanoCores := aggregates[prefix+":usageNanoCores"]
	totalMillicores := aggregates[prefix+":totalMillicores"]
	memUsageBytes := aggregates[prefix+":memUsageBytes"]
	memTotalBytes := aggregates[prefix+":memTotalBytes"]
	networkTxBytes := aggregates[prefix+":networkTxBytes"]
	networkRxBytes := aggregates[prefix+":networkRxBytes"]
	// We create a fake timespan here with the current time as the endpoint;
	// the important part is that the duration is correct
	networkSeconds := aggregates[prefix+":networkSeconds"]
	if networkSeconds == 0 {
		networkSeconds = math.MaxUint64
	}

	fsCapacityBytes := aggregates[prefix+":fsCapacityBytes"]
	fsUsedBytes := aggregates[prefix+":fsUsedBytes"]
	volCapacityBytes := aggregates[prefix+":volCapacityBytes"]
	volUsedBytes := aggregates[prefix+":volUsedBytes"]
	podCount := aggregates[prefix+":pods"]
	containerCount := aggregates[prefix+":containers"]

	return &Summary{

		CPU: newSummaryStat(
			usageNanoCores/1000000,
			totalMillicores,
			"millicores"),
		Memory: newSummaryStat(
			memUsageBytes,
			memTotalBytes,
			"bytes"),
		Disk: newSummaryStat(
			fsUsedBytes,
			fsCapacityBytes,
			"bytes"),
		Volumes: newSummaryStat(
			volUsedBytes,
			volCapacityBytes,
			"bytes"),
		NetRx: newSummaryStat(
			safeDivideInt(networkRxBytes, networkSeconds),
			1,
			"bytes/sec"),
		NetTx: newSummaryStat(
			safeDivideInt(networkTxBytes, networkSeconds),
			1,
			"bytes/sec"),
		Pods: &SummaryStat{
			Usage: podCount,
		},
		Containers: &SummaryStat{
			Usage: containerCount,
		},
	}
}

func (m *Provider) readNodeSummary(path string) *KubeletStatsSummary {

	req, err := http.NewRequest("GET", fmt.Sprintf("%s", path), nil)
	if err != nil {
		log.Errorf("Failed to create request for path %s: %v", path, err)
		return nil
	}
	if len(m.kubeClients.BearerToken) > 0 {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", string(m.kubeClients.BearerToken)))
	}

	resp, err := m.kubeClients.HTTP.Do(req)
	if err == nil {
		if resp.StatusCode != http.StatusOK {
			log.Errorf("Failed to read node summary response from '%s'; %s: %v", path, resp.Status, err)
		}
		b, err := ioutil.ReadAll(resp.Body)
		if err == nil {
			// var data stats.Summary
			var data KubeletStatsSummary
			if err = json.Unmarshal(b, &data); err == nil {
				return &data
			}
			log.Errorf("Failed to unmarshal node summary response from '%s'; %v", path, string(b))
		} else {
			log.Errorf("Failed to read node summary response from '%s'; %v", path, err)
		}
	} else {
		log.Errorf("Failed to read node summary from '%s': %v", path, err)
	}
	return nil
}

func (m *Provider) listNodes() []v1.Node {
	nodes, err := m.kubeClients.Standard.Core().Nodes().List(meta_v1.ListOptions{})
	if err == nil {
		return nodes.Items
	}
	log.Error(err)
	return []v1.Node{}
}
<|endoftext|>


<commit_before>package models

import (
	"encoding/json"
	"errors"
	"fmt"
	"time"
)

// Typed errors
var (
	ErrInvalidRoleType     = errors.New("Invalid role type")
	ErrLastOrgAdmin        = errors.New("Cannot remove last organization admin")
	ErrOrgUserNotFound     = errors.New("Cannot find the organization user")
	ErrOrgUserAlreadyAdded = errors.New("User is already added to organization")
)

type RoleType string

const (
	ROLE_VIEWER RoleType = "Viewer"
	ROLE_EDITOR RoleType = "Editor"
	ROLE_ADMIN  RoleType = "Admin"
)

func (r RoleType) IsValid() bool {
	return r == ROLE_VIEWER || r == ROLE_ADMIN || r == ROLE_EDITOR
}

func (r RoleType) Includes(other RoleType) bool {
	if r == ROLE_ADMIN {
		return true
	}

	if r == ROLE_EDITOR {
		return other != ROLE_ADMIN
	}

	return false
}

func (r *RoleType) UnmarshalJSON(data []byte) error {
	var str string
	err := json.Unmarshal(data, &str)
	if err != nil {
		return err
	}

	*r = RoleType(str)

	if !(*r).IsValid() {
		if (*r) != "" {
			return fmt.Errorf("JSON validation error: invalid role value: %s", *r)
		}

		*r = ROLE_VIEWER
	}

	return nil
}

type OrgUser struct {
	Id      int64
	OrgId   int64
	UserId  int64
	Role    RoleType
	Created time.Time
	Updated time.Time
}

// ---------------------
// COMMANDS

type RemoveOrgUserCommand struct {
	UserId                   int64
	OrgId                    int64
	ShouldDeleteOrphanedUser bool
	UserWasDeleted           bool
}

type AddOrgUserCommand struct {
	LoginOrEmail string   `json:"loginOrEmail" binding:"Required"`
	Role         RoleType `json:"role" binding:"Required"`

	OrgId  int64 `json:"-"`
	UserId int64 `json:"-"`
}

type UpdateOrgUserCommand struct {
	Role RoleType `json:"role" binding:"Required"`

	OrgId  int64 `json:"-"`
	UserId int64 `json:"-"`
}

// ----------------------
// QUERIES

type GetOrgUsersQuery struct {
	OrgId int64
	Query string
	Limit int

	Result []*OrgUserDTO
}

// ----------------------
// Projections and DTOs

type OrgUserDTO struct {
	OrgId         int64     `json:"orgId"`
	UserId        int64     `json:"userId"`
	Email         string    `json:"email"`
	AvatarUrl     string    `json:"avatarUrl"`
	Login         string    `json:"login"`
	Role          string    `json:"role"`
	LastSeenAt    time.Time `json:"lastSeenAt"`
	LastSeenAtAge string    `json:"lastSeenAtAge"`
}
<commit_msg>Update org_user.go<commit_after>package models

import (
	"encoding/json"
	"errors"
	"fmt"
	"time"
)

// Typed errors
var (
	ErrInvalidRoleType     = errors.New("Invalid role type")
	ErrLastOrgAdmin        = errors.New("Cannot remove last organization admin")
	ErrOrgUserNotFound     = errors.New("Cannot find the organization user")
	ErrOrgUserAlreadyAdded = errors.New("User is already added to organization")
)

type RoleType string

const (
	ROLE_VIEWER RoleType = "Viewer"
	ROLE_EDITOR RoleType = "Editor"
	ROLE_ADMIN  RoleType = "Admin"
)

func (r RoleType) IsValid() bool {
	return r == ROLE_VIEWER || r == ROLE_ADMIN || r == ROLE_EDITOR
}

func (r RoleType) Includes(other RoleType) bool {
	if r == ROLE_ADMIN {
		return true
	}

	if r == ROLE_EDITOR {
		return other != ROLE_ADMIN
	}

	if r == ROLE_VIEWER {
		return other == ROLE_VIEWER
	}

	return false
}

func (r *RoleType) UnmarshalJSON(data []byte) error {
	var str string
	err := json.Unmarshal(data, &str)
	if err != nil {
		return err
	}

	*r = RoleType(str)

	if !(*r).IsValid() {
		if (*r) != "" {
			return fmt.Errorf("JSON validation error: invalid role value: %s", *r)
		}

		*r = ROLE_VIEWER
	}

	return nil
}

type OrgUser struct {
	Id      int64
	OrgId   int64
	UserId  int64
	Role    RoleType
	Created time.Time
	Updated time.Time
}

// ---------------------
// COMMANDS

type RemoveOrgUserCommand struct {
	UserId                   int64
	OrgId                    int64
	ShouldDeleteOrphanedUser bool
	UserWasDeleted           bool
}

type AddOrgUserCommand struct {
	LoginOrEmail string   `json:"loginOrEmail" binding:"Required"`
	Role         RoleType `json:"role" binding:"Required"`

	OrgId  int64 `json:"-"`
	UserId int64 `json:"-"`
}

type UpdateOrgUserCommand struct {
	Role RoleType `json:"role" binding:"Required"`

	OrgId  int64 `json:"-"`
	UserId int64 `json:"-"`
}

// ----------------------
// QUERIES

type GetOrgUsersQuery struct {
	OrgId int64
	Query string
	Limit int

	Result []*OrgUserDTO
}

// ----------------------
// Projections and DTOs

type OrgUserDTO struct {
	OrgId         int64     `json:"orgId"`
	UserId        int64     `json:"userId"`
	Email         string    `json:"email"`
	AvatarUrl     string    `json:"avatarUrl"`
	Login         string    `json:"login"`
	Role          string    `json:"role"`
	LastSeenAt    time.Time `json:"lastSeenAt"`
	LastSeenAtAge string    `json:"lastSeenAtAge"`
}
<|endoftext|>


<commit_before>package crtapp

import (
	"net/url"
	"path"
	"strings"

	"github.com/spf13/cobra"
	"github.com/srinandan/apigeecli/cmd/shared"
)

var Cmd = &cobra.Command{
	Use:   "create",
	Short: "Create a Developer App",
	Long:  "Create a Developer App",
	RunE: func(cmd *cobra.Command, args []string) error {
		u, _ := url.Parse(shared.BaseURL)

		app := []string{}

		app = append(app, "\"name\":\""+name+"\"")
		app = append(app, "\"apiProducts\":[\""+getArrayStr(apiProducts)+"\"]")

		if callback != "" {
			app = append(app, "\"callbackUrl\":\""+callback+"\"")
		}

		if expires != "" {
			app = append(app, "\"keyExpiresIn\":\""+expires+"\"")
		}

		if len(scopes) > 0 {
			app = append(app, "\"scopes\":[\""+getArrayStr(scopes)+"\"]")
		}

		if len(attrs) != 0 {
			attributes := []string{}
			for key, value := range attrs {
				attributes = append(attributes, "{\"name\":\""+key+"\",\"value\":\""+value+"\"}")
			}
			attributesStr := "\"attributes\":[" + strings.Join(attributes, ",") + "]"
			app = append(app, attributesStr)
		}

		payload := "{" + strings.Join(app, ",") + "}"
		u.Path = path.Join(u.Path, shared.RootArgs.Org, "developers", email, "apps")
		return shared.HttpClient(u.String(), payload)
	},
}

var name, email, expires, callback string
var apiProducts, scopes []string
var attrs map[string]string

func init() {

	Cmd.Flags().StringVarP(&name, "name", "n",
		"", "Name of the developer app")
	Cmd.Flags().StringVarP(&email, "email", "e",
		"", "The developer's email or id")
	Cmd.Flags().StringVarP(&expires, "expires", "x",
		"", "A setting, in milliseconds, for the lifetime of the consumer key")
	Cmd.Flags().StringVarP(&callback, "callback", "c",
		"", "The callbackUrl is used by OAuth")
	Cmd.Flags().StringArrayVarP(&apiProducts, "prods", "p",
		[]string{}, "A list of api products")
	Cmd.Flags().StringArrayVarP(&scopes, "scopes", "s",
		[]string{}, "OAuth scopes")
	Cmd.Flags().StringToStringVar(&attrs, "attrs",
		nil, "Custom attributes")

	_ = Cmd.MarkFlagRequired("name")
	_ = Cmd.MarkFlagRequired("email")
	_ = Cmd.MarkFlagRequired("prods")
}

func getArrayStr(str []string) string {
	tmp := strings.Join(str, ",")
	tmp = strings.ReplaceAll(tmp, ",", "\",\"")
	return tmp
}
<commit_msg>prod is optional<commit_after>package crtapp

import (
	"net/url"
	"path"
	"strings"

	"github.com/spf13/cobra"
	"github.com/srinandan/apigeecli/cmd/shared"
)

var Cmd = &cobra.Command{
	Use:   "create",
	Short: "Create a Developer App",
	Long:  "Create a Developer App",
	RunE: func(cmd *cobra.Command, args []string) error {
		u, _ := url.Parse(shared.BaseURL)

		app := []string{}

		app = append(app, "\"name\":\""+name+"\"")

		if len(apiProducts) > 0 {
			app = append(app, "\"apiProducts\":[\""+getArrayStr(apiProducts)+"\"]")
		}

		if callback != "" {
			app = append(app, "\"callbackUrl\":\""+callback+"\"")
		}

		if expires != "" {
			app = append(app, "\"keyExpiresIn\":\""+expires+"\"")
		}

		if len(scopes) > 0 {
			app = append(app, "\"scopes\":[\""+getArrayStr(scopes)+"\"]")
		}

		if len(attrs) != 0 {
			attributes := []string{}
			for key, value := range attrs {
				attributes = append(attributes, "{\"name\":\""+key+"\",\"value\":\""+value+"\"}")
			}
			attributesStr := "\"attributes\":[" + strings.Join(attributes, ",") + "]"
			app = append(app, attributesStr)
		}

		payload := "{" + strings.Join(app, ",") + "}"
		u.Path = path.Join(u.Path, shared.RootArgs.Org, "developers", email, "apps")
		return shared.HttpClient(u.String(), payload)
	},
}

var name, email, expires, callback string
var apiProducts, scopes []string
var attrs map[string]string

func init() {

	Cmd.Flags().StringVarP(&name, "name", "n",
		"", "Name of the developer app")
	Cmd.Flags().StringVarP(&email, "email", "e",
		"", "The developer's email or id")
	Cmd.Flags().StringVarP(&expires, "expires", "x",
		"", "A setting, in milliseconds, for the lifetime of the consumer key")
	Cmd.Flags().StringVarP(&callback, "callback", "c",
		"", "The callbackUrl is used by OAuth")
	Cmd.Flags().StringArrayVarP(&apiProducts, "prods", "p",
		[]string{}, "A list of api products")
	Cmd.Flags().StringArrayVarP(&scopes, "scopes", "s",
		[]string{}, "OAuth scopes")
	Cmd.Flags().StringToStringVar(&attrs, "attrs",
		nil, "Custom attributes")

	_ = Cmd.MarkFlagRequired("name")
	_ = Cmd.MarkFlagRequired("email")
}

func getArrayStr(str []string) string {
	tmp := strings.Join(str, ",")
	tmp = strings.ReplaceAll(tmp, ",", "\",\"")
	return tmp
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

package types

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"

	"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"

	istioV1alpha3 "istio.io/api/networking/v1alpha3"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/ingress-gce/pkg/annotations"
)

type NetworkEndpointType string

const (
	VmIpPortEndpointType      = NetworkEndpointType("GCE_VM_IP_PORT")
	VmPrimaryIpEndpointType   = NetworkEndpointType("GCE_VM_PRIMARY_IP")
	NonGCPPrivateEndpointType = NetworkEndpointType("NON_GCP_PRIVATE_IP_PORT")
)

// SvcPortTuple is the tuple representing one service port
type SvcPortTuple struct {
	// Port is the service port number
	Port int32
	// Name is the service port name
	Name string
	// TargetPort is the service target port.
	// This can be a port number or named port
	TargetPort string
}

// String returns the string representation of SvcPortTuple
func (t SvcPortTuple) String() string {
	return fmt.Sprintf("%s/%v-%s", t.Name, t.Port, t.TargetPort)
}

// SvcPortTupleSet is a set of SvcPortTuple
type SvcPortTupleSet map[SvcPortTuple]struct{}

// NewSvcPortTupleSet returns SvcPortTupleSet with the input tuples
func NewSvcPortTupleSet(tuples ...SvcPortTuple) SvcPortTupleSet {
	set := SvcPortTupleSet{}
	set.Insert(tuples...)
	return set
}

// Insert inserts a SvcPortTuple into SvcPortTupleSet
func (set SvcPortTupleSet) Insert(tuples ...SvcPortTuple) {
	for _, tuple := range tuples {
		set[tuple] = struct{}{}
	}
}

// Get returns the SvcPortTuple with matching svc port if found
func (set SvcPortTupleSet) Get(svcPort int32) (SvcPortTuple, bool) {
	for tuple := range set {
		if svcPort == tuple.Port {
			return tuple, true
		}
	}
	return SvcPortTuple{}, false
}

// PortInfo contains information associated with service port
type PortInfo struct {
	// PortTuple is port tuple of a service.
	PortTuple SvcPortTuple

	// Subset name, subset is defined in Istio:DestinationRule, this is only used
	// when --enable-csm=true.
	Subset string

	// Subset label, should set together with Subset.
	SubsetLabels string

	// NegName is the name of the NEG
	NegName string
	// ReadinessGate indicates if the NEG associated with the port has NEG readiness gate enabled
	// This is enabled with service port is reference by ingress.
	// If the service port is only exposed as stand alone NEG, it should not be enbled.
	ReadinessGate bool
}

// PortInfoMapKey is the Key of PortInfoMap
type PortInfoMapKey struct {
	// ServicePort is the service port
	ServicePort int32

	// Istio:DestinationRule Subset, only used when --enable-csm=true
	Subset string
}

// PortInfoMap is a map of PortInfoMapKey:PortInfo
type PortInfoMap map[PortInfoMapKey]PortInfo

func NewPortInfoMap(namespace, name string, svcPortTupleSet SvcPortTupleSet, namer NetworkEndpointGroupNamer, readinessGate bool) PortInfoMap {
	ret := PortInfoMap{}
	for svcPortTuple := range svcPortTupleSet {
		ret[PortInfoMapKey{svcPortTuple.Port, ""}] = PortInfo{
			PortTuple:     svcPortTuple,
			NegName:       namer.NEG(namespace, name, svcPortTuple.Port),
			ReadinessGate: readinessGate,
		}
	}
	return ret
}

// NewPortInfoMapWithDestinationRule create PortInfoMap based on a gaven DesinationRule.
// Return error message if the DestinationRule contains duplicated subsets.
func NewPortInfoMapWithDestinationRule(namespace, name string, svcPortTupleSet SvcPortTupleSet, namer NetworkEndpointGroupNamer, readinessGate bool,
	destinationRule *istioV1alpha3.DestinationRule) (PortInfoMap, error) {
	ret := PortInfoMap{}
	var duplicateSubset []string
	for _, subset := range destinationRule.Subsets {
		for tuple := range svcPortTupleSet {
			key := PortInfoMapKey{tuple.Port, subset.Name}
			if _, ok := ret[key]; ok {
				duplicateSubset = append(duplicateSubset, subset.Name)
			}
			ret[key] = PortInfo{
				PortTuple:     tuple,
				NegName:       namer.NEGWithSubset(namespace, name, subset.Name, tuple.Port),
				ReadinessGate: readinessGate,
				Subset:        subset.Name,
				SubsetLabels:  labels.Set(subset.Labels).String(),
			}
		}
	}
	if len(duplicateSubset) != 0 {
		return ret, fmt.Errorf("Duplicated subsets: %s", strings.Join(duplicateSubset, ", "))
	}
	return ret, nil
}

// Merge merges p2 into p1 PortInfoMap
// It assumes the same key (service port) will have the same target port and negName
// If not, it will throw error
// If a key in p1 or p2 has readiness gate enabled, the merged port info will also has readiness gate enabled
func (p1 PortInfoMap) Merge(p2 PortInfoMap) error {
	var err error
	for mapKey, portInfo := range p2 {
		mergedInfo := PortInfo{}
		if existingPortInfo, ok := p1[mapKey]; ok {
			if existingPortInfo.PortTuple != portInfo.PortTuple {
				return fmt.Errorf("for service port %v, port tuple in existing map is %q, but the merge map has %q", mapKey, existingPortInfo.PortTuple, portInfo.PortTuple)
			}
			if existingPortInfo.NegName != portInfo.NegName {
				return fmt.Errorf("for service port %v, NEG name in existing map is %q, but the merge map has %q", mapKey, existingPortInfo.NegName, portInfo.NegName)
			}
			if existingPortInfo.Subset != portInfo.Subset {
				return fmt.Errorf("for service port %v, Subset name in existing map is %q, but the merge map has %q", mapKey, existingPortInfo.Subset, portInfo.Subset)
			}
			mergedInfo.ReadinessGate = existingPortInfo.ReadinessGate
		}
		mergedInfo.PortTuple = portInfo.PortTuple
		mergedInfo.NegName = portInfo.NegName
		// Turn on the readiness gate if one of them is on
		mergedInfo.ReadinessGate = mergedInfo.ReadinessGate || portInfo.ReadinessGate
		mergedInfo.Subset = portInfo.Subset
		mergedInfo.SubsetLabels = portInfo.SubsetLabels

		p1[mapKey] = mergedInfo
	}
	return err
}

// Difference returns the entries of PortInfoMap which satisfies one of the following 2 conditions:
// 1. portInfo entry is not the present in p1
// 2. or the portInfo entry is not the same in p1 and p2.
func (p1 PortInfoMap) Difference(p2 PortInfoMap) PortInfoMap {
	result := make(PortInfoMap)
	for mapKey, p1PortInfo := range p1 {
		p2PortInfo, ok := p2[mapKey]
		if ok && reflect.DeepEqual(p1[mapKey], p2PortInfo) {
			continue
		}
		result[mapKey] = p1PortInfo
	}
	return result
}

func (p1 PortInfoMap) ToPortNegMap() annotations.PortNegMap {
	ret := annotations.PortNegMap{}
	for mapKey, portInfo := range p1 {
		ret[strconv.Itoa(int(mapKey.ServicePort))] = portInfo.NegName
	}
	return ret
}

func (p1 PortInfoMap) ToPortSubsetNegMap() annotations.PortSubsetNegMap {
	ret := annotations.PortSubsetNegMap{}
	for mapKey, portInfo := range p1 {
		if m, ok := ret[mapKey.Subset]; ok {
			m[strconv.Itoa(int(mapKey.ServicePort))] = portInfo.NegName
		} else {
			ret[mapKey.Subset] = map[string]string{strconv.Itoa(int(mapKey.ServicePort)): portInfo.NegName}
		}
	}
	return ret
}

// NegsWithReadinessGate returns the NegNames which has readiness gate enabled
func (p1 PortInfoMap) NegsWithReadinessGate() sets.String {
	ret := sets.NewString()
	for _, info := range p1 {
		if info.ReadinessGate {
			ret.Insert(info.NegName)
		}
	}
	return ret
}

// NegSyncerKey includes information to uniquely identify a NEG syncer
type NegSyncerKey struct {
	// Namespace of service
	Namespace string
	// Name of service
	Name string
	// PortTuple is the port tuple of the service backing the NEG
	PortTuple SvcPortTuple

	// Subset name, subset is defined in Istio:DestinationRule, this is only used
	// when --enable-csm=true.
	Subset string

	// Subset label, should set together with Subset.
	SubsetLabels string

	// NegType is the type of the network endpoints in this NEG.
	NegType NetworkEndpointType
}

func (key NegSyncerKey) String() string {
	return fmt.Sprintf("%s/%s-%s-%s", key.Namespace, key.Name, key.Subset, key.PortTuple.String())
}

// GetAPIVersion returns the compute API version to be used in order
// to create the negType specified in the given NegSyncerKey.
func (key NegSyncerKey) GetAPIVersion() meta.Version {
	switch key.NegType {
	case VmPrimaryIpEndpointType:
		return meta.VersionAlpha
	default:
		return meta.VersionGA
	}
}

// EndpointPodMap is a map from network endpoint to a namespaced name of a pod
type EndpointPodMap map[NetworkEndpoint]types.NamespacedName
<commit_msg>Use Alpha API for hybrid NEGs.<commit_after>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

package types

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"

	"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"

	istioV1alpha3 "istio.io/api/networking/v1alpha3"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/ingress-gce/pkg/annotations"
)

type NetworkEndpointType string

const (
	VmIpPortEndpointType      = NetworkEndpointType("GCE_VM_IP_PORT")
	VmPrimaryIpEndpointType   = NetworkEndpointType("GCE_VM_PRIMARY_IP")
	NonGCPPrivateEndpointType = NetworkEndpointType("NON_GCP_PRIVATE_IP_PORT")
)

// SvcPortTuple is the tuple representing one service port
type SvcPortTuple struct {
	// Port is the service port number
	Port int32
	// Name is the service port name
	Name string
	// TargetPort is the service target port.
	// This can be a port number or named port
	TargetPort string
}

// String returns the string representation of SvcPortTuple
func (t SvcPortTuple) String() string {
	return fmt.Sprintf("%s/%v-%s", t.Name, t.Port, t.TargetPort)
}

// SvcPortTupleSet is a set of SvcPortTuple
type SvcPortTupleSet map[SvcPortTuple]struct{}

// NewSvcPortTupleSet returns SvcPortTupleSet with the input tuples
func NewSvcPortTupleSet(tuples ...SvcPortTuple) SvcPortTupleSet {
	set := SvcPortTupleSet{}
	set.Insert(tuples...)
	return set
}

// Insert inserts a SvcPortTuple into SvcPortTupleSet
func (set SvcPortTupleSet) Insert(tuples ...SvcPortTuple) {
	for _, tuple := range tuples {
		set[tuple] = struct{}{}
	}
}

// Get returns the SvcPortTuple with matching svc port if found
func (set SvcPortTupleSet) Get(svcPort int32) (SvcPortTuple, bool) {
	for tuple := range set {
		if svcPort == tuple.Port {
			return tuple, true
		}
	}
	return SvcPortTuple{}, false
}

// PortInfo contains information associated with service port
type PortInfo struct {
	// PortTuple is port tuple of a service.
	PortTuple SvcPortTuple

	// Subset name, subset is defined in Istio:DestinationRule, this is only used
	// when --enable-csm=true.
	Subset string

	// Subset label, should set together with Subset.
	SubsetLabels string

	// NegName is the name of the NEG
	NegName string
	// ReadinessGate indicates if the NEG associated with the port has NEG readiness gate enabled
	// This is enabled with service port is reference by ingress.
	// If the service port is only exposed as stand alone NEG, it should not be enbled.
	ReadinessGate bool
}

// PortInfoMapKey is the Key of PortInfoMap
type PortInfoMapKey struct {
	// ServicePort is the service port
	ServicePort int32

	// Istio:DestinationRule Subset, only used when --enable-csm=true
	Subset string
}

// PortInfoMap is a map of PortInfoMapKey:PortInfo
type PortInfoMap map[PortInfoMapKey]PortInfo

func NewPortInfoMap(namespace, name string, svcPortTupleSet SvcPortTupleSet, namer NetworkEndpointGroupNamer, readinessGate bool) PortInfoMap {
	ret := PortInfoMap{}
	for svcPortTuple := range svcPortTupleSet {
		ret[PortInfoMapKey{svcPortTuple.Port, ""}] = PortInfo{
			PortTuple:     svcPortTuple,
			NegName:       namer.NEG(namespace, name, svcPortTuple.Port),
			ReadinessGate: readinessGate,
		}
	}
	return ret
}

// NewPortInfoMapWithDestinationRule create PortInfoMap based on a gaven DesinationRule.
// Return error message if the DestinationRule contains duplicated subsets.
func NewPortInfoMapWithDestinationRule(namespace, name string, svcPortTupleSet SvcPortTupleSet, namer NetworkEndpointGroupNamer, readinessGate bool,
	destinationRule *istioV1alpha3.DestinationRule) (PortInfoMap, error) {
	ret := PortInfoMap{}
	var duplicateSubset []string
	for _, subset := range destinationRule.Subsets {
		for tuple := range svcPortTupleSet {
			key := PortInfoMapKey{tuple.Port, subset.Name}
			if _, ok := ret[key]; ok {
				duplicateSubset = append(duplicateSubset, subset.Name)
			}
			ret[key] = PortInfo{
				PortTuple:     tuple,
				NegName:       namer.NEGWithSubset(namespace, name, subset.Name, tuple.Port),
				ReadinessGate: readinessGate,
				Subset:        subset.Name,
				SubsetLabels:  labels.Set(subset.Labels).String(),
			}
		}
	}
	if len(duplicateSubset) != 0 {
		return ret, fmt.Errorf("Duplicated subsets: %s", strings.Join(duplicateSubset, ", "))
	}
	return ret, nil
}

// Merge merges p2 into p1 PortInfoMap
// It assumes the same key (service port) will have the same target port and negName
// If not, it will throw error
// If a key in p1 or p2 has readiness gate enabled, the merged port info will also has readiness gate enabled
func (p1 PortInfoMap) Merge(p2 PortInfoMap) error {
	var err error
	for mapKey, portInfo := range p2 {
		mergedInfo := PortInfo{}
		if existingPortInfo, ok := p1[mapKey]; ok {
			if existingPortInfo.PortTuple != portInfo.PortTuple {
				return fmt.Errorf("for service port %v, port tuple in existing map is %q, but the merge map has %q", mapKey, existingPortInfo.PortTuple, portInfo.PortTuple)
			}
			if existingPortInfo.NegName != portInfo.NegName {
				return fmt.Errorf("for service port %v, NEG name in existing map is %q, but the merge map has %q", mapKey, existingPortInfo.NegName, portInfo.NegName)
			}
			if existingPortInfo.Subset != portInfo.Subset {
				return fmt.Errorf("for service port %v, Subset name in existing map is %q, but the merge map has %q", mapKey, existingPortInfo.Subset, portInfo.Subset)
			}
			mergedInfo.ReadinessGate = existingPortInfo.ReadinessGate
		}
		mergedInfo.PortTuple = portInfo.PortTuple
		mergedInfo.NegName = portInfo.NegName
		// Turn on the readiness gate if one of them is on
		mergedInfo.ReadinessGate = mergedInfo.ReadinessGate || portInfo.ReadinessGate
		mergedInfo.Subset = portInfo.Subset
		mergedInfo.SubsetLabels = portInfo.SubsetLabels

		p1[mapKey] = mergedInfo
	}
	return err
}

// Difference returns the entries of PortInfoMap which satisfies one of the following 2 conditions:
// 1. portInfo entry is not the present in p1
// 2. or the portInfo entry is not the same in p1 and p2.
func (p1 PortInfoMap) Difference(p2 PortInfoMap) PortInfoMap {
	result := make(PortInfoMap)
	for mapKey, p1PortInfo := range p1 {
		p2PortInfo, ok := p2[mapKey]
		if ok && reflect.DeepEqual(p1[mapKey], p2PortInfo) {
			continue
		}
		result[mapKey] = p1PortInfo
	}
	return result
}

func (p1 PortInfoMap) ToPortNegMap() annotations.PortNegMap {
	ret := annotations.PortNegMap{}
	for mapKey, portInfo := range p1 {
		ret[strconv.Itoa(int(mapKey.ServicePort))] = portInfo.NegName
	}
	return ret
}

func (p1 PortInfoMap) ToPortSubsetNegMap() annotations.PortSubsetNegMap {
	ret := annotations.PortSubsetNegMap{}
	for mapKey, portInfo := range p1 {
		if m, ok := ret[mapKey.Subset]; ok {
			m[strconv.Itoa(int(mapKey.ServicePort))] = portInfo.NegName
		} else {
			ret[mapKey.Subset] = map[string]string{strconv.Itoa(int(mapKey.ServicePort)): portInfo.NegName}
		}
	}
	return ret
}

// NegsWithReadinessGate returns the NegNames which has readiness gate enabled
func (p1 PortInfoMap) NegsWithReadinessGate() sets.String {
	ret := sets.NewString()
	for _, info := range p1 {
		if info.ReadinessGate {
			ret.Insert(info.NegName)
		}
	}
	return ret
}

// NegSyncerKey includes information to uniquely identify a NEG syncer
type NegSyncerKey struct {
	// Namespace of service
	Namespace string
	// Name of service
	Name string
	// PortTuple is the port tuple of the service backing the NEG
	PortTuple SvcPortTuple

	// Subset name, subset is defined in Istio:DestinationRule, this is only used
	// when --enable-csm=true.
	Subset string

	// Subset label, should set together with Subset.
	SubsetLabels string

	// NegType is the type of the network endpoints in this NEG.
	NegType NetworkEndpointType
}

func (key NegSyncerKey) String() string {
	return fmt.Sprintf("%s/%s-%s-%s", key.Namespace, key.Name, key.Subset, key.PortTuple.String())
}

// GetAPIVersion returns the compute API version to be used in order
// to create the negType specified in the given NegSyncerKey.
func (key NegSyncerKey) GetAPIVersion() meta.Version {
	switch key.NegType {
	case VmPrimaryIpEndpointType:
		return meta.VersionAlpha
	case NonGCPPrivateEndpointType:
		return meta.VersionAlpha
	default:
		return meta.VersionGA
	}
}

// EndpointPodMap is a map from network endpoint to a namespaced name of a pod
type EndpointPodMap map[NetworkEndpoint]types.NamespacedName
<|endoftext|>


<commit_before>// Copyright 2015 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"testing"
	"text/template"
)

var gopath string

func TestInit(t *testing.T) {
	buf := new(bytes.Buffer)
	gopathorig := os.Getenv("GOPATH")
	defer func() {
		xout = os.Stderr
		buildN = false
		buildX = false
		initU = false
		os.Setenv("GOPATH", gopathorig)
	}()
	xout = buf
	buildN = true
	buildX = true
	initU = true

	// Test that first GOPATH element is chosen correctly.
	gopath = "/GOPATH1"
	paths := []string{"/GOPATH1", "/path2", "/path3"}
	os.Setenv("GOPATH", strings.Join(paths, string(os.PathListSeparator)))
	os.Setenv("GOROOT_BOOTSTRAP", "go1.4")
	if goos == "windows" {
		os.Setenv("HOMEDRIVE", "C:")
	}

	err := runInit(cmdInit)
	if err != nil {
		t.Log(buf.String())
		t.Fatal(err)
	}

	diff, err := diffOutput(buf.String(), initTmpl)
	if err != nil {
		t.Fatalf("computing diff failed: %v", err)
	}
	if diff != "" {
		t.Errorf("unexpected output:\n%s", diff)
	}
}

func diffOutput(got string, wantTmpl *template.Template) (string, error) {
	got = filepath.ToSlash(got)

	wantBuf := new(bytes.Buffer)
	data := outputData{
		NDK:       ndkVersion,
		GOOS:      goos,
		GOARCH:    goarch,
		GOPATH:    gopath,
		NDKARCH:   ndkarch,
		Xproj:     projPbxproj,
		Xcontents: contentsJSON,
		Xinfo:     infoplistTmplData{Name: "Basic"},
		NumCPU:    strconv.Itoa(runtime.NumCPU()),
	}
	if goos == "windows" {
		data.EXE = ".exe"
	}
	if err := wantTmpl.Execute(wantBuf, data); err != nil {
		return "", err
	}
	want := wantBuf.String()
	if got != want {
		return diff(got, want)
	}
	return "", nil
}

type outputData struct {
	NDK       string
	GOOS      string
	GOARCH    string
	GOPATH    string
	NDKARCH   string
	EXE       string // .extension for executables. (ex. ".exe" for windows)
	Xproj     string
	Xcontents string
	Xinfo     infoplistTmplData
	NumCPU    string
}

var initTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
mkdir -p $GOMOBILE/android-{{.NDK}}
WORK={{.GOPATH}}/pkg/gomobile/work
mkdir -p $GOMOBILE/dl
curl -o$GOMOBILE/dl/gomobile-{{.NDK}}-{{.GOOS}}-{{.NDKARCH}}.tar.gz https://dl.google.com/go/mobile/gomobile-{{.NDK}}-{{.GOOS}}-{{.NDKARCH}}.tar.gz
tar xfz $GOMOBILE/dl/gomobile-{{.NDK}}-{{.GOOS}}-{{.NDKARCH}}.tar.gz
mkdir -p $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr
mv $WORK/android-{{.NDK}}/platforms/android-15/arch-arm/usr/include $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr/include
mv $WORK/android-{{.NDK}}/platforms/android-15/arch-arm/usr/lib $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr/lib
mv $WORK/android-{{.NDK}}/toolchains/arm-linux-androideabi-4.8/prebuilt/{{.GOOS}}-{{.NDKARCH}}/bin $GOMOBILE/android-{{.NDK}}/arm/bin
mv $WORK/android-{{.NDK}}/toolchains/arm-linux-androideabi-4.8/prebuilt/{{.GOOS}}-{{.NDKARCH}}/lib $GOMOBILE/android-{{.NDK}}/arm/lib
mv $WORK/android-{{.NDK}}/toolchains/arm-linux-androideabi-4.8/prebuilt/{{.GOOS}}-{{.NDKARCH}}/libexec $GOMOBILE/android-{{.NDK}}/arm/libexec
mkdir -p $GOMOBILE/android-{{.NDK}}/arm/arm-linux-androideabi/bin
ln -s $GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-ld{{.EXE}} $GOMOBILE/android-{{.NDK}}/arm/arm-linux-androideabi/bin/ld{{.EXE}}
ln -s $GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-as{{.EXE}} $GOMOBILE/android-{{.NDK}}/arm/arm-linux-androideabi/bin/as{{.EXE}}
ln -s $GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-gcc{{.EXE}} $GOMOBILE/android-{{.NDK}}/arm/arm-linux-androideabi/bin/gcc{{.EXE}}
ln -s $GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-g++{{.EXE}} $GOMOBILE/android-{{.NDK}}/arm/arm-linux-androideabi/bin/g++{{.EXE}}
mkdir -p $GOMOBILE/dl
curl -o$GOMOBILE/dl/gomobile-openal-soft-1.16.0.1.tar.gz https://dl.google.com/go/mobile/gomobile-openal-soft-1.16.0.1.tar.gz
tar xfz $GOMOBILE/dl/gomobile-openal-soft-1.16.0.1.tar.gz
mv $WORK/openal/include/AL $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr/include/AL
mkdir -p $GOMOBILE/android-{{.NDK}}/openal
mv $WORK/openal/lib $GOMOBILE/android-{{.NDK}}/openal/lib
{{if eq .GOOS "darwin"}}GOOS=android GOARCH=arm GOARM=7 CC=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-gcc{{.EXE}} CXX=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-g++ CGO_ENABLED=1 go install -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_android_arm -x std
GOOS=darwin GOARCH=arm GOARM=7 CC=clang-iphoneos CXX=clang-iphoneos CGO_CFLAGS=-isysroot=iphoneos -arch armv7 CGO_LDFLAGS=-isysroot=iphoneos -arch armv7 CGO_ENABLED=1 go install -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_darwin_arm -x std
GOOS=darwin GOARCH=arm64 CC=clang-iphoneos CXX=clang-iphoneos CGO_CFLAGS=-isysroot=iphoneos -arch arm64 CGO_LDFLAGS=-isysroot=iphoneos -arch arm64 CGO_ENABLED=1 go install -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_darwin_arm64 -x std
GOOS=darwin GOARCH=amd64 CC=clang-iphonesimulator CXX=clang-iphonesimulator CGO_CFLAGS=-isysroot=iphonesimulator -mios-simulator-version-min=6.1 -arch x86_64 CGO_LDFLAGS=-isysroot=iphonesimulator -mios-simulator-version-min=6.1 -arch x86_64 CGO_ENABLED=1 go install -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_darwin_amd64 -tags=ios -x std
{{end}}go version > $GOMOBILE/version
rm -r -f "$WORK"
`))
<commit_msg>cmd/gomobile: fix init test (linux)<commit_after>// Copyright 2015 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"testing"
	"text/template"
)

var gopath string

func TestInit(t *testing.T) {
	buf := new(bytes.Buffer)
	gopathorig := os.Getenv("GOPATH")
	defer func() {
		xout = os.Stderr
		buildN = false
		buildX = false
		initU = false
		os.Setenv("GOPATH", gopathorig)
	}()
	xout = buf
	buildN = true
	buildX = true
	initU = true

	// Test that first GOPATH element is chosen correctly.
	gopath = "/GOPATH1"
	paths := []string{"/GOPATH1", "/path2", "/path3"}
	os.Setenv("GOPATH", strings.Join(paths, string(os.PathListSeparator)))
	os.Setenv("GOROOT_BOOTSTRAP", "go1.4")
	if goos == "windows" {
		os.Setenv("HOMEDRIVE", "C:")
	}

	err := runInit(cmdInit)
	if err != nil {
		t.Log(buf.String())
		t.Fatal(err)
	}

	diff, err := diffOutput(buf.String(), initTmpl)
	if err != nil {
		t.Fatalf("computing diff failed: %v", err)
	}
	if diff != "" {
		t.Errorf("unexpected output:\n%s", diff)
	}
}

func diffOutput(got string, wantTmpl *template.Template) (string, error) {
	got = filepath.ToSlash(got)

	wantBuf := new(bytes.Buffer)
	data := outputData{
		NDK:       ndkVersion,
		GOOS:      goos,
		GOARCH:    goarch,
		GOPATH:    gopath,
		NDKARCH:   ndkarch,
		Xproj:     projPbxproj,
		Xcontents: contentsJSON,
		Xinfo:     infoplistTmplData{Name: "Basic"},
		NumCPU:    strconv.Itoa(runtime.NumCPU()),
	}
	if goos == "windows" {
		data.EXE = ".exe"
	}
	if err := wantTmpl.Execute(wantBuf, data); err != nil {
		return "", err
	}
	want := wantBuf.String()
	if got != want {
		return diff(got, want)
	}
	return "", nil
}

type outputData struct {
	NDK       string
	GOOS      string
	GOARCH    string
	GOPATH    string
	NDKARCH   string
	EXE       string // .extension for executables. (ex. ".exe" for windows)
	Xproj     string
	Xcontents string
	Xinfo     infoplistTmplData
	NumCPU    string
}

var initTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
mkdir -p $GOMOBILE/android-{{.NDK}}
WORK={{.GOPATH}}/pkg/gomobile/work
mkdir -p $GOMOBILE/dl
curl -o$GOMOBILE/dl/gomobile-{{.NDK}}-{{.GOOS}}-{{.NDKARCH}}.tar.gz https://dl.google.com/go/mobile/gomobile-{{.NDK}}-{{.GOOS}}-{{.NDKARCH}}.tar.gz
tar xfz $GOMOBILE/dl/gomobile-{{.NDK}}-{{.GOOS}}-{{.NDKARCH}}.tar.gz
mkdir -p $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr
mv $WORK/android-{{.NDK}}/platforms/android-15/arch-arm/usr/include $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr/include
mv $WORK/android-{{.NDK}}/platforms/android-15/arch-arm/usr/lib $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr/lib
mv $WORK/android-{{.NDK}}/toolchains/arm-linux-androideabi-4.8/prebuilt/{{.GOOS}}-{{.NDKARCH}}/bin $GOMOBILE/android-{{.NDK}}/arm/bin
mv $WORK/android-{{.NDK}}/toolchains/arm-linux-androideabi-4.8/prebuilt/{{.GOOS}}-{{.NDKARCH}}/lib $GOMOBILE/android-{{.NDK}}/arm/lib
mv $WORK/android-{{.NDK}}/toolchains/arm-linux-androideabi-4.8/prebuilt/{{.GOOS}}-{{.NDKARCH}}/libexec $GOMOBILE/android-{{.NDK}}/arm/libexec
mkdir -p $GOMOBILE/android-{{.NDK}}/arm/arm-linux-androideabi/bin
ln -s $GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-ld{{.EXE}} $GOMOBILE/android-{{.NDK}}/arm/arm-linux-androideabi/bin/ld{{.EXE}}
ln -s $GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-as{{.EXE}} $GOMOBILE/android-{{.NDK}}/arm/arm-linux-androideabi/bin/as{{.EXE}}
ln -s $GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-gcc{{.EXE}} $GOMOBILE/android-{{.NDK}}/arm/arm-linux-androideabi/bin/gcc{{.EXE}}
ln -s $GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-g++{{.EXE}} $GOMOBILE/android-{{.NDK}}/arm/arm-linux-androideabi/bin/g++{{.EXE}}
mkdir -p $GOMOBILE/dl
curl -o$GOMOBILE/dl/gomobile-openal-soft-1.16.0.1.tar.gz https://dl.google.com/go/mobile/gomobile-openal-soft-1.16.0.1.tar.gz
tar xfz $GOMOBILE/dl/gomobile-openal-soft-1.16.0.1.tar.gz
mv $WORK/openal/include/AL $GOMOBILE/android-{{.NDK}}/arm/sysroot/usr/include/AL
mkdir -p $GOMOBILE/android-{{.NDK}}/openal
mv $WORK/openal/lib $GOMOBILE/android-{{.NDK}}/openal/lib
GOOS=android GOARCH=arm GOARM=7 CC=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-gcc{{.EXE}} CXX=$GOMOBILE/android-{{.NDK}}/arm/bin/arm-linux-androideabi-g++ CGO_ENABLED=1 go install -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_android_arm -x std
{{if eq .GOOS "darwin"}}GOOS=darwin GOARCH=arm GOARM=7 CC=clang-iphoneos CXX=clang-iphoneos CGO_CFLAGS=-isysroot=iphoneos -arch armv7 CGO_LDFLAGS=-isysroot=iphoneos -arch armv7 CGO_ENABLED=1 go install -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_darwin_arm -x std
GOOS=darwin GOARCH=arm64 CC=clang-iphoneos CXX=clang-iphoneos CGO_CFLAGS=-isysroot=iphoneos -arch arm64 CGO_LDFLAGS=-isysroot=iphoneos -arch arm64 CGO_ENABLED=1 go install -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_darwin_arm64 -x std
GOOS=darwin GOARCH=amd64 CC=clang-iphonesimulator CXX=clang-iphonesimulator CGO_CFLAGS=-isysroot=iphonesimulator -mios-simulator-version-min=6.1 -arch x86_64 CGO_LDFLAGS=-isysroot=iphonesimulator -mios-simulator-version-min=6.1 -arch x86_64 CGO_ENABLED=1 go install -p={{.NumCPU}} -pkgdir=$GOMOBILE/pkg_darwin_amd64 -tags=ios -x std
{{end}}go version > $GOMOBILE/version
rm -r -f "$WORK"
`))
<|endoftext|>


<commit_before>// Copyright 2016-2019 Authors of Cilium
//
// 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 policy

import (
	"github.com/cilium/cilium/pkg/identity"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/policy/trafficdirection"

	"github.com/sirupsen/logrus"
)

var (
	// localHostKey represents an ingress L3 allow from the local host.
	localHostKey = Key{
		Identity:         identity.ReservedIdentityHost.Uint32(),
		TrafficDirection: trafficdirection.Ingress.Uint8(),
	}
)

// MapState is a state of a policy map.
type MapState map[Key]MapStateEntry

// Key is the userspace representation of a policy key in BPF. It is
// intentionally duplicated from pkg/maps/policymap to avoid pulling in the
// BPF dependency to this package.
type Key struct {
	// Identity is the numeric identity to / from which traffic is allowed.
	Identity uint32
	// DestPort is the port at L4 to / from which traffic is allowed, in
	// host-byte order.
	DestPort uint16
	// NextHdr is the protocol which is allowed.
	Nexthdr uint8
	// TrafficDirection indicates in which direction Identity is allowed
	// communication (egress or ingress).
	TrafficDirection uint8
}

// IsIngress returns true if the key refers to an ingress policy key
func (k Key) IsIngress() bool {
	return k.TrafficDirection == trafficdirection.Ingress.Uint8()
}

// IsEgress returns true if the key refers to an egress policy key
func (k Key) IsEgress() bool {
	return k.TrafficDirection == trafficdirection.Egress.Uint8()
}

// MapStateEntry is the configuration associated with a Key in a
// MapState. This is a minimized version of policymap.PolicyEntry.
type MapStateEntry struct {
	// The proxy port, in host byte order.
	// If 0 (default), there is no proxy redirection for the corresponding
	// Key.
	ProxyPort uint16
}

// DetermineAllowLocalhostIngress determines whether communication should be allowed
// from the localhost. It inserts the Key corresponding to the localhost in
// the desiredPolicyKeys if the localhost is allowed to communicate with the
// endpoint.
func (keys MapState) DetermineAllowLocalhostIngress(l4Policy *L4Policy) {

	if option.Config.AlwaysAllowLocalhost() || (l4Policy != nil && l4Policy.HasRedirect()) {
		keys[localHostKey] = MapStateEntry{}
	}
}

// AllowAllIdentities translates all identities in selectorCache to their
// corresponding Keys in the specified direction (ingress, egress) which allows
// all at L3.
func (keys MapState) AllowAllIdentities(ingress, egress bool) {
	if ingress {
		keyToAdd := Key{
			Identity:         0,
			DestPort:         0,
			Nexthdr:          0,
			TrafficDirection: trafficdirection.Ingress.Uint8(),
		}
		keys[keyToAdd] = MapStateEntry{}
	}
	if egress {
		keyToAdd := Key{
			Identity:         0,
			DestPort:         0,
			Nexthdr:          0,
			TrafficDirection: trafficdirection.Egress.Uint8(),
		}
		keys[keyToAdd] = MapStateEntry{}
	}
}

// MapChanges collects updates to the endpoint policy on the
// granularity of individual mapstate key-value pairs for both adds
// and deletes. 'mutex' must be held for any access.
type MapChanges struct {
	mutex   lock.Mutex
	adds    MapState
	deletes MapState
}

// AccumulateMapChanges accumulates the given changes to the
// MapChanges, updating both maps for each add and delete, as
// applicable.
//
// The caller is responsible for making sure the same identity is not
// present in both 'adds' and 'deletes'.  Accross multiple calls we
// maintain the adds and deletes within the MapChanges are disjoint in
// cases where an identity is first added and then deleted, or first
// deleted and then added.
func (mc *MapChanges) AccumulateMapChanges(adds, deletes []identity.NumericIdentity,
	port uint16, proto uint8, direction trafficdirection.TrafficDirection) {
	key := Key{
		// The actual identity is set in the loops below
		Identity: 0,
		// NOTE: Port is in host byte-order!
		DestPort:         port,
		Nexthdr:          proto,
		TrafficDirection: direction.Uint8(),
	}
	value := MapStateEntry{
		ProxyPort: 0, // Will be updated by the caller when applicable
	}

	if option.Config.Debug {
		log.WithFields(logrus.Fields{
			logfields.AddedPolicyID:    adds,
			logfields.DeletedPolicyID:  deletes,
			logfields.Port:             port,
			logfields.Protocol:         proto,
			logfields.TrafficDirection: direction,
		}).Debug("AccumulateMapChanges")
	}

	mc.mutex.Lock()
	if len(adds) > 0 {
		if mc.adds == nil {
			mc.adds = make(MapState)
		}
		for _, id := range adds {
			key.Identity = id.Uint32()
			mc.adds[key] = value
			// Remove a potential previously deleted key
			if mc.deletes != nil {
				delete(mc.deletes, key)
			}
		}
	}
	if len(deletes) > 0 {
		if mc.deletes == nil {
			mc.deletes = make(MapState)
		}
		for _, id := range deletes {
			key.Identity = id.Uint32()
			mc.deletes[key] = value
			// Remove a potential previously added key
			if mc.adds != nil {
				delete(mc.adds, key)
			}
		}
	}
	mc.mutex.Unlock()
}

// ConsumeMapChanges transfers the changes from MapChanges to the caller.
// May return nil maps.
func (mc *MapChanges) ConsumeMapChanges() (adds, deletes MapState) {
	mc.mutex.Lock()
	adds = mc.adds
	mc.adds = nil
	deletes = mc.deletes
	mc.deletes = nil
	mc.mutex.Unlock()
	return adds, deletes
}
<commit_msg>policy: Don't open localhost when allowing L7 traffic<commit_after>// Copyright 2016-2019 Authors of Cilium
//
// 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 policy

import (
	"github.com/cilium/cilium/pkg/identity"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/policy/trafficdirection"

	"github.com/sirupsen/logrus"
)

var (
	// localHostKey represents an ingress L3 allow from the local host.
	localHostKey = Key{
		Identity:         identity.ReservedIdentityHost.Uint32(),
		TrafficDirection: trafficdirection.Ingress.Uint8(),
	}
)

// MapState is a state of a policy map.
type MapState map[Key]MapStateEntry

// Key is the userspace representation of a policy key in BPF. It is
// intentionally duplicated from pkg/maps/policymap to avoid pulling in the
// BPF dependency to this package.
type Key struct {
	// Identity is the numeric identity to / from which traffic is allowed.
	Identity uint32
	// DestPort is the port at L4 to / from which traffic is allowed, in
	// host-byte order.
	DestPort uint16
	// NextHdr is the protocol which is allowed.
	Nexthdr uint8
	// TrafficDirection indicates in which direction Identity is allowed
	// communication (egress or ingress).
	TrafficDirection uint8
}

// IsIngress returns true if the key refers to an ingress policy key
func (k Key) IsIngress() bool {
	return k.TrafficDirection == trafficdirection.Ingress.Uint8()
}

// IsEgress returns true if the key refers to an egress policy key
func (k Key) IsEgress() bool {
	return k.TrafficDirection == trafficdirection.Egress.Uint8()
}

// MapStateEntry is the configuration associated with a Key in a
// MapState. This is a minimized version of policymap.PolicyEntry.
type MapStateEntry struct {
	// The proxy port, in host byte order.
	// If 0 (default), there is no proxy redirection for the corresponding
	// Key.
	ProxyPort uint16
}

// DetermineAllowLocalhostIngress determines whether communication should be allowed
// from the localhost. It inserts the Key corresponding to the localhost in
// the desiredPolicyKeys if the localhost is allowed to communicate with the
// endpoint.
func (keys MapState) DetermineAllowLocalhostIngress(l4Policy *L4Policy) {

	if option.Config.AlwaysAllowLocalhost() {
		keys[localHostKey] = MapStateEntry{}
	}
}

// AllowAllIdentities translates all identities in selectorCache to their
// corresponding Keys in the specified direction (ingress, egress) which allows
// all at L3.
func (keys MapState) AllowAllIdentities(ingress, egress bool) {
	if ingress {
		keyToAdd := Key{
			Identity:         0,
			DestPort:         0,
			Nexthdr:          0,
			TrafficDirection: trafficdirection.Ingress.Uint8(),
		}
		keys[keyToAdd] = MapStateEntry{}
	}
	if egress {
		keyToAdd := Key{
			Identity:         0,
			DestPort:         0,
			Nexthdr:          0,
			TrafficDirection: trafficdirection.Egress.Uint8(),
		}
		keys[keyToAdd] = MapStateEntry{}
	}
}

// MapChanges collects updates to the endpoint policy on the
// granularity of individual mapstate key-value pairs for both adds
// and deletes. 'mutex' must be held for any access.
type MapChanges struct {
	mutex   lock.Mutex
	adds    MapState
	deletes MapState
}

// AccumulateMapChanges accumulates the given changes to the
// MapChanges, updating both maps for each add and delete, as
// applicable.
//
// The caller is responsible for making sure the same identity is not
// present in both 'adds' and 'deletes'.  Accross multiple calls we
// maintain the adds and deletes within the MapChanges are disjoint in
// cases where an identity is first added and then deleted, or first
// deleted and then added.
func (mc *MapChanges) AccumulateMapChanges(adds, deletes []identity.NumericIdentity,
	port uint16, proto uint8, direction trafficdirection.TrafficDirection) {
	key := Key{
		// The actual identity is set in the loops below
		Identity: 0,
		// NOTE: Port is in host byte-order!
		DestPort:         port,
		Nexthdr:          proto,
		TrafficDirection: direction.Uint8(),
	}
	value := MapStateEntry{
		ProxyPort: 0, // Will be updated by the caller when applicable
	}

	if option.Config.Debug {
		log.WithFields(logrus.Fields{
			logfields.AddedPolicyID:    adds,
			logfields.DeletedPolicyID:  deletes,
			logfields.Port:             port,
			logfields.Protocol:         proto,
			logfields.TrafficDirection: direction,
		}).Debug("AccumulateMapChanges")
	}

	mc.mutex.Lock()
	if len(adds) > 0 {
		if mc.adds == nil {
			mc.adds = make(MapState)
		}
		for _, id := range adds {
			key.Identity = id.Uint32()
			mc.adds[key] = value
			// Remove a potential previously deleted key
			if mc.deletes != nil {
				delete(mc.deletes, key)
			}
		}
	}
	if len(deletes) > 0 {
		if mc.deletes == nil {
			mc.deletes = make(MapState)
		}
		for _, id := range deletes {
			key.Identity = id.Uint32()
			mc.deletes[key] = value
			// Remove a potential previously added key
			if mc.adds != nil {
				delete(mc.adds, key)
			}
		}
	}
	mc.mutex.Unlock()
}

// ConsumeMapChanges transfers the changes from MapChanges to the caller.
// May return nil maps.
func (mc *MapChanges) ConsumeMapChanges() (adds, deletes MapState) {
	mc.mutex.Lock()
	adds = mc.adds
	mc.adds = nil
	deletes = mc.deletes
	mc.deletes = nil
	mc.mutex.Unlock()
	return adds, deletes
}
<|endoftext|>


<commit_before>package rancher

import (
	"context"
	"fmt"
	"net/http"

	responsewriter "github.com/rancher/apiserver/pkg/middleware"
	"github.com/rancher/rancher/pkg/api/norman/customization/kontainerdriver"
	"github.com/rancher/rancher/pkg/api/norman/customization/podsecuritypolicytemplate"
	steveapi "github.com/rancher/rancher/pkg/api/steve"
	"github.com/rancher/rancher/pkg/api/steve/aggregation"
	"github.com/rancher/rancher/pkg/api/steve/proxy"
	"github.com/rancher/rancher/pkg/auth"
	"github.com/rancher/rancher/pkg/auth/audit"
	"github.com/rancher/rancher/pkg/auth/requests"
	"github.com/rancher/rancher/pkg/controllers/dashboard"
	"github.com/rancher/rancher/pkg/controllers/dashboardapi"
	managementauth "github.com/rancher/rancher/pkg/controllers/management/auth"
	crds "github.com/rancher/rancher/pkg/crds/dashboard"
	dashboarddata "github.com/rancher/rancher/pkg/data/dashboard"
	"github.com/rancher/rancher/pkg/features"
	"github.com/rancher/rancher/pkg/multiclustermanager"
	"github.com/rancher/rancher/pkg/namespace"
	"github.com/rancher/rancher/pkg/tls"
	"github.com/rancher/rancher/pkg/ui"
	"github.com/rancher/rancher/pkg/websocket"
	"github.com/rancher/rancher/pkg/wrangler"
	steveauth "github.com/rancher/steve/pkg/auth"
	steveserver "github.com/rancher/steve/pkg/server"
	"github.com/rancher/wrangler/pkg/k8scheck"
	"github.com/urfave/cli"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

type Options struct {
	ACMEDomains       cli.StringSlice
	AddLocal          string
	Embedded          bool
	BindHost          string
	HTTPListenPort    int
	HTTPSListenPort   int
	K8sMode           string
	Debug             bool
	Trace             bool
	NoCACerts         bool
	AuditLogPath      string
	AuditLogMaxage    int
	AuditLogMaxsize   int
	AuditLogMaxbackup int
	AuditLevel        int
	Features          string
}

type Rancher struct {
	Auth     steveauth.Middleware
	Handler  http.Handler
	Wrangler *wrangler.Context
	Steve    *steveserver.Server

	auditLog   *audit.LogWriter
	authServer *auth.Server
	opts       *Options
}

func New(ctx context.Context, clientConfg clientcmd.ClientConfig, opts *Options) (*Rancher, error) {
	var (
		authServer *auth.Server
	)

	if opts == nil {
		opts = &Options{}
	}

	restConfig, err := clientConfg.ClientConfig()
	if err != nil {
		return nil, err
	}

	restConfig, err = setupAndValidationRESTConfig(ctx, restConfig)
	if err != nil {
		return nil, err
	}

	wranglerContext, err := wrangler.NewContext(ctx, clientConfg, restConfig)
	if err != nil {
		return nil, err
	}
	wranglerContext.MultiClusterManager = newMCM(wranglerContext, opts)

	// Initialize Features as early as possible
	if err := crds.CreateFeatureCRD(ctx, restConfig); err != nil {
		return nil, err
	}
	features.InitializeFeatures(wranglerContext.Mgmt.Feature(), opts.Features)

	podsecuritypolicytemplate.RegisterIndexers(wranglerContext)
	kontainerdriver.RegisterIndexers(wranglerContext)
	managementauth.RegisterWranglerIndexers(wranglerContext)

	if err := crds.Create(ctx, restConfig); err != nil {
		return nil, err
	}

	if features.MCM.Enabled() && !features.Fleet.Enabled() {
		return nil, fmt.Errorf("multi-cluster-management features requires feature fleet=true")
	}

	if features.Auth.Enabled() {
		authServer, err = auth.NewServer(ctx, restConfig)
		if err != nil {
			return nil, err
		}
	} else {
		authServer, err = auth.NewAlwaysAdmin()
		if err != nil {
			return nil, err
		}
	}

	steve, err := steveserver.New(ctx, restConfig, &steveserver.Options{
		Controllers:                wranglerContext.Controllers,
		AccessSetLookup:            wranglerContext.ASL,
		AuthMiddleware:             steveauth.ExistingContext,
		AggregationSecretNamespace: namespace.System,
		AggregationSecretName:      "steve-aggregation",
		Next:                       ui.New(wranglerContext.Mgmt.Preference().Cache()),
	})
	if err != nil {
		return nil, err
	}

	clusterProxy, err := proxy.NewProxyMiddleware(wranglerContext.K8s.AuthorizationV1().SubjectAccessReviews(),
		wranglerContext.TunnelServer.Dialer,
		wranglerContext.Mgmt.Cluster().Cache(),
		localClusterEnabled(opts),
		steve,
	)
	if err != nil {
		return nil, err
	}

	additionalAPIPreMCM := steveapi.AdditionalAPIsPreMCM(wranglerContext)
	additionalAPI, err := steveapi.AdditionalAPIs(ctx, wranglerContext, steve)
	if err != nil {
		return nil, err
	}

	auditLogWriter := audit.NewLogWriter(opts.AuditLogPath, opts.AuditLevel, opts.AuditLogMaxage, opts.AuditLogMaxbackup, opts.AuditLogMaxsize)
	auditFilter := audit.NewAuditLogMiddleware(auditLogWriter)
	aggregation := aggregation.NewMiddleware(ctx, wranglerContext.Mgmt.APIService(), wranglerContext.TunnelServer)

	return &Rancher{
		Auth: authServer.Authenticator.Chain(
			auditFilter),
		Handler: responsewriter.Chain{
			responsewriter.ContentTypeOptions,
			websocket.NewWebsocketHandler,
			proxy.RewriteLocalCluster,
			clusterProxy,
			aggregation,
			additionalAPIPreMCM,
			wranglerContext.MultiClusterManager.Middleware,
			authServer.Management,
			additionalAPI,
			requests.NewRequireAuthenticatedFilter("/v1/"),
		}.Handler(steve),
		Wrangler:   wranglerContext,
		Steve:      steve,
		auditLog:   auditLogWriter,
		authServer: authServer,
		opts:       opts,
	}, nil
}

func (r *Rancher) Start(ctx context.Context) error {
	if err := dashboarddata.EarlyData(ctx, r.Wrangler.K8s); err != nil {
		return err
	}

	if err := dashboardapi.Register(ctx, r.Wrangler); err != nil {
		return err
	}

	if err := steveapi.Setup(ctx, r.Steve, r.Wrangler); err != nil {
		return err
	}

	if features.MCM.Enabled() {
		if err := r.Wrangler.MultiClusterManager.Start(ctx); err != nil {
			return err
		}
	}

	r.Wrangler.OnLeader(func(ctx context.Context) error {
		if err := dashboarddata.Add(ctx, r.Wrangler, localClusterEnabled(r.opts), r.opts.AddLocal == "false", r.opts.Embedded); err != nil {
			return err
		}
		err := r.Wrangler.StartWithTransaction(ctx, func(ctx context.Context) error {
			return dashboard.Register(ctx, r.Wrangler)
		})
		if err != nil {
			return err
		}
		return r.Wrangler.StartWithTransaction(ctx, func(ctx context.Context) error {
			dashboard.RegisterFleet(ctx, r.Wrangler)
			return nil
		})
	})

	if err := r.authServer.Start(ctx, false); err != nil {
		return err
	}

	r.Wrangler.OnLeader(r.authServer.OnLeader)
	r.auditLog.Start(ctx)

	return r.Wrangler.Start(ctx)
}

func (r *Rancher) ListenAndServe(ctx context.Context) error {
	if err := r.Start(ctx); err != nil {
		return err
	}

	r.Wrangler.MultiClusterManager.Wait(ctx)

	if err := tls.ListenAndServe(ctx, r.Wrangler.RESTConfig,
		r.Auth(r.Handler),
		r.opts.BindHost,
		r.opts.HTTPSListenPort,
		r.opts.HTTPListenPort,
		r.opts.ACMEDomains,
		r.opts.NoCACerts); err != nil {
		return err
	}

	r.Steve.StartAggregation(ctx)

	<-ctx.Done()
	return ctx.Err()
}

func newMCM(wrangler *wrangler.Context, opts *Options) wrangler.MultiClusterManager {
	return multiclustermanager.NewDeferredServer(wrangler, &multiclustermanager.Options{
		RemoveLocalCluster:  opts.AddLocal == "false",
		LocalClusterEnabled: localClusterEnabled(opts),
		Embedded:            opts.Embedded,
		HTTPSListenPort:     opts.HTTPSListenPort,
		Debug:               opts.Debug,
		Trace:               opts.Trace,
	})
}

func setupAndValidationRESTConfig(ctx context.Context, restConfig *rest.Config) (*rest.Config, error) {
	restConfig = steveserver.RestConfigDefaults(restConfig)
	return restConfig, k8scheck.Wait(ctx, *restConfig)
}

func localClusterEnabled(opts *Options) bool {
	if opts.AddLocal == "true" || opts.AddLocal == "auto" {
		return true
	}
	return false
}
<commit_msg>tls call is blocking, move watch before<commit_after>package rancher

import (
	"context"
	"fmt"
	"net/http"

	responsewriter "github.com/rancher/apiserver/pkg/middleware"
	"github.com/rancher/rancher/pkg/api/norman/customization/kontainerdriver"
	"github.com/rancher/rancher/pkg/api/norman/customization/podsecuritypolicytemplate"
	steveapi "github.com/rancher/rancher/pkg/api/steve"
	"github.com/rancher/rancher/pkg/api/steve/aggregation"
	"github.com/rancher/rancher/pkg/api/steve/proxy"
	"github.com/rancher/rancher/pkg/auth"
	"github.com/rancher/rancher/pkg/auth/audit"
	"github.com/rancher/rancher/pkg/auth/requests"
	"github.com/rancher/rancher/pkg/controllers/dashboard"
	"github.com/rancher/rancher/pkg/controllers/dashboardapi"
	managementauth "github.com/rancher/rancher/pkg/controllers/management/auth"
	crds "github.com/rancher/rancher/pkg/crds/dashboard"
	dashboarddata "github.com/rancher/rancher/pkg/data/dashboard"
	"github.com/rancher/rancher/pkg/features"
	"github.com/rancher/rancher/pkg/multiclustermanager"
	"github.com/rancher/rancher/pkg/namespace"
	"github.com/rancher/rancher/pkg/tls"
	"github.com/rancher/rancher/pkg/ui"
	"github.com/rancher/rancher/pkg/websocket"
	"github.com/rancher/rancher/pkg/wrangler"
	steveauth "github.com/rancher/steve/pkg/auth"
	steveserver "github.com/rancher/steve/pkg/server"
	"github.com/rancher/wrangler/pkg/k8scheck"
	"github.com/urfave/cli"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

type Options struct {
	ACMEDomains       cli.StringSlice
	AddLocal          string
	Embedded          bool
	BindHost          string
	HTTPListenPort    int
	HTTPSListenPort   int
	K8sMode           string
	Debug             bool
	Trace             bool
	NoCACerts         bool
	AuditLogPath      string
	AuditLogMaxage    int
	AuditLogMaxsize   int
	AuditLogMaxbackup int
	AuditLevel        int
	Features          string
}

type Rancher struct {
	Auth     steveauth.Middleware
	Handler  http.Handler
	Wrangler *wrangler.Context
	Steve    *steveserver.Server

	auditLog   *audit.LogWriter
	authServer *auth.Server
	opts       *Options
}

func New(ctx context.Context, clientConfg clientcmd.ClientConfig, opts *Options) (*Rancher, error) {
	var (
		authServer *auth.Server
	)

	if opts == nil {
		opts = &Options{}
	}

	restConfig, err := clientConfg.ClientConfig()
	if err != nil {
		return nil, err
	}

	restConfig, err = setupAndValidationRESTConfig(ctx, restConfig)
	if err != nil {
		return nil, err
	}

	wranglerContext, err := wrangler.NewContext(ctx, clientConfg, restConfig)
	if err != nil {
		return nil, err
	}
	wranglerContext.MultiClusterManager = newMCM(wranglerContext, opts)

	// Initialize Features as early as possible
	if err := crds.CreateFeatureCRD(ctx, restConfig); err != nil {
		return nil, err
	}
	features.InitializeFeatures(wranglerContext.Mgmt.Feature(), opts.Features)

	podsecuritypolicytemplate.RegisterIndexers(wranglerContext)
	kontainerdriver.RegisterIndexers(wranglerContext)
	managementauth.RegisterWranglerIndexers(wranglerContext)

	if err := crds.Create(ctx, restConfig); err != nil {
		return nil, err
	}

	if features.MCM.Enabled() && !features.Fleet.Enabled() {
		return nil, fmt.Errorf("multi-cluster-management features requires feature fleet=true")
	}

	if features.Auth.Enabled() {
		authServer, err = auth.NewServer(ctx, restConfig)
		if err != nil {
			return nil, err
		}
	} else {
		authServer, err = auth.NewAlwaysAdmin()
		if err != nil {
			return nil, err
		}
	}

	steve, err := steveserver.New(ctx, restConfig, &steveserver.Options{
		Controllers:                wranglerContext.Controllers,
		AccessSetLookup:            wranglerContext.ASL,
		AuthMiddleware:             steveauth.ExistingContext,
		AggregationSecretNamespace: namespace.System,
		AggregationSecretName:      "steve-aggregation",
		Next:                       ui.New(wranglerContext.Mgmt.Preference().Cache()),
	})
	if err != nil {
		return nil, err
	}

	clusterProxy, err := proxy.NewProxyMiddleware(wranglerContext.K8s.AuthorizationV1().SubjectAccessReviews(),
		wranglerContext.TunnelServer.Dialer,
		wranglerContext.Mgmt.Cluster().Cache(),
		localClusterEnabled(opts),
		steve,
	)
	if err != nil {
		return nil, err
	}

	additionalAPIPreMCM := steveapi.AdditionalAPIsPreMCM(wranglerContext)
	additionalAPI, err := steveapi.AdditionalAPIs(ctx, wranglerContext, steve)
	if err != nil {
		return nil, err
	}

	auditLogWriter := audit.NewLogWriter(opts.AuditLogPath, opts.AuditLevel, opts.AuditLogMaxage, opts.AuditLogMaxbackup, opts.AuditLogMaxsize)
	auditFilter := audit.NewAuditLogMiddleware(auditLogWriter)
	aggregation := aggregation.NewMiddleware(ctx, wranglerContext.Mgmt.APIService(), wranglerContext.TunnelServer)

	return &Rancher{
		Auth: authServer.Authenticator.Chain(
			auditFilter),
		Handler: responsewriter.Chain{
			responsewriter.ContentTypeOptions,
			websocket.NewWebsocketHandler,
			proxy.RewriteLocalCluster,
			clusterProxy,
			aggregation,
			additionalAPIPreMCM,
			wranglerContext.MultiClusterManager.Middleware,
			authServer.Management,
			additionalAPI,
			requests.NewRequireAuthenticatedFilter("/v1/"),
		}.Handler(steve),
		Wrangler:   wranglerContext,
		Steve:      steve,
		auditLog:   auditLogWriter,
		authServer: authServer,
		opts:       opts,
	}, nil
}

func (r *Rancher) Start(ctx context.Context) error {
	if err := dashboarddata.EarlyData(ctx, r.Wrangler.K8s); err != nil {
		return err
	}

	if err := dashboardapi.Register(ctx, r.Wrangler); err != nil {
		return err
	}

	if err := steveapi.Setup(ctx, r.Steve, r.Wrangler); err != nil {
		return err
	}

	if features.MCM.Enabled() {
		if err := r.Wrangler.MultiClusterManager.Start(ctx); err != nil {
			return err
		}
	}

	r.Wrangler.OnLeader(func(ctx context.Context) error {
		if err := dashboarddata.Add(ctx, r.Wrangler, localClusterEnabled(r.opts), r.opts.AddLocal == "false", r.opts.Embedded); err != nil {
			return err
		}
		err := r.Wrangler.StartWithTransaction(ctx, func(ctx context.Context) error {
			return dashboard.Register(ctx, r.Wrangler)
		})
		if err != nil {
			return err
		}
		return r.Wrangler.StartWithTransaction(ctx, func(ctx context.Context) error {
			dashboard.RegisterFleet(ctx, r.Wrangler)
			return nil
		})
	})

	if err := r.authServer.Start(ctx, false); err != nil {
		return err
	}

	r.Wrangler.OnLeader(r.authServer.OnLeader)
	r.auditLog.Start(ctx)

	return r.Wrangler.Start(ctx)
}

func (r *Rancher) ListenAndServe(ctx context.Context) error {
	if err := r.Start(ctx); err != nil {
		return err
	}

	r.Wrangler.MultiClusterManager.Wait(ctx)

	go r.Steve.StartAggregation(ctx)
	if err := tls.ListenAndServe(ctx, r.Wrangler.RESTConfig,
		r.Auth(r.Handler),
		r.opts.BindHost,
		r.opts.HTTPSListenPort,
		r.opts.HTTPListenPort,
		r.opts.ACMEDomains,
		r.opts.NoCACerts); err != nil {
		return err
	}

	<-ctx.Done()
	return ctx.Err()
}

func newMCM(wrangler *wrangler.Context, opts *Options) wrangler.MultiClusterManager {
	return multiclustermanager.NewDeferredServer(wrangler, &multiclustermanager.Options{
		RemoveLocalCluster:  opts.AddLocal == "false",
		LocalClusterEnabled: localClusterEnabled(opts),
		Embedded:            opts.Embedded,
		HTTPSListenPort:     opts.HTTPSListenPort,
		Debug:               opts.Debug,
		Trace:               opts.Trace,
	})
}

func setupAndValidationRESTConfig(ctx context.Context, restConfig *rest.Config) (*rest.Config, error) {
	restConfig = steveserver.RestConfigDefaults(restConfig)
	return restConfig, k8scheck.Wait(ctx, *restConfig)
}

func localClusterEnabled(opts *Options) bool {
	if opts.AddLocal == "true" || opts.AddLocal == "auto" {
		return true
	}
	return false
}
<|endoftext|>