| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You 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 v1 |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "errors" |
| "strconv" |
| "time" |
| ) |
| |
| const ( |
| // HashOnVars means the hash scope is variable. |
| HashOnVars = "vars" |
| // HashOnVarsCombination means the hash scope is the |
| // variable combination. |
| HashOnVarsCombination = "vars_combinations" |
| // HashOnHeader means the hash scope is HTTP request |
| // headers. |
| HashOnHeader = "header" |
| // HashOnCookie means the hash scope is HTTP Cookie. |
| HashOnCookie = "cookie" |
| // HashOnConsumer means the hash scope is APISIX consumer. |
| HashOnConsumer = "consumer" |
| |
| // LbRoundRobin is the round robin load balancer. |
| LbRoundRobin = "roundrobin" |
| // LbConsistentHash is the consistent hash load balancer. |
| LbConsistentHash = "chash" |
| // LbEwma is the ewma load balancer. |
| LbEwma = "ewma" |
| // LbLeaseConn is the least connection load balancer. |
| LbLeastConn = "least_conn" |
| |
| // SchemeHTTP represents the HTTP protocol. |
| SchemeHTTP = "http" |
| // SchemeGRPC represents the GRPC protocol. |
| SchemeGRPC = "grpc" |
| |
| // HealthCheckHTTP represents the HTTP kind health check. |
| HealthCheckHTTP = "http" |
| // HealthCheckHTTPS represents the HTTPS kind health check. |
| HealthCheckHTTPS = "https" |
| // HealthCheckTCP represents the TCP kind health check. |
| HealthCheckTCP = "tcp" |
| |
| // HealthCheckMaxConsecutiveNumber is the max number for |
| // the consecutive success/failure in upstream health check. |
| HealthCheckMaxConsecutiveNumber = 254 |
| // ActiveHealthCheckMinInterval is the minimum interval for |
| // the active health check. |
| ActiveHealthCheckMinInterval = time.Second |
| |
| // DefaultUpstreamTimeout represents the default connect, |
| // read and send timeout (in seconds) with upstreams. |
| DefaultUpstreamTimeout = 60 |
| ) |
| |
| // Metadata contains all meta information about resources. |
| // +k8s:deepcopy-gen=true |
| type Metadata struct { |
| ID string `json:"id,omitempty" yaml:"id,omitempty"` |
| Name string `json:"name,omitempty" yaml:"name,omitempty"` |
| Desc string `json:"desc,omitempty" yaml:"desc,omitempty"` |
| Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` |
| } |
| |
| // Route apisix route object |
| // +k8s:deepcopy-gen=true |
| type Route struct { |
| Metadata `json:",inline" yaml:",inline"` |
| |
| Host string `json:"host,omitempty" yaml:"host,omitempty"` |
| Hosts []string `json:"hosts,omitempty" yaml:"hosts,omitempty"` |
| Uri string `json:"uri,omitempty" yaml:"uri,omitempty"` |
| Priority int `json:"priority,omitempty" yaml:"priority,omitempty"` |
| Vars Vars `json:"vars,omitempty" yaml:"vars,omitempty"` |
| Uris []string `json:"uris,omitempty" yaml:"uris,omitempty"` |
| Methods []string `json:"methods,omitempty" yaml:"methods,omitempty"` |
| EnableWebsocket bool `json:"enable_websocket,omitempty" yaml:"enable_websocket,omitempty"` |
| RemoteAddrs []string `json:"remote_addrs,omitempty" yaml:"remote_addrs,omitempty"` |
| UpstreamId string `json:"upstream_id,omitempty" yaml:"upstream_id,omitempty"` |
| Plugins Plugins `json:"plugins,omitempty" yaml:"plugins,omitempty"` |
| } |
| |
| // Vars represents the route match expressions of APISIX. |
| type Vars [][]StringOrSlice |
| |
| // UnmarshalJSON implements json.Unmarshaler interface. |
| // lua-cjson doesn't distinguish empty array and table, |
| // and by default empty array will be encoded as '{}'. |
| // We have to maintain the compatibility. |
| func (vars *Vars) UnmarshalJSON(p []byte) error { |
| if p[0] == '{' { |
| if len(p) != 2 { |
| return errors.New("unexpected non-empty object") |
| } |
| return nil |
| } |
| var data [][]StringOrSlice |
| if err := json.Unmarshal(p, &data); err != nil { |
| return err |
| } |
| *vars = data |
| return nil |
| } |
| |
| // StringOrSlice represents a string or a string slice. |
| // TODO Do not use interface{} to avoid the reflection overheads. |
| // +k8s:deepcopy-gen=true |
| type StringOrSlice struct { |
| StrVal string `json:"-"` |
| SliceVal []string `json:"-"` |
| } |
| |
| func (s *StringOrSlice) MarshalJSON() ([]byte, error) { |
| var ( |
| p []byte |
| err error |
| ) |
| if s.SliceVal != nil { |
| p, err = json.Marshal(s.SliceVal) |
| } else { |
| p, err = json.Marshal(s.StrVal) |
| } |
| return p, err |
| } |
| |
| func (s *StringOrSlice) UnmarshalJSON(p []byte) error { |
| var err error |
| |
| if len(p) == 0 { |
| return errors.New("empty object") |
| } |
| if p[0] == '[' { |
| err = json.Unmarshal(p, &s.SliceVal) |
| } else { |
| err = json.Unmarshal(p, &s.StrVal) |
| } |
| return err |
| } |
| |
| type Plugins map[string]interface{} |
| |
| func (p *Plugins) DeepCopyInto(out *Plugins) { |
| b, _ := json.Marshal(&p) |
| _ = json.Unmarshal(b, out) |
| } |
| |
| func (p *Plugins) DeepCopy() *Plugins { |
| if p == nil { |
| return nil |
| } |
| out := new(Plugins) |
| p.DeepCopyInto(out) |
| return out |
| } |
| |
| // Upstream is the apisix upstream definition. |
| // +k8s:deepcopy-gen=true |
| type Upstream struct { |
| Metadata `json:",inline" yaml:",inline"` |
| |
| Type string `json:"type,omitempty" yaml:"type,omitempty"` |
| HashOn string `json:"hash_on,omitempty" yaml:"hash_on,omitempty"` |
| Key string `json:"key,omitempty" yaml:"key,omitempty"` |
| Checks *UpstreamHealthCheck `json:"checks,omitempty" yaml:"checks,omitempty"` |
| Nodes UpstreamNodes `json:"nodes" yaml:"nodes"` |
| Scheme string `json:"scheme,omitempty" yaml:"scheme,omitempty"` |
| Retries int `json:"retries,omitempty" yaml:"retries,omitempty"` |
| Timeout *UpstreamTimeout `json:"timeout,omitempty" yaml:"timeout,omitempty"` |
| } |
| |
| // UpstreamTimeout represents the timeout settings on Upstream. |
| type UpstreamTimeout struct { |
| // Connect is the connect timeout |
| Connect int `json:"connect" yaml:"connect"` |
| // Send is the send timeout |
| Send int `json:"send" yaml:"send"` |
| // Read is the read timeout |
| Read int `json:"read" yaml:"read"` |
| } |
| |
| // UpstreamNodes is the upstream node list. |
| type UpstreamNodes []UpstreamNode |
| |
| // UnmarshalJSON implements json.Unmarshaler interface. |
| // lua-cjson doesn't distinguish empty array and table, |
| // and by default empty array will be encoded as '{}'. |
| // We have to maintain the compatibility. |
| func (n *UpstreamNodes) UnmarshalJSON(p []byte) error { |
| if p[0] == '{' { |
| if len(p) != 2 { |
| return errors.New("unexpected non-empty object") |
| } |
| return nil |
| } |
| var data []UpstreamNode |
| if err := json.Unmarshal(p, &data); err != nil { |
| return err |
| } |
| *n = data |
| return nil |
| } |
| |
| // UpstreamNode is the node in upstream |
| // +k8s:deepcopy-gen=true |
| type UpstreamNode struct { |
| Host string `json:"host,omitempty" yaml:"host,omitempty"` |
| Port int `json:"port,omitempty" yaml:"port,omitempty"` |
| Weight int `json:"weight,omitempty" yaml:"weight,omitempty"` |
| } |
| |
| // UpstreamHealthCheck defines the active and/or passive health check for an Upstream, |
| // with the upstream health check feature, pods can be kicked out or joined in quickly, |
| // if the feedback of Kubernetes liveness/readiness probe is long. |
| // +k8s:deepcopy-gen=true |
| type UpstreamHealthCheck struct { |
| Active *UpstreamActiveHealthCheck `json:"active" yaml:"active"` |
| Passive *UpstreamPassiveHealthCheck `json:"passive,omitempty" yaml:"passive,omitempty"` |
| } |
| |
| // UpstreamActiveHealthCheck defines the active kind of upstream health check. |
| // +k8s:deepcopy-gen=true |
| type UpstreamActiveHealthCheck struct { |
| Type string `json:"type,omitempty" yaml:"type,omitempty"` |
| Timeout int `json:"timeout,omitempty" yaml:"timeout,omitempty"` |
| Concurrency int `json:"concurrency,omitempty" yaml:"concurrency,omitempty"` |
| Host string `json:"host,omitempty" yaml:"host,omitempty"` |
| Port int32 `json:"port,omitempty" yaml:"port,omitempty"` |
| HTTPPath string `json:"http_path,omitempty" yaml:"http_path,omitempty"` |
| HTTPSVerifyCert bool `json:"https_verify_certificate,omitempty" yaml:"https_verify_certificate,omitempty"` |
| HTTPRequestHeaders []string `json:"req_headers,omitempty" yaml:"req_headers,omitempty"` |
| Healthy UpstreamActiveHealthCheckHealthy `json:"healthy,omitempty" yaml:"healthy,omitempty"` |
| Unhealthy UpstreamActiveHealthCheckUnhealthy `json:"unhealthy,omitempty" yaml:"unhealthy,omitempty"` |
| } |
| |
| // UpstreamPassiveHealthCheck defines the passive kind of upstream health check. |
| // +k8s:deepcopy-gen=true |
| type UpstreamPassiveHealthCheck struct { |
| Type string `json:"type,omitempty" yaml:"type,omitempty"` |
| Healthy UpstreamPassiveHealthCheckHealthy `json:"healthy,omitempty" yaml:"healthy,omitempty"` |
| Unhealthy UpstreamPassiveHealthCheckUnhealthy `json:"unhealthy,omitempty" yaml:"unhealthy,omitempty"` |
| } |
| |
| // UpstreamActiveHealthCheckHealthy defines the conditions to judge whether |
| // an upstream node is healthy with the active manner. |
| // +k8s:deepcopy-gen=true |
| type UpstreamActiveHealthCheckHealthy struct { |
| UpstreamPassiveHealthCheckHealthy `json:",inline" yaml:",inline"` |
| |
| Interval int `json:"interval,omitempty" yaml:"interval,omitempty"` |
| } |
| |
| // UpstreamPassiveHealthCheckHealthy defines the conditions to judge whether |
| // an upstream node is healthy with the passive manner. |
| // +k8s:deepcopy-gen=true |
| type UpstreamPassiveHealthCheckHealthy struct { |
| HTTPStatuses []int `json:"http_statuses,omitempty" yaml:"http_statuses,omitempty"` |
| Successes int `json:"successes,omitempty" yaml:"successes,omitempty"` |
| } |
| |
| // UpstreamActiveHealthCheckUnhealthy defines the conditions to judge whether |
| // an upstream node is unhealthy with the active manager. |
| // +k8s:deepcopy-gen=true |
| type UpstreamActiveHealthCheckUnhealthy struct { |
| UpstreamPassiveHealthCheckUnhealthy `json:",inline" yaml:",inline"` |
| |
| Interval int `json:"interval,omitempty" yaml:"interval,omitempty"` |
| } |
| |
| // UpstreamPassiveHealthCheckUnhealthy defines the conditions to judge whether |
| // an upstream node is unhealthy with the passive manager. |
| // +k8s:deepcopy-gen=true |
| type UpstreamPassiveHealthCheckUnhealthy struct { |
| HTTPStatuses []int `json:"http_statuses,omitempty" yaml:"http_statuses,omitempty"` |
| HTTPFailures int `json:"http_failures,omitempty" yaml:"http_failures,omitempty"` |
| TCPFailures int `json:"tcp_failures,omitempty" yaml:"tcp_failures,omitempty"` |
| Timeouts float64 `json:"timeouts,omitempty" yaml:"timeouts,omitempty"` |
| } |
| |
| // Ssl apisix ssl object |
| // +k8s:deepcopy-gen=true |
| type Ssl struct { |
| ID string `json:"id,omitempty" yaml:"id,omitempty"` |
| Snis []string `json:"snis,omitempty" yaml:"snis,omitempty"` |
| Cert string `json:"cert,omitempty" yaml:"cert,omitempty"` |
| Key string `json:"key,omitempty" yaml:"key,omitempty"` |
| Status int `json:"status,omitempty" yaml:"status,omitempty"` |
| Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` |
| } |
| |
| // StreamRoute represents the stream_route object in APISIX. |
| // +k8s:deepcopy-gen=true |
| type StreamRoute struct { |
| // TODO metadata should use Metadata type |
| ID string `json:"id,omitempty" yaml:"id,omitempty"` |
| Desc string `json:"desc,omitempty" yaml:"desc,omitempty"` |
| Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` |
| ServerPort int32 `json:"server_port,omitempty" yaml:"server_port,omitempty"` |
| UpstreamId string `json:"upstream_id,omitempty" yaml:"upstream_id,omitempty"` |
| Upstream *Upstream `json:"upstream,omitempty" yaml:"upstream,omitempty"` |
| } |
| |
| // GlobalRule represents the global_rule object in APISIX. |
| // +k8s:deepcopy-gen=true |
| type GlobalRule struct { |
| ID string `json:"id,omitempty" yaml:"id,omitempty"` |
| Plugins Plugins `json:"plugins,omitempty" yaml:"plugins,omitempty"` |
| } |
| |
| // NewDefaultUpstream returns an empty Upstream with default values. |
| func NewDefaultUpstream() *Upstream { |
| return &Upstream{ |
| Type: LbRoundRobin, |
| Key: "", |
| Nodes: make(UpstreamNodes, 0), |
| Scheme: SchemeHTTP, |
| Metadata: Metadata{ |
| Desc: "Created by apisix-ingress-controller, DO NOT modify it manually", |
| Labels: map[string]string{ |
| "managed-by": "apisix-ingress-controller", |
| }, |
| }, |
| } |
| } |
| |
| // NewDefaultRoute returns an empty Route with default values. |
| func NewDefaultRoute() *Route { |
| return &Route{ |
| Metadata: Metadata{ |
| Desc: "Created by apisix-ingress-controller, DO NOT modify it manually", |
| Labels: map[string]string{ |
| "managed-by": "apisix-ingress-controller", |
| }, |
| }, |
| } |
| } |
| |
| // NewDefaultStreamRoute returns an empty StreamRoute with default values. |
| func NewDefaultStreamRoute() *StreamRoute { |
| return &StreamRoute{ |
| Desc: "Created by apisix-ingress-controller, DO NOT modify it manually", |
| Labels: map[string]string{ |
| "managed-by": "apisix-ingress-controller", |
| }, |
| } |
| } |
| |
| // ComposeUpstreamName uses namespace, name and port info to compose |
| // the upstream name. |
| func ComposeUpstreamName(namespace, name string, port int32) string { |
| pstr := strconv.Itoa(int(port)) |
| // FIXME Use sync.Pool to reuse this buffer if the upstream |
| // name composing code path is hot. |
| p := make([]byte, 0, len(namespace)+len(name)+len(pstr)+2) |
| buf := bytes.NewBuffer(p) |
| |
| buf.WriteString(namespace) |
| buf.WriteByte('_') |
| buf.WriteString(name) |
| buf.WriteByte('_') |
| buf.WriteString(pstr) |
| |
| return buf.String() |
| } |
| |
| // ComposeRouteName uses namespace, name and rule name to compose |
| // the route name. |
| func ComposeRouteName(namespace, name string, rule string) string { |
| // FIXME Use sync.Pool to reuse this buffer if the upstream |
| // name composing code path is hot. |
| p := make([]byte, 0, len(namespace)+len(name)+len(rule)+2) |
| buf := bytes.NewBuffer(p) |
| |
| buf.WriteString(namespace) |
| buf.WriteByte('_') |
| buf.WriteString(name) |
| buf.WriteByte('_') |
| buf.WriteString(rule) |
| |
| return buf.String() |
| } |
| |
| // ComposeStreamRouteName uses namespace, name and rule name to compose |
| // the stream_route name. |
| func ComposeStreamRouteName(namespace, name string, rule string) string { |
| // FIXME Use sync.Pool to reuse this buffer if the upstream |
| // name composing code path is hot. |
| p := make([]byte, 0, len(namespace)+len(name)+len(rule)+6) |
| buf := bytes.NewBuffer(p) |
| |
| buf.WriteString(namespace) |
| buf.WriteByte('_') |
| buf.WriteString(name) |
| buf.WriteByte('_') |
| buf.WriteString(rule) |
| buf.WriteString("_tcp") |
| |
| return buf.String() |
| } |