<commit_before>/*
Copyright 2019 The Knative 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 v1alpha1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"knative.dev/pkg/apis"
	duckv1 "knative.dev/pkg/apis/duck/v1"
	"knative.dev/pkg/kmeta"
)

// +genclient
// +genreconciler:class=networking.knative.dev/ingress.class,krshapedlogic=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// Ingress is a collection of rules that allow inbound connections to reach the endpoints defined
// by a backend. An Ingress can be configured to give services externally-reachable URLs, load
// balance traffic, offer name based virtual hosting, etc.
//
// This is heavily based on K8s Ingress https://godoc.org/k8s.io/api/networking/v1beta1#Ingress
// which some highlighted modifications.
type Ingress struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object's metadata.
	// More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty"`

	// Spec is the desired state of the Ingress.
	// More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
	// +optional
	Spec IngressSpec `json:"spec,omitempty"`

	// Status is the current state of the Ingress.
	// More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
	// +optional
	Status IngressStatus `json:"status,omitempty"`
}

// Verify that Ingress adheres to the appropriate interfaces.
var (
	// Check that Ingress may be validated and defaulted.
	_ apis.Validatable = (*Ingress)(nil)
	_ apis.Defaultable = (*Ingress)(nil)

	// Check that we can create OwnerReferences to a Ingress.
	_ kmeta.OwnerRefable = (*Ingress)(nil)

	// Check that the type conforms to the duck Knative Resource shape.
	_ duckv1.KRShaped = (*Ingress)(nil)
)

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// IngressList is a collection of Ingress objects.
type IngressList struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object metadata.
	// More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata
	// +optional
	metav1.ListMeta `json:"metadata,omitempty"`

	// Items is the list of Ingress objects.
	Items []Ingress `json:"items"`
}

// IngressSpec describes the Ingress the user wishes to exist.
//
// In general this follows the same shape as K8s Ingress.
// Some notable differences:
// - Backends now can have namespace:
// - Traffic can be split across multiple backends.
// - Timeout & Retry can be configured.
// - Headers can be appended.
type IngressSpec struct {
	// TLS configuration. Currently Ingress only supports a single TLS
	// port: 443. If multiple members of this list specify different hosts, they
	// will be multiplexed on the same port according to the hostname specified
	// through the SNI TLS extension, if the ingress controller fulfilling the
	// ingress supports SNI.
	// +optional
	TLS []IngressTLS `json:"tls,omitempty"`

	// A list of host rules used to configure the Ingress.
	// +optional
	Rules []IngressRule `json:"rules,omitempty"`

	// HTTPOption is the option of HTTP. It has the following two values:
	// `HTTPOptionEnabled`, `HTTPOptionRedirected`
	HTTPOption HTTPOption `json:"httpOption,omitempty"`

	// DeprecatedVisibility was used for the fallback when spec.rules.visibility
	// isn't set.
	//
	// Now spec.rules.visibility is not optional and so we make this field deprecated.
	//
	// +optional
	DeprecatedVisibility IngressVisibility `json:"visibility,omitempty"`
}

type HTTPOption string

const (
	// The knative ingress will be able to serve HTTP connections.
	HTTPOptionEnabled HTTPOption = "Enabled"
	// The knative will return redirection HTTP status for the clients,
	// asking the clients to redirect their requests to HTTPS.
	HTTPOptionRedirected HTTPOption = "Redirected"
)

// IngressVisibility describes whether the Ingress should be exposed to
// public gateways or not.
type IngressVisibility string

const (
	// IngressVisibilityExternalIP is used to denote that the Ingress
	// should be exposed via an external IP, for example a LoadBalancer
	// Service.  This is the default value for IngressVisibility.
	IngressVisibilityExternalIP IngressVisibility = "ExternalIP"
	// IngressVisibilityClusterLocal is used to denote that the Ingress
	// should be only be exposed locally to the cluster.
	IngressVisibilityClusterLocal IngressVisibility = "ClusterLocal"
)

// IngressTLS describes the transport layer security associated with an Ingress.
type IngressTLS struct {
	// Hosts is a list of hosts included in the TLS certificate. The values in
	// this list must match the name/s used in the tlsSecret. Defaults to the
	// wildcard host setting for the loadbalancer controller fulfilling this
	// Ingress, if left unspecified.
	// +optional
	Hosts []string `json:"hosts,omitempty"`

	// SecretName is the name of the secret used to terminate SSL traffic.
	SecretName string `json:"secretName,omitempty"`

	// SecretNamespace is the namespace of the secret used to terminate SSL traffic.
	SecretNamespace string `json:"secretNamespace,omitempty"`

	// ServerCertificate identifies the certificate filename in the secret.
	// Defaults to `tls.crt`.
	// +optional
	DeprecatedServerCertificate string `json:"serverCertificate,omitempty"`

	// PrivateKey identifies the private key filename in the secret.
	// Defaults to `tls.key`.
	// +optional
	DeprecatedPrivateKey string `json:"privateKey,omitempty"`
}

// IngressRule represents the rules mapping the paths under a specified host to
// the related backend services. Incoming requests are first evaluated for a host
// match, then routed to the backend associated with the matching IngressRuleValue.
type IngressRule struct {
	// Host is the fully qualified domain name of a network host, as defined
	// by RFC 3986. Note the following deviations from the "host" part of the
	// URI as defined in the RFC:
	// 1. IPs are not allowed. Currently a rule value can only apply to the
	//	  IP in the Spec of the parent .
	// 2. The `:` delimiter is not respected because ports are not allowed.
	//	  Currently the port of an Ingress is implicitly :80 for http and
	//	  :443 for https.
	// Both these may change in the future.
	// If the host is unspecified, the Ingress routes all traffic based on the
	// specified IngressRuleValue.
	// If multiple matching Hosts were provided, the first rule will take precedent.
	// +optional
	Hosts []string `json:"hosts,omitempty"`

	// Visibility signifies whether this rule should `ClusterLocal`. If it's not
	// specified then it defaults to `ExternalIP`.
	Visibility IngressVisibility `json:"visibility,omitempty"`

	// HTTP represents a rule to apply against incoming requests. If the
	// rule is satisfied, the request is routed to the specified backend.
	HTTP *HTTPIngressRuleValue `json:"http,omitempty"`
}

// HTTPIngressRuleValue is a list of http selectors pointing to backends.
// In the example: http://<host>/<path>?<searchpart> -> backend where
// where parts of the url correspond to RFC 3986, this resource will be used
// to match against everything after the last '/' and before the first '?'
// or '#'.
type HTTPIngressRuleValue struct {
	// A collection of paths that map requests to backends.
	//
	// If they are multiple matching paths, the first match takes precedence.
	Paths []HTTPIngressPath `json:"paths"`

	// TODO: Consider adding fields for ingress-type specific global
	// options usable by a loadbalancer, like http keep-alive.
}

// HTTPIngressPath associates a path regex with a backend. Incoming URLs matching
// the path are forwarded to the backend.
type HTTPIngressPath struct {
	// Path is an extended POSIX regex as defined by IEEE Std 1003.1,
	// (i.e this follows the egrep/unix syntax, not the perl syntax)
	// matched against the path of an incoming request. Currently it can
	// contain characters disallowed from the conventional "path"
	// part of a URL as defined by RFC 3986. Paths must begin with
	// a '/'. If unspecified, the path defaults to a catch all sending
	// traffic to the backend.
	// +optional
	Path string `json:"path,omitempty"`

	// RewriteHost rewrites the incoming request's host header.
	//
	// This field is currently experimental and not supported by all Ingress
	// implementations.
	RewriteHost string `json:"rewriteHost,omitempty"`

	// Headers defines header matching rules which is a map from a header name
	// to HeaderMatch which specify a matching condition.
	// When a request matched with all the header matching rules,
	// the request is routed by the corresponding ingress rule.
	// If it is empty, the headers are not used for matching
	// +optional
	Headers map[string]HeaderMatch `json:"headers,omitempty"`

	// Splits defines the referenced service endpoints to which the traffic
	// will be forwarded to.
	Splits []IngressBackendSplit `json:"splits"`

	// AppendHeaders allow specifying additional HTTP headers to add
	// before forwarding a request to the destination service.
	//
	// NOTE: This differs from K8s Ingress which doesn't allow header appending.
	// +optional
	AppendHeaders map[string]string `json:"appendHeaders,omitempty"`

	// DeprecatedTimeout is DEPRECATED.
	// Timeout is not used anymore. See https://github.com/knative/networking/issues/91
	//
	// NOTE: This differs from K8s Ingress which doesn't allow setting timeouts.
	// +optional
	DeprecatedTimeout *metav1.Duration `json:"timeout,omitempty"`

	// DeprecatedRetries is DEPRECATED.
	// Retry in Kingress is not used anymore. See https://github.com/knative/serving/issues/6549
	// +optional
	DeprecatedRetries *HTTPRetry `json:"retries,omitempty"`
}

// IngressBackendSplit describes all endpoints for a given service and port.
type IngressBackendSplit struct {
	// Specifies the backend receiving the traffic split.
	IngressBackend `json:",inline"`

	// Specifies the split percentage, a number between 0 and 100.  If
	// only one split is specified, we default to 100.
	//
	// NOTE: This differs from K8s Ingress to allow percentage split.
	Percent int `json:"percent,omitempty"`

	// AppendHeaders allow specifying additional HTTP headers to add
	// before forwarding a request to the destination service.
	//
	// NOTE: This differs from K8s Ingress which doesn't allow header appending.
	// +optional
	AppendHeaders map[string]string `json:"appendHeaders,omitempty"`
}

// IngressBackend describes all endpoints for a given service and port.
type IngressBackend struct {
	// Specifies the namespace of the referenced service.
	//
	// NOTE: This differs from K8s Ingress to allow routing to different namespaces.
	ServiceNamespace string `json:"serviceNamespace"`

	// Specifies the name of the referenced service.
	ServiceName string `json:"serviceName"`

	// Specifies the port of the referenced service.
	ServicePort intstr.IntOrString `json:"servicePort"`
}

// HTTPRetry is DEPRECATED. Retry is not used in KIngress.
type HTTPRetry struct {
	// Number of retries for a given request.
	Attempts int `json:"attempts"`

	// Timeout per retry attempt for a given request. format: 1h/1m/1s/1ms. MUST BE >=1ms.
	PerTryTimeout *metav1.Duration `json:"perTryTimeout"`
}

// IngressStatus describe the current state of the Ingress.
type IngressStatus struct {
	duckv1.Status `json:",inline"`

	// DeprecatedLoadBalancer contains the current status of the load-balancer.
	// DEPRECATED: Use `PublicLoadBalancer` and `PrivateLoadBalancer` instead.
	// +optional
	DeprecatedLoadBalancer *LoadBalancerStatus `json:"loadBalancer,omitempty"`

	// PublicLoadBalancer contains the current status of the load-balancer.
	// +optional
	PublicLoadBalancer *LoadBalancerStatus `json:"publicLoadBalancer,omitempty"`

	// PrivateLoadBalancer contains the current status of the load-balancer.
	// +optional
	PrivateLoadBalancer *LoadBalancerStatus `json:"privateLoadBalancer,omitempty"`
}

// LoadBalancerStatus represents the status of a load-balancer.
type LoadBalancerStatus struct {
	// Ingress is a list containing ingress points for the load-balancer.
	// Traffic intended for the service should be sent to these ingress points.
	// +optional
	Ingress []LoadBalancerIngressStatus `json:"ingress,omitempty"`
}

// LoadBalancerIngressStatus represents the status of a load-balancer ingress point:
// traffic intended for the service should be sent to an ingress point.
type LoadBalancerIngressStatus struct {
	// IP is set for load-balancer ingress points that are IP based
	// (typically GCE or OpenStack load-balancers)
	// +optional
	IP string `json:"ip,omitempty"`

	// Domain is set for load-balancer ingress points that are DNS based
	// (typically AWS load-balancers)
	// +optional
	Domain string `json:"domain,omitempty"`

	// DomainInternal is set if there is a cluster-local DNS name to access the Ingress.
	//
	// NOTE: This differs from K8s Ingress, since we also desire to have a cluster-local
	//       DNS name to allow routing in case of not having a mesh.
	//
	// +optional
	DomainInternal string `json:"domainInternal,omitempty"`

	// MeshOnly is set if the Ingress is only load-balanced through a Service mesh.
	// +optional
	MeshOnly bool `json:"meshOnly,omitempty"`
}

// ConditionType represents a Ingress condition value
const (
	// IngressConditionReady is set when the Ingress networking setting is
	// configured and it has a load balancer address.
	IngressConditionReady = apis.ConditionReady

	// IngressConditionNetworkConfigured is set when the Ingress's underlying
	// network programming has been configured.  This doesn't include conditions of the
	// backends, so even if this should remain true when network is configured and backends
	// are not ready.
	IngressConditionNetworkConfigured apis.ConditionType = "NetworkConfigured"

	// IngressConditionLoadBalancerReady is set when the Ingress has a ready LoadBalancer.
	IngressConditionLoadBalancerReady apis.ConditionType = "LoadBalancerReady"
)

// GetStatus retrieves the status of the Ingress. Implements the KRShaped interface.
func (i *Ingress) GetStatus() *duckv1.Status {
	return &i.Status.Status
}

// HeaderMatch represents a matching value of Headers in HTTPIngressPath.
// Currently, only the exact matching is supported.
type HeaderMatch struct {
	Exact string `json:"exact"`
}
<commit_msg>Update KIngress rule path to be a literal string (#364)<commit_after>/*
Copyright 2019 The Knative 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 v1alpha1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"knative.dev/pkg/apis"
	duckv1 "knative.dev/pkg/apis/duck/v1"
	"knative.dev/pkg/kmeta"
)

// +genclient
// +genreconciler:class=networking.knative.dev/ingress.class,krshapedlogic=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// Ingress is a collection of rules that allow inbound connections to reach the endpoints defined
// by a backend. An Ingress can be configured to give services externally-reachable URLs, load
// balance traffic, offer name based virtual hosting, etc.
//
// This is heavily based on K8s Ingress https://godoc.org/k8s.io/api/networking/v1beta1#Ingress
// which some highlighted modifications.
type Ingress struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object's metadata.
	// More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty"`

	// Spec is the desired state of the Ingress.
	// More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
	// +optional
	Spec IngressSpec `json:"spec,omitempty"`

	// Status is the current state of the Ingress.
	// More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
	// +optional
	Status IngressStatus `json:"status,omitempty"`
}

// Verify that Ingress adheres to the appropriate interfaces.
var (
	// Check that Ingress may be validated and defaulted.
	_ apis.Validatable = (*Ingress)(nil)
	_ apis.Defaultable = (*Ingress)(nil)

	// Check that we can create OwnerReferences to a Ingress.
	_ kmeta.OwnerRefable = (*Ingress)(nil)

	// Check that the type conforms to the duck Knative Resource shape.
	_ duckv1.KRShaped = (*Ingress)(nil)
)

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// IngressList is a collection of Ingress objects.
type IngressList struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object metadata.
	// More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata
	// +optional
	metav1.ListMeta `json:"metadata,omitempty"`

	// Items is the list of Ingress objects.
	Items []Ingress `json:"items"`
}

// IngressSpec describes the Ingress the user wishes to exist.
//
// In general this follows the same shape as K8s Ingress.
// Some notable differences:
// - Backends now can have namespace:
// - Traffic can be split across multiple backends.
// - Timeout & Retry can be configured.
// - Headers can be appended.
type IngressSpec struct {
	// TLS configuration. Currently Ingress only supports a single TLS
	// port: 443. If multiple members of this list specify different hosts, they
	// will be multiplexed on the same port according to the hostname specified
	// through the SNI TLS extension, if the ingress controller fulfilling the
	// ingress supports SNI.
	// +optional
	TLS []IngressTLS `json:"tls,omitempty"`

	// A list of host rules used to configure the Ingress.
	// +optional
	Rules []IngressRule `json:"rules,omitempty"`

	// HTTPOption is the option of HTTP. It has the following two values:
	// `HTTPOptionEnabled`, `HTTPOptionRedirected`
	HTTPOption HTTPOption `json:"httpOption,omitempty"`

	// DeprecatedVisibility was used for the fallback when spec.rules.visibility
	// isn't set.
	//
	// Now spec.rules.visibility is not optional and so we make this field deprecated.
	//
	// +optional
	DeprecatedVisibility IngressVisibility `json:"visibility,omitempty"`
}

type HTTPOption string

const (
	// The knative ingress will be able to serve HTTP connections.
	HTTPOptionEnabled HTTPOption = "Enabled"
	// The knative will return redirection HTTP status for the clients,
	// asking the clients to redirect their requests to HTTPS.
	HTTPOptionRedirected HTTPOption = "Redirected"
)

// IngressVisibility describes whether the Ingress should be exposed to
// public gateways or not.
type IngressVisibility string

const (
	// IngressVisibilityExternalIP is used to denote that the Ingress
	// should be exposed via an external IP, for example a LoadBalancer
	// Service.  This is the default value for IngressVisibility.
	IngressVisibilityExternalIP IngressVisibility = "ExternalIP"
	// IngressVisibilityClusterLocal is used to denote that the Ingress
	// should be only be exposed locally to the cluster.
	IngressVisibilityClusterLocal IngressVisibility = "ClusterLocal"
)

// IngressTLS describes the transport layer security associated with an Ingress.
type IngressTLS struct {
	// Hosts is a list of hosts included in the TLS certificate. The values in
	// this list must match the name/s used in the tlsSecret. Defaults to the
	// wildcard host setting for the loadbalancer controller fulfilling this
	// Ingress, if left unspecified.
	// +optional
	Hosts []string `json:"hosts,omitempty"`

	// SecretName is the name of the secret used to terminate SSL traffic.
	SecretName string `json:"secretName,omitempty"`

	// SecretNamespace is the namespace of the secret used to terminate SSL traffic.
	SecretNamespace string `json:"secretNamespace,omitempty"`

	// ServerCertificate identifies the certificate filename in the secret.
	// Defaults to `tls.crt`.
	// +optional
	DeprecatedServerCertificate string `json:"serverCertificate,omitempty"`

	// PrivateKey identifies the private key filename in the secret.
	// Defaults to `tls.key`.
	// +optional
	DeprecatedPrivateKey string `json:"privateKey,omitempty"`
}

// IngressRule represents the rules mapping the paths under a specified host to
// the related backend services. Incoming requests are first evaluated for a host
// match, then routed to the backend associated with the matching IngressRuleValue.
type IngressRule struct {
	// Host is the fully qualified domain name of a network host, as defined
	// by RFC 3986. Note the following deviations from the "host" part of the
	// URI as defined in the RFC:
	// 1. IPs are not allowed. Currently a rule value can only apply to the
	//	  IP in the Spec of the parent .
	// 2. The `:` delimiter is not respected because ports are not allowed.
	//	  Currently the port of an Ingress is implicitly :80 for http and
	//	  :443 for https.
	// Both these may change in the future.
	// If the host is unspecified, the Ingress routes all traffic based on the
	// specified IngressRuleValue.
	// If multiple matching Hosts were provided, the first rule will take precedent.
	// +optional
	Hosts []string `json:"hosts,omitempty"`

	// Visibility signifies whether this rule should `ClusterLocal`. If it's not
	// specified then it defaults to `ExternalIP`.
	Visibility IngressVisibility `json:"visibility,omitempty"`

	// HTTP represents a rule to apply against incoming requests. If the
	// rule is satisfied, the request is routed to the specified backend.
	HTTP *HTTPIngressRuleValue `json:"http,omitempty"`
}

// HTTPIngressRuleValue is a list of http selectors pointing to backends.
// In the example: http://<host>/<path>?<searchpart> -> backend where
// where parts of the url correspond to RFC 3986, this resource will be used
// to match against everything after the last '/' and before the first '?'
// or '#'.
type HTTPIngressRuleValue struct {
	// A collection of paths that map requests to backends.
	//
	// If they are multiple matching paths, the first match takes precedence.
	Paths []HTTPIngressPath `json:"paths"`

	// TODO: Consider adding fields for ingress-type specific global
	// options usable by a loadbalancer, like http keep-alive.
}

// HTTPIngressPath associates a path regex with a backend. Incoming URLs matching
// the path are forwarded to the backend.
type HTTPIngressPath struct {
	// Path represents a literal prefix to which this rule should apply.
	// Currently it can contain characters disallowed from the conventional
	// "path" part of a URL as defined by RFC 3986. Paths must begin with
	// a '/'. If unspecified, the path defaults to a catch all sending
	// traffic to the backend.
	// +optional
	Path string `json:"path,omitempty"`

	// RewriteHost rewrites the incoming request's host header.
	//
	// This field is currently experimental and not supported by all Ingress
	// implementations.
	RewriteHost string `json:"rewriteHost,omitempty"`

	// Headers defines header matching rules which is a map from a header name
	// to HeaderMatch which specify a matching condition.
	// When a request matched with all the header matching rules,
	// the request is routed by the corresponding ingress rule.
	// If it is empty, the headers are not used for matching
	// +optional
	Headers map[string]HeaderMatch `json:"headers,omitempty"`

	// Splits defines the referenced service endpoints to which the traffic
	// will be forwarded to.
	Splits []IngressBackendSplit `json:"splits"`

	// AppendHeaders allow specifying additional HTTP headers to add
	// before forwarding a request to the destination service.
	//
	// NOTE: This differs from K8s Ingress which doesn't allow header appending.
	// +optional
	AppendHeaders map[string]string `json:"appendHeaders,omitempty"`

	// DeprecatedTimeout is DEPRECATED.
	// Timeout is not used anymore. See https://github.com/knative/networking/issues/91
	//
	// NOTE: This differs from K8s Ingress which doesn't allow setting timeouts.
	// +optional
	DeprecatedTimeout *metav1.Duration `json:"timeout,omitempty"`

	// DeprecatedRetries is DEPRECATED.
	// Retry in Kingress is not used anymore. See https://github.com/knative/serving/issues/6549
	// +optional
	DeprecatedRetries *HTTPRetry `json:"retries,omitempty"`
}

// IngressBackendSplit describes all endpoints for a given service and port.
type IngressBackendSplit struct {
	// Specifies the backend receiving the traffic split.
	IngressBackend `json:",inline"`

	// Specifies the split percentage, a number between 0 and 100.  If
	// only one split is specified, we default to 100.
	//
	// NOTE: This differs from K8s Ingress to allow percentage split.
	Percent int `json:"percent,omitempty"`

	// AppendHeaders allow specifying additional HTTP headers to add
	// before forwarding a request to the destination service.
	//
	// NOTE: This differs from K8s Ingress which doesn't allow header appending.
	// +optional
	AppendHeaders map[string]string `json:"appendHeaders,omitempty"`
}

// IngressBackend describes all endpoints for a given service and port.
type IngressBackend struct {
	// Specifies the namespace of the referenced service.
	//
	// NOTE: This differs from K8s Ingress to allow routing to different namespaces.
	ServiceNamespace string `json:"serviceNamespace"`

	// Specifies the name of the referenced service.
	ServiceName string `json:"serviceName"`

	// Specifies the port of the referenced service.
	ServicePort intstr.IntOrString `json:"servicePort"`
}

// HTTPRetry is DEPRECATED. Retry is not used in KIngress.
type HTTPRetry struct {
	// Number of retries for a given request.
	Attempts int `json:"attempts"`

	// Timeout per retry attempt for a given request. format: 1h/1m/1s/1ms. MUST BE >=1ms.
	PerTryTimeout *metav1.Duration `json:"perTryTimeout"`
}

// IngressStatus describe the current state of the Ingress.
type IngressStatus struct {
	duckv1.Status `json:",inline"`

	// DeprecatedLoadBalancer contains the current status of the load-balancer.
	// DEPRECATED: Use `PublicLoadBalancer` and `PrivateLoadBalancer` instead.
	// +optional
	DeprecatedLoadBalancer *LoadBalancerStatus `json:"loadBalancer,omitempty"`

	// PublicLoadBalancer contains the current status of the load-balancer.
	// +optional
	PublicLoadBalancer *LoadBalancerStatus `json:"publicLoadBalancer,omitempty"`

	// PrivateLoadBalancer contains the current status of the load-balancer.
	// +optional
	PrivateLoadBalancer *LoadBalancerStatus `json:"privateLoadBalancer,omitempty"`
}

// LoadBalancerStatus represents the status of a load-balancer.
type LoadBalancerStatus struct {
	// Ingress is a list containing ingress points for the load-balancer.
	// Traffic intended for the service should be sent to these ingress points.
	// +optional
	Ingress []LoadBalancerIngressStatus `json:"ingress,omitempty"`
}

// LoadBalancerIngressStatus represents the status of a load-balancer ingress point:
// traffic intended for the service should be sent to an ingress point.
type LoadBalancerIngressStatus struct {
	// IP is set for load-balancer ingress points that are IP based
	// (typically GCE or OpenStack load-balancers)
	// +optional
	IP string `json:"ip,omitempty"`

	// Domain is set for load-balancer ingress points that are DNS based
	// (typically AWS load-balancers)
	// +optional
	Domain string `json:"domain,omitempty"`

	// DomainInternal is set if there is a cluster-local DNS name to access the Ingress.
	//
	// NOTE: This differs from K8s Ingress, since we also desire to have a cluster-local
	//       DNS name to allow routing in case of not having a mesh.
	//
	// +optional
	DomainInternal string `json:"domainInternal,omitempty"`

	// MeshOnly is set if the Ingress is only load-balanced through a Service mesh.
	// +optional
	MeshOnly bool `json:"meshOnly,omitempty"`
}

// ConditionType represents a Ingress condition value
const (
	// IngressConditionReady is set when the Ingress networking setting is
	// configured and it has a load balancer address.
	IngressConditionReady = apis.ConditionReady

	// IngressConditionNetworkConfigured is set when the Ingress's underlying
	// network programming has been configured.  This doesn't include conditions of the
	// backends, so even if this should remain true when network is configured and backends
	// are not ready.
	IngressConditionNetworkConfigured apis.ConditionType = "NetworkConfigured"

	// IngressConditionLoadBalancerReady is set when the Ingress has a ready LoadBalancer.
	IngressConditionLoadBalancerReady apis.ConditionType = "LoadBalancerReady"
)

// GetStatus retrieves the status of the Ingress. Implements the KRShaped interface.
func (i *Ingress) GetStatus() *duckv1.Status {
	return &i.Status.Status
}

// HeaderMatch represents a matching value of Headers in HTTPIngressPath.
// Currently, only the exact matching is supported.
type HeaderMatch struct {
	Exact string `json:"exact"`
}
<|endoftext|>


<commit_before>package userverify

import (
	"encoding/json"
	"net/http"
	"time"

	"github.com/sirupsen/logrus"
	"github.com/skygeario/skygear-server/pkg/auth"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/provider/password"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/userprofile"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/userverify"
	"github.com/skygeario/skygear-server/pkg/auth/response"
	coreAuth "github.com/skygeario/skygear-server/pkg/core/auth"
	"github.com/skygeario/skygear-server/pkg/core/auth/authz"
	"github.com/skygeario/skygear-server/pkg/core/auth/authz/policy"
	"github.com/skygeario/skygear-server/pkg/core/db"
	"github.com/skygeario/skygear-server/pkg/core/handler"
	"github.com/skygeario/skygear-server/pkg/core/inject"
	"github.com/skygeario/skygear-server/pkg/core/server"
	"github.com/skygeario/skygear-server/pkg/core/skyerr"
)

// AttachVerifyRequestHandler attaches VerifyRequestHandler to server
func AttachVerifyRequestHandler(
	server *server.Server,
	authDependency auth.DependencyMap,
) *server.Server {
	server.Handle("/verify_request", &VerifyRequestHandlerFactory{
		authDependency,
	}).Methods("OPTIONS", "POST")
	server.Handle("/verify_request/test", &VerifyRequestTestHandlerFactory{
		authDependency,
	}).Methods("OPTIONS", "POST")
	return server
}

// VerifyRequestHandlerFactory creates VerifyRequestHandler
type VerifyRequestHandlerFactory struct {
	Dependency auth.DependencyMap
}

// NewHandler creates new VerifyRequestHandler
func (f VerifyRequestHandlerFactory) NewHandler(request *http.Request) http.Handler {
	h := &VerifyRequestHandler{}
	inject.DefaultRequestInject(h, f.Dependency, request)
	return handler.APIHandlerToHandler(h, h.TxContext)
}

// ProvideAuthzPolicy provides authorization policy of handler
func (f VerifyRequestHandlerFactory) ProvideAuthzPolicy() authz.Policy {
	return policy.AllOf(
		authz.PolicyFunc(policy.DenyNoAccessKey),
		authz.PolicyFunc(policy.RequireAuthenticated),
		authz.PolicyFunc(policy.DenyDisabledUser),
	)
}

type VerifyRequestPayload struct {
	LoginIDKey string `json:"login_id_key"`
	LoginID    string `json:"login_id"`
}

func (payload VerifyRequestPayload) Validate() error {
	if payload.LoginIDKey == "" {
		return skyerr.NewInvalidArgument("empty login ID key", []string{"login_id_key"})
	}

	if payload.LoginID == "" {
		return skyerr.NewInvalidArgument("empty login ID", []string{"login_id"})
	}

	return nil
}

// VerifyRequestHandler allows client to request verification (i.e. send email or send SMS).
//
//  curl -X POST -H "Content-Type: application/json" \
//    -d @- http://localhost:3000/verify_request <<EOF
//  {
//    "login_id_key": "email",
//    "login_id": "user@example.com"
//  }
//  EOF
//
type VerifyRequestHandler struct {
	TxContext            db.TxContext                    `dependency:"TxContext"`
	AuthContext          coreAuth.ContextGetter          `dependency:"AuthContextGetter"`
	CodeSenderFactory    userverify.CodeSenderFactory    `dependency:"UserVerifyCodeSenderFactory"`
	CodeGeneratorFactory userverify.CodeGeneratorFactory `dependency:"VerifyCodeCodeGeneratorFactory"`
	UserProfileStore     userprofile.Store               `dependency:"UserProfileStore"`
	VerifyCodeStore      userverify.Store                `dependency:"VerifyCodeStore"`
	Logger               *logrus.Entry                   `dependency:"HandlerLogger"`
	PasswordAuthProvider password.Provider               `dependency:"PasswordAuthProvider"`
}

func (h VerifyRequestHandler) WithTx() bool {
	return true
}

// DecodeRequest decode request payload
func (h VerifyRequestHandler) DecodeRequest(request *http.Request) (handler.RequestPayload, error) {
	payload := VerifyRequestPayload{}
	if err := json.NewDecoder(request.Body).Decode(&payload); err != nil {
		return nil, skyerr.NewError(skyerr.BadRequest, "fails to decode the request payload")
	}

	return payload, nil
}

func (h VerifyRequestHandler) Handle(req interface{}) (resp interface{}, err error) {
	payload := req.(VerifyRequestPayload)
	authInfo := h.AuthContext.AuthInfo()
	codeSender := h.CodeSenderFactory.NewCodeSender(payload.LoginIDKey)
	if codeSender == nil {
		err = skyerr.NewInvalidArgument("invalid login_id_key", []string{payload.LoginIDKey})
	}

	// Get Profile
	var userProfile userprofile.UserProfile
	if userProfile, err = h.UserProfileStore.GetUserProfile(authInfo.ID); err != nil {
		// TODO:
		// return proper error
		err = skyerr.NewError(skyerr.UnexpectedError, "Unable to fetch user profile")
		return
	}

	userFactory := response.UserFactory{
		PasswordAuthProvider: h.PasswordAuthProvider,
	}
	user := userFactory.NewUser(*authInfo, userProfile)

	// We don't check realms. i.e. Verifying a email means every email login IDs
	// of that email is verified, regardless the realm.
	principals, err := h.PasswordAuthProvider.GetPrincipalsByLoginID(payload.LoginIDKey, payload.LoginID)
	if err != nil {
		return
	}

	var userPrincipal *password.Principal
	for _, principal := range principals {
		if principal.UserID == authInfo.ID {
			userPrincipal = principal
			break
		}
	}
	if userPrincipal == nil {
		err = skyerr.NewError(skyerr.UnexpectedError, "Value of "+payload.LoginID+" doesn't exist.")
		return
	}

	codeGenerator := h.CodeGeneratorFactory.NewCodeGenerator(payload.LoginIDKey)
	code := codeGenerator.Generate()

	verifyCode := userverify.NewVerifyCode()
	verifyCode.UserID = authInfo.ID
	verifyCode.RecordKey = userPrincipal.LoginIDKey
	verifyCode.RecordValue = userPrincipal.LoginID
	verifyCode.Code = code
	verifyCode.Consumed = false
	verifyCode.CreatedAt = time.Now()

	if err = h.VerifyCodeStore.CreateVerifyCode(&verifyCode); err != nil {
		return
	}

	if err = codeSender.Send(verifyCode, user); err != nil {
		h.Logger.WithFields(logrus.Fields{
			"error":        err,
			"login_id_key": userPrincipal.LoginIDKey,
			"login_id":     userPrincipal.LoginID,
		}).Error("fail to send verify request")
		return
	}

	resp = "OK"
	return
}

// VerifyRequestTestHandlerFactory creates VerifyRequestTestHandler
type VerifyRequestTestHandlerFactory struct {
	Dependency auth.DependencyMap
}

// NewHandler creates new VerifyRequestTestHandler
func (f VerifyRequestTestHandlerFactory) NewHandler(request *http.Request) http.Handler {
	h := &VerifyRequestTestHandler{}
	inject.DefaultRequestInject(h, f.Dependency, request)
	return handler.APIHandlerToHandler(h, nil)
}

// ProvideAuthzPolicy provides authorization policy of handler
func (f VerifyRequestTestHandlerFactory) ProvideAuthzPolicy() authz.Policy {
	return policy.AllOf(
		authz.PolicyFunc(policy.RequireMasterKey),
	)
}

type VerifyRequestTestPayload struct {
	RecordKey        string            `json:"record_key"`
	RecordValue      string            `json:"record_value"`
	ProviderSettings map[string]string `json:"provider_settings"`
	Templates        map[string]string `json:"templates"`
}

func (payload VerifyRequestTestPayload) Validate() error {
	if payload.RecordKey == "" {
		return skyerr.NewInvalidArgument("empty record_key", []string{"record_key"})
	}

	if payload.RecordValue == "" {
		return skyerr.NewInvalidArgument("empty record_value", []string{"record_value"})
	}

	if payload.ProviderSettings == nil || payload.ProviderSettings["name"] == "" {
		return skyerr.NewInvalidArgument("missing provider name", []string{"provider_settings.name"})
	}

	return nil
}

// VerifyRequestTestHandler sends a dummy verification request (i.e. email or SMS).
//
//  curl -X POST -H "Content-Type: application/json" \
//    -d @- http://localhost:3000/verify_request/test <<EOF
//  {
//    "record_key": "email",
//    "record_value": "test@example.com",
//    "provider_settings": {
//      "name": "smtp"
//    },
//    "templates": {
//      "text": "testing",
//      "html": "testing html"
//    }
//  }
//  EOF
//
//  curl -X POST -H "Content-Type: application/json" \
//    -d @- http://localhost:3000/verify_request/test <<EOF
//  {
//    "record_key": "phone",
//    "record_value": "+15005550009",
//    "provider_settings": {
//      "name": "twilio",
//      "twilio_from": "+15005550009",
//      "twilio_account_sid": "",
//      "twilio_auth_token": ""
//    },
//    "templates": {
//      "text": "testing sms"
//    }
//  }
//  EOF
//
type VerifyRequestTestHandler struct {
	TestCodeSenderFactory userverify.TestCodeSenderFactory `dependency:"UserVerifyTestCodeSenderFactory"`
	Logger                *logrus.Entry                    `dependency:"HandlerLogger"`
}

func (h VerifyRequestTestHandler) WithTx() bool {
	return false
}

// DecodeRequest decode request payload
func (h VerifyRequestTestHandler) DecodeRequest(request *http.Request) (handler.RequestPayload, error) {
	payload := VerifyRequestTestPayload{}
	if err := json.NewDecoder(request.Body).Decode(&payload); err != nil {
		return nil, skyerr.NewError(skyerr.BadRequest, "fails to decode the request payload")
	}

	return payload, nil
}

func (h VerifyRequestTestHandler) Handle(req interface{}) (resp interface{}, err error) {
	payload := req.(VerifyRequestTestPayload)
	codeSender := h.TestCodeSenderFactory.NewTestCodeSender(payload.RecordKey, payload.ProviderSettings, payload.Templates)
	if codeSender == nil {
		err = skyerr.NewInvalidArgument("invalid provider name", []string{"provider_settings.name"})
		return
	}

	if err = codeSender.Send(payload.RecordKey, payload.RecordValue); err == nil {
		resp = "OK"
	}

	return
}
<commit_msg>Use login ID type for verify request<commit_after>package userverify

import (
	"encoding/json"
	"net/http"
	"time"

	"github.com/skygeario/skygear-server/pkg/core/utils"

	"github.com/sirupsen/logrus"
	"github.com/skygeario/skygear-server/pkg/auth"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/provider/password"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/userprofile"
	"github.com/skygeario/skygear-server/pkg/auth/dependency/userverify"
	"github.com/skygeario/skygear-server/pkg/auth/response"
	coreAuth "github.com/skygeario/skygear-server/pkg/core/auth"
	"github.com/skygeario/skygear-server/pkg/core/auth/authz"
	"github.com/skygeario/skygear-server/pkg/core/auth/authz/policy"
	"github.com/skygeario/skygear-server/pkg/core/auth/metadata"
	"github.com/skygeario/skygear-server/pkg/core/db"
	"github.com/skygeario/skygear-server/pkg/core/handler"
	"github.com/skygeario/skygear-server/pkg/core/inject"
	"github.com/skygeario/skygear-server/pkg/core/server"
	"github.com/skygeario/skygear-server/pkg/core/skyerr"
)

// AttachVerifyRequestHandler attaches VerifyRequestHandler to server
func AttachVerifyRequestHandler(
	server *server.Server,
	authDependency auth.DependencyMap,
) *server.Server {
	server.Handle("/verify_request", &VerifyRequestHandlerFactory{
		authDependency,
	}).Methods("OPTIONS", "POST")
	server.Handle("/verify_request/test", &VerifyRequestTestHandlerFactory{
		authDependency,
	}).Methods("OPTIONS", "POST")
	return server
}

// VerifyRequestHandlerFactory creates VerifyRequestHandler
type VerifyRequestHandlerFactory struct {
	Dependency auth.DependencyMap
}

// NewHandler creates new VerifyRequestHandler
func (f VerifyRequestHandlerFactory) NewHandler(request *http.Request) http.Handler {
	h := &VerifyRequestHandler{}
	inject.DefaultRequestInject(h, f.Dependency, request)
	return handler.APIHandlerToHandler(h, h.TxContext)
}

// ProvideAuthzPolicy provides authorization policy of handler
func (f VerifyRequestHandlerFactory) ProvideAuthzPolicy() authz.Policy {
	return policy.AllOf(
		authz.PolicyFunc(policy.DenyNoAccessKey),
		authz.PolicyFunc(policy.RequireAuthenticated),
		authz.PolicyFunc(policy.DenyDisabledUser),
	)
}

type loginIDType string

const (
	loginIDTypeEmail loginIDType = "email"
)

var allLoginIDTypes = []string{
	string(loginIDTypeEmail),
}

func (t loginIDType) MatchPrincipal(principal *password.Principal, provider password.Provider) bool {
	switch t {
	case loginIDTypeEmail:
		return provider.CheckLoginIDKeyType(principal.LoginIDKey, metadata.Email)
	default:
		return false
	}
}

type VerifyRequestPayload struct {
	RawLoginIDType string      `json:"login_id_type"`
	LoginIDType    loginIDType `json:"-"`
	LoginID        string      `json:"login_id"`
}

func (payload VerifyRequestPayload) Validate() error {
	if payload.LoginID == "" {
		return skyerr.NewInvalidArgument("empty login ID", []string{"login_id"})
	}

	return nil
}

// VerifyRequestHandler allows client to request verification (i.e. send email or send SMS).
//
//  curl -X POST -H "Content-Type: application/json" \
//    -d @- http://localhost:3000/verify_request <<EOF
//  {
//    "login_id_type": "email",
//    "login_id": "user@example.com"
//  }
//  EOF
//
type VerifyRequestHandler struct {
	TxContext            db.TxContext                    `dependency:"TxContext"`
	AuthContext          coreAuth.ContextGetter          `dependency:"AuthContextGetter"`
	CodeSenderFactory    userverify.CodeSenderFactory    `dependency:"UserVerifyCodeSenderFactory"`
	CodeGeneratorFactory userverify.CodeGeneratorFactory `dependency:"VerifyCodeCodeGeneratorFactory"`
	UserProfileStore     userprofile.Store               `dependency:"UserProfileStore"`
	VerifyCodeStore      userverify.Store                `dependency:"VerifyCodeStore"`
	Logger               *logrus.Entry                   `dependency:"HandlerLogger"`
	PasswordAuthProvider password.Provider               `dependency:"PasswordAuthProvider"`
}

func (h VerifyRequestHandler) WithTx() bool {
	return true
}

// DecodeRequest decode request payload
func (h VerifyRequestHandler) DecodeRequest(request *http.Request) (handler.RequestPayload, error) {
	payload := VerifyRequestPayload{}
	if err := json.NewDecoder(request.Body).Decode(&payload); err != nil {
		return nil, skyerr.NewError(skyerr.BadRequest, "fails to decode the request payload")
	}

	if !utils.StringSliceContains(allLoginIDTypes, payload.RawLoginIDType) {
		return nil, skyerr.NewInvalidArgument("invalid login ID type", []string{"login_id_type"})
	}
	payload.LoginIDType = loginIDType(payload.RawLoginIDType)

	return payload, nil
}

func (h VerifyRequestHandler) Handle(req interface{}) (resp interface{}, err error) {
	payload := req.(VerifyRequestPayload)
	authInfo := h.AuthContext.AuthInfo()

	// Get Profile
	var userProfile userprofile.UserProfile
	if userProfile, err = h.UserProfileStore.GetUserProfile(authInfo.ID); err != nil {
		// TODO:
		// return proper error
		err = skyerr.NewError(skyerr.UnexpectedError, "Unable to fetch user profile")
		return
	}

	userFactory := response.UserFactory{
		PasswordAuthProvider: h.PasswordAuthProvider,
	}
	user := userFactory.NewUser(*authInfo, userProfile)

	// We don't check realms. i.e. Verifying a email means every email login IDs
	// of that email is verified, regardless the realm.
	principals, err := h.PasswordAuthProvider.GetPrincipalsByLoginID("", payload.LoginID)
	if err != nil {
		return
	}

	var userPrincipal *password.Principal
	for _, principal := range principals {
		if principal.UserID == authInfo.ID && payload.LoginIDType.MatchPrincipal(principal, h.PasswordAuthProvider) {
			userPrincipal = principal
			break
		}
	}
	if userPrincipal == nil {
		err = skyerr.NewError(skyerr.UnexpectedError, "Value of "+payload.LoginID+" doesn't exist.")
		return
	}

	codeSender := h.CodeSenderFactory.NewCodeSender(userPrincipal.LoginIDKey)
	if codeSender == nil {
		err = skyerr.NewError(skyerr.UnexpectedError, "User verification not configured for login ID key")
		return
	}

	codeGenerator := h.CodeGeneratorFactory.NewCodeGenerator(userPrincipal.LoginIDKey)
	code := codeGenerator.Generate()

	verifyCode := userverify.NewVerifyCode()
	verifyCode.UserID = authInfo.ID
	verifyCode.RecordKey = userPrincipal.LoginIDKey
	verifyCode.RecordValue = userPrincipal.LoginID
	verifyCode.Code = code
	verifyCode.Consumed = false
	verifyCode.CreatedAt = time.Now()

	if err = h.VerifyCodeStore.CreateVerifyCode(&verifyCode); err != nil {
		return
	}

	if err = codeSender.Send(verifyCode, user); err != nil {
		h.Logger.WithFields(logrus.Fields{
			"error":        err,
			"login_id_key": userPrincipal.LoginIDKey,
			"login_id":     userPrincipal.LoginID,
		}).Error("fail to send verify request")
		return
	}

	resp = "OK"
	return
}

// VerifyRequestTestHandlerFactory creates VerifyRequestTestHandler
type VerifyRequestTestHandlerFactory struct {
	Dependency auth.DependencyMap
}

// NewHandler creates new VerifyRequestTestHandler
func (f VerifyRequestTestHandlerFactory) NewHandler(request *http.Request) http.Handler {
	h := &VerifyRequestTestHandler{}
	inject.DefaultRequestInject(h, f.Dependency, request)
	return handler.APIHandlerToHandler(h, nil)
}

// ProvideAuthzPolicy provides authorization policy of handler
func (f VerifyRequestTestHandlerFactory) ProvideAuthzPolicy() authz.Policy {
	return policy.AllOf(
		authz.PolicyFunc(policy.RequireMasterKey),
	)
}

type VerifyRequestTestPayload struct {
	RecordKey        string            `json:"record_key"`
	RecordValue      string            `json:"record_value"`
	ProviderSettings map[string]string `json:"provider_settings"`
	Templates        map[string]string `json:"templates"`
}

func (payload VerifyRequestTestPayload) Validate() error {
	if payload.RecordKey == "" {
		return skyerr.NewInvalidArgument("empty record_key", []string{"record_key"})
	}

	if payload.RecordValue == "" {
		return skyerr.NewInvalidArgument("empty record_value", []string{"record_value"})
	}

	if payload.ProviderSettings == nil || payload.ProviderSettings["name"] == "" {
		return skyerr.NewInvalidArgument("missing provider name", []string{"provider_settings.name"})
	}

	return nil
}

// VerifyRequestTestHandler sends a dummy verification request (i.e. email or SMS).
//
//  curl -X POST -H "Content-Type: application/json" \
//    -d @- http://localhost:3000/verify_request/test <<EOF
//  {
//    "record_key": "email",
//    "record_value": "test@example.com",
//    "provider_settings": {
//      "name": "smtp"
//    },
//    "templates": {
//      "text": "testing",
//      "html": "testing html"
//    }
//  }
//  EOF
//
//  curl -X POST -H "Content-Type: application/json" \
//    -d @- http://localhost:3000/verify_request/test <<EOF
//  {
//    "record_key": "phone",
//    "record_value": "+15005550009",
//    "provider_settings": {
//      "name": "twilio",
//      "twilio_from": "+15005550009",
//      "twilio_account_sid": "",
//      "twilio_auth_token": ""
//    },
//    "templates": {
//      "text": "testing sms"
//    }
//  }
//  EOF
//
type VerifyRequestTestHandler struct {
	TestCodeSenderFactory userverify.TestCodeSenderFactory `dependency:"UserVerifyTestCodeSenderFactory"`
	Logger                *logrus.Entry                    `dependency:"HandlerLogger"`
}

func (h VerifyRequestTestHandler) WithTx() bool {
	return false
}

// DecodeRequest decode request payload
func (h VerifyRequestTestHandler) DecodeRequest(request *http.Request) (handler.RequestPayload, error) {
	payload := VerifyRequestTestPayload{}
	if err := json.NewDecoder(request.Body).Decode(&payload); err != nil {
		return nil, skyerr.NewError(skyerr.BadRequest, "fails to decode the request payload")
	}

	return payload, nil
}

func (h VerifyRequestTestHandler) Handle(req interface{}) (resp interface{}, err error) {
	payload := req.(VerifyRequestTestPayload)
	codeSender := h.TestCodeSenderFactory.NewTestCodeSender(payload.RecordKey, payload.ProviderSettings, payload.Templates)
	if codeSender == nil {
		err = skyerr.NewInvalidArgument("invalid provider name", []string{"provider_settings.name"})
		return
	}

	if err = codeSender.Send(payload.RecordKey, payload.RecordValue); err == nil {
		resp = "OK"
	}

	return
}
<|endoftext|>


<commit_before>/*
Copyright 2017 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 cm

import (
	"fmt"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/golang/glog"

	"k8s.io/apimachinery/pkg/util/wait"

	units "github.com/docker/go-units"
	cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
	"k8s.io/api/core/v1"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	v1qos "k8s.io/kubernetes/pkg/api/v1/helper/qos"
	"k8s.io/kubernetes/pkg/api/v1/resource"
	kubefeatures "k8s.io/kubernetes/pkg/features"
)

const (
	// how often the qos cgroup manager will perform periodic update
	// of the qos level cgroup resource constraints
	periodicQOSCgroupUpdateInterval = 1 * time.Minute
)

type QOSContainerManager interface {
	Start(func() v1.ResourceList, ActivePodsFunc) error
	GetQOSContainersInfo() QOSContainersInfo
	UpdateCgroups() error
}

type qosContainerManagerImpl struct {
	sync.Mutex
	nodeInfo           *v1.Node
	qosContainersInfo  QOSContainersInfo
	subsystems         *CgroupSubsystems
	cgroupManager      CgroupManager
	activePods         ActivePodsFunc
	getNodeAllocatable func() v1.ResourceList
	cgroupRoot         string
	qosReserved        map[v1.ResourceName]int64
}

func NewQOSContainerManager(subsystems *CgroupSubsystems, cgroupRoot string, nodeConfig NodeConfig) (QOSContainerManager, error) {
	if !nodeConfig.CgroupsPerQOS {
		return &qosContainerManagerNoop{
			cgroupRoot: CgroupName(nodeConfig.CgroupRoot),
		}, nil
	}

	return &qosContainerManagerImpl{
		subsystems:    subsystems,
		cgroupManager: NewCgroupManager(subsystems, nodeConfig.CgroupDriver),
		cgroupRoot:    cgroupRoot,
		qosReserved:   nodeConfig.ExperimentalQOSReserved,
	}, nil
}

func (m *qosContainerManagerImpl) GetQOSContainersInfo() QOSContainersInfo {
	return m.qosContainersInfo
}

func (m *qosContainerManagerImpl) Start(getNodeAllocatable func() v1.ResourceList, activePods ActivePodsFunc) error {
	cm := m.cgroupManager
	rootContainer := m.cgroupRoot
	if !cm.Exists(CgroupName(rootContainer)) {
		return fmt.Errorf("root container %s doesn't exist", rootContainer)
	}

	// Top level for Qos containers are created only for Burstable
	// and Best Effort classes
	qosClasses := map[v1.PodQOSClass]string{
		v1.PodQOSBurstable:  path.Join(rootContainer, strings.ToLower(string(v1.PodQOSBurstable))),
		v1.PodQOSBestEffort: path.Join(rootContainer, strings.ToLower(string(v1.PodQOSBestEffort))),
	}

	// Create containers for both qos classes
	for qosClass, containerName := range qosClasses {
		// get the container's absolute name
		absoluteContainerName := CgroupName(containerName)

		resourceParameters := &ResourceConfig{}
		// the BestEffort QoS class has a statically configured minShares value
		if qosClass == v1.PodQOSBestEffort {
			minShares := uint64(MinShares)
			resourceParameters.CpuShares = &minShares
		}

		// containerConfig object stores the cgroup specifications
		containerConfig := &CgroupConfig{
			Name:               absoluteContainerName,
			ResourceParameters: resourceParameters,
		}

		// for each enumerated huge page size, the qos tiers are unbounded
		if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.HugePages) {
			m.setHugePagesUnbounded(containerConfig)
		}

		// check if it exists
		if !cm.Exists(absoluteContainerName) {
			if err := cm.Create(containerConfig); err != nil {
				return fmt.Errorf("failed to create top level %v QOS cgroup : %v", qosClass, err)
			}
		} else {
			// to ensure we actually have the right state, we update the config on startup
			if err := cm.Update(containerConfig); err != nil {
				return fmt.Errorf("failed to update top level %v QOS cgroup : %v", qosClass, err)
			}
		}
	}
	// Store the top level qos container names
	m.qosContainersInfo = QOSContainersInfo{
		Guaranteed: rootContainer,
		Burstable:  qosClasses[v1.PodQOSBurstable],
		BestEffort: qosClasses[v1.PodQOSBestEffort],
	}
	m.getNodeAllocatable = getNodeAllocatable
	m.activePods = activePods

	// update qos cgroup tiers on startup and in periodic intervals
	// to ensure desired state is in sync with actual state.
	go wait.Until(func() {
		err := m.UpdateCgroups()
		if err != nil {
			glog.Warningf("[ContainerManager] Failed to reserve QoS requests: %v", err)
		}
	}, periodicQOSCgroupUpdateInterval, wait.NeverStop)

	return nil
}

// setHugePagesUnbounded ensures hugetlb is effectively unbounded
func (m *qosContainerManagerImpl) setHugePagesUnbounded(cgroupConfig *CgroupConfig) error {
	hugePageLimit := map[int64]int64{}
	for _, pageSize := range cgroupfs.HugePageSizes {
		pageSizeBytes, err := units.RAMInBytes(pageSize)
		if err != nil {
			return err
		}
		hugePageLimit[pageSizeBytes] = int64(1 << 62)
	}
	cgroupConfig.ResourceParameters.HugePageLimit = hugePageLimit
	return nil
}

func (m *qosContainerManagerImpl) setHugePagesConfig(configs map[v1.PodQOSClass]*CgroupConfig) error {
	for _, v := range configs {
		if err := m.setHugePagesUnbounded(v); err != nil {
			return err
		}
	}
	return nil
}

func (m *qosContainerManagerImpl) setCPUCgroupConfig(configs map[v1.PodQOSClass]*CgroupConfig) error {
	pods := m.activePods()
	burstablePodCPURequest := int64(0)
	for i := range pods {
		pod := pods[i]
		qosClass := v1qos.GetPodQOS(pod)
		if qosClass != v1.PodQOSBurstable {
			// we only care about the burstable qos tier
			continue
		}
		req, _ := resource.PodRequestsAndLimits(pod)
		if request, found := req[v1.ResourceCPU]; found {
			burstablePodCPURequest += request.MilliValue()
		}
	}

	// make sure best effort is always 2 shares
	bestEffortCPUShares := uint64(MinShares)
	configs[v1.PodQOSBestEffort].ResourceParameters.CpuShares = &bestEffortCPUShares

	// set burstable shares based on current observe state
	burstableCPUShares := MilliCPUToShares(burstablePodCPURequest)
	if burstableCPUShares < uint64(MinShares) {
		burstableCPUShares = uint64(MinShares)
	}
	configs[v1.PodQOSBurstable].ResourceParameters.CpuShares = &burstableCPUShares
	return nil
}

// setMemoryReserve sums the memory limits of all pods in a QOS class,
// calculates QOS class memory limits, and set those limits in the
// CgroupConfig for each QOS class.
func (m *qosContainerManagerImpl) setMemoryReserve(configs map[v1.PodQOSClass]*CgroupConfig, percentReserve int64) {
	qosMemoryRequests := map[v1.PodQOSClass]int64{
		v1.PodQOSGuaranteed: 0,
		v1.PodQOSBurstable:  0,
	}

	// Sum the pod limits for pods in each QOS class
	pods := m.activePods()
	for _, pod := range pods {
		podMemoryRequest := int64(0)
		qosClass := v1qos.GetPodQOS(pod)
		if qosClass == v1.PodQOSBestEffort {
			// limits are not set for Best Effort pods
			continue
		}
		req, _ := resource.PodRequestsAndLimits(pod)
		if request, found := req[v1.ResourceMemory]; found {
			podMemoryRequest += request.Value()
		}
		qosMemoryRequests[qosClass] += podMemoryRequest
	}

	resources := m.getNodeAllocatable()
	allocatableResource, ok := resources[v1.ResourceMemory]
	if !ok {
		glog.V(2).Infof("[Container Manager] Allocatable memory value could not be determined.  Not setting QOS memory limts.")
		return
	}
	allocatable := allocatableResource.Value()
	if allocatable == 0 {
		glog.V(2).Infof("[Container Manager] Memory allocatable reported as 0, might be in standalone mode.  Not setting QOS memory limts.")
		return
	}

	for qos, limits := range qosMemoryRequests {
		glog.V(2).Infof("[Container Manager] %s pod requests total %d bytes (reserve %d%%)", qos, limits, percentReserve)
	}

	// Calculate QOS memory limits
	burstableLimit := allocatable - (qosMemoryRequests[v1.PodQOSGuaranteed] * percentReserve / 100)
	bestEffortLimit := burstableLimit - (qosMemoryRequests[v1.PodQOSBurstable] * percentReserve / 100)
	configs[v1.PodQOSBurstable].ResourceParameters.Memory = &burstableLimit
	configs[v1.PodQOSBestEffort].ResourceParameters.Memory = &bestEffortLimit
}

// retrySetMemoryReserve checks for any QoS cgroups over the limit
// that was attempted to be set in the first Update() and adjusts
// their memory limit to the usage to prevent further growth.
func (m *qosContainerManagerImpl) retrySetMemoryReserve(configs map[v1.PodQOSClass]*CgroupConfig, percentReserve int64) {
	// Unreclaimable memory usage may already exceeded the desired limit
	// Attempt to set the limit near the current usage to put pressure
	// on the cgroup and prevent further growth.
	for qos, config := range configs {
		stats, err := m.cgroupManager.GetResourceStats(config.Name)
		if err != nil {
			glog.V(2).Infof("[Container Manager] %v", err)
			return
		}
		usage := stats.MemoryStats.Usage

		// Because there is no good way to determine of the original Update()
		// on the memory resource was successful, we determine failure of the
		// first attempt by checking if the usage is above the limit we attempt
		// to set.  If it is, we assume the first attempt to set the limit failed
		// and try again setting the limit to the usage.  Otherwise we leave
		// the CgroupConfig as is.
		if configs[qos].ResourceParameters.Memory != nil && usage > *configs[qos].ResourceParameters.Memory {
			configs[qos].ResourceParameters.Memory = &usage
		}
	}
}

func (m *qosContainerManagerImpl) UpdateCgroups() error {
	m.Lock()
	defer m.Unlock()

	qosConfigs := map[v1.PodQOSClass]*CgroupConfig{
		v1.PodQOSBurstable: {
			Name:               CgroupName(m.qosContainersInfo.Burstable),
			ResourceParameters: &ResourceConfig{},
		},
		v1.PodQOSBestEffort: {
			Name:               CgroupName(m.qosContainersInfo.BestEffort),
			ResourceParameters: &ResourceConfig{},
		},
	}

	// update the qos level cgroup settings for cpu shares
	if err := m.setCPUCgroupConfig(qosConfigs); err != nil {
		return err
	}

	// update the qos level cgroup settings for huge pages (ensure they remain unbounded)
	if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.HugePages) {
		if err := m.setHugePagesConfig(qosConfigs); err != nil {
			return err
		}
	}

	for resource, percentReserve := range m.qosReserved {
		switch resource {
		case v1.ResourceMemory:
			m.setMemoryReserve(qosConfigs, percentReserve)
		}
	}
	updateSuccess := true
	for _, config := range qosConfigs {
		err := m.cgroupManager.Update(config)
		if err != nil {
			updateSuccess = false
		}
	}
	if updateSuccess {
		glog.V(2).Infof("[ContainerManager]: Updated QoS cgroup configuration")
		return nil
	}

	// If the resource can adjust the ResourceConfig to increase likelihood of
	// success, call the adjustment function here.  Otherwise, the Update() will
	// be called again with the same values.
	for resource, percentReserve := range m.qosReserved {
		switch resource {
		case v1.ResourceMemory:
			m.retrySetMemoryReserve(qosConfigs, percentReserve)
		}
	}

	for _, config := range qosConfigs {
		err := m.cgroupManager.Update(config)
		if err != nil {
			glog.V(2).Infof("[ContainerManager]: Failed to update QoS cgroup configuration")
			return err
		}
	}

	glog.V(2).Infof("[ContainerManager]: Updated QoS cgroup configuration on retry")
	return nil
}

type qosContainerManagerNoop struct {
	cgroupRoot CgroupName
}

var _ QOSContainerManager = &qosContainerManagerNoop{}

func (m *qosContainerManagerNoop) GetQOSContainersInfo() QOSContainersInfo {
	return QOSContainersInfo{}
}

func (m *qosContainerManagerNoop) Start(_ func() v1.ResourceList, _ ActivePodsFunc) error {
	return nil
}

func (m *qosContainerManagerNoop) UpdateCgroups() error {
	return nil
}
<commit_msg>remove redundancy code in setCPUCgroupConfig<commit_after>/*
Copyright 2017 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 cm

import (
	"fmt"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/golang/glog"

	"k8s.io/apimachinery/pkg/util/wait"

	units "github.com/docker/go-units"
	cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
	"k8s.io/api/core/v1"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	v1qos "k8s.io/kubernetes/pkg/api/v1/helper/qos"
	"k8s.io/kubernetes/pkg/api/v1/resource"
	kubefeatures "k8s.io/kubernetes/pkg/features"
)

const (
	// how often the qos cgroup manager will perform periodic update
	// of the qos level cgroup resource constraints
	periodicQOSCgroupUpdateInterval = 1 * time.Minute
)

type QOSContainerManager interface {
	Start(func() v1.ResourceList, ActivePodsFunc) error
	GetQOSContainersInfo() QOSContainersInfo
	UpdateCgroups() error
}

type qosContainerManagerImpl struct {
	sync.Mutex
	nodeInfo           *v1.Node
	qosContainersInfo  QOSContainersInfo
	subsystems         *CgroupSubsystems
	cgroupManager      CgroupManager
	activePods         ActivePodsFunc
	getNodeAllocatable func() v1.ResourceList
	cgroupRoot         string
	qosReserved        map[v1.ResourceName]int64
}

func NewQOSContainerManager(subsystems *CgroupSubsystems, cgroupRoot string, nodeConfig NodeConfig) (QOSContainerManager, error) {
	if !nodeConfig.CgroupsPerQOS {
		return &qosContainerManagerNoop{
			cgroupRoot: CgroupName(nodeConfig.CgroupRoot),
		}, nil
	}

	return &qosContainerManagerImpl{
		subsystems:    subsystems,
		cgroupManager: NewCgroupManager(subsystems, nodeConfig.CgroupDriver),
		cgroupRoot:    cgroupRoot,
		qosReserved:   nodeConfig.ExperimentalQOSReserved,
	}, nil
}

func (m *qosContainerManagerImpl) GetQOSContainersInfo() QOSContainersInfo {
	return m.qosContainersInfo
}

func (m *qosContainerManagerImpl) Start(getNodeAllocatable func() v1.ResourceList, activePods ActivePodsFunc) error {
	cm := m.cgroupManager
	rootContainer := m.cgroupRoot
	if !cm.Exists(CgroupName(rootContainer)) {
		return fmt.Errorf("root container %s doesn't exist", rootContainer)
	}

	// Top level for Qos containers are created only for Burstable
	// and Best Effort classes
	qosClasses := map[v1.PodQOSClass]string{
		v1.PodQOSBurstable:  path.Join(rootContainer, strings.ToLower(string(v1.PodQOSBurstable))),
		v1.PodQOSBestEffort: path.Join(rootContainer, strings.ToLower(string(v1.PodQOSBestEffort))),
	}

	// Create containers for both qos classes
	for qosClass, containerName := range qosClasses {
		// get the container's absolute name
		absoluteContainerName := CgroupName(containerName)

		resourceParameters := &ResourceConfig{}
		// the BestEffort QoS class has a statically configured minShares value
		if qosClass == v1.PodQOSBestEffort {
			minShares := uint64(MinShares)
			resourceParameters.CpuShares = &minShares
		}

		// containerConfig object stores the cgroup specifications
		containerConfig := &CgroupConfig{
			Name:               absoluteContainerName,
			ResourceParameters: resourceParameters,
		}

		// for each enumerated huge page size, the qos tiers are unbounded
		if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.HugePages) {
			m.setHugePagesUnbounded(containerConfig)
		}

		// check if it exists
		if !cm.Exists(absoluteContainerName) {
			if err := cm.Create(containerConfig); err != nil {
				return fmt.Errorf("failed to create top level %v QOS cgroup : %v", qosClass, err)
			}
		} else {
			// to ensure we actually have the right state, we update the config on startup
			if err := cm.Update(containerConfig); err != nil {
				return fmt.Errorf("failed to update top level %v QOS cgroup : %v", qosClass, err)
			}
		}
	}
	// Store the top level qos container names
	m.qosContainersInfo = QOSContainersInfo{
		Guaranteed: rootContainer,
		Burstable:  qosClasses[v1.PodQOSBurstable],
		BestEffort: qosClasses[v1.PodQOSBestEffort],
	}
	m.getNodeAllocatable = getNodeAllocatable
	m.activePods = activePods

	// update qos cgroup tiers on startup and in periodic intervals
	// to ensure desired state is in sync with actual state.
	go wait.Until(func() {
		err := m.UpdateCgroups()
		if err != nil {
			glog.Warningf("[ContainerManager] Failed to reserve QoS requests: %v", err)
		}
	}, periodicQOSCgroupUpdateInterval, wait.NeverStop)

	return nil
}

// setHugePagesUnbounded ensures hugetlb is effectively unbounded
func (m *qosContainerManagerImpl) setHugePagesUnbounded(cgroupConfig *CgroupConfig) error {
	hugePageLimit := map[int64]int64{}
	for _, pageSize := range cgroupfs.HugePageSizes {
		pageSizeBytes, err := units.RAMInBytes(pageSize)
		if err != nil {
			return err
		}
		hugePageLimit[pageSizeBytes] = int64(1 << 62)
	}
	cgroupConfig.ResourceParameters.HugePageLimit = hugePageLimit
	return nil
}

func (m *qosContainerManagerImpl) setHugePagesConfig(configs map[v1.PodQOSClass]*CgroupConfig) error {
	for _, v := range configs {
		if err := m.setHugePagesUnbounded(v); err != nil {
			return err
		}
	}
	return nil
}

func (m *qosContainerManagerImpl) setCPUCgroupConfig(configs map[v1.PodQOSClass]*CgroupConfig) error {
	pods := m.activePods()
	burstablePodCPURequest := int64(0)
	for i := range pods {
		pod := pods[i]
		qosClass := v1qos.GetPodQOS(pod)
		if qosClass != v1.PodQOSBurstable {
			// we only care about the burstable qos tier
			continue
		}
		req, _ := resource.PodRequestsAndLimits(pod)
		if request, found := req[v1.ResourceCPU]; found {
			burstablePodCPURequest += request.MilliValue()
		}
	}

	// make sure best effort is always 2 shares
	bestEffortCPUShares := uint64(MinShares)
	configs[v1.PodQOSBestEffort].ResourceParameters.CpuShares = &bestEffortCPUShares

	// set burstable shares based on current observe state
	burstableCPUShares := MilliCPUToShares(burstablePodCPURequest)
	configs[v1.PodQOSBurstable].ResourceParameters.CpuShares = &burstableCPUShares
	return nil
}

// setMemoryReserve sums the memory limits of all pods in a QOS class,
// calculates QOS class memory limits, and set those limits in the
// CgroupConfig for each QOS class.
func (m *qosContainerManagerImpl) setMemoryReserve(configs map[v1.PodQOSClass]*CgroupConfig, percentReserve int64) {
	qosMemoryRequests := map[v1.PodQOSClass]int64{
		v1.PodQOSGuaranteed: 0,
		v1.PodQOSBurstable:  0,
	}

	// Sum the pod limits for pods in each QOS class
	pods := m.activePods()
	for _, pod := range pods {
		podMemoryRequest := int64(0)
		qosClass := v1qos.GetPodQOS(pod)
		if qosClass == v1.PodQOSBestEffort {
			// limits are not set for Best Effort pods
			continue
		}
		req, _ := resource.PodRequestsAndLimits(pod)
		if request, found := req[v1.ResourceMemory]; found {
			podMemoryRequest += request.Value()
		}
		qosMemoryRequests[qosClass] += podMemoryRequest
	}

	resources := m.getNodeAllocatable()
	allocatableResource, ok := resources[v1.ResourceMemory]
	if !ok {
		glog.V(2).Infof("[Container Manager] Allocatable memory value could not be determined.  Not setting QOS memory limts.")
		return
	}
	allocatable := allocatableResource.Value()
	if allocatable == 0 {
		glog.V(2).Infof("[Container Manager] Memory allocatable reported as 0, might be in standalone mode.  Not setting QOS memory limts.")
		return
	}

	for qos, limits := range qosMemoryRequests {
		glog.V(2).Infof("[Container Manager] %s pod requests total %d bytes (reserve %d%%)", qos, limits, percentReserve)
	}

	// Calculate QOS memory limits
	burstableLimit := allocatable - (qosMemoryRequests[v1.PodQOSGuaranteed] * percentReserve / 100)
	bestEffortLimit := burstableLimit - (qosMemoryRequests[v1.PodQOSBurstable] * percentReserve / 100)
	configs[v1.PodQOSBurstable].ResourceParameters.Memory = &burstableLimit
	configs[v1.PodQOSBestEffort].ResourceParameters.Memory = &bestEffortLimit
}

// retrySetMemoryReserve checks for any QoS cgroups over the limit
// that was attempted to be set in the first Update() and adjusts
// their memory limit to the usage to prevent further growth.
func (m *qosContainerManagerImpl) retrySetMemoryReserve(configs map[v1.PodQOSClass]*CgroupConfig, percentReserve int64) {
	// Unreclaimable memory usage may already exceeded the desired limit
	// Attempt to set the limit near the current usage to put pressure
	// on the cgroup and prevent further growth.
	for qos, config := range configs {
		stats, err := m.cgroupManager.GetResourceStats(config.Name)
		if err != nil {
			glog.V(2).Infof("[Container Manager] %v", err)
			return
		}
		usage := stats.MemoryStats.Usage

		// Because there is no good way to determine of the original Update()
		// on the memory resource was successful, we determine failure of the
		// first attempt by checking if the usage is above the limit we attempt
		// to set.  If it is, we assume the first attempt to set the limit failed
		// and try again setting the limit to the usage.  Otherwise we leave
		// the CgroupConfig as is.
		if configs[qos].ResourceParameters.Memory != nil && usage > *configs[qos].ResourceParameters.Memory {
			configs[qos].ResourceParameters.Memory = &usage
		}
	}
}

func (m *qosContainerManagerImpl) UpdateCgroups() error {
	m.Lock()
	defer m.Unlock()

	qosConfigs := map[v1.PodQOSClass]*CgroupConfig{
		v1.PodQOSBurstable: {
			Name:               CgroupName(m.qosContainersInfo.Burstable),
			ResourceParameters: &ResourceConfig{},
		},
		v1.PodQOSBestEffort: {
			Name:               CgroupName(m.qosContainersInfo.BestEffort),
			ResourceParameters: &ResourceConfig{},
		},
	}

	// update the qos level cgroup settings for cpu shares
	if err := m.setCPUCgroupConfig(qosConfigs); err != nil {
		return err
	}

	// update the qos level cgroup settings for huge pages (ensure they remain unbounded)
	if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.HugePages) {
		if err := m.setHugePagesConfig(qosConfigs); err != nil {
			return err
		}
	}

	for resource, percentReserve := range m.qosReserved {
		switch resource {
		case v1.ResourceMemory:
			m.setMemoryReserve(qosConfigs, percentReserve)
		}
	}
	updateSuccess := true
	for _, config := range qosConfigs {
		err := m.cgroupManager.Update(config)
		if err != nil {
			updateSuccess = false
		}
	}
	if updateSuccess {
		glog.V(2).Infof("[ContainerManager]: Updated QoS cgroup configuration")
		return nil
	}

	// If the resource can adjust the ResourceConfig to increase likelihood of
	// success, call the adjustment function here.  Otherwise, the Update() will
	// be called again with the same values.
	for resource, percentReserve := range m.qosReserved {
		switch resource {
		case v1.ResourceMemory:
			m.retrySetMemoryReserve(qosConfigs, percentReserve)
		}
	}

	for _, config := range qosConfigs {
		err := m.cgroupManager.Update(config)
		if err != nil {
			glog.V(2).Infof("[ContainerManager]: Failed to update QoS cgroup configuration")
			return err
		}
	}

	glog.V(2).Infof("[ContainerManager]: Updated QoS cgroup configuration on retry")
	return nil
}

type qosContainerManagerNoop struct {
	cgroupRoot CgroupName
}

var _ QOSContainerManager = &qosContainerManagerNoop{}

func (m *qosContainerManagerNoop) GetQOSContainersInfo() QOSContainersInfo {
	return QOSContainersInfo{}
}

func (m *qosContainerManagerNoop) Start(_ func() v1.ResourceList, _ ActivePodsFunc) error {
	return nil
}

func (m *qosContainerManagerNoop) UpdateCgroups() error {
	return nil
}
<|endoftext|>


<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2018 Red Hat, Inc.
 *
 */

package network

//go:generate mockgen -source $GOFILE -package=$GOPACKAGE -destination=generated_mock_$GOFILE

/*
 ATTENTION: Rerun code generators when interface signatures are modified.
*/

import (
	"fmt"

	v1 "kubevirt.io/client-go/api/v1"
	"kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
	"kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/network/cache"
)

const primaryPodInterfaceName = "eth0"

var vifCacheFile = "/proc/%s/root/var/run/kubevirt-private/vif-cache-%s.json"
var podNICFactory = newpodNIC

// Network configuration is split into two parts, or phases, each executed in a
// different context.
// Phase1 is run by virt-handler and heavylifts most configuration steps. It
// also creates the tap device that will be passed by name to virt-launcher,
// thus allowing unprivileged libvirt to consume a pre-configured device.
// Phase2 is run by virt-launcher in the pod context and completes steps left
// out of virt-handler. The reason to have a separate phase for virt-launcher
// and not just have all the work done by virt-handler is because there is no
// ready solution for DHCP server startup in virt-handler context yet. This is
// a temporary limitation and the split is expected to go once the final gap is
// closed.
// Moving all configuration steps into virt-handler will also allow to
// downgrade privileges for virt-launcher, specifically, to remove NET_ADMIN
// capability. Future patches should address that. See:
// https://github.com/kubevirt/kubevirt/issues/3085
type podNIC interface {
	PlugPhase1(vmi *v1.VirtualMachineInstance, iface *v1.Interface, network *v1.Network, podInterfaceName string, pid int) error
	PlugPhase2(vmi *v1.VirtualMachineInstance, iface *v1.Interface, network *v1.Network, domain *api.Domain, podInterfaceName string) error
}

