github.com/google/martian/v3@v3.3.3/trafficshape/utils.go (about) 1 package trafficshape 2 3 import ( 4 "errors" 5 "fmt" 6 "regexp" 7 "sort" 8 "strconv" 9 "strings" 10 "time" 11 ) 12 13 // Converts a sorted slice of Throttles to their ChangeBandwidth actions. In adddition, checks for 14 // overlapping throttle ranges. Returns a slice of actions and an error specifying if the throttles 15 // passed the non-overlapping verification. 16 // 17 // Idea: For every throttle, add two ChangeBandwidth actions (one for start and one for end), unless 18 // the ending byte of one throttle is the same as the starting byte of the next throttle, in which 19 // case we do not add the end ChangeBandwidth for the first throttle, or if the end of a throttle 20 // is -1 (representing EOF), in which case we do not add the end ChangeBandwidth action for the throttle. 21 // 22 // Note, we only allow the last throttle in the sorted list to have an end of -1 in order to avoid an overlap. 23 func getActionsFromThrottles(throttles []*Throttle, defaultBandwidth int64) ([]Action, error) { 24 25 lenThr := len(throttles) 26 var actions []Action 27 for index, throttle := range throttles { 28 start := throttle.ByteStart 29 end := throttle.ByteEnd 30 31 if index == lenThr-1 { 32 if end == -1 { 33 actions = append(actions, 34 Action(&ChangeBandwidth{ 35 Byte: start, 36 Bandwidth: throttle.Bandwidth, 37 })) 38 } else { 39 actions = append(actions, 40 Action(&ChangeBandwidth{ 41 Byte: start, 42 Bandwidth: throttle.Bandwidth, 43 }), 44 Action(&ChangeBandwidth{ 45 Byte: end, 46 Bandwidth: defaultBandwidth, 47 })) 48 } 49 break 50 } 51 52 if end > throttles[index+1].ByteStart || end == -1 { 53 return actions, errors.New("overlapping throttle intervals found") 54 } 55 56 if end == throttles[index+1].ByteStart { 57 actions = append(actions, 58 Action(&ChangeBandwidth{ 59 Byte: start, 60 Bandwidth: throttle.Bandwidth, 61 })) 62 } else { 63 actions = append(actions, 64 Action(&ChangeBandwidth{ 65 Byte: start, 66 Bandwidth: throttle.Bandwidth, 67 }), 68 Action(&ChangeBandwidth{ 69 Byte: end, 70 Bandwidth: defaultBandwidth, 71 })) 72 } 73 } 74 return actions, nil 75 } 76 77 // Parses a Trafficshape and updates Traffficshape.Shapes while performing verifications. 78 // 79 // Returns an error in case a verification check fails. 80 func parseShapes(ts *Trafficshape) error { 81 var err error 82 for shapeIndex, shape := range ts.Shapes { 83 if shape == nil { 84 return fmt.Errorf("nil shape at index: %d", shapeIndex) 85 } 86 if shape.URLRegex == "" { 87 return fmt.Errorf("no url_regex for shape at index: %d", shapeIndex) 88 } 89 90 if _, err = regexp.Compile(shape.URLRegex); err != nil { 91 return fmt.Errorf("url_regex for shape at index doesn't compile: %d", shapeIndex) 92 } 93 94 if shape.MaxBandwidth < 0 { 95 return fmt.Errorf("max_bandwidth cannot be negative for shape at index: %d", shapeIndex) 96 } 97 98 if shape.MaxBandwidth == 0 { 99 shape.MaxBandwidth = DefaultBitrate / 8 100 } 101 102 shape.WriteBucket = NewBucket(shape.MaxBandwidth, time.Second) 103 104 // Verify and process the throttles, filling in their ByteStart and ByteEnd. 105 for throttleIndex, throttle := range shape.Throttles { 106 if throttle == nil { 107 return fmt.Errorf("nil throttle at index %d in shape index %d", throttleIndex, shapeIndex) 108 } 109 110 if throttle.Bandwidth <= 0 { 111 return fmt.Errorf("invalid bandwidth: %d at throttle index %d in shape index %d", 112 throttle.Bandwidth, throttleIndex, shapeIndex) 113 } 114 sl := strings.Split(throttle.Bytes, "-") 115 116 if len(sl) != 2 { 117 return fmt.Errorf("invalid bytes: %s at throttle index %d in shape index %d", 118 throttle.Bytes, throttleIndex, shapeIndex) 119 } 120 121 start := sl[0] 122 end := sl[1] 123 124 if start == "" { 125 throttle.ByteStart = 0 126 } else { 127 throttle.ByteStart, err = strconv.ParseInt(start, 10, 64) 128 if err != nil { 129 return fmt.Errorf("invalid bytes: %s at throttle index %d in shape index %d", 130 throttle.Bytes, throttleIndex, shapeIndex) 131 } 132 } 133 134 if end == "" { 135 throttle.ByteEnd = -1 136 } else { 137 throttle.ByteEnd, err = strconv.ParseInt(end, 10, 64) 138 if err != nil { 139 return fmt.Errorf("invalid bytes: %s at throttle index %d in shape index %d", 140 throttle.Bytes, throttleIndex, shapeIndex) 141 } 142 if throttle.ByteEnd < throttle.ByteStart { 143 return fmt.Errorf("invalid bytes: %s at throttle index %d in shape index %d", 144 throttle.Bytes, throttleIndex, shapeIndex) 145 } 146 } 147 148 if throttle.ByteStart == throttle.ByteEnd { 149 return fmt.Errorf("invalid bytes: %s at throttle index %d in shape index %d", 150 throttle.Bytes, throttleIndex, shapeIndex) 151 } 152 } 153 // Fill in the actions, while performing verification. 154 shape.Actions = make([]Action, len(shape.Halts)+len(shape.CloseConnections)) 155 156 for index, value := range shape.Halts { 157 if value == nil { 158 return fmt.Errorf("nil halt at index %d in shape index %d", index, shapeIndex) 159 } 160 if value.Duration < 0 || value.Byte < 0 { 161 return fmt.Errorf("invalid halt at index %d in shape index %d", index, shapeIndex) 162 } 163 if value.Count == 0 { 164 return fmt.Errorf(" 0 count for halt at index %d in shape index %d", index, shapeIndex) 165 } 166 shape.Actions[index] = Action(value) 167 } 168 offset := len(shape.Halts) 169 for index, value := range shape.CloseConnections { 170 if value == nil { 171 return fmt.Errorf("nil close_connection at index %d in shape index %d", 172 index, shapeIndex) 173 } 174 if value.Byte < 0 { 175 return fmt.Errorf("invalid close_connection at index %d in shape index %d", 176 index, shapeIndex) 177 } 178 if value.Count == 0 { 179 return fmt.Errorf("0 count for close_connection at index %d in shape index %d", 180 index, shapeIndex) 181 } 182 shape.Actions[offset+index] = Action(value) 183 } 184 185 sort.SliceStable(shape.Throttles, func(i, j int) bool { 186 return shape.Throttles[i].ByteStart < shape.Throttles[j].ByteStart 187 }) 188 189 defaultBandwidth := DefaultBitrate / 8 190 if shape.MaxBandwidth > 0 { 191 defaultBandwidth = shape.MaxBandwidth 192 } 193 194 throttleActions, err := getActionsFromThrottles(shape.Throttles, defaultBandwidth) 195 if err != nil { 196 return fmt.Errorf("err: %s in shape index %d", err.Error(), shapeIndex) 197 } 198 shape.Actions = append(shape.Actions, throttleActions...) 199 200 // Sort the actions according to their byte offset. 201 sort.SliceStable(shape.Actions, func(i, j int) bool { return shape.Actions[i].getByte() < shape.Actions[j].getByte() }) 202 } 203 return nil 204 }