go.ligato.io/vpp-agent/v3@v3.5.0/plugins/vpp/srplugin/descriptor/policy.go (about) 1 // Copyright (c) 2019 Bell Canada, Pantheon Technologies and/or its affiliates. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at: 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package descriptor 16 17 import ( 18 "github.com/pkg/errors" 19 "google.golang.org/protobuf/encoding/prototext" 20 21 "go.ligato.io/cn-infra/v2/logging" 22 23 scheduler "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api" 24 "go.ligato.io/vpp-agent/v3/plugins/vpp/srplugin/descriptor/adapter" 25 "go.ligato.io/vpp-agent/v3/plugins/vpp/srplugin/vppcalls" 26 vpp_l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" 27 srv6 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/srv6" 28 ) 29 30 const ( 31 // PolicyDescriptorName is the name of the descriptor for VPP SRv6 policies 32 PolicyDescriptorName = "vpp-sr-policy" 33 34 // dependency labels 35 policyVRFDep = "sr-policy-vrf-table-exists" 36 ) 37 38 // PolicyDescriptor teaches KVScheduler how to configure VPP SRv6 policies. 39 // 40 // Implementation note: according to https://tools.ietf.org/html/draft-ietf-spring-segment-routing-policy-02#section-2.6, 41 // (weight,segments) tuple is not unique identification of candidate path(segment list) in policy (VPP also allows to 42 // have multiple segment lists that have the same (weight,segments) but differs in index (in ietf material they call it 43 // "Discriminator of a Candidate Path")). However, in validation and equivalency stage of vpp-agent there is no way how 44 // to get from VPP the index (validation precedes creation call and VPP doesn't have it). As result, restriction must be 45 // made, (weight,segments) is unique within given Policy. The drawback is that you can't create multiple segment lists 46 // with the same (weight,segments), but that is not issue if you don't want to explicitly rely on special attributes 47 // used by tie-breaking rules from https://tools.ietf.org/html/draft-ietf-spring-segment-routing-policy-02#section-2.9 48 // (i.e. VPP-internal index). These special attributes can't be explicitly set in VPP, so can't rely on them anyway. So 49 // we should not miss any client use case for current VPP implementation (by removing duplicated segment list we don't 50 // break any use case). 51 type PolicyDescriptor struct { 52 // dependencies 53 log logging.Logger 54 srHandler vppcalls.SRv6VppAPI 55 } 56 57 // PolicyMetadata are Policy-related metadata that KVscheduler bundles with Policy data. They are served by KVScheduler in Create/Update descriptor methods. 58 type PolicyMetadata struct { 59 segmentListIndexes map[string]uint32 // map[marshalled segment list]index in policy 60 } 61 62 // NewPolicyDescriptor creates a new instance of the Srv6 policy descriptor. 63 func NewPolicyDescriptor(srHandler vppcalls.SRv6VppAPI, log logging.PluginLogger) *scheduler.KVDescriptor { 64 ctx := &PolicyDescriptor{ 65 log: log.NewLogger("policy-descriptor"), 66 srHandler: srHandler, 67 } 68 69 typedDescr := &adapter.PolicyDescriptor{ 70 Name: PolicyDescriptorName, 71 NBKeyPrefix: srv6.ModelPolicy.KeyPrefix(), 72 ValueTypeName: srv6.ModelPolicy.ProtoName(), 73 KeySelector: srv6.ModelPolicy.IsKeyValid, 74 KeyLabel: srv6.ModelPolicy.StripKeyPrefix, 75 ValueComparator: ctx.EquivalentPolicies, 76 Validate: ctx.Validate, 77 Create: ctx.Create, 78 Delete: ctx.Delete, 79 Update: ctx.Update, 80 UpdateWithRecreate: ctx.UpdateWithRecreate, 81 Dependencies: ctx.Dependencies, 82 WithMetadata: true, 83 } 84 return adapter.NewPolicyDescriptor(typedDescr) 85 } 86 87 // Validate validates VPP policies. 88 func (d *PolicyDescriptor) Validate(key string, policy *srv6.Policy) error { 89 _, err := ParseIPv6(policy.GetBsid()) 90 if err != nil { 91 return scheduler.NewInvalidValueError(errors.Errorf("failed to parse binding sid %s, should be a valid ipv6 address: %v", policy.GetBsid(), err), "bsid") 92 } 93 if len(policy.SegmentLists) == 0 { // includes nil check 94 return scheduler.NewInvalidValueError(errors.New("there must be defined at least one segment list"), "SegmentLists") 95 } 96 97 for i, sl := range policy.SegmentLists { 98 for _, segment := range sl.Segments { 99 _, err := ParseIPv6(segment) 100 if err != nil { 101 return scheduler.NewInvalidValueError(errors.Errorf("failed to parse segment %s in segments %v, should be a valid ipv6 address: %v", segment, sl.Segments, err), "SegmentLists.segments") 102 } 103 } 104 105 // checking for segment list duplicity (existence of this check is used in equivalency check too), see PolicyDescriptor's godoc implementation note for more info 106 for j, previousSL := range policy.SegmentLists[:i] { 107 if d.equivalentSegmentList(previousSL, sl) { 108 return scheduler.NewInvalidValueError(errors.Errorf("found duplicated segment list: %+v (list index %v) and %+v (list index %v) ", sl, i, previousSL, j), "SegmentLists") 109 } 110 } 111 } 112 return nil 113 } 114 115 // Create creates new Policy into VPP using VPP's binary api 116 func (d *PolicyDescriptor) Create(key string, policy *srv6.Policy) (metadata interface{}, err error) { 117 // add policy (including segment lists) 118 err = d.srHandler.AddPolicy(policy) // there exist first segment (validation checked it) 119 if err != nil { 120 return nil, errors.Errorf("failed to write policy %s with first segment %s: %v", policy.GetBsid(), policy.SegmentLists[0], err) 121 } 122 123 // retrieve from VPP indexes of just added Policy/Segment Lists and store it as metadata 124 slIndexes, err := d.policyIndexInfo(policy) 125 if err != nil { 126 return nil, errors.Wrapf(err, "can't retrieve indexes of created srv6 policy with bsid %v", policy.GetBsid()) 127 } 128 metadata = &PolicyMetadata{ 129 segmentListIndexes: slIndexes, 130 } 131 return metadata, nil 132 } 133 134 // Delete removes Policy from VPP using VPP's binary api 135 func (d *PolicyDescriptor) Delete(key string, policy *srv6.Policy, metadata interface{}) error { 136 bsid, _ := ParseIPv6(policy.GetBsid()) // already validated 137 if err := d.srHandler.DeletePolicy(bsid); err != nil { // expecting that delete of SB defined policy will also delete policy segments in vpp 138 return errors.Errorf("failed to delete policy %s: %v", bsid.String(), err) 139 } 140 return nil 141 } 142 143 // Update updates Policy in VPP using VPP's binary api. Only changes of segment list handled here. Other updates are 144 // handle by recreation (see function UpdateWithRecreate) 145 func (d *PolicyDescriptor) Update(key string, oldPolicy, newPolicy *srv6.Policy, oldMetadata interface{}) (newMetadata interface{}, err error) { 146 // compute segment lists for delete (removePool) and segment lists to add (addPool) 147 removePool := make([]*srv6.Policy_SegmentList, len(oldPolicy.SegmentLists)) 148 addPool := make([]*srv6.Policy_SegmentList, 0, len(newPolicy.SegmentLists)) 149 copy(removePool, oldPolicy.SegmentLists) 150 for _, newSL := range newPolicy.SegmentLists { 151 found := false 152 for i, oldSL := range removePool { 153 if d.equivalentSegmentList(oldSL, newSL) { 154 removePool = append(removePool[:i], removePool[i+1:]...) 155 found = true 156 break 157 } 158 } 159 if !found { 160 addPool = append(addPool, newSL) 161 } 162 } 163 164 // add new segment lists not present in oldPolicy 165 for _, sl := range addPool { 166 if err := d.srHandler.AddPolicySegmentList(sl, newPolicy); err != nil { 167 return nil, errors.Errorf("failed update policy: failed to add policy segment %s: %v", newPolicy.GetBsid(), err) 168 } 169 } 170 171 // remove old segment lists not present in newPolicy 172 slIndexes := oldMetadata.(*PolicyMetadata).segmentListIndexes 173 for _, sl := range removePool { 174 index, exists := slIndexes[prototext.MarshalOptions{}.Format(sl)] 175 if !exists { 176 return nil, errors.Errorf("failed update policy: failed to find index for segment list "+ 177 "%+v in policy with bsid %v (metadata segment list indexes: %+v)", sl, oldPolicy.GetBsid(), slIndexes) 178 } 179 if err := d.srHandler.DeletePolicySegmentList(sl, index, newPolicy); err != nil { 180 return nil, errors.Errorf("failed update policy: failed to delete policy segment %s: %v", oldPolicy.GetBsid(), err) 181 } 182 } 183 184 // update metadata be recreation it from scratch 185 slIndexes, err = d.policyIndexInfo(newPolicy) 186 if err != nil { 187 return nil, errors.Wrapf(err, "can't retrieve indexes of updated srv6 policy with bsid %v", newPolicy.GetBsid()) 188 } 189 newMetadata = &PolicyMetadata{ 190 segmentListIndexes: slIndexes, 191 } 192 193 return newMetadata, nil 194 } 195 196 // policyIndexInfo retrieves indexes for segment lists of given policy 197 func (d *PolicyDescriptor) policyIndexInfo(policy *srv6.Policy) (map[string]uint32, error) { 198 _, indexes, err := d.srHandler.RetrievePolicyIndexInfo(policy) 199 if err != nil { 200 return nil, errors.Wrapf(err, "can't retrieve indexes of srv6 policy with bsid %v from vpp", policy.GetBsid()) 201 } 202 result := make(map[string]uint32) 203 for slist, index := range indexes { 204 result[prototext.MarshalOptions{}.Format(slist)] = index 205 } 206 return result, nil 207 } 208 209 // UpdateWithRecreate define whether update case should be handled by complete policy recreation 210 func (d *PolicyDescriptor) UpdateWithRecreate(key string, oldPolicy, newPolicy *srv6.Policy, oldMetadata interface{}) bool { 211 return !d.equivalentPolicyAttributes(oldPolicy, newPolicy) // update with recreate only when policy attributes changes because segment list change can be handled more efficiently 212 } 213 214 func (d *PolicyDescriptor) Dependencies(key string, policy *srv6.Policy) (dependencies []scheduler.Dependency) { 215 if policy.InstallationVrfId != 0 { 216 dependencies = append(dependencies, scheduler.Dependency{ 217 Label: policyVRFDep, 218 Key: vpp_l3.VrfTableKey(policy.InstallationVrfId, vpp_l3.VrfTable_IPV6), 219 }) 220 } 221 return dependencies 222 } 223 224 // EquivalentPolicies determines whether 2 policies are logically equal. This comparison takes into consideration also 225 // semantics that couldn't be modeled into proto models (i.e. SID is IPv6 address and not only string) 226 func (d *PolicyDescriptor) EquivalentPolicies(key string, oldPolicy, newPolicy *srv6.Policy) bool { 227 return d.equivalentPolicyAttributes(oldPolicy, newPolicy) && 228 d.equivalentSegmentLists(oldPolicy.SegmentLists, newPolicy.SegmentLists) 229 } 230 231 func (d *PolicyDescriptor) equivalentPolicyAttributes(oldPolicy, newPolicy *srv6.Policy) bool { 232 return oldPolicy.InstallationVrfId == newPolicy.InstallationVrfId && 233 equivalentSIDs(oldPolicy.Bsid, newPolicy.Bsid) && 234 oldPolicy.SprayBehaviour == newPolicy.SprayBehaviour && 235 oldPolicy.SrhEncapsulation == newPolicy.SrhEncapsulation 236 } 237 238 func (d *PolicyDescriptor) equivalentSegmentLists(oldSLs, newSLs []*srv6.Policy_SegmentList) bool { 239 if oldSLs == nil || newSLs == nil { 240 return oldSLs == nil && newSLs == nil 241 } 242 if len(oldSLs) != len(newSLs) { 243 return false 244 } 245 246 // checking segment lists equality (segment lists with just reordered segment list items are considered equal) 247 // we know that: 248 // 1. lists have the same length (due to previous checks), 249 // 2. there are no segment list equivalence duplicates (due to validation check) 250 // => hence if we check that each segment list from newSLs has its equivalent in oldSLs then we got equivalent bijection 251 // and that means that they are equal (items maybe reordered but equal). If we find one segment list from newSLs that 252 // is not in oldSLs, the SLs are obviously not equal. 253 for _, newSL := range newSLs { 254 found := false 255 for _, oldSL := range oldSLs { 256 if d.equivalentSegmentList(oldSL, newSL) { 257 found = true 258 break 259 } 260 } 261 if !found { 262 return false 263 } 264 } 265 return true 266 } 267 268 func (d *PolicyDescriptor) equivalentSegmentList(oldSL, newSL *srv6.Policy_SegmentList) bool { 269 return oldSL.Weight == newSL.Weight && d.equivalentSegments(oldSL.Segments, newSL.Segments) 270 } 271 272 func (d *PolicyDescriptor) equivalentSegments(segments1, segments2 []string) bool { 273 if segments1 == nil || segments2 == nil { 274 return segments1 == nil && segments2 == nil 275 } 276 if len(segments1) != len(segments2) { 277 return false 278 } 279 for i := range segments1 { 280 if !equivalentSIDs(segments1[i], segments2[i]) { 281 return false 282 } 283 } 284 return true 285 }