diff --git a/dist/images/cleanup.sh b/dist/images/cleanup.sh index c029840755d..0eaf2484a86 100644 --- a/dist/images/cleanup.sh +++ b/dist/images/cleanup.sh @@ -113,7 +113,7 @@ kubectl delete --ignore-not-found crd htbqoses.kubeovn.io security-groups.kubeov vpc-nat-gateways.kubeovn.io vpcs.kubeovn.io vlans.kubeovn.io provider-networks.kubeovn.io \ iptables-dnat-rules.kubeovn.io iptables-eips.kubeovn.io iptables-fip-rules.kubeovn.io \ iptables-snat-rules.kubeovn.io vips.kubeovn.io switch-lb-rules.kubeovn.io vpc-dnses.kubeovn.io \ - ovn-eips.kubeovn.io ovn-fips.kubeovn.io ovn-snat-rules.kubeovn.io + ovn-eips.kubeovn.io ovn-fips.kubeovn.io ovn-snat-rules.kubeovn.io ovn-dnat-rules.kubeovn.io # Remove annotations/labels in namespaces and nodes kubectl annotate no --all ovn.kubernetes.io/cidr- diff --git a/dist/images/install.sh b/dist/images/install.sh index 5534bfc4874..71d01d9b1a4 100755 --- a/dist/images/install.sh +++ b/dist/images/install.sh @@ -1008,6 +1008,109 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + name: ovn-dnat-rules.kubeovn.io +spec: + group: kubeovn.io + names: + plural: ovn-dnat-rules + singular: ovn-dnat-rule + shortNames: + - odnat + kind: OvnDnatRule + listKind: OvnDnatRuleList + scope: Cluster + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .spec.ovnEip + name: Eip + type: string + - jsonPath: .status.protocol + name: Protocol + type: string + - jsonPath: .status.v4Eip + name: V4Eip + type: string + - jsonPath: .status.v4Ip + name: V4Ip + type: string + - jsonPath: .status.internalPort + name: InternalPort + type: string + - jsonPath: .status.externalPort + name: ExternalPort + type: string + - jsonPath: .spec.ipName + name: IpName + type: string + - jsonPath: .status.ready + name: Ready + type: boolean + schema: + openAPIV3Schema: + type: object + properties: + status: + type: object + properties: + ready: + type: boolean + v4Eip: + type: string + v4Ip: + type: string + macAddress: + type: string + vpc: + type: string + externalPort: + type: string + internalPort: + type: string + protocol: + type: string + ipName: + type: string + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + reason: + type: string + message: + type: string + lastUpdateTime: + type: string + lastTransitionTime: + type: string + spec: + type: object + properties: + ovnEip: + type: string + ipType: + type: string + ipName: + type: string + externalPort: + type: string + internalPort: + type: string + protocol: + type: string +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: name: vpcs.kubeovn.io spec: @@ -1844,6 +1947,8 @@ rules: - ovn-eips/status - ovn-fips/status - ovn-snat-rules/status + - ovn-dnat-rules + - ovn-dnat-rules/status - switch-lb-rules - switch-lb-rules/status - vpc-dnses @@ -2353,6 +2458,8 @@ rules: - ovn-eips/status - ovn-fips/status - ovn-snat-rules/status + - ovn-dnat-rules + - ovn-dnat-rules/status - vpc-dnses - vpc-dnses/status - switch-lb-rules diff --git a/kubeovn-helm/templates/kube-ovn-crd.yaml b/kubeovn-helm/templates/kube-ovn-crd.yaml index d7fee7a33a4..1ddfc2379ba 100644 --- a/kubeovn-helm/templates/kube-ovn-crd.yaml +++ b/kubeovn-helm/templates/kube-ovn-crd.yaml @@ -790,6 +790,109 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + name: ovn-dnat-rules.kubeovn.io +spec: + group: kubeovn.io + names: + plural: ovn-dnat-rules + singular: ovn-dnat-rule + shortNames: + - odnat + kind: OvnDnatRule + listKind: OvnDnatRuleList + scope: Cluster + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .spec.ovnEip + name: Eip + type: string + - jsonPath: .status.protocol + name: Protocol + type: string + - jsonPath: .status.v4Eip + name: V4Eip + type: string + - jsonPath: .status.v4Ip + name: V4Ip + type: string + - jsonPath: .status.internalPort + name: InternalPort + type: string + - jsonPath: .status.externalPort + name: ExternalPort + type: string + - jsonPath: .spec.ipName + name: IpName + type: string + - jsonPath: .status.ready + name: Ready + type: boolean + schema: + openAPIV3Schema: + type: object + properties: + status: + type: object + properties: + ready: + type: boolean + v4Eip: + type: string + v4Ip: + type: string + macAddress: + type: string + vpc: + type: string + externalPort: + type: string + internalPort: + type: string + protocol: + type: string + ipName: + type: string + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + reason: + type: string + message: + type: string + lastUpdateTime: + type: string + lastTransitionTime: + type: string + spec: + type: object + properties: + ovnEip: + type: string + ipType: + type: string + ipName: + type: string + externalPort: + type: string + internalPort: + type: string + protocol: + type: string +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: name: vpcs.kubeovn.io spec: diff --git a/kubeovn-helm/templates/ovn-CR.yaml b/kubeovn-helm/templates/ovn-CR.yaml index 131478cc443..48498d4e26f 100644 --- a/kubeovn-helm/templates/ovn-CR.yaml +++ b/kubeovn-helm/templates/ovn-CR.yaml @@ -36,6 +36,8 @@ rules: - ovn-eips/status - ovn-fips/status - ovn-snat-rules/status + - ovn-dnat-rules + - ovn-dnat-rules/status - vpc-dnses - vpc-dnses/status - switch-lb-rules diff --git a/pkg/apis/kubeovn/v1/register.go b/pkg/apis/kubeovn/v1/register.go index 472156939a2..22c757970fa 100644 --- a/pkg/apis/kubeovn/v1/register.go +++ b/pkg/apis/kubeovn/v1/register.go @@ -59,6 +59,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &OvnFipList{}, &OvnSnatRule{}, &OvnSnatRuleList{}, + &OvnDnatRule{}, + &OvnDnatRuleList{}, &SecurityGroup{}, &SecurityGroupList{}, &SwitchLBRule{}, diff --git a/pkg/apis/kubeovn/v1/status.go b/pkg/apis/kubeovn/v1/status.go index 291b97c6589..cad79054208 100644 --- a/pkg/apis/kubeovn/v1/status.go +++ b/pkg/apis/kubeovn/v1/status.go @@ -117,3 +117,13 @@ func (osrs *OvnSnatRuleStatus) Bytes() ([]byte, error) { klog.V(5).Info("status body", newStr) return []byte(newStr), nil } + +func (odrs *OvnDnatRuleStatus) Bytes() ([]byte, error) { + bytes, err := json.Marshal(odrs) + if err != nil { + return nil, err + } + newStr := fmt.Sprintf(`{"status": %s}`, string(bytes)) + klog.V(5).Info("status body", newStr) + return []byte(newStr), nil +} diff --git a/pkg/apis/kubeovn/v1/types.go b/pkg/apis/kubeovn/v1/types.go index a42756d99be..2ba43e9dc1f 100644 --- a/pkg/apis/kubeovn/v1/types.go +++ b/pkg/apis/kubeovn/v1/types.go @@ -1184,3 +1184,76 @@ type OvnSnatRuleList struct { Items []OvnSnatRule `json:"items"` } + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +genclient:nonNamespaced +// +resourceName=ovn-dnat-rules + +type OvnDnatRule struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec OvnDnatRuleSpec `json:"spec"` + Status OvnDnatRuleStatus `json:"status,omitempty"` +} + +type OvnDnatRuleSpec struct { + OvnEip string `json:"ovnEip"` + IpType string `json:"ipType"` // vip, ip + IpName string `json:"ipName"` // vip, ip crd name + InternalPort string `json:"internalPort"` + ExternalPort string `json:"externalPort"` + Protocol string `json:"protocol,omitempty"` +} + +// OvnDnatRuleCondition describes the state of an object at a certain point. +// +k8s:deepcopy-gen=true +type OvnDnatRuleCondition struct { + // Type of condition. + Type ConditionType `json:"type"` + // Status of the condition, one of True, False, Unknown. + Status corev1.ConditionStatus `json:"status"` + // The reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty"` + // A human readable message indicating details about the transition. + // +optional + Message string `json:"message,omitempty"` + // Last time the condition was probed + // +optional + LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"` + // Last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"` +} + +// +k8s:deepcopy-gen=true +type OvnDnatRuleStatus struct { + // +optional + // +patchStrategy=merge + Ready bool `json:"ready" patchStrategy:"merge"` + V4Eip string `json:"v4Eip" patchStrategy:"merge"` + V4Ip string `json:"v4Ip" patchStrategy:"merge"` + MacAddress string `json:"macAddress" patchStrategy:"merge"` + Vpc string `json:"vpc" patchStrategy:"merge"` + InternalPort string `json:"internalPort"` + ExternalPort string `json:"externalPort"` + Protocol string `json:"protocol,omitempty"` + IpName string `json:"ipName"` + + // Conditions represents the latest state of the object + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + Conditions []OvnDnatRuleCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type OvnDnatRuleList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []OvnDnatRule `json:"items"` +} diff --git a/pkg/apis/kubeovn/v1/zz_generated.deepcopy.go b/pkg/apis/kubeovn/v1/zz_generated.deepcopy.go index 611c8ca5e12..0f60d2128f0 100644 --- a/pkg/apis/kubeovn/v1/zz_generated.deepcopy.go +++ b/pkg/apis/kubeovn/v1/zz_generated.deepcopy.go @@ -625,6 +625,124 @@ func (in *IptablesSnatRuleStatus) DeepCopy() *IptablesSnatRuleStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OvnDnatRule) DeepCopyInto(out *OvnDnatRule) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OvnDnatRule. +func (in *OvnDnatRule) DeepCopy() *OvnDnatRule { + if in == nil { + return nil + } + out := new(OvnDnatRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OvnDnatRule) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OvnDnatRuleCondition) DeepCopyInto(out *OvnDnatRuleCondition) { + *out = *in + in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime) + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OvnDnatRuleCondition. +func (in *OvnDnatRuleCondition) DeepCopy() *OvnDnatRuleCondition { + if in == nil { + return nil + } + out := new(OvnDnatRuleCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OvnDnatRuleList) DeepCopyInto(out *OvnDnatRuleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]OvnDnatRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OvnDnatRuleList. +func (in *OvnDnatRuleList) DeepCopy() *OvnDnatRuleList { + if in == nil { + return nil + } + out := new(OvnDnatRuleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OvnDnatRuleList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OvnDnatRuleSpec) DeepCopyInto(out *OvnDnatRuleSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OvnDnatRuleSpec. +func (in *OvnDnatRuleSpec) DeepCopy() *OvnDnatRuleSpec { + if in == nil { + return nil + } + out := new(OvnDnatRuleSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OvnDnatRuleStatus) DeepCopyInto(out *OvnDnatRuleStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]OvnDnatRuleCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OvnDnatRuleStatus. +func (in *OvnDnatRuleStatus) DeepCopy() *OvnDnatRuleStatus { + if in == nil { + return nil + } + out := new(OvnDnatRuleStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OvnEip) DeepCopyInto(out *OvnEip) { *out = *in diff --git a/pkg/client/clientset/versioned/typed/kubeovn/v1/fake/fake_kubeovn_client.go b/pkg/client/clientset/versioned/typed/kubeovn/v1/fake/fake_kubeovn_client.go index 0e614bd52a9..1972dfa52f9 100644 --- a/pkg/client/clientset/versioned/typed/kubeovn/v1/fake/fake_kubeovn_client.go +++ b/pkg/client/clientset/versioned/typed/kubeovn/v1/fake/fake_kubeovn_client.go @@ -48,6 +48,10 @@ func (c *FakeKubeovnV1) IptablesSnatRules() v1.IptablesSnatRuleInterface { return &FakeIptablesSnatRules{c} } +func (c *FakeKubeovnV1) OvnDnatRules() v1.OvnDnatRuleInterface { + return &FakeOvnDnatRules{c} +} + func (c *FakeKubeovnV1) OvnEips() v1.OvnEipInterface { return &FakeOvnEips{c} } diff --git a/pkg/client/clientset/versioned/typed/kubeovn/v1/fake/fake_ovndnatrule.go b/pkg/client/clientset/versioned/typed/kubeovn/v1/fake/fake_ovndnatrule.go new file mode 100644 index 00000000000..fecb7421d4f --- /dev/null +++ b/pkg/client/clientset/versioned/typed/kubeovn/v1/fake/fake_ovndnatrule.go @@ -0,0 +1,133 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeOvnDnatRules implements OvnDnatRuleInterface +type FakeOvnDnatRules struct { + Fake *FakeKubeovnV1 +} + +var ovndnatrulesResource = schema.GroupVersionResource{Group: "kubeovn.io", Version: "v1", Resource: "ovn-dnat-rules"} + +var ovndnatrulesKind = schema.GroupVersionKind{Group: "kubeovn.io", Version: "v1", Kind: "OvnDnatRule"} + +// Get takes name of the ovnDnatRule, and returns the corresponding ovnDnatRule object, and an error if there is any. +func (c *FakeOvnDnatRules) Get(ctx context.Context, name string, options v1.GetOptions) (result *kubeovnv1.OvnDnatRule, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(ovndnatrulesResource, name), &kubeovnv1.OvnDnatRule{}) + if obj == nil { + return nil, err + } + return obj.(*kubeovnv1.OvnDnatRule), err +} + +// List takes label and field selectors, and returns the list of OvnDnatRules that match those selectors. +func (c *FakeOvnDnatRules) List(ctx context.Context, opts v1.ListOptions) (result *kubeovnv1.OvnDnatRuleList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(ovndnatrulesResource, ovndnatrulesKind, opts), &kubeovnv1.OvnDnatRuleList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &kubeovnv1.OvnDnatRuleList{ListMeta: obj.(*kubeovnv1.OvnDnatRuleList).ListMeta} + for _, item := range obj.(*kubeovnv1.OvnDnatRuleList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested ovnDnatRules. +func (c *FakeOvnDnatRules) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(ovndnatrulesResource, opts)) +} + +// Create takes the representation of a ovnDnatRule and creates it. Returns the server's representation of the ovnDnatRule, and an error, if there is any. +func (c *FakeOvnDnatRules) Create(ctx context.Context, ovnDnatRule *kubeovnv1.OvnDnatRule, opts v1.CreateOptions) (result *kubeovnv1.OvnDnatRule, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(ovndnatrulesResource, ovnDnatRule), &kubeovnv1.OvnDnatRule{}) + if obj == nil { + return nil, err + } + return obj.(*kubeovnv1.OvnDnatRule), err +} + +// Update takes the representation of a ovnDnatRule and updates it. Returns the server's representation of the ovnDnatRule, and an error, if there is any. +func (c *FakeOvnDnatRules) Update(ctx context.Context, ovnDnatRule *kubeovnv1.OvnDnatRule, opts v1.UpdateOptions) (result *kubeovnv1.OvnDnatRule, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(ovndnatrulesResource, ovnDnatRule), &kubeovnv1.OvnDnatRule{}) + if obj == nil { + return nil, err + } + return obj.(*kubeovnv1.OvnDnatRule), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeOvnDnatRules) UpdateStatus(ctx context.Context, ovnDnatRule *kubeovnv1.OvnDnatRule, opts v1.UpdateOptions) (*kubeovnv1.OvnDnatRule, error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(ovndnatrulesResource, "status", ovnDnatRule), &kubeovnv1.OvnDnatRule{}) + if obj == nil { + return nil, err + } + return obj.(*kubeovnv1.OvnDnatRule), err +} + +// Delete takes name of the ovnDnatRule and deletes it. Returns an error if one occurs. +func (c *FakeOvnDnatRules) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteActionWithOptions(ovndnatrulesResource, name, opts), &kubeovnv1.OvnDnatRule{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeOvnDnatRules) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(ovndnatrulesResource, listOpts) + + _, err := c.Fake.Invokes(action, &kubeovnv1.OvnDnatRuleList{}) + return err +} + +// Patch applies the patch and returns the patched ovnDnatRule. +func (c *FakeOvnDnatRules) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *kubeovnv1.OvnDnatRule, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(ovndnatrulesResource, name, pt, data, subresources...), &kubeovnv1.OvnDnatRule{}) + if obj == nil { + return nil, err + } + return obj.(*kubeovnv1.OvnDnatRule), err +} diff --git a/pkg/client/clientset/versioned/typed/kubeovn/v1/generated_expansion.go b/pkg/client/clientset/versioned/typed/kubeovn/v1/generated_expansion.go index 0f47344d9b1..e9bbb7988ad 100644 --- a/pkg/client/clientset/versioned/typed/kubeovn/v1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/kubeovn/v1/generated_expansion.go @@ -28,6 +28,8 @@ type IptablesFIPRuleExpansion interface{} type IptablesSnatRuleExpansion interface{} +type OvnDnatRuleExpansion interface{} + type OvnEipExpansion interface{} type OvnFipExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/kubeovn/v1/kubeovn_client.go b/pkg/client/clientset/versioned/typed/kubeovn/v1/kubeovn_client.go index b9e3e1d0afa..bc301f886a2 100644 --- a/pkg/client/clientset/versioned/typed/kubeovn/v1/kubeovn_client.go +++ b/pkg/client/clientset/versioned/typed/kubeovn/v1/kubeovn_client.go @@ -33,6 +33,7 @@ type KubeovnV1Interface interface { IptablesEIPsGetter IptablesFIPRulesGetter IptablesSnatRulesGetter + OvnDnatRulesGetter OvnEipsGetter OvnFipsGetter OvnSnatRulesGetter @@ -72,6 +73,10 @@ func (c *KubeovnV1Client) IptablesSnatRules() IptablesSnatRuleInterface { return newIptablesSnatRules(c) } +func (c *KubeovnV1Client) OvnDnatRules() OvnDnatRuleInterface { + return newOvnDnatRules(c) +} + func (c *KubeovnV1Client) OvnEips() OvnEipInterface { return newOvnEips(c) } diff --git a/pkg/client/clientset/versioned/typed/kubeovn/v1/ovndnatrule.go b/pkg/client/clientset/versioned/typed/kubeovn/v1/ovndnatrule.go new file mode 100644 index 00000000000..e7665899be0 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/kubeovn/v1/ovndnatrule.go @@ -0,0 +1,184 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1 + +import ( + "context" + "time" + + v1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + scheme "github.com/kubeovn/kube-ovn/pkg/client/clientset/versioned/scheme" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// OvnDnatRulesGetter has a method to return a OvnDnatRuleInterface. +// A group's client should implement this interface. +type OvnDnatRulesGetter interface { + OvnDnatRules() OvnDnatRuleInterface +} + +// OvnDnatRuleInterface has methods to work with OvnDnatRule resources. +type OvnDnatRuleInterface interface { + Create(ctx context.Context, ovnDnatRule *v1.OvnDnatRule, opts metav1.CreateOptions) (*v1.OvnDnatRule, error) + Update(ctx context.Context, ovnDnatRule *v1.OvnDnatRule, opts metav1.UpdateOptions) (*v1.OvnDnatRule, error) + UpdateStatus(ctx context.Context, ovnDnatRule *v1.OvnDnatRule, opts metav1.UpdateOptions) (*v1.OvnDnatRule, error) + Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error + Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.OvnDnatRule, error) + List(ctx context.Context, opts metav1.ListOptions) (*v1.OvnDnatRuleList, error) + Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.OvnDnatRule, err error) + OvnDnatRuleExpansion +} + +// ovnDnatRules implements OvnDnatRuleInterface +type ovnDnatRules struct { + client rest.Interface +} + +// newOvnDnatRules returns a OvnDnatRules +func newOvnDnatRules(c *KubeovnV1Client) *ovnDnatRules { + return &ovnDnatRules{ + client: c.RESTClient(), + } +} + +// Get takes name of the ovnDnatRule, and returns the corresponding ovnDnatRule object, and an error if there is any. +func (c *ovnDnatRules) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.OvnDnatRule, err error) { + result = &v1.OvnDnatRule{} + err = c.client.Get(). + Resource("ovn-dnat-rules"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of OvnDnatRules that match those selectors. +func (c *ovnDnatRules) List(ctx context.Context, opts metav1.ListOptions) (result *v1.OvnDnatRuleList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1.OvnDnatRuleList{} + err = c.client.Get(). + Resource("ovn-dnat-rules"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested ovnDnatRules. +func (c *ovnDnatRules) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Resource("ovn-dnat-rules"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a ovnDnatRule and creates it. Returns the server's representation of the ovnDnatRule, and an error, if there is any. +func (c *ovnDnatRules) Create(ctx context.Context, ovnDnatRule *v1.OvnDnatRule, opts metav1.CreateOptions) (result *v1.OvnDnatRule, err error) { + result = &v1.OvnDnatRule{} + err = c.client.Post(). + Resource("ovn-dnat-rules"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(ovnDnatRule). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a ovnDnatRule and updates it. Returns the server's representation of the ovnDnatRule, and an error, if there is any. +func (c *ovnDnatRules) Update(ctx context.Context, ovnDnatRule *v1.OvnDnatRule, opts metav1.UpdateOptions) (result *v1.OvnDnatRule, err error) { + result = &v1.OvnDnatRule{} + err = c.client.Put(). + Resource("ovn-dnat-rules"). + Name(ovnDnatRule.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(ovnDnatRule). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *ovnDnatRules) UpdateStatus(ctx context.Context, ovnDnatRule *v1.OvnDnatRule, opts metav1.UpdateOptions) (result *v1.OvnDnatRule, err error) { + result = &v1.OvnDnatRule{} + err = c.client.Put(). + Resource("ovn-dnat-rules"). + Name(ovnDnatRule.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(ovnDnatRule). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the ovnDnatRule and deletes it. Returns an error if one occurs. +func (c *ovnDnatRules) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { + return c.client.Delete(). + Resource("ovn-dnat-rules"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *ovnDnatRules) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Resource("ovn-dnat-rules"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched ovnDnatRule. +func (c *ovnDnatRules) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.OvnDnatRule, err error) { + result = &v1.OvnDnatRule{} + err = c.client.Patch(pt). + Resource("ovn-dnat-rules"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index cac2c8df264..9953547457b 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -63,6 +63,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Kubeovn().V1().IptablesFIPRules().Informer()}, nil case v1.SchemeGroupVersion.WithResource("iptables-snat-rules"): return &genericInformer{resource: resource.GroupResource(), informer: f.Kubeovn().V1().IptablesSnatRules().Informer()}, nil + case v1.SchemeGroupVersion.WithResource("ovn-dnat-rules"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Kubeovn().V1().OvnDnatRules().Informer()}, nil case v1.SchemeGroupVersion.WithResource("ovn-eips"): return &genericInformer{resource: resource.GroupResource(), informer: f.Kubeovn().V1().OvnEips().Informer()}, nil case v1.SchemeGroupVersion.WithResource("ovn-fips"): diff --git a/pkg/client/informers/externalversions/kubeovn/v1/interface.go b/pkg/client/informers/externalversions/kubeovn/v1/interface.go index f4af610ced6..ea966b028c9 100644 --- a/pkg/client/informers/externalversions/kubeovn/v1/interface.go +++ b/pkg/client/informers/externalversions/kubeovn/v1/interface.go @@ -34,6 +34,8 @@ type Interface interface { IptablesFIPRules() IptablesFIPRuleInformer // IptablesSnatRules returns a IptablesSnatRuleInformer. IptablesSnatRules() IptablesSnatRuleInformer + // OvnDnatRules returns a OvnDnatRuleInformer. + OvnDnatRules() OvnDnatRuleInformer // OvnEips returns a OvnEipInformer. OvnEips() OvnEipInformer // OvnFips returns a OvnFipInformer. @@ -96,6 +98,11 @@ func (v *version) IptablesSnatRules() IptablesSnatRuleInformer { return &iptablesSnatRuleInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// OvnDnatRules returns a OvnDnatRuleInformer. +func (v *version) OvnDnatRules() OvnDnatRuleInformer { + return &ovnDnatRuleInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + // OvnEips returns a OvnEipInformer. func (v *version) OvnEips() OvnEipInformer { return &ovnEipInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/informers/externalversions/kubeovn/v1/ovndnatrule.go b/pkg/client/informers/externalversions/kubeovn/v1/ovndnatrule.go new file mode 100644 index 00000000000..8489533cfaa --- /dev/null +++ b/pkg/client/informers/externalversions/kubeovn/v1/ovndnatrule.go @@ -0,0 +1,89 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1 + +import ( + "context" + time "time" + + kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + versioned "github.com/kubeovn/kube-ovn/pkg/client/clientset/versioned" + internalinterfaces "github.com/kubeovn/kube-ovn/pkg/client/informers/externalversions/internalinterfaces" + v1 "github.com/kubeovn/kube-ovn/pkg/client/listers/kubeovn/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// OvnDnatRuleInformer provides access to a shared informer and lister for +// OvnDnatRules. +type OvnDnatRuleInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1.OvnDnatRuleLister +} + +type ovnDnatRuleInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewOvnDnatRuleInformer constructs a new informer for OvnDnatRule type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewOvnDnatRuleInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredOvnDnatRuleInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredOvnDnatRuleInformer constructs a new informer for OvnDnatRule type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredOvnDnatRuleInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.KubeovnV1().OvnDnatRules().List(context.TODO(), options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.KubeovnV1().OvnDnatRules().Watch(context.TODO(), options) + }, + }, + &kubeovnv1.OvnDnatRule{}, + resyncPeriod, + indexers, + ) +} + +func (f *ovnDnatRuleInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredOvnDnatRuleInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *ovnDnatRuleInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&kubeovnv1.OvnDnatRule{}, f.defaultInformer) +} + +func (f *ovnDnatRuleInformer) Lister() v1.OvnDnatRuleLister { + return v1.NewOvnDnatRuleLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/listers/kubeovn/v1/expansion_generated.go b/pkg/client/listers/kubeovn/v1/expansion_generated.go index f60b7615dbc..a5f65d73bcb 100644 --- a/pkg/client/listers/kubeovn/v1/expansion_generated.go +++ b/pkg/client/listers/kubeovn/v1/expansion_generated.go @@ -38,6 +38,10 @@ type IptablesFIPRuleListerExpansion interface{} // IptablesSnatRuleLister. type IptablesSnatRuleListerExpansion interface{} +// OvnDnatRuleListerExpansion allows custom methods to be added to +// OvnDnatRuleLister. +type OvnDnatRuleListerExpansion interface{} + // OvnEipListerExpansion allows custom methods to be added to // OvnEipLister. type OvnEipListerExpansion interface{} diff --git a/pkg/client/listers/kubeovn/v1/ovndnatrule.go b/pkg/client/listers/kubeovn/v1/ovndnatrule.go new file mode 100644 index 00000000000..629841e1e0a --- /dev/null +++ b/pkg/client/listers/kubeovn/v1/ovndnatrule.go @@ -0,0 +1,68 @@ +/* +Copyright 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1 + +import ( + v1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// OvnDnatRuleLister helps list OvnDnatRules. +// All objects returned here must be treated as read-only. +type OvnDnatRuleLister interface { + // List lists all OvnDnatRules in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1.OvnDnatRule, err error) + // Get retrieves the OvnDnatRule from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1.OvnDnatRule, error) + OvnDnatRuleListerExpansion +} + +// ovnDnatRuleLister implements the OvnDnatRuleLister interface. +type ovnDnatRuleLister struct { + indexer cache.Indexer +} + +// NewOvnDnatRuleLister returns a new OvnDnatRuleLister. +func NewOvnDnatRuleLister(indexer cache.Indexer) OvnDnatRuleLister { + return &ovnDnatRuleLister{indexer: indexer} +} + +// List lists all OvnDnatRules in the indexer. +func (s *ovnDnatRuleLister) List(selector labels.Selector) (ret []*v1.OvnDnatRule, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1.OvnDnatRule)) + }) + return ret, err +} + +// Get retrieves the OvnDnatRule from the index for a given name. +func (s *ovnDnatRuleLister) Get(name string) (*v1.OvnDnatRule, error) { + obj, exists, err := s.indexer.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1.Resource("ovndnatrule"), name) + } + return obj.(*v1.OvnDnatRule), nil +} diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index fa81063c34d..98837cfe75f 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -170,6 +170,12 @@ type Controller struct { updateOvnSnatRuleQueue workqueue.RateLimitingInterface delOvnSnatRuleQueue workqueue.RateLimitingInterface + ovnDnatRulesLister kubeovnlister.OvnDnatRuleLister + ovnDnatRuleSynced cache.InformerSynced + addOvnDnatRuleQueue workqueue.RateLimitingInterface + updateOvnDnatRuleQueue workqueue.RateLimitingInterface + delOvnDnatRuleQueue workqueue.RateLimitingInterface + vlansLister kubeovnlister.VlanLister vlanSynced cache.InformerSynced @@ -623,6 +629,20 @@ func NewController(config *Configuration) *Controller { util.LogFatalAndExit(err, "failed to add ovn snat rule event handler") } + ovnDnatRuleInformer := kubeovnInformerFactory.Kubeovn().V1().OvnDnatRules() + controller.ovnDnatRulesLister = ovnDnatRuleInformer.Lister() + controller.ovnDnatRuleSynced = ovnDnatRuleInformer.Informer().HasSynced + controller.addOvnDnatRuleQueue = workqueue.NewNamedRateLimitingQueue(custCrdRateLimiter, "addOvnDnatRule") + controller.updateOvnDnatRuleQueue = workqueue.NewNamedRateLimitingQueue(custCrdRateLimiter, "updateOvnDnatRule") + controller.delOvnDnatRuleQueue = workqueue.NewNamedRateLimitingQueue(custCrdRateLimiter, "delOvnDnatRule") + if _, err = ovnDnatRuleInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: controller.enqueueAddOvnDnatRule, + UpdateFunc: controller.enqueueUpdateOvnDnatRule, + DeleteFunc: controller.enqueueDelOvnDnatRule, + }); err != nil { + util.LogFatalAndExit(err, "failed to add ovn dnat rule event handler") + } + if _, err = podAnnotatedIptablesEipInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: controller.enqueueAddPodAnnotatedIptablesEip, UpdateFunc: controller.enqueueUpdatePodAnnotatedIptablesEip, @@ -662,6 +682,7 @@ func (c *Controller) Run(ctx context.Context) { c.vlanSynced, c.podsSynced, c.namespacesSynced, c.nodesSynced, c.serviceSynced, c.endpointsSynced, c.configMapsSynced, c.ovnEipSynced, c.ovnFipSynced, c.ovnSnatRuleSynced, + c.ovnDnatRuleSynced, } if c.config.EnableNP { @@ -833,6 +854,10 @@ func (c *Controller) shutdown() { c.updateOvnSnatRuleQueue.ShutDown() c.delOvnSnatRuleQueue.ShutDown() + c.addOvnDnatRuleQueue.ShutDown() + c.updateOvnDnatRuleQueue.ShutDown() + c.delOvnDnatRuleQueue.ShutDown() + if c.config.PodDefaultFipType == util.IptablesFip { c.addPodAnnotatedIptablesEipQueue.ShutDown() c.updatePodAnnotatedIptablesEipQueue.ShutDown() @@ -1003,6 +1028,10 @@ func (c *Controller) startWorkers(ctx context.Context) { go wait.Until(c.runUpdateOvnSnatRuleWorker, time.Second, ctx.Done()) go wait.Until(c.runDelOvnSnatRuleWorker, time.Second, ctx.Done()) + go wait.Until(c.runAddOvnDnatRuleWorker, time.Second, ctx.Done()) + go wait.Until(c.runUpdateOvnDnatRuleWorker, time.Second, ctx.Done()) + go wait.Until(c.runDelOvnDnatRuleWorker, time.Second, ctx.Done()) + if c.config.EnableNP { go wait.Until(c.CheckNodePortGroup, time.Duration(c.config.NodePgProbeTime)*time.Minute, ctx.Done()) } diff --git a/pkg/controller/ovn_dnat.go b/pkg/controller/ovn_dnat.go new file mode 100644 index 00000000000..a0c05b0edde --- /dev/null +++ b/pkg/controller/ovn_dnat.go @@ -0,0 +1,608 @@ +package controller + +import ( + "context" + "encoding/json" + "fmt" + kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + "github.com/kubeovn/kube-ovn/pkg/util" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/tools/cache" + "k8s.io/klog/v2" + "net" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func (c *Controller) enqueueAddOvnDnatRule(obj interface{}) { + var key string + var err error + if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil { + utilruntime.HandleError(err) + return + } + klog.V(3).Infof("enqueue add ovn dnat %s", key) + c.addOvnDnatRuleQueue.Add(key) +} + +func (c *Controller) enqueueUpdateOvnDnatRule(old, new interface{}) { + var key string + var err error + if key, err = cache.MetaNamespaceKeyFunc(new); err != nil { + utilruntime.HandleError(err) + return + } + + oldDnat := old.(*kubeovnv1.OvnDnatRule) + newDnat := new.(*kubeovnv1.OvnDnatRule) + if !newDnat.DeletionTimestamp.IsZero() { + if len(newDnat.Finalizers) == 0 { + // avoid delete dnat twice + return + } else { + klog.V(3).Infof("enqueue del ovn dnat %s", key) + c.delOvnDnatRuleQueue.Add(key) + return + } + } + + if oldDnat.Spec.OvnEip != newDnat.Spec.OvnEip { + c.resetOvnEipQueue.Add(oldDnat.Spec.OvnEip) + } + + if oldDnat.Spec.OvnEip != newDnat.Spec.OvnEip || + oldDnat.Spec.Protocol != newDnat.Spec.Protocol || + oldDnat.Spec.IpName != newDnat.Spec.IpName || + oldDnat.Spec.InternalPort != newDnat.Spec.InternalPort || + oldDnat.Spec.ExternalPort != newDnat.Spec.ExternalPort { + klog.V(3).Infof("enqueue update dnat %s", key) + c.updateOvnDnatRuleQueue.Add(key) + } +} + +func (c *Controller) enqueueDelOvnDnatRule(obj interface{}) { + var key string + var err error + if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil { + utilruntime.HandleError(err) + return + } + klog.V(3).Infof("enqueue delete ovn dnat %s", key) + c.delOvnDnatRuleQueue.Add(key) +} + +func (c *Controller) runAddOvnDnatRuleWorker() { + for c.processNextAddOvnDnatRuleWorkItem() { + } +} + +func (c *Controller) runUpdateOvnDnatRuleWorker() { + for c.processNextUpdateOvnDnatRuleWorkItem() { + } +} + +func (c *Controller) runDelOvnDnatRuleWorker() { + for c.processNextDeleteOvnDnatRuleWorkItem() { + } +} + +func (c *Controller) processNextAddOvnDnatRuleWorkItem() bool { + obj, shutdown := c.addOvnDnatRuleQueue.Get() + if shutdown { + return false + } + + err := func(obj interface{}) error { + defer c.addOvnDnatRuleQueue.Done(obj) + var key string + var ok bool + if key, ok = obj.(string); !ok { + c.addOvnDnatRuleQueue.Forget(obj) + utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj)) + return nil + } + if err := c.handleAddOvnDnatRule(key); err != nil { + c.addOvnDnatRuleQueue.AddRateLimited(key) + return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error()) + } + c.addOvnDnatRuleQueue.Forget(obj) + return nil + }(obj) + + if err != nil { + utilruntime.HandleError(err) + return true + } + return true +} + +func (c *Controller) processNextUpdateOvnDnatRuleWorkItem() bool { + obj, shutdown := c.updateOvnDnatRuleQueue.Get() + if shutdown { + return false + } + + err := func(obj interface{}) error { + defer c.updateOvnDnatRuleQueue.Done(obj) + var key string + var ok bool + if key, ok = obj.(string); !ok { + c.updateOvnDnatRuleQueue.Forget(obj) + utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj)) + return nil + } + if err := c.handleUpdateOvnDnatRule(key); err != nil { + c.updateOvnDnatRuleQueue.AddRateLimited(key) + return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error()) + } + c.updateOvnDnatRuleQueue.Forget(obj) + return nil + }(obj) + + if err != nil { + utilruntime.HandleError(err) + return true + } + return true +} + +func (c *Controller) processNextDeleteOvnDnatRuleWorkItem() bool { + obj, shutdown := c.delOvnDnatRuleQueue.Get() + if shutdown { + return false + } + + err := func(obj interface{}) error { + defer c.delOvnDnatRuleQueue.Done(obj) + var key string + var ok bool + if key, ok = obj.(string); !ok { + c.delOvnDnatRuleQueue.Forget(obj) + utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj)) + return nil + } + if err := c.handleDelOvnDnatRule(key); err != nil { + c.delOvnDnatRuleQueue.AddRateLimited(key) + return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error()) + } + c.delOvnDnatRuleQueue.Forget(obj) + return nil + }(obj) + + if err != nil { + utilruntime.HandleError(err) + return true + } + return true +} + +func (c *Controller) handleAddOvnDnatRule(key string) error { + cachedDnat, err := c.ovnDnatRulesLister.Get(key) + if err != nil { + if k8serrors.IsNotFound(err) { + return nil + } + return err + } + + if cachedDnat.Status.Ready && cachedDnat.Status.V4Ip != "" { + // already ok + return nil + } + klog.V(3).Infof("handle add dnat %s", key) + + var internalV4Ip, mac, subnetName string + if cachedDnat.Spec.IpType == util.NatUsingVip { + internalVip, err := c.virtualIpsLister.Get(cachedDnat.Spec.IpName) + if err != nil { + klog.Errorf("failed to get vip %s, %v", cachedDnat.Spec.IpName, err) + return err + } + internalV4Ip = internalVip.Status.V4ip + mac = internalVip.Status.Mac + subnetName = internalVip.Spec.Subnet + } else { + internalIp, err := c.ipsLister.Get(cachedDnat.Spec.IpName) + if err != nil { + klog.Errorf("failed to get ip %s, %v", cachedDnat.Spec.IpName, err) + return err + } + internalV4Ip = internalIp.Spec.V4IPAddress + mac = internalIp.Spec.MacAddress + subnetName = internalIp.Spec.Subnet + } + + eipName := cachedDnat.Spec.OvnEip + if len(eipName) == 0 { + err := fmt.Errorf("failed to create dnat %s, should set eip", cachedDnat.Name) + klog.Error(err) + return err + } + + cachedEip, err := c.GetOvnEip(eipName) + if err != nil { + klog.Errorf("failed to get eip, %v", err) + return err + } + + subnet, err := c.subnetsLister.Get(subnetName) + if err != nil { + klog.Errorf("failed to get vpc subnet %s, %v", subnetName, err) + return err + } + + if cachedEip.Status.V4Ip == "" || internalV4Ip == "" { + err := fmt.Errorf("failed to create v4 dnat %s", cachedDnat.Name) + klog.Error(err) + return err + } + + vpcName := subnet.Spec.Vpc + if cachedEip.Spec.Type != "" && cachedEip.Spec.Type != util.DnatUsingEip { + err = fmt.Errorf("failed to create ovn dnat %s, eip '%s' is using by %s", key, eipName, cachedEip.Spec.Type) + return err + } + + if cachedEip.Spec.Type == util.DnatUsingEip && + cachedEip.Annotations[util.VpcNatAnnotation] != "" && + cachedEip.Annotations[util.VpcNatAnnotation] != cachedDnat.Name { + err = fmt.Errorf("failed to create dnat %s, eip '%s' is using by other dnat %s", key, eipName, cachedEip.Annotations[util.VpcNatAnnotation]) + return err + } + + if err = c.handleAddOvnEipFinalizer(cachedEip, util.OvnDnatUseEipFinalizer); err != nil { + klog.Errorf("failed to add finalizer for ovn eip, %v", err) + return err + } + + if err = c.handleAddOvnDnatFinalizer(cachedDnat); err != nil { + klog.Errorf("failed to handle finalizer for ovn dnat, %v", err) + return err + } + + if err = c.AddDnatRule(vpcName, cachedDnat.Name, cachedEip.Status.V4Ip, internalV4Ip, + cachedDnat.Spec.ExternalPort, cachedDnat.Spec.InternalPort, cachedDnat.Spec.Protocol); err != nil { + klog.Errorf("failed to create v4 dnat, %v", err) + return err + } + + // patch dnat eip relationship + if err = c.natLabelAndAnnoOvnEip(eipName, cachedDnat.Name, vpcName); err != nil { + klog.Errorf("failed to label dnat '%s' in eip %s, %v", cachedDnat.Name, eipName, err) + return err + } + + if err = c.patchOvnDnatAnnotations(key, eipName); err != nil { + klog.Errorf("failed to update annotations for dnat %s, %v", key, err) + return err + } + + if err = c.patchOvnDnatStatus(key, vpcName, cachedEip.Status.V4Ip, + internalV4Ip, mac, true); err != nil { + klog.Errorf("failed to patch status for dnat %s, %v", key, err) + return err + } + + return nil +} + +func (c *Controller) handleDelOvnDnatRule(key string) error { + klog.V(3).Infof("handle del ovn dnat %s", key) + cachedDnat, err := c.ovnDnatRulesLister.Get(key) + if err != nil { + if k8serrors.IsNotFound(err) { + return nil + } + return err + } + + eipName := cachedDnat.Spec.OvnEip + if len(eipName) == 0 { + klog.Errorf("failed to delete ovn dnat, should set eip") + } + + cachedEip, err := c.GetOvnEip(eipName) + if err != nil { + klog.Errorf("failed to get eip, %v", err) + return err + } + + if cachedDnat.Status.Ready { + if err = c.DelDnatRule(cachedDnat.Status.Vpc, cachedDnat.Name, + cachedDnat.Status.V4Eip, cachedDnat.Status.ExternalPort); err != nil { + klog.Errorf("failed to delete dnat, %v", err) + return err + } + } + + if err = c.handleDelOvnDnatFinalizer(cachedDnat); err != nil { + klog.Errorf("failed to handle remove finalizer from ovn dnat, %v", err) + return err + } + + c.resetOvnEipQueue.Add(cachedDnat.Spec.OvnEip) + if err = c.handleDelOvnEipFinalizer(cachedEip, util.OvnDnatUseEipFinalizer); err != nil { + klog.Errorf("failed to handle remove finalizer from ovn eip, %v", err) + return err + } + return nil +} + +func (c *Controller) handleUpdateOvnDnatRule(key string) error { + cachedDnat, err := c.ovnDnatRulesLister.Get(key) + if err != nil { + if k8serrors.IsNotFound(err) { + return nil + } + return err + } + + klog.V(3).Infof("handle update dnat %s", key) + var internalV4Ip, mac, subnetName string + if cachedDnat.Spec.IpType == util.NatUsingVip { + internalVip, err := c.virtualIpsLister.Get(cachedDnat.Spec.IpName) + if err != nil { + klog.Errorf("failed to get vip %s, %v", cachedDnat.Spec.IpName, err) + return err + } + internalV4Ip = internalVip.Status.V4ip + mac = internalVip.Status.Mac + subnetName = internalVip.Spec.Subnet + } else { + internalIp, err := c.ipsLister.Get(cachedDnat.Spec.IpName) + if err != nil { + klog.Errorf("failed to get ip %s, %v", cachedDnat.Spec.IpName, err) + return err + } + internalV4Ip = internalIp.Spec.V4IPAddress + mac = internalIp.Spec.MacAddress + subnetName = internalIp.Spec.Subnet + } + + eipName := cachedDnat.Spec.OvnEip + if len(eipName) == 0 { + err := fmt.Errorf("failed to create dnat %s, should set eip", cachedDnat.Name) + klog.Error(err) + return err + } + + cachedEip, err := c.GetOvnEip(eipName) + if err != nil { + klog.Errorf("failed to get eip, %v", err) + return err + } + + subnet, err := c.subnetsLister.Get(subnetName) + if err != nil { + klog.Errorf("failed to get vpc subnet %s, %v", subnetName, err) + return err + } + vpcName := subnet.Spec.Vpc + + if cachedEip.Status.V4Ip == "" || internalV4Ip == "" { + err := fmt.Errorf("failed to create v4 dnat %s", cachedDnat.Name) + klog.Error(err) + return err + } + + if cachedEip.Spec.Type != "" && cachedEip.Spec.Type != util.DnatUsingEip { + // eip is in use by other nat + err = fmt.Errorf("failed to update dnat %s, eip '%s' is using by %s", key, eipName, cachedEip.Spec.Type) + return err + } + + if cachedEip.Spec.Type == util.DnatUsingEip && + cachedEip.Annotations[util.VpcNatAnnotation] != "" && + cachedEip.Annotations[util.VpcNatAnnotation] != cachedDnat.Name { + err = fmt.Errorf("failed to update dnat %s, eip '%s' is using by other dnat %s", key, eipName, cachedEip.Annotations[util.VpcNatAnnotation]) + return err + } + + dnat := cachedDnat.DeepCopy() + if dnat.Status.Ready { + klog.V(3).Infof("dnat change ip, old ip '%s', new ip %s", dnat.Status.V4Ip, cachedEip.Status.V4Ip) + if err = c.DelDnatRule(dnat.Status.Vpc, dnat.Name, dnat.Status.V4Eip, dnat.Status.ExternalPort); err != nil { + klog.Errorf("failed to delete dnat, %v", err) + return err + } + + if err = c.AddDnatRule(vpcName, dnat.Name, cachedEip.Status.V4Ip, internalV4Ip, + dnat.Spec.ExternalPort, dnat.Spec.InternalPort, dnat.Spec.Protocol); err != nil { + klog.Errorf("failed to create dnat, %v", err) + return err + } + + if err = c.natLabelAndAnnoOvnEip(eipName, dnat.Name, vpcName); err != nil { + klog.Errorf("failed to label dnat '%s' in eip %s, %v", dnat.Name, eipName, err) + return err + } + + if err = c.patchOvnDnatAnnotations(key, eipName); err != nil { + klog.Errorf("failed to update annotations for dnat %s, %v", key, err) + return err + } + + if err = c.patchOvnDnatStatus(key, vpcName, cachedEip.Status.V4Ip, internalV4Ip, mac, true); err != nil { + klog.Errorf("failed to patch status for dnat '%s', %v", key, err) + return err + } + return nil + } + return nil +} + +func (c *Controller) handleAddOvnDnatFinalizer(cachedDnat *kubeovnv1.OvnDnatRule) error { + if cachedDnat.DeletionTimestamp.IsZero() { + if util.ContainsString(cachedDnat.Finalizers, util.ControllerName) { + return nil + } + } + + newDnat := cachedDnat.DeepCopy() + controllerutil.AddFinalizer(newDnat, util.ControllerName) + patch, err := util.GenerateMergePatchPayload(cachedDnat, newDnat) + if err != nil { + return err + } + + if _, err := c.config.KubeOvnClient.KubeovnV1().OvnDnatRules().Patch(context.Background(), cachedDnat.Name, + types.MergePatchType, patch, metav1.PatchOptions{}, ""); err != nil { + if k8serrors.IsNotFound(err) { + return nil + } + klog.Errorf("failed to add finalizer for ovn dnat '%s', %v", cachedDnat.Name, err) + return err + } + return nil +} + +func (c *Controller) patchOvnDnatAnnotations(key, eipName string) error { + oriDnat, err := c.ovnDnatRulesLister.Get(key) + if err != nil { + if k8serrors.IsNotFound(err) { + return nil + } + return err + } + + dnat := oriDnat.DeepCopy() + var needUpdateAnno bool + var op string + if len(dnat.Annotations) == 0 { + op = "add" + dnat.Annotations = map[string]string{ + util.VpcEipAnnotation: eipName, + } + needUpdateAnno = true + } + if dnat.Annotations[util.VpcEipAnnotation] != eipName { + op = "replace" + dnat.Annotations[util.VpcEipAnnotation] = eipName + needUpdateAnno = true + } + if needUpdateAnno { + patchPayloadTemplate := `[{ "op": "%s", "path": "/metadata/annotations", "value": %s }]` + raw, _ := json.Marshal(dnat.Annotations) + patchPayload := fmt.Sprintf(patchPayloadTemplate, op, raw) + _, err := c.config.KubeOvnClient.KubeovnV1().OvnDnatRules().Patch(context.Background(), dnat.Name, types.JSONPatchType, []byte(patchPayload), metav1.PatchOptions{}) + if err != nil { + klog.Errorf("failed to patch annotation for ovn dnat %s, %v", dnat.Name, err) + return err + } + } + return nil +} + +func (c *Controller) patchOvnDnatStatus(key, vpcName, v4Eip, podIp, podMac string, ready bool) error { + oriDnat, err := c.ovnDnatRulesLister.Get(key) + if err != nil { + if k8serrors.IsNotFound(err) { + return nil + } + return err + } + + dnat := oriDnat.DeepCopy() + var changed bool + if dnat.Status.Ready != ready { + dnat.Status.Ready = ready + changed = true + } + + if ready && v4Eip != "" && dnat.Status.V4Eip != v4Eip { + dnat.Status.Vpc = vpcName + dnat.Status.V4Eip = v4Eip + dnat.Status.V4Ip = podIp + dnat.Status.MacAddress = podMac + changed = true + } + + if ready && dnat.Spec.Protocol != "" && dnat.Status.Protocol != dnat.Spec.Protocol { + dnat.Status.Protocol = dnat.Spec.Protocol + changed = true + } + + if ready && dnat.Spec.IpName != "" && dnat.Spec.IpName != dnat.Status.IpName { + dnat.Status.IpName = dnat.Spec.IpName + changed = true + } + + if ready && dnat.Spec.InternalPort != "" && dnat.Status.InternalPort != dnat.Spec.InternalPort { + dnat.Status.InternalPort = dnat.Spec.InternalPort + changed = true + } + + if ready && dnat.Spec.ExternalPort != "" && dnat.Status.ExternalPort != dnat.Spec.ExternalPort { + dnat.Status.ExternalPort = dnat.Spec.ExternalPort + changed = true + } + + if changed { + bytes, err := dnat.Status.Bytes() + if err != nil { + return err + } + if _, err = c.config.KubeOvnClient.KubeovnV1().OvnDnatRules().Patch(context.Background(), dnat.Name, + types.MergePatchType, bytes, metav1.PatchOptions{}, "status"); err != nil { + klog.Errorf("failed to patch dnat %s, %v", dnat.Name, err) + return err + } + } + return nil +} + +func (c *Controller) handleDelOvnDnatFinalizer(cachedDnat *kubeovnv1.OvnDnatRule) error { + if len(cachedDnat.Finalizers) == 0 { + return nil + } + + newDnat := cachedDnat.DeepCopy() + controllerutil.RemoveFinalizer(newDnat, util.ControllerName) + patch, err := util.GenerateMergePatchPayload(cachedDnat, newDnat) + if err != nil { + return err + } + + if _, err := c.config.KubeOvnClient.KubeovnV1().OvnDnatRules().Patch(context.Background(), cachedDnat.Name, + types.MergePatchType, patch, metav1.PatchOptions{}, ""); err != nil { + if k8serrors.IsNotFound(err) { + return nil + } + klog.Errorf("failed to remove finalizer from ovn dnat '%s', %v", cachedDnat.Name, err) + return err + } + return nil +} + +func (c *Controller) AddDnatRule(vpcName, dnatName, externalIp, internalIp, externalPort, internalPort, protocol string) error { + externalEndpoint := net.JoinHostPort(externalIp, externalPort) + internalEndpoint := net.JoinHostPort(internalIp, internalPort) + + if err := c.ovnLegacyClient.CreateLoadBalancerRule(dnatName, externalEndpoint, internalEndpoint, protocol); err != nil { + klog.Errorf("failed to create loadBalancer rule, %v", err) + return err + } + + if err := c.ovnLegacyClient.AddLoadBalancerToLogicalRouter(dnatName, vpcName); err != nil { + klog.Errorf("failed to add lb %s to vpc %s, %v", dnatName, vpcName, err) + return err + } + return nil +} + +func (c *Controller) DelDnatRule(vpcName, dnatName, externalIp, externalPort string) error { + externalEndpoint := net.JoinHostPort(externalIp, externalPort) + + if err := c.ovnLegacyClient.DeleteLoadBalancerVip(externalEndpoint, dnatName); err != nil { + klog.Errorf("failed to delete loadBalancer rule, %v", err) + return err + } + + if err := c.ovnLegacyClient.RemoveLoadBalancerFromLogicalRouter(dnatName, vpcName); err != nil { + klog.Errorf("failed to remove lb %s from vpc %s, dnatName, vpcName, %v", err) + return err + } + return nil +} diff --git a/pkg/controller/ovn_fip.go b/pkg/controller/ovn_fip.go index 21a52bbc304..e2eecddaad3 100644 --- a/pkg/controller/ovn_fip.go +++ b/pkg/controller/ovn_fip.go @@ -185,7 +185,7 @@ func (c *Controller) handleAddOvnFip(key string) error { } klog.V(3).Infof("handle add fip %s", key) var internalV4Ip, mac, subnetName string - if cachedFip.Spec.IpType == util.FipUsingVip { + if cachedFip.Spec.IpType == util.NatUsingVip { internalVip, err := c.virtualIpsLister.Get(cachedFip.Spec.IpName) if err != nil { klog.Errorf("failed to get vip %s, %v", cachedFip.Spec.IpName, err) @@ -277,7 +277,7 @@ func (c *Controller) handleUpdateOvnFip(key string) error { } klog.V(3).Infof("handle update fip %s", key) var internalV4Ip, mac, subnetName string - if cachedFip.Spec.IpType == util.FipUsingVip { + if cachedFip.Spec.IpType == util.NatUsingVip { internalVip, err := c.virtualIpsLister.Get(cachedFip.Spec.IpName) if err != nil { klog.Errorf("failed to get vip %s, %v", cachedFip.Spec.IpName, err) diff --git a/pkg/ovs/ovn-nbctl-legacy.go b/pkg/ovs/ovn-nbctl-legacy.go index 1f3c551e3e8..af4f11adefa 100644 --- a/pkg/ovs/ovn-nbctl-legacy.go +++ b/pkg/ovs/ovn-nbctl-legacy.go @@ -792,6 +792,11 @@ func (c LegacyClient) addLoadBalancerToLogicalSwitch(lb, ls string) error { return err } +func (c LegacyClient) AddLoadBalancerToLogicalRouter(lb, lr string) error { + _, err := c.ovnNbCommand(MayExist, "lr-lb-add", lr, lb) + return err +} + func (c LegacyClient) removeLoadBalancerFromLogicalSwitch(lb, ls string) error { if lb == "" { return nil @@ -808,6 +813,22 @@ func (c LegacyClient) removeLoadBalancerFromLogicalSwitch(lb, ls string) error { return err } +func (c LegacyClient) RemoveLoadBalancerFromLogicalRouter(lb, lr string) error { + if lb == "" { + return nil + } + lbUuid, err := c.FindLoadbalancer(lb) + if err != nil { + return err + } + if lbUuid == "" { + return nil + } + + _, err = c.ovnNbCommand(IfExists, "lr-lb-del", lr, lb) + return err +} + // DeleteLoadBalancerVip delete a vip rule from loadbalancer func (c LegacyClient) DeleteLoadBalancerVip(vip, lb string) error { _, err := c.ovnNbCommand(IfExists, "lb-del", lb, vip) diff --git a/pkg/util/const.go b/pkg/util/const.go index 9928de1a78d..2b82f5a15a2 100644 --- a/pkg/util/const.go +++ b/pkg/util/const.go @@ -25,6 +25,7 @@ const ( OvnFipUseEipFinalizer = "ovn.kubernetes.io/ovn_fip" OvnSnatUseEipFinalizer = "ovn.kubernetes.io/ovn_snat" + OvnDnatUseEipFinalizer = "ovn.kubernetes.io/ovn_dnat" OvnLrpUseEipFinalizer = "ovn.kubernetes.io/ovn_lrp" ExternalIpAnnotation = "ovn.kubernetes.io/external_ip" @@ -176,7 +177,7 @@ const ( LrpUsingEip = "lrp" FipUsingEip = "fip" - FipUsingVip = "vip" + NatUsingVip = "vip" SnatUsingEip = "snat" DnatUsingEip = "dnat" NodeExtGwUsingEip = "node-ext-gw" diff --git a/yamls/crd.yaml b/yamls/crd.yaml index d7fee7a33a4..c9b53360c59 100644 --- a/yamls/crd.yaml +++ b/yamls/crd.yaml @@ -790,6 +790,89 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + name: ovn-dnat-rules.kubeovn.io +spec: + group: kubeovn.io + names: + plural: ovn-dnat-rules + singular: ovn-dnat-rule + shortNames: + - odnat + kind: OvnDnatRule + listKind: OvnDnatRuleList + scope: Cluster + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status.vpc + name: Vpc + type: string + - jsonPath: .status.v4Eip + name: V4Eip + type: string + - jsonPath: .status.v4Ip + name: V4Ip + type: string + - jsonPath: .status.ready + name: Ready + type: boolean + - jsonPath: .spec.ipType + name: IpType + type: string + - jsonPath: .spec.ipName + name: IpName + type: string + schema: + openAPIV3Schema: + type: object + properties: + status: + type: object + properties: + ready: + type: boolean + v4Eip: + type: string + v4Ip: + type: string + macAddress: + type: string + vpc: + type: string + conditions: + type: array + items: + type: object + properties: + type: + type: string + status: + type: string + reason: + type: string + message: + type: string + lastUpdateTime: + type: string + lastTransitionTime: + type: string + spec: + type: object + properties: + ovnEip: + type: string + ipType: + type: string + ipName: + type: string +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: name: vpcs.kubeovn.io spec: