| /* |
| * 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 model |
| |
| import ( |
| "fmt" |
| "hash/fnv" |
| "reflect" |
| "strings" |
| "time" |
| ) |
| |
| import ( |
| "github.com/pkg/errors" |
| |
| "k8s.io/kube-openapi/pkg/validation/spec" |
| ) |
| |
| import ( |
| mesh_proto "github.com/apache/dubbo-kubernetes/api/mesh/v1alpha1" |
| config_core "github.com/apache/dubbo-kubernetes/pkg/config/core" |
| ) |
| |
| const ( |
| DefaultMesh = "default" |
| // NoMesh defines a marker that resource is not bound to a Mesh. |
| // Resources not bound to a mesh (ScopeGlobal) should have an empty string in Mesh field. |
| NoMesh = "" |
| ) |
| |
| // ResourceNameExtensionsUnsupported is a convenience constant |
| // that is meant to make source code more readable. |
| var ResourceNameExtensionsUnsupported = ResourceNameExtensions(nil) |
| |
| func WithMesh(mesh string, name string) ResourceKey { |
| return ResourceKey{Mesh: mesh, Name: name} |
| } |
| |
| func WithoutMesh(name string) ResourceKey { |
| return ResourceKey{Mesh: NoMesh, Name: name} |
| } |
| |
| type ResourceKey struct { |
| Mesh string |
| Name string |
| } |
| |
| type ResourceReq struct { |
| Mesh string |
| Name string |
| PodName string |
| Namespace string |
| } |
| |
| type ResourceScope string |
| |
| const ( |
| ScopeMesh = "Mesh" |
| ScopeGlobal = "Global" |
| ) |
| |
| type DDSFlagType uint32 |
| |
| const ( |
| // DDSDisabledFlag is a flag that indicates that this resource type is not sent using DDS. |
| DDSDisabledFlag = DDSFlagType(0) |
| |
| // ZoneToGlobalFlag is a flag that indicates that this resource type is sent from Zone CP to Global CP. |
| ZoneToGlobalFlag = DDSFlagType(1) |
| |
| // GlobalToAllZonesFlag is a flag that indicates that this resource type is sent from Global CP to all zones. |
| GlobalToAllZonesFlag = DDSFlagType(1 << 2) |
| |
| // GlobalToAllButOriginalZoneFlag is a flag that indicates that this resource type is sent from Global CP to |
| // all zones except the zone where the resource was originally created. Today the only resource that has this |
| // flag is ZoneIngress. |
| GlobalToAllButOriginalZoneFlag = DDSFlagType(1 << 3) |
| ) |
| |
| const ( |
| // GlobalToZoneSelector is selector for all flags that indicate resource sync from Global to Zone. |
| // Can't be used as DDS flag for resource type. |
| GlobalToZoneSelector = GlobalToAllZonesFlag | GlobalToAllButOriginalZoneFlag |
| |
| // AllowedOnGlobalSelector is selector for all flags that indicate resource can be created on Global. |
| AllowedOnGlobalSelector = GlobalToAllZonesFlag |
| |
| // AllowedOnZoneSelector is selector for all flags that indicate resource can be created on Zone. |
| AllowedOnZoneSelector = ZoneToGlobalFlag | GlobalToAllButOriginalZoneFlag |
| ) |
| |
| // Has return whether this flag has all the passed flags on. |
| func (kt DDSFlagType) Has(flag DDSFlagType) bool { |
| return kt&flag != 0 |
| } |
| |
| type ResourceSpec interface{} |
| |
| type Resource interface { |
| GetMeta() ResourceMeta |
| SetMeta(ResourceMeta) |
| GetSpec() ResourceSpec |
| SetSpec(ResourceSpec) error |
| Descriptor() ResourceTypeDescriptor |
| } |
| |
| type ResourceHasher interface { |
| Hash() []byte |
| } |
| |
| func Hash(resource Resource) []byte { |
| if r, ok := resource.(ResourceHasher); ok { |
| return r.Hash() |
| } |
| return HashMeta(resource) |
| } |
| |
| func HashMeta(r Resource) []byte { |
| meta := r.GetMeta() |
| hasher := fnv.New128a() |
| _, _ = hasher.Write([]byte(r.Descriptor().Name)) |
| _, _ = hasher.Write([]byte(meta.GetMesh())) |
| _, _ = hasher.Write([]byte(meta.GetName())) |
| _, _ = hasher.Write([]byte(meta.GetVersion())) |
| return hasher.Sum(nil) |
| } |
| |
| type ResourceValidator interface { |
| Validate() error |
| } |
| |
| func Validate(resource Resource) error { |
| if rv, ok := resource.(ResourceValidator); ok { |
| return rv.Validate() |
| } |
| return nil |
| } |
| |
| type OverviewResource interface { |
| SetOverviewSpec(resource Resource, insight Resource) error |
| } |
| |
| type ResourceWithInsights interface { |
| NewInsightList() ResourceList |
| NewOverviewList() ResourceList |
| } |
| |
| type ResourceTypeDescriptor struct { |
| // Name identifier of this resourceType this maps to the k8s entity and universal name. |
| Name ResourceType |
| // Resource a created element of this type |
| Resource Resource |
| // ResourceList a create list container of this type |
| ResourceList ResourceList |
| // ReadOnly if this type will be created, modified and deleted by the system. |
| ReadOnly bool |
| // AdminOnly if this type requires users to be admin to access. |
| AdminOnly bool |
| // Scope whether this resource is Global or Mesh scoped. |
| Scope ResourceScope |
| // DDSFlags a set of flags that defines how this entity is sent using DDS (if unset DDS is disabled). |
| DDSFlags DDSFlagType |
| // WsPath the path to access on the REST api. |
| WsPath string |
| // DubboctlArg the name of the cmdline argument when doing `get` or `delete`. |
| DubboctlArg string |
| // DubboctlListArg the name of the cmdline argument when doing `list`. |
| DubboctlListArg string |
| // AllowToInspect if it's required to generate Inspect API endpoint for this type |
| AllowToInspect bool |
| // IsPolicy if this type is a policy (Dataplanes, Insights, Ingresses are not policies as they describe either metadata or workload, Retries are policies). |
| IsPolicy bool |
| // DisplayName the name of the policy showed as plural to be displayed in the UI and maybe CLI |
| SingularDisplayName string |
| // PluralDisplayName the name of the policy showed as plural to be displayed in the UI and maybe CLI |
| PluralDisplayName string |
| // IsExperimental indicates if a policy is in experimental state (might not be production ready). |
| IsExperimental bool |
| // IsPluginOriginated indicates if a policy is implemented as a plugin |
| IsPluginOriginated bool |
| // Schema contains an unmarshalled OpenAPI schema of the resource |
| Schema *spec.Schema |
| // Insight contains the insight type attached to this resourceType |
| Insight Resource |
| // Overview contains the overview type attached to this resourceType |
| Overview Resource |
| // DumpForGlobal whether resources of this type should be dumped when exporting a zone to migrate to global |
| DumpForGlobal bool |
| } |
| |
| func newObject(baseResource Resource) Resource { |
| specType := reflect.TypeOf(baseResource.GetSpec()).Elem() |
| newSpec := reflect.New(specType).Interface().(ResourceSpec) |
| |
| resType := reflect.TypeOf(baseResource).Elem() |
| resource := reflect.New(resType).Interface().(Resource) |
| |
| if err := resource.SetSpec(newSpec); err != nil { |
| panic(errors.Wrap(err, "could not set spec on the new resource")) |
| } |
| |
| return resource |
| } |
| |
| func (d ResourceTypeDescriptor) NewObject() Resource { |
| return newObject(d.Resource) |
| } |
| |
| func (d ResourceTypeDescriptor) NewList() ResourceList { |
| listType := reflect.TypeOf(d.ResourceList).Elem() |
| return reflect.New(listType).Interface().(ResourceList) |
| } |
| |
| func (d ResourceTypeDescriptor) HasInsights() bool { |
| return d.Insight != nil |
| } |
| |
| func (d ResourceTypeDescriptor) NewInsight() Resource { |
| if !d.HasInsights() { |
| panic("No insight type precondition broken") |
| } |
| return newObject(d.Insight) |
| } |
| |
| func (d ResourceTypeDescriptor) NewInsightList() ResourceList { |
| if !d.HasInsights() { |
| panic("No insight type precondition broken") |
| } |
| return d.Insight.Descriptor().NewList() |
| } |
| |
| func (d ResourceTypeDescriptor) NewOverview() Resource { |
| if !d.HasInsights() { |
| panic("No insight type precondition broken") |
| } |
| return newObject(d.Overview) |
| } |
| |
| func (d ResourceTypeDescriptor) NewOverviewList() ResourceList { |
| if !d.HasInsights() { |
| panic("No insight type precondition broken") |
| } |
| return d.Overview.Descriptor().NewList() |
| } |
| |
| type TypeFilter interface { |
| Apply(descriptor ResourceTypeDescriptor) bool |
| } |
| |
| type TypeFilterFn func(descriptor ResourceTypeDescriptor) bool |
| |
| func (f TypeFilterFn) Apply(descriptor ResourceTypeDescriptor) bool { |
| return f(descriptor) |
| } |
| |
| func HasDDSFlag(flagType DDSFlagType) TypeFilter { |
| return TypeFilterFn(func(descriptor ResourceTypeDescriptor) bool { |
| return descriptor.DDSFlags.Has(flagType) |
| }) |
| } |
| |
| func HasDdsEnabled() TypeFilter { |
| return TypeFilterFn(func(descriptor ResourceTypeDescriptor) bool { |
| return descriptor.DDSFlags != DDSDisabledFlag |
| }) |
| } |
| |
| func HasDubboctlEnabled() TypeFilter { |
| return TypeFilterFn(func(descriptor ResourceTypeDescriptor) bool { |
| return descriptor.DubboctlArg != "" |
| }) |
| } |
| |
| func HasWsEnabled() TypeFilter { |
| return TypeFilterFn(func(descriptor ResourceTypeDescriptor) bool { |
| return descriptor.WsPath != "" |
| }) |
| } |
| |
| func AllowedToInspect() TypeFilter { |
| return TypeFilterFn(func(descriptor ResourceTypeDescriptor) bool { |
| return descriptor.AllowToInspect |
| }) |
| } |
| |
| func HasScope(scope ResourceScope) TypeFilter { |
| return TypeFilterFn(func(descriptor ResourceTypeDescriptor) bool { |
| return descriptor.Scope == scope |
| }) |
| } |
| |
| func IsPolicy() TypeFilter { |
| return TypeFilterFn(func(descriptor ResourceTypeDescriptor) bool { |
| return descriptor.IsPolicy |
| }) |
| } |
| |
| func Named(names ...ResourceType) TypeFilter { |
| included := map[ResourceType]bool{} |
| for _, n := range names { |
| included[n] = true |
| } |
| |
| return TypeFilterFn(func(descriptor ResourceTypeDescriptor) bool { |
| return included[descriptor.Name] |
| }) |
| } |
| |
| func Not(filter TypeFilter) TypeFilter { |
| return TypeFilterFn(func(descriptor ResourceTypeDescriptor) bool { |
| return !filter.Apply(descriptor) |
| }) |
| } |
| |
| func Or(filters ...TypeFilter) TypeFilter { |
| return TypeFilterFn(func(descriptor ResourceTypeDescriptor) bool { |
| for _, filter := range filters { |
| if filter.Apply(descriptor) { |
| return true |
| } |
| } |
| |
| return false |
| }) |
| } |
| |
| type ByMeta []Resource |
| |
| func (a ByMeta) Len() int { return len(a) } |
| |
| func (a ByMeta) Less(i, j int) bool { |
| if a[i].GetMeta().GetMesh() == a[j].GetMeta().GetMesh() { |
| return a[i].GetMeta().GetName() < a[j].GetMeta().GetName() |
| } |
| return a[i].GetMeta().GetMesh() < a[j].GetMeta().GetMesh() |
| } |
| |
| func (a ByMeta) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
| |
| const ( |
| // K8sNamespaceComponent identifies the namespace component of a resource name on Kubernetes. |
| // The value is considered a part of user-facing dubbo API and should not be changed lightly. |
| // The value has a format of a Kubernetes label name. |
| K8sNamespaceComponent = "k8s.dubbo.io/namespace" |
| |
| // K8sNameComponent identifies the name component of a resource name on Kubernetes. |
| // The value is considered a part of user-facing dubbo API and should not be changed lightly. |
| // The value has a format of a Kubernetes label name. |
| K8sNameComponent = "k8s.dubbo.io/name" |
| ) |
| |
| type ResourceType string |
| |
| // ResourceNameExtensions represents an composite resource name in environments |
| // other than Universal. |
| // |
| // E.g., name of a Kubernetes resource consists of a namespace component |
| // and a name component that is local to that namespace. |
| // |
| // Technically, ResourceNameExtensions is a mapping between |
| // a component identifier and a component value, e.g. |
| // |
| // "k8s.dubbo.io/namespace" => "my-namespace" |
| // "k8s.dubbo.io/name" => "my-policy" |
| // |
| // Component identifier must be considered a part of user-facing dubbo API. |
| // In other words, it is supposed to be visible to users and should not be changed lightly. |
| // |
| // Component identifier might have any value, however, it's preferable |
| // to choose one that is intuitive to users of that particular environment. |
| // E.g., on Kubernetes component identifiers should use a label name format, |
| // like in "k8s.dubbo.io/namespace" and "k8s.dubbo.io/name". |
| type ResourceNameExtensions map[string]string |
| |
| type ResourceMeta interface { |
| GetName() string |
| GetNameExtensions() ResourceNameExtensions |
| GetVersion() string |
| GetMesh() string |
| GetCreationTime() time.Time |
| GetModificationTime() time.Time |
| GetLabels() map[string]string |
| } |
| |
| // ZoneOfResource returns zone from which the resource was synced to Global CP |
| // There is no information in the resource itself whether the resource is synced or created on the CP. |
| // Therefore, it's a caller responsibility to make use it only on synced resources. |
| func ZoneOfResource(res Resource) string { |
| if labels := res.GetMeta().GetLabels(); labels != nil && labels[mesh_proto.ZoneTag] != "" { |
| return labels[mesh_proto.ZoneTag] |
| } |
| parts := strings.Split(res.GetMeta().GetName(), ".") |
| return parts[0] |
| } |
| |
| func ResourceOrigin(rm ResourceMeta) (mesh_proto.ResourceOrigin, bool) { |
| if labels := rm.GetLabels(); labels != nil && labels[mesh_proto.ResourceOriginLabel] != "" { |
| return mesh_proto.ResourceOrigin(labels[mesh_proto.ResourceOriginLabel]), true |
| } |
| return "", false |
| } |
| |
| func IsLocallyOriginated(mode config_core.CpMode, r Resource) bool { |
| switch mode { |
| case config_core.Global: |
| origin, ok := ResourceOrigin(r.GetMeta()) |
| return !ok || origin == mesh_proto.GlobalResourceOrigin |
| case config_core.Zone: |
| origin, ok := ResourceOrigin(r.GetMeta()) |
| return !ok || origin == mesh_proto.ZoneResourceOrigin |
| default: |
| return true |
| } |
| } |
| |
| func MetaToResourceKey(meta ResourceMeta) ResourceKey { |
| if meta == nil { |
| return ResourceKey{} |
| } |
| return ResourceKey{ |
| Mesh: meta.GetMesh(), |
| Name: meta.GetName(), |
| } |
| } |
| |
| func ResourceListToResourceKeys(rl ResourceList) []ResourceKey { |
| rkey := []ResourceKey{} |
| for _, r := range rl.GetItems() { |
| rkey = append(rkey, MetaToResourceKey(r.GetMeta())) |
| } |
| return rkey |
| } |
| |
| func ResourceListByMesh(rl ResourceList) (map[string]ResourceList, error) { |
| res := map[string]ResourceList{} |
| for _, r := range rl.GetItems() { |
| mrl, ok := res[r.GetMeta().GetMesh()] |
| if !ok { |
| mrl = r.Descriptor().NewList() |
| res[r.GetMeta().GetMesh()] = mrl |
| } |
| if err := mrl.AddItem(r); err != nil { |
| return nil, err |
| } |
| } |
| return res, nil |
| } |
| |
| func GetDisplayName(r Resource) string { |
| // prefer display name as it's more predictable, because |
| // * Kubernetes expects sorting to be by just a name. Considering suffix with namespace breaks this |
| // * When policies are synced to Zone, hash suffix also breaks sorting |
| if labels := r.GetMeta().GetLabels(); labels != nil && labels[mesh_proto.DisplayName] != "" { |
| return labels[mesh_proto.DisplayName] |
| } |
| return r.GetMeta().GetName() |
| } |
| |
| func ResourceListHash(rl ResourceList) []byte { |
| hasher := fnv.New128() |
| for _, entity := range rl.GetItems() { |
| _, _ = hasher.Write(Hash(entity)) |
| } |
| return hasher.Sum(nil) |
| } |
| |
| type ResourceList interface { |
| GetItemType() ResourceType |
| GetItems() []Resource |
| NewItem() Resource |
| AddItem(Resource) error |
| GetPagination() *Pagination |
| SetPagination(pagination Pagination) |
| } |
| |
| type Pagination struct { |
| Total uint32 |
| NextOffset string |
| } |
| |
| func (p *Pagination) GetTotal() uint32 { |
| return p.Total |
| } |
| |
| func (p *Pagination) SetTotal(total uint32) { |
| p.Total = total |
| } |
| |
| func (p *Pagination) GetNextOffset() string { |
| return p.NextOffset |
| } |
| |
| func (p *Pagination) SetNextOffset(nextOffset string) { |
| p.NextOffset = nextOffset |
| } |
| |
| func ErrorInvalidItemType(expected, actual interface{}) error { |
| return fmt.Errorf("Invalid argument type: expected=%q got=%q", reflect.TypeOf(expected), reflect.TypeOf(actual)) |
| } |
| |
| type ResourceWithAddress interface { |
| Resource |
| AdminAddress(defaultAdminPort uint32) string |
| } |
| |
| type PolicyItem interface { |
| GetDefault() interface{} |
| } |
| |
| type TransformDefaultAfterMerge interface { |
| Transform() |
| } |
| |
| type Policy interface { |
| ResourceSpec |
| } |
| |
| type PolicyWithToList interface { |
| Policy |
| GetToList() []PolicyItem |
| } |
| |
| type PolicyWithFromList interface { |
| Policy |
| GetFromList() []PolicyItem |
| } |
| |
| type PolicyWithSingleItem interface { |
| Policy |
| GetPolicyItem() PolicyItem |
| } |