| /* |
| * 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 mesh |
| |
| import ( |
| "fmt" |
| "net" |
| "net/url" |
| ) |
| |
| import ( |
| "github.com/asaskevich/govalidator" |
| ) |
| |
| import ( |
| mesh_proto "github.com/apache/dubbo-kubernetes/api/mesh/v1alpha1" |
| "github.com/apache/dubbo-kubernetes/pkg/core/validators" |
| ) |
| |
| func (d *DataplaneResource) Validate() error { |
| var err validators.ValidationError |
| |
| net := validators.RootedAt("networking") |
| |
| if d.Spec.GetNetworking() == nil { |
| err.AddViolationAt(net, "must be defined") |
| return err.OrNil() |
| } |
| |
| if admin := d.Spec.GetNetworking().GetAdmin(); admin != nil { |
| adminPort := net.Field("admin").Field("port") |
| |
| if d.UsesInboundInterface(IPv4Loopback, admin.GetPort()) { |
| err.AddViolationAt(adminPort, "must differ from inbound") |
| } |
| if d.UsesOutboundInterface(IPv4Loopback, admin.GetPort()) { |
| err.AddViolationAt(adminPort, "must differ from outbound") |
| } |
| } |
| |
| if len(d.Spec.GetNetworking().GetInbound()) == 0 { |
| err.AddViolationAt(net, "has to contain at least one inbound interface or gateway") |
| } |
| err.Add(validateNetworking(d.Spec.GetNetworking())) |
| err.Add(validateProbes(d.Spec.GetProbes())) |
| |
| return err.OrNil() |
| } |
| |
| // For networking section validation we need to take into account our legacy model. |
| // Sotw model is detected by having interface defined on inbound listeners. |
| // We do not allow networking.address with the old format. Instead, we recommend switching to the new format. |
| // When we've got dataplane in the new format, we require networking.address field to be defined. |
| func validateNetworking(networking *mesh_proto.Dataplane_Networking) validators.ValidationError { |
| var err validators.ValidationError |
| path := validators.RootedAt("networking") |
| err.Add(validateAddress(path, networking.Address)) |
| for i, inbound := range networking.GetInbound() { |
| field := path.Field("inbound").Index(i) |
| result := validateInbound(inbound, networking.Address) |
| err.AddErrorAt(field, result) |
| if _, exist := inbound.Tags[mesh_proto.ServiceTag]; !exist { |
| err.AddViolationAt(field.Field("tags").Key(mesh_proto.ServiceTag), `tag has to exist`) |
| } |
| } |
| for i, outbound := range networking.GetOutbound() { |
| result := validateOutbound(outbound) |
| err.AddErrorAt(path.Field("outbound").Index(i), result) |
| } |
| return err |
| } |
| |
| func validateProbes(probes *mesh_proto.Dataplane_Probes) validators.ValidationError { |
| if probes == nil { |
| return validators.ValidationError{} |
| } |
| var err validators.ValidationError |
| path := validators.RootedAt("probes") |
| err.Add(ValidatePort(path.Field("port"), probes.GetPort())) |
| for i, endpoint := range probes.Endpoints { |
| indexPath := path.Field("endpoints").Index(i) |
| err.Add(ValidatePort(indexPath.Field("inboundPort"), endpoint.GetInboundPort())) |
| if _, URIErr := url.ParseRequestURI(endpoint.InboundPath); URIErr != nil { |
| err.AddViolationAt(indexPath.Field("inboundPath"), `should be a valid URL Path`) |
| } |
| if _, URIErr := url.ParseRequestURI(endpoint.Path); URIErr != nil { |
| err.AddViolationAt(indexPath.Field("path"), `should be a valid URL Path`) |
| } |
| } |
| return err |
| } |
| |
| func validateAddress(path validators.PathBuilder, address string) validators.ValidationError { |
| var err validators.ValidationError |
| if address == "" { |
| err.AddViolationAt(path.Field("address"), "address can't be empty") |
| return err |
| } |
| if address == "0.0.0.0" || address == "::" { |
| err.AddViolationAt(path.Field("address"), "must not be 0.0.0.0 or ::") |
| } |
| if !govalidator.IsIP(address) && !govalidator.IsDNSName(address) { |
| err.AddViolationAt(path.Field("address"), "address has to be valid IP address or domain name") |
| } |
| return err |
| } |
| |
| func validateInbound(inbound *mesh_proto.Dataplane_Networking_Inbound, dpAddress string) validators.ValidationError { |
| var result validators.ValidationError |
| result.Add(ValidatePort(validators.RootedAt("port"), inbound.GetPort())) |
| if inbound.GetServicePort() != 0 { |
| result.Add(ValidatePort(validators.RootedAt("servicePort"), inbound.GetServicePort())) |
| } |
| if inbound.ServiceAddress != "" { |
| if net.ParseIP(inbound.ServiceAddress) == nil { |
| result.AddViolationAt(validators.RootedAt("serviceAddress"), `serviceAddress has to be valid IP address`) |
| } |
| if inbound.ServiceAddress == dpAddress { |
| if inbound.ServicePort == 0 || inbound.ServicePort == inbound.Port { |
| result.AddViolationAt(validators.RootedAt("serviceAddress"), `serviceAddress and servicePort has to differ from address and port`) |
| } |
| } |
| } |
| if inbound.Address != "" { |
| if net.ParseIP(inbound.Address) == nil { |
| result.AddViolationAt(validators.RootedAt("address"), `address has to be valid IP address`) |
| } |
| if inbound.Address == inbound.ServiceAddress { |
| if inbound.ServicePort == 0 || inbound.ServicePort == inbound.Port { |
| result.AddViolationAt(validators.RootedAt("serviceAddress"), `serviceAddress and servicePort has to differ from address and port`) |
| } |
| } |
| } |
| |
| validateProtocol := func(path validators.PathBuilder, selector map[string]string) validators.ValidationError { |
| var result validators.ValidationError |
| if value, exist := selector[mesh_proto.ProtocolTag]; exist { |
| if ParseProtocol(value) == ProtocolUnknown { |
| result.AddViolationAt( |
| path.Key(mesh_proto.ProtocolTag), fmt.Sprintf("tag %q has an invalid value %q. %s", mesh_proto.ProtocolTag, value, AllowedValuesHint(SupportedProtocols.Strings()...)), |
| ) |
| } |
| } |
| return result |
| } |
| result.Add(ValidateTags(validators.RootedAt("tags"), inbound.Tags, ValidateTagsOpts{ |
| ExtraTagsValidators: []TagsValidatorFunc{validateProtocol}, |
| })) |
| |
| result.Add(validateServiceProbe(inbound.ServiceProbe)) |
| |
| return result |
| } |
| |
| func validateServiceProbe(serviceProbe *mesh_proto.Dataplane_Networking_Inbound_ServiceProbe) validators.ValidationError { |
| var err validators.ValidationError |
| if serviceProbe == nil { |
| return err |
| } |
| path := validators.RootedAt("serviceProbe") |
| if serviceProbe.Interval != nil { |
| err.Add(ValidateDuration(path.Field("interval"), serviceProbe.Interval)) |
| } |
| if serviceProbe.Timeout != nil { |
| err.Add(ValidateDuration(path.Field("timeout"), serviceProbe.Timeout)) |
| } |
| if serviceProbe.UnhealthyThreshold != nil { |
| err.Add(ValidateThreshold(path.Field("unhealthyThreshold"), serviceProbe.UnhealthyThreshold.GetValue())) |
| } |
| if serviceProbe.HealthyThreshold != nil { |
| err.Add(ValidateThreshold(path.Field("healthyThreshold"), serviceProbe.HealthyThreshold.GetValue())) |
| } |
| return err |
| } |
| |
| func validateOutbound(outbound *mesh_proto.Dataplane_Networking_Outbound) validators.ValidationError { |
| var result validators.ValidationError |
| |
| result.Add(ValidatePort(validators.RootedAt("port"), outbound.GetPort())) |
| |
| if outbound.Address != "" && net.ParseIP(outbound.Address) == nil { |
| result.AddViolation("address", "address has to be valid IP address") |
| } |
| |
| if len(outbound.Tags) == 0 { |
| // nolint:staticcheck |
| if outbound.GetService() == "" { |
| result.AddViolationAt(validators.RootedAt("tags"), `mandatory tag "dubbo.io/service" is missing`) |
| } |
| } else { |
| result.Add(ValidateTags(validators.RootedAt("tags"), outbound.Tags, ValidateTagsOpts{ |
| RequireService: true, |
| })) |
| } |
| |
| return result |
| } |