blob: 6818546f40026593697867375515da874211d3e9 [file] [log] [blame]
// Copyright Istio 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 apigen
import (
"strings"
)
import (
discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
"istio.io/pkg/log"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/model"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/networking/util"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/serviceregistry/provider"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/serviceregistry/serviceentry"
"github.com/apache/dubbo-go-pixiu/pkg/config"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/collections"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/gvk"
)
// APIGenerator supports generation of high-level API resources, similar with the MCP
// protocol. This is a replacement for MCP, using XDS (and in future UDPA) as a transport.
// Based on lessons from MCP, the protocol allows incremental updates by
// default, using the same mechanism that EDS is using, i.e. sending only changed resources
// in a push. Incremental deletes are sent as a resource with empty body.
//
// Example: networking.istio.io/v1alpha3/VirtualService
//
// TODO: we can also add a special marker in the header)
type APIGenerator struct {
// ConfigStore interface for listing istio api resources.
store model.ConfigStore
}
func NewGenerator(store model.ConfigStore) *APIGenerator {
return &APIGenerator{
store: store,
}
}
// TODO: take 'updates' into account, don't send pushes for resources that haven't changed
// TODO: support WorkloadEntry - to generate endpoints (equivalent with EDS)
// TODO: based on lessons from MCP, we want to send 'chunked' responses, like apiserver does.
// A first attempt added a 'sync' record at the end. Based on feedback and common use, a
// different approach can be used - for large responses, we can mark the last one as 'hasMore'
// by adding a field to the envelope.
// Generate implements the generate method for high level APIs, like Istio config types.
// This provides similar functionality with MCP and :8080/debug/configz.
//
// Names are based on the current resource naming in istiod stores.
func (g *APIGenerator) Generate(proxy *model.Proxy, w *model.WatchedResource, req *model.PushRequest) (model.Resources, model.XdsLogDetails, error) {
resp := model.Resources{}
// Note: this is the style used by MCP and its config. Pilot is using 'Group/Version/Kind' as the
// key, which is similar.
//
// The actual type in the Any should be a real proto - which is based on the generated package name.
// For example: type is for Any is 'type.googlepis.com/istio.networking.v1alpha3.EnvoyFilter
// We use: networking.istio.io/v1alpha3/EnvoyFilter
kind := strings.SplitN(w.TypeUrl, "/", 3)
if len(kind) != 3 {
log.Warnf("ADS: Unknown watched resources %s", w.TypeUrl)
// Still return an empty response - to not break waiting code. It is fine to not know about some resource.
return resp, model.DefaultXdsLogDetails, nil
}
// TODO: extra validation may be needed - at least logging that a resource
// of unknown type was requested. This should not be an error - maybe client asks
// for a valid CRD we just don't know about. An empty set indicates we have no such config.
rgvk := config.GroupVersionKind{
Group: kind[0],
Version: kind[1],
Kind: kind[2],
}
if w.TypeUrl == collections.IstioMeshV1Alpha1MeshConfig.Resource().GroupVersionKind().String() {
resp = append(resp, &discovery.Resource{
Resource: util.MessageToAny(req.Push.Mesh),
})
return resp, model.DefaultXdsLogDetails, nil
}
// TODO: what is the proper way to handle errors ?
// Normally once istio is 'ready' List can't return errors on a valid config -
// even if k8s is disconnected, we still cache all previous results.
// This needs further consideration - I don't think XDS or MCP transports
// have a clear recommendation.
cfg, err := g.store.List(rgvk, "")
if err != nil {
log.Warnf("ADS: Error reading resource %s %v", w.TypeUrl, err)
return resp, model.DefaultXdsLogDetails, nil
}
for _, c := range cfg {
// Right now model.Config is not a proto - until we change it, mcp.Resource.
// This also helps migrating MCP users.
b, err := config.PilotConfigToResource(&c)
if err != nil {
log.Warn("Resource error ", err, " ", c.Namespace, "/", c.Name)
continue
}
resp = append(resp, &discovery.Resource{
Name: c.Namespace + "/" + c.Name,
Resource: util.MessageToAny(b),
})
}
// TODO: MeshConfig, current dynamic ProxyConfig (for this proxy), Networks
if w.TypeUrl == gvk.ServiceEntry.String() {
// Include 'synthetic' SE - but without the endpoints. Used to generate CDS, LDS.
// EDS is pass-through.
svcs := proxy.SidecarScope.Services()
for _, s := range svcs {
// Ignore services that are result of conversion from ServiceEntry.
if s.Attributes.ServiceRegistry == provider.External {
continue
}
c := serviceentry.ServiceToServiceEntry(s, proxy)
b, err := config.PilotConfigToResource(c)
if err != nil {
log.Warn("Resource error ", err, " ", c.Namespace, "/", c.Name)
continue
}
resp = append(resp, &discovery.Resource{
Name: c.Namespace + "/" + c.Name,
Resource: util.MessageToAny(b),
})
}
}
return resp, model.DefaultXdsLogDetails, nil
}