func getNetworksAndCniNetworks(vmi *v1.VirtualMachineInstance) (map[string]*v1.Network, map[string]int) {
	networks := map[string]*v1.Network{}
	cniNetworks := map[string]int{}
	for _, network := range vmi.Spec.Networks {
		networks[network.Name] = network.DeepCopy()
		if networks[network.Name].Multus != nil && !networks[network.Name].Multus.Default {
			// multus pod interfaces start from 1
			cniNetworks[network.Name] = len(cniNetworks) + 1
		}
	}
	return networks, cniNetworks
}

func getPodInterfaceName(networks map[string]*v1.Network, cniNetworks map[string]int, ifaceName string) string {
	if networks[ifaceName].Multus != nil && !networks[ifaceName].Multus.Default {
		// multus pod interfaces named netX
		return fmt.Sprintf("net%d", cniNetworks[ifaceName])
	} else {
		return primaryPodInterfaceName
	}
}

func SetupPodNetworkPhase1(vmi *v1.VirtualMachineInstance, pid int, cacheFactory cache.InterfaceCacheFactory) error {
	networks, cniNetworks := getNetworksAndCniNetworks(vmi)
	for i, iface := range vmi.Spec.Domain.Devices.Interfaces {
		network, ok := networks[iface.Name]
		if !ok {
			return fmt.Errorf("failed to find a network %s", iface.Name)
		}
		podnic := podNICFactory(cacheFactory)
		podInterfaceName := getPodInterfaceName(networks, cniNetworks, iface.Name)
		err = podNIC.PlugPhase1(podnic, vmi, &vmi.Spec.Domain.Devices.Interfaces[i], network, podInterfaceName, pid)
		if err != nil {
			return err
		}
	}
	return nil
}

