github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/machine_ports_ops_test.go (about) 1 // Copyright 2020 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "github.com/juju/collections/set" 8 "github.com/juju/testing" 9 jc "github.com/juju/testing/checkers" 10 gc "gopkg.in/check.v1" 11 12 "github.com/juju/juju/core/network" 13 ) 14 15 type MachinePortsOpsSuite struct { 16 testing.IsolationSuite 17 } 18 19 var _ = gc.Suite(&MachinePortsOpsSuite{}) 20 21 func (MachinePortsOpsSuite) TestPruneOpenPorts(c *gc.C) { 22 op := &openClosePortRangesOperation{ 23 updatedUnitPortRanges: map[string]network.GroupedPortRanges{ 24 "enigma/0": { 25 "": []network.PortRange{ 26 network.MustParsePortRange("1234-1337/tcp"), 27 network.MustParsePortRange("8080/tcp"), 28 network.MustParsePortRange("17017/tcp"), 29 }, 30 // The following ranges are also present in the wildcard list. 31 // They are therefore redundant and should be pruned. 32 "dmz": []network.PortRange{ 33 network.MustParsePortRange("1234-1337/tcp"), 34 }, 35 "public": []network.PortRange{ 36 network.MustParsePortRange("8080/tcp"), 37 }, 38 }, 39 }, 40 } 41 42 modified := op.pruneOpenPorts() 43 c.Assert(modified, jc.IsTrue, gc.Commentf("expected pruneOpenPorts to modify the port list")) 44 45 exp := map[string]network.GroupedPortRanges{ 46 "enigma/0": { 47 "": []network.PortRange{ 48 network.MustParsePortRange("1234-1337/tcp"), 49 network.MustParsePortRange("8080/tcp"), 50 network.MustParsePortRange("17017/tcp"), 51 }, 52 // Pruned endpoint lists should remain empty 53 "dmz": []network.PortRange{}, 54 "public": []network.PortRange{}, 55 }, 56 } 57 c.Assert(op.updatedUnitPortRanges, gc.DeepEquals, exp, gc.Commentf("expected pruneOpenPorts to remove redundant sections for the dmz, public endpoints")) 58 } 59 60 func (MachinePortsOpsSuite) TestPruneEmptySections(c *gc.C) { 61 op := &openClosePortRangesOperation{ 62 updatedUnitPortRanges: map[string]network.GroupedPortRanges{ 63 "enigma/0": { 64 "": []network.PortRange{ 65 network.MustParsePortRange("1234-1337/tcp"), 66 network.MustParsePortRange("8080/tcp"), 67 network.MustParsePortRange("17017/tcp"), 68 }, 69 "dmz": []network.PortRange{}, 70 "public": []network.PortRange{}, 71 }, 72 // Since all sections are empty, the prune code is expected 73 // to remove the entire map entry for enigma/1 74 "enigma/1": { 75 "": []network.PortRange{}, 76 "coffee": []network.PortRange{}, 77 "private": []network.PortRange{}, 78 }, 79 }, 80 } 81 82 modified := op.pruneEmptySections() 83 c.Assert(modified, jc.IsTrue, gc.Commentf("expected pruneEmptySections to modify the port list")) 84 85 exp := map[string]network.GroupedPortRanges{ 86 "enigma/0": { 87 "": []network.PortRange{ 88 network.MustParsePortRange("1234-1337/tcp"), 89 network.MustParsePortRange("8080/tcp"), 90 network.MustParsePortRange("17017/tcp"), 91 }, 92 }, 93 } 94 c.Assert(op.updatedUnitPortRanges, gc.DeepEquals, exp, gc.Commentf("expected prineEmptySections to remove all empty sections and unit docs")) 95 } 96 97 func (MachinePortsOpsSuite) TestMergePendingOpenPortRangesConflict(c *gc.C) { 98 op := &openClosePortRangesOperation{ 99 mpr: &machinePortRanges{ 100 doc: machinePortRangesDoc{ 101 UnitRanges: map[string]network.GroupedPortRanges{ 102 "enigma/0": { 103 "": []network.PortRange{ 104 network.MustParsePortRange("1234-1337/tcp"), 105 network.MustParsePortRange("8080/tcp"), 106 network.MustParsePortRange("17017/tcp"), 107 }, 108 }, 109 }, 110 }, 111 pendingOpenRanges: map[string]network.GroupedPortRanges{ 112 "enigma/1": { 113 "tea": []network.PortRange{ 114 network.MustParsePortRange("1242/tcp"), 115 }, 116 }, 117 }, 118 }, 119 } 120 121 op.cloneExistingUnitPortRanges() 122 op.buildPortRangeToUnitMap() 123 124 _, err := op.mergePendingOpenPortRanges() 125 c.Assert(err, gc.ErrorMatches, `.*port ranges 1234-1337/tcp \("enigma/0"\) and 1242/tcp \("enigma/1"\) conflict`) 126 } 127 128 func (MachinePortsOpsSuite) TestMergePendingOpenPortRangeDupHandling(c *gc.C) { 129 specs := []struct { 130 descr string 131 existing map[string]network.GroupedPortRanges 132 pendingOpen map[string]network.GroupedPortRanges 133 exp map[string]network.GroupedPortRanges 134 expModified bool 135 }{ 136 { 137 descr: "port range already opened by the unit for all endpoints", 138 existing: map[string]network.GroupedPortRanges{ 139 "enigma/0": { 140 "": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 141 }, 142 }, 143 pendingOpen: map[string]network.GroupedPortRanges{ 144 "enigma/0": { 145 "sky": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 146 }, 147 }, 148 exp: map[string]network.GroupedPortRanges{ 149 "enigma/0": { 150 "": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 151 }, 152 }, 153 expModified: false, 154 }, 155 { 156 descr: "port range already opened by the unit for same endpoint", 157 existing: map[string]network.GroupedPortRanges{ 158 "enigma/0": { 159 "sky": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 160 }, 161 }, 162 pendingOpen: map[string]network.GroupedPortRanges{ 163 "enigma/0": { 164 "sky": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 165 }, 166 }, 167 exp: map[string]network.GroupedPortRanges{ 168 "enigma/0": { 169 "sky": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 170 }, 171 }, 172 expModified: false, 173 }, 174 { 175 descr: "port range already opened by the unit for other endpoint", 176 existing: map[string]network.GroupedPortRanges{ 177 "enigma/0": { 178 "sky": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 179 }, 180 }, 181 pendingOpen: map[string]network.GroupedPortRanges{ 182 "enigma/0": { 183 "sea": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 184 }, 185 }, 186 exp: map[string]network.GroupedPortRanges{ 187 "enigma/0": { 188 "sky": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 189 "sea": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 190 }, 191 }, 192 expModified: true, 193 }, 194 } 195 196 for i, spec := range specs { 197 c.Logf("%d: %s", i, spec.descr) 198 199 op := &openClosePortRangesOperation{ 200 mpr: &machinePortRanges{ 201 doc: machinePortRangesDoc{UnitRanges: spec.existing}, 202 pendingOpenRanges: spec.pendingOpen, 203 }, 204 } 205 206 op.cloneExistingUnitPortRanges() 207 op.buildPortRangeToUnitMap() 208 209 modified, err := op.mergePendingOpenPortRanges() 210 c.Assert(err, jc.ErrorIsNil) 211 c.Assert(modified, gc.Equals, spec.expModified) 212 c.Assert(op.updatedUnitPortRanges, gc.DeepEquals, spec.exp) 213 } 214 } 215 216 func (MachinePortsOpsSuite) TestMergePendingClosePortRanges(c *gc.C) { 217 specs := []struct { 218 descr string 219 endpointNamesByApp map[string]set.Strings 220 existing map[string]network.GroupedPortRanges 221 pendingClose map[string]network.GroupedPortRanges 222 exp map[string]network.GroupedPortRanges 223 expModified bool 224 }{ 225 { 226 descr: "port range opened by the unit for same endpoint", 227 existing: map[string]network.GroupedPortRanges{ 228 "enigma/0": { 229 "lava": []network.PortRange{ 230 network.MustParsePortRange("8080/tcp"), 231 network.MustParsePortRange("9999/tcp"), 232 }, 233 }, 234 }, 235 pendingClose: map[string]network.GroupedPortRanges{ 236 "enigma/0": { 237 "lava": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 238 }, 239 }, 240 exp: map[string]network.GroupedPortRanges{ 241 "enigma/0": { 242 "lava": []network.PortRange{network.MustParsePortRange("9999/tcp")}, 243 }, 244 }, 245 expModified: true, 246 }, 247 { 248 descr: "port range opened by the unit for another endpoint", 249 existing: map[string]network.GroupedPortRanges{ 250 "enigma/0": { 251 "lava": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 252 }, 253 }, 254 pendingClose: map[string]network.GroupedPortRanges{ 255 "enigma/0": { 256 "volcano": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 257 }, 258 }, 259 exp: map[string]network.GroupedPortRanges{ 260 "enigma/0": { 261 // Close request is a no-op 262 "lava": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 263 }, 264 }, 265 expModified: false, 266 }, 267 { 268 descr: "port range opened by the unit for all endpoints and closed for specific endpoint", 269 endpointNamesByApp: map[string]set.Strings{ 270 "enigma": set.NewStrings("volcano", "lava", "sea"), 271 }, 272 existing: map[string]network.GroupedPortRanges{ 273 "enigma/0": { 274 "": []network.PortRange{ 275 network.MustParsePortRange("7337/tcp"), 276 network.MustParsePortRange("8080/tcp"), 277 }, 278 }, 279 }, 280 pendingClose: map[string]network.GroupedPortRanges{ 281 "enigma/0": { 282 "lava": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 283 }, 284 }, 285 exp: map[string]network.GroupedPortRanges{ 286 "enigma/0": { 287 // range removed from wildcard and replaced with 288 // entries for the all _other_ known endpoints 289 "": []network.PortRange{network.MustParsePortRange("7337/tcp")}, 290 "volcano": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 291 "sea": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 292 }, 293 }, 294 expModified: true, 295 }, 296 } 297 298 for i, spec := range specs { 299 c.Logf("%d: %s", i, spec.descr) 300 301 op := &openClosePortRangesOperation{ 302 mpr: &machinePortRanges{ 303 doc: machinePortRangesDoc{UnitRanges: spec.existing}, 304 pendingCloseRanges: spec.pendingClose, 305 }, 306 endpointsNamesByApp: spec.endpointNamesByApp, 307 } 308 309 op.cloneExistingUnitPortRanges() 310 op.buildPortRangeToUnitMap() 311 312 modified, err := op.mergePendingClosePortRanges() 313 c.Assert(err, jc.ErrorIsNil) 314 c.Assert(modified, gc.Equals, spec.expModified) 315 c.Assert(op.updatedUnitPortRanges, gc.DeepEquals, spec.exp) 316 } 317 } 318 319 func (MachinePortsOpsSuite) TestMergePendingClosePortRangesConflict(c *gc.C) { 320 op := &openClosePortRangesOperation{ 321 mpr: &machinePortRanges{ 322 doc: machinePortRangesDoc{ 323 UnitRanges: map[string]network.GroupedPortRanges{ 324 "enigma/0": { 325 "": []network.PortRange{ 326 network.MustParsePortRange("1234-1337/tcp"), 327 network.MustParsePortRange("8080/tcp"), 328 network.MustParsePortRange("17017/tcp"), 329 }, 330 }, 331 }, 332 }, 333 pendingCloseRanges: map[string]network.GroupedPortRanges{ 334 "codebreaker/0": { 335 "tea": []network.PortRange{ 336 network.MustParsePortRange("1242/tcp"), 337 }, 338 }, 339 }, 340 }, 341 } 342 343 op.cloneExistingUnitPortRanges() 344 op.buildPortRangeToUnitMap() 345 346 _, err := op.mergePendingClosePortRanges() 347 c.Assert(err, gc.ErrorMatches, `.*port ranges 1234-1337/tcp \("enigma/0"\) and 1242/tcp \("codebreaker/0"\) conflict`) 348 } 349 350 func (MachinePortsOpsSuite) TestValidatePendingChanges(c *gc.C) { 351 specs := []struct { 352 descr string 353 endpointNamesByApp map[string]set.Strings 354 pendingOpen map[string]network.GroupedPortRanges 355 pendingClose map[string]network.GroupedPortRanges 356 expErr string 357 }{ 358 { 359 descr: "unknown endpoint in open request", 360 endpointNamesByApp: map[string]set.Strings{ 361 "foo": set.NewStrings("dmz"), 362 }, 363 pendingOpen: map[string]network.GroupedPortRanges{ 364 "foo/0": { 365 "dmz": []network.PortRange{network.MustParsePortRange("1337/tcp")}, 366 "unknown": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 367 }, 368 }, 369 expErr: `open port range: endpoint "unknown" for unit "foo/0" not found`, 370 }, 371 { 372 descr: "unknown endpoint in close request", 373 endpointNamesByApp: map[string]set.Strings{ 374 "foo": set.NewStrings("dmz"), 375 }, 376 pendingOpen: map[string]network.GroupedPortRanges{ 377 "foo/0": { 378 "dmz": []network.PortRange{network.MustParsePortRange("1337/tcp")}, 379 }, 380 }, 381 pendingClose: map[string]network.GroupedPortRanges{ 382 "foo/0": { 383 "dmz": []network.PortRange{network.MustParsePortRange("1337/tcp")}, 384 "unknown": []network.PortRange{network.MustParsePortRange("8080/tcp")}, 385 }, 386 }, 387 expErr: `close port range: endpoint "unknown" for unit "foo/0" not found`, 388 }, 389 { 390 descr: "valid endpoints in open/close requests", 391 endpointNamesByApp: map[string]set.Strings{ 392 "foo": set.NewStrings("dmz"), 393 }, 394 pendingOpen: map[string]network.GroupedPortRanges{ 395 "foo/0": { 396 "dmz": []network.PortRange{network.MustParsePortRange("1337/tcp")}, 397 }, 398 }, 399 pendingClose: map[string]network.GroupedPortRanges{ 400 "foo/0": { 401 "dmz": []network.PortRange{network.MustParsePortRange("1337/tcp")}, 402 }, 403 }, 404 }, 405 } 406 407 for i, spec := range specs { 408 c.Logf("%d: %s", i, spec.descr) 409 op := &openClosePortRangesOperation{ 410 mpr: &machinePortRanges{ 411 pendingOpenRanges: spec.pendingOpen, 412 pendingCloseRanges: spec.pendingClose, 413 }, 414 endpointsNamesByApp: spec.endpointNamesByApp, 415 } 416 417 err := op.validatePendingChanges() 418 if spec.expErr == "" { 419 c.Assert(err, jc.ErrorIsNil) 420 } else { 421 c.Assert(err, gc.ErrorMatches, spec.expErr) 422 } 423 } 424 }