func SetupPodNetworkPhase2(vmi *v1.VirtualMachineInstance, domain *api.Domain, cacheFactory cache.InterfaceCacheFactory) error {
	networks, cniNetworks := getNetworksAndCniNetworks(vmi)
	for i, iface := range vmi.Spec.Domain.Devices.Interfaces {
		network, ok := networks[iface.Name]
		if !ok {
			return fmt.Errorf("failed to find a network %s", iface.Name)
		}
		podnic := podNICFactory(cacheFactory)
		podInterfaceName := getPodInterfaceName(networks, cniNetworks, iface.Name)
		err = podNIC.PlugPhase2(podnic, vmi, &vmi.Spec.Domain.Devices.Interfaces[i], network, domain, podInterfaceName)
		if err != nil {
			return err
		}
	}
	return nil
}

func newpodNIC(cacheFactory cache.InterfaceCacheFactory) podNIC {
	return &podNICImpl{cacheFactory: cacheFactory}
}
<commit_msg>network: rename cniNetworks map<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2018 Red Hat, Inc.
 *
 */

package network

//go:generate mockgen -source $GOFILE -package=$GOPACKAGE -destination=generated_mock_$GOFILE

/*
 ATTENTION: Rerun code generators when interface signatures are modified.
*/

import (
	"fmt"

	v1 "kubevirt.io/client-go/api/v1"
	"kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
	"kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/network/cache"
)

const primaryPodInterfaceName = "eth0"

var vifCacheFile = "/proc/%s/root/var/run/kubevirt-private/vif-cache-%s.json"
var podNICFactory = newpodNIC

// Network configuration is split into two parts, or phases, each executed in a
// different context.
// Phase1 is run by virt-handler and heavylifts most configuration steps. It
// also creates the tap device that will be passed by name to virt-launcher,
// thus allowing unprivileged libvirt to consume a pre-configured device.
// Phase2 is run by virt-launcher in the pod context and completes steps left
// out of virt-handler. The reason to have a separate phase for virt-launcher
// and not just have all the work done by virt-handler is because there is no
// ready solution for DHCP server startup in virt-handler context yet. This is
// a temporary limitation and the split is expected to go once the final gap is
// closed.
// Moving all configuration steps into virt-handler will also allow to
// downgrade privileges for virt-launcher, specifically, to remove NET_ADMIN
// capability. Future patches should address that. See:
// https://github.com/kubevirt/kubevirt/issues/3085
type podNIC interface {
	PlugPhase1(vmi *v1.VirtualMachineInstance, iface *v1.Interface, network *v1.Network, podInterfaceName string, pid int) error
	PlugPhase2(vmi *v1.VirtualMachineInstance, iface *v1.Interface, network *v1.Network, domain *api.Domain, podInterfaceName string) error
}

func getNetworksAndMultusIndices(vmi *v1.VirtualMachineInstance) (map[string]*v1.Network, map[string]int) {
	networks := map[string]*v1.Network{}
	multusIndices := map[string]int{}
	for _, network := range vmi.Spec.Networks {
		networks[network.Name] = network.DeepCopy()
		if networks[network.Name].Multus != nil && !networks[network.Name].Multus.Default {
			// multus pod interfaces start from 1
			multusIndices[network.Name] = len(multusIndices) + 1
		}
	}
	return networks, multusIndices
}

func getPodInterfaceName(networks map[string]*v1.Network, multusIndices map[string]int, ifaceName string) string {
	if networks[ifaceName].Multus != nil && !networks[ifaceName].Multus.Default {
		// multus pod interfaces named netX
		return fmt.Sprintf("net%d", multusIndices[ifaceName])
	} else {
		return primaryPodInterfaceName
	}
}

func SetupPodNetworkPhase1(vmi *v1.VirtualMachineInstance, pid int, cacheFactory cache.InterfaceCacheFactory) error {
	networks, multusIndices := getNetworksAndMultusIndices(vmi)
	for i, iface := range vmi.Spec.Domain.Devices.Interfaces {
		network, ok := networks[iface.Name]
		if !ok {
			return fmt.Errorf("failed to find a network %s", iface.Name)
		}
		podnic := podNICFactory(cacheFactory)
		podInterfaceName := getPodInterfaceName(networks, multusIndices, iface.Name)
		err := podNIC.PlugPhase1(podnic, vmi, &vmi.Spec.Domain.Devices.Interfaces[i], network, podInterfaceName, pid)
		if err != nil {
			return err
		}
	}
	return nil
}

func SetupPodNetworkPhase2(vmi *v1.VirtualMachineInstance, domain *api.Domain, cacheFactory cache.InterfaceCacheFactory) error {
	networks, multusIndices := getNetworksAndMultusIndices(vmi)
	for i, iface := range vmi.Spec.Domain.Devices.Interfaces {
		network, ok := networks[iface.Name]
		if !ok {
			return fmt.Errorf("failed to find a network %s", iface.Name)
		}
		podnic := podNICFactory(cacheFactory)
		podInterfaceName := getPodInterfaceName(networks, multusIndices, iface.Name)
		err := podNIC.PlugPhase2(podnic, vmi, &vmi.Spec.Domain.Devices.Interfaces[i], network, domain, podInterfaceName)
		if err != nil {
			return err
		}
	}
	return nil
}

func newpodNIC(cacheFactory cache.InterfaceCacheFactory) podNIC {
	return &podNICImpl{cacheFactory: cacheFactory}
}
<|endoftext|>