github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/gce/google/conn_network_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package google_test 5 6 import ( 7 "regexp" 8 "sort" 9 10 "github.com/juju/collections/set" 11 "github.com/juju/errors" 12 jc "github.com/juju/testing/checkers" 13 "google.golang.org/api/compute/v1" 14 gc "gopkg.in/check.v1" 15 16 "github.com/juju/juju/core/network" 17 corefirewall "github.com/juju/juju/core/network/firewall" 18 "github.com/juju/juju/provider/gce/google" 19 ) 20 21 func (s *connSuite) TestConnectionIngressRules(c *gc.C) { 22 s.FakeConn.Firewalls = []*compute.Firewall{{ 23 Name: "spam", 24 TargetTags: []string{"spam"}, 25 SourceRanges: []string{"10.0.0.0/24", "192.168.1.0/24"}, 26 Allowed: []*compute.FirewallAllowed{ 27 { 28 IPProtocol: "tcp", 29 Ports: []string{"80-81", "92"}, 30 }, { 31 IPProtocol: "udp", 32 Ports: []string{"443", "100-120"}, 33 }, 34 }, 35 }} 36 37 ports, err := s.Conn.IngressRules("spam") 38 c.Assert(err, jc.ErrorIsNil) 39 c.Check( 40 ports, jc.DeepEquals, 41 corefirewall.IngressRules{ 42 corefirewall.NewIngressRule(network.MustParsePortRange("80-81/tcp"), "10.0.0.0/24", "192.168.1.0/24"), 43 corefirewall.NewIngressRule(network.MustParsePortRange("92/tcp"), "10.0.0.0/24", "192.168.1.0/24"), 44 corefirewall.NewIngressRule(network.MustParsePortRange("100-120/udp"), "10.0.0.0/24", "192.168.1.0/24"), 45 corefirewall.NewIngressRule(network.MustParsePortRange("443/udp"), "10.0.0.0/24", "192.168.1.0/24"), 46 }, 47 ) 48 } 49 50 func (s *connSuite) TestConnectionIngressRulesCollapse(c *gc.C) { 51 s.FakeConn.Firewalls = []*compute.Firewall{{ 52 Name: "spam", 53 TargetTags: []string{"spam"}, 54 SourceRanges: []string{"10.0.0.0/24", "192.168.1.0/24"}, 55 Allowed: []*compute.FirewallAllowed{{ 56 IPProtocol: "tcp", 57 Ports: []string{"81"}, 58 }, { 59 IPProtocol: "tcp", 60 Ports: []string{"82"}, 61 }, { 62 IPProtocol: "tcp", 63 Ports: []string{"80"}, 64 }, { 65 IPProtocol: "tcp", 66 Ports: []string{"83"}, 67 }, { 68 IPProtocol: "tcp", 69 Ports: []string{"92"}, 70 }}, 71 }} 72 73 ports, err := s.Conn.IngressRules("spam") 74 c.Assert(err, jc.ErrorIsNil) 75 c.Check( 76 ports, jc.DeepEquals, 77 corefirewall.IngressRules{ 78 corefirewall.NewIngressRule(network.MustParsePortRange("80-83/tcp"), "10.0.0.0/24", "192.168.1.0/24"), 79 corefirewall.NewIngressRule(network.MustParsePortRange("92/tcp"), "10.0.0.0/24", "192.168.1.0/24"), 80 }, 81 ) 82 } 83 84 func (s *connSuite) TestConnectionIngressRulesDefaultCIDR(c *gc.C) { 85 s.FakeConn.Firewalls = []*compute.Firewall{{ 86 Name: "spam", 87 TargetTags: []string{"spam"}, 88 Allowed: []*compute.FirewallAllowed{{ 89 IPProtocol: "tcp", 90 Ports: []string{"80-81", "92"}, 91 }}, 92 }} 93 94 ports, err := s.Conn.IngressRules("spam") 95 c.Assert(err, jc.ErrorIsNil) 96 c.Check( 97 ports, jc.DeepEquals, 98 corefirewall.IngressRules{ 99 corefirewall.NewIngressRule(network.MustParsePortRange("80-81/tcp"), corefirewall.AllNetworksIPV4CIDR), 100 corefirewall.NewIngressRule(network.MustParsePortRange("92/tcp"), corefirewall.AllNetworksIPV4CIDR), 101 }, 102 ) 103 } 104 105 func (s *connSuite) TestConnectionPortsAPI(c *gc.C) { 106 s.FakeConn.Firewalls = []*compute.Firewall{{ 107 Name: "spam", 108 TargetTags: []string{"spam"}, 109 SourceRanges: []string{"0.0.0.0/0"}, 110 Allowed: []*compute.FirewallAllowed{{ 111 IPProtocol: "tcp", 112 Ports: []string{"80-81"}, 113 }}, 114 }} 115 116 _, err := s.Conn.IngressRules("eggs") 117 c.Assert(err, jc.ErrorIsNil) 118 119 c.Check(s.FakeConn.Calls, gc.HasLen, 1) 120 c.Check(s.FakeConn.Calls[0].FuncName, gc.Equals, "GetFirewalls") 121 c.Check(s.FakeConn.Calls[0].ProjectID, gc.Equals, "spam") 122 c.Check(s.FakeConn.Calls[0].Name, gc.Equals, "eggs") 123 } 124 125 func (s *connSuite) TestConnectionOpenPortsAdd(c *gc.C) { 126 s.FakeConn.Err = errors.NotFoundf("spam") 127 128 rules := corefirewall.IngressRules{ 129 corefirewall.NewIngressRule(network.MustParsePortRange("80-81/tcp")), // leave out CIDR to check default 130 corefirewall.NewIngressRule(network.MustParsePortRange("80-81/udp"), corefirewall.AllNetworksIPV4CIDR), 131 corefirewall.NewIngressRule(network.MustParsePortRange("100-120/tcp"), "192.168.1.0/24", "10.0.0.0/24"), 132 corefirewall.NewIngressRule(network.MustParsePortRange("67/udp"), "10.0.0.0/24"), 133 } 134 err := s.Conn.OpenPortsWithNamer("spam", google.HashSuffixNamer, rules) 135 c.Assert(err, jc.ErrorIsNil) 136 137 c.Check(s.FakeConn.Calls, gc.HasLen, 4) 138 c.Check(s.FakeConn.Calls[0].FuncName, gc.Equals, "GetFirewalls") 139 c.Check(s.FakeConn.Calls[1].FuncName, gc.Equals, "AddFirewall") 140 c.Check(s.FakeConn.Calls[1].Firewall, jc.DeepEquals, &compute.Firewall{ 141 Name: "spam-4eebe8d7a9", 142 TargetTags: []string{"spam"}, 143 SourceRanges: []string{"10.0.0.0/24", "192.168.1.0/24"}, 144 Allowed: []*compute.FirewallAllowed{{ 145 IPProtocol: "tcp", 146 Ports: []string{"100-120"}, 147 }}, 148 }) 149 c.Check(s.FakeConn.Calls[2].FuncName, gc.Equals, "AddFirewall") 150 c.Check(s.FakeConn.Calls[2].Firewall, jc.DeepEquals, &compute.Firewall{ 151 Name: "spam-a34d80f7b6", 152 TargetTags: []string{"spam"}, 153 SourceRanges: []string{"10.0.0.0/24"}, 154 Allowed: []*compute.FirewallAllowed{{ 155 IPProtocol: "udp", 156 Ports: []string{"67"}, 157 }}, 158 }) 159 c.Check(s.FakeConn.Calls[3].FuncName, gc.Equals, "AddFirewall") 160 c.Check(s.FakeConn.Calls[3].Firewall, jc.DeepEquals, &compute.Firewall{ 161 Name: "spam", 162 TargetTags: []string{"spam"}, 163 SourceRanges: []string{"0.0.0.0/0"}, 164 Allowed: []*compute.FirewallAllowed{{ 165 IPProtocol: "tcp", 166 Ports: []string{"80-81"}, 167 }, { 168 IPProtocol: "udp", 169 Ports: []string{"80-81"}, 170 }}, 171 }) 172 } 173 174 func (s *connSuite) TestConnectionOpenPortsUpdateSameCIDR(c *gc.C) { 175 s.FakeConn.Firewalls = []*compute.Firewall{{ 176 Name: "spam-ad7554", 177 TargetTags: []string{"spam"}, 178 SourceRanges: []string{"192.168.1.0/24", "10.0.0.0/24"}, 179 Allowed: []*compute.FirewallAllowed{{ 180 IPProtocol: "tcp", 181 Ports: []string{"80-81"}, 182 }}, 183 }} 184 185 rules := corefirewall.IngressRules{ 186 corefirewall.NewIngressRule(network.MustParsePortRange("443/tcp"), "192.168.1.0/24", "10.0.0.0/24"), 187 } 188 err := s.Conn.OpenPortsWithNamer("spam", google.HashSuffixNamer, rules) 189 c.Assert(err, jc.ErrorIsNil) 190 191 c.Check(s.FakeConn.Calls, gc.HasLen, 2) 192 c.Check(s.FakeConn.Calls[0].FuncName, gc.Equals, "GetFirewalls") 193 c.Check(s.FakeConn.Calls[1].FuncName, gc.Equals, "UpdateFirewall") 194 sort.Strings(s.FakeConn.Calls[1].Firewall.Allowed[0].Ports) 195 c.Check(s.FakeConn.Calls[1].Firewall, jc.DeepEquals, &compute.Firewall{ 196 Name: "spam-ad7554", 197 TargetTags: []string{"spam"}, 198 SourceRanges: []string{"10.0.0.0/24", "192.168.1.0/24"}, 199 Allowed: []*compute.FirewallAllowed{{ 200 IPProtocol: "tcp", 201 Ports: []string{"443", "80-81"}, 202 }}, 203 }) 204 } 205 206 func (s *connSuite) TestConnectionOpenPortsUpdateAddCIDR(c *gc.C) { 207 s.FakeConn.Firewalls = []*compute.Firewall{{ 208 Name: "spam-arbitrary-name", 209 TargetTags: []string{"spam"}, 210 SourceRanges: []string{"192.168.1.0/24"}, 211 Allowed: []*compute.FirewallAllowed{{ 212 IPProtocol: "tcp", 213 Ports: []string{"80-81"}, 214 }}, 215 }} 216 217 rules := corefirewall.IngressRules{ 218 corefirewall.NewIngressRule(network.MustParsePortRange("80-81/tcp"), "10.0.0.0/24"), 219 } 220 err := s.Conn.OpenPortsWithNamer("spam", google.HashSuffixNamer, rules) 221 c.Assert(err, jc.ErrorIsNil) 222 223 c.Check(s.FakeConn.Calls, gc.HasLen, 2) 224 c.Check(s.FakeConn.Calls[0].FuncName, gc.Equals, "GetFirewalls") 225 c.Check(s.FakeConn.Calls[1].FuncName, gc.Equals, "UpdateFirewall") 226 sort.Strings(s.FakeConn.Calls[1].Firewall.Allowed[0].Ports) 227 c.Check(s.FakeConn.Calls[1].Name, gc.Equals, "spam-arbitrary-name") 228 c.Check(s.FakeConn.Calls[1].Firewall, jc.DeepEquals, &compute.Firewall{ 229 Name: "spam-arbitrary-name", 230 TargetTags: []string{"spam"}, 231 SourceRanges: []string{"10.0.0.0/24", "192.168.1.0/24"}, 232 Allowed: []*compute.FirewallAllowed{{ 233 IPProtocol: "tcp", 234 Ports: []string{"80-81"}, 235 }}, 236 }) 237 } 238 239 func (s *connSuite) TestConnectionOpenPortsUpdateAndAdd(c *gc.C) { 240 s.FakeConn.Firewalls = []*compute.Firewall{{ 241 Name: "spam-d01a82", 242 TargetTags: []string{"spam"}, 243 SourceRanges: []string{"192.168.1.0/24"}, 244 Allowed: []*compute.FirewallAllowed{{ 245 IPProtocol: "tcp", 246 Ports: []string{"80-81"}, 247 }}, 248 }, { 249 Name: "spam-8e65efabcd", 250 TargetTags: []string{"spam"}, 251 SourceRanges: []string{"172.0.0.0/24"}, 252 Allowed: []*compute.FirewallAllowed{{ 253 IPProtocol: "tcp", 254 Ports: []string{"100-120", "443"}, 255 }}, 256 }} 257 258 rules := corefirewall.IngressRules{ 259 corefirewall.NewIngressRule(network.MustParsePortRange("443/tcp"), "192.168.1.0/24"), 260 corefirewall.NewIngressRule(network.MustParsePortRange("80-100/tcp"), "10.0.0.0/24"), 261 corefirewall.NewIngressRule(network.MustParsePortRange("443/tcp"), "10.0.0.0/24"), 262 corefirewall.NewIngressRule(network.MustParsePortRange("67/udp"), "172.0.0.0/24"), 263 } 264 err := s.Conn.OpenPortsWithNamer("spam", google.HashSuffixNamer, rules) 265 c.Assert(err, jc.ErrorIsNil) 266 267 c.Check(s.FakeConn.Calls, gc.HasLen, 4) 268 c.Check(s.FakeConn.Calls[0].FuncName, gc.Equals, "GetFirewalls") 269 c.Check(s.FakeConn.Calls[1].FuncName, gc.Equals, "UpdateFirewall") 270 sort.Strings(s.FakeConn.Calls[1].Firewall.Allowed[0].Ports) 271 c.Check(s.FakeConn.Calls[1].Name, gc.Equals, "spam-8e65efabcd") 272 c.Check(s.FakeConn.Calls[1].Firewall, jc.DeepEquals, &compute.Firewall{ 273 Name: "spam-8e65efabcd", 274 TargetTags: []string{"spam"}, 275 SourceRanges: []string{"172.0.0.0/24"}, 276 Allowed: []*compute.FirewallAllowed{{ 277 IPProtocol: "tcp", 278 Ports: []string{"100-120", "443"}, 279 }, { 280 IPProtocol: "udp", 281 Ports: []string{"67"}, 282 }}, 283 }) 284 c.Check(s.FakeConn.Calls[2].FuncName, gc.Equals, "AddFirewall") 285 sort.Strings(s.FakeConn.Calls[2].Firewall.Allowed[0].Ports) 286 c.Check(s.FakeConn.Calls[2].Firewall, jc.DeepEquals, &compute.Firewall{ 287 Name: "spam-a34d80f7b6", 288 TargetTags: []string{"spam"}, 289 SourceRanges: []string{"10.0.0.0/24"}, 290 Allowed: []*compute.FirewallAllowed{{ 291 IPProtocol: "tcp", 292 Ports: []string{"443", "80-100"}, 293 }}, 294 }) 295 c.Check(s.FakeConn.Calls[3].FuncName, gc.Equals, "UpdateFirewall") 296 sort.Strings(s.FakeConn.Calls[3].Firewall.Allowed[0].Ports) 297 c.Check(s.FakeConn.Calls[3].Name, gc.Equals, "spam-d01a82") 298 c.Check(s.FakeConn.Calls[3].Firewall, jc.DeepEquals, &compute.Firewall{ 299 Name: "spam-d01a82", 300 TargetTags: []string{"spam"}, 301 SourceRanges: []string{"192.168.1.0/24"}, 302 Allowed: []*compute.FirewallAllowed{{ 303 IPProtocol: "tcp", 304 Ports: []string{"443", "80-81"}, 305 }}, 306 }) 307 } 308 309 func (s *connSuite) TestConnectionClosePortsRemove(c *gc.C) { 310 s.FakeConn.Firewalls = []*compute.Firewall{{ 311 Name: "spam", 312 TargetTags: []string{"spam"}, 313 SourceRanges: []string{"0.0.0.0/0"}, 314 Allowed: []*compute.FirewallAllowed{{ 315 IPProtocol: "tcp", 316 Ports: []string{"443"}, 317 }}, 318 }} 319 320 rules := corefirewall.IngressRules{ 321 corefirewall.NewIngressRule(network.MustParsePortRange("443/tcp")), 322 } 323 err := s.Conn.ClosePorts("spam", rules) 324 c.Assert(err, jc.ErrorIsNil) 325 326 c.Check(s.FakeConn.Calls, gc.HasLen, 2) 327 c.Check(s.FakeConn.Calls[0].FuncName, gc.Equals, "GetFirewalls") 328 c.Check(s.FakeConn.Calls[1].FuncName, gc.Equals, "RemoveFirewall") 329 c.Check(s.FakeConn.Calls[1].Name, gc.Equals, "spam") 330 } 331 332 func (s *connSuite) TestConnectionClosePortsUpdate(c *gc.C) { 333 s.FakeConn.Firewalls = []*compute.Firewall{{ 334 Name: "spam", 335 TargetTags: []string{"spam"}, 336 SourceRanges: []string{"0.0.0.0/0"}, 337 Allowed: []*compute.FirewallAllowed{{ 338 IPProtocol: "tcp", 339 Ports: []string{"80-81", "443"}, 340 }}, 341 }} 342 343 rules := corefirewall.IngressRules{ 344 corefirewall.NewIngressRule(network.MustParsePortRange("443/tcp")), 345 } 346 err := s.Conn.ClosePorts("spam", rules) 347 c.Assert(err, jc.ErrorIsNil) 348 349 c.Check(s.FakeConn.Calls, gc.HasLen, 2) 350 c.Check(s.FakeConn.Calls[0].FuncName, gc.Equals, "GetFirewalls") 351 c.Check(s.FakeConn.Calls[1].FuncName, gc.Equals, "UpdateFirewall") 352 sort.Strings(s.FakeConn.Calls[1].Firewall.Allowed[0].Ports) 353 c.Check(s.FakeConn.Calls[1].Firewall, jc.DeepEquals, &compute.Firewall{ 354 Name: "spam", 355 TargetTags: []string{"spam"}, 356 SourceRanges: []string{"0.0.0.0/0"}, 357 Allowed: []*compute.FirewallAllowed{{ 358 IPProtocol: "tcp", 359 Ports: []string{"80-81"}, 360 }}, 361 }) 362 } 363 364 func (s *connSuite) TestConnectionClosePortsCollapseUpdate(c *gc.C) { 365 s.FakeConn.Firewalls = []*compute.Firewall{{ 366 Name: "spam", 367 TargetTags: []string{"spam"}, 368 SourceRanges: []string{"0.0.0.0/0"}, 369 Allowed: []*compute.FirewallAllowed{{ 370 IPProtocol: "tcp", 371 Ports: []string{"80-80", "100-120", "81-81", "82-82"}, 372 }}, 373 }} 374 375 rules := corefirewall.IngressRules{ 376 corefirewall.NewIngressRule(network.MustParsePortRange("80-82/tcp")), 377 } 378 err := s.Conn.ClosePorts("spam", rules) 379 c.Assert(err, jc.ErrorIsNil) 380 381 c.Check(s.FakeConn.Calls, gc.HasLen, 2) 382 c.Check(s.FakeConn.Calls[0].FuncName, gc.Equals, "GetFirewalls") 383 c.Check(s.FakeConn.Calls[1].FuncName, gc.Equals, "UpdateFirewall") 384 sort.Strings(s.FakeConn.Calls[1].Firewall.Allowed[0].Ports) 385 c.Check(s.FakeConn.Calls[1].Firewall, jc.DeepEquals, &compute.Firewall{ 386 Name: "spam", 387 TargetTags: []string{"spam"}, 388 SourceRanges: []string{"0.0.0.0/0"}, 389 Allowed: []*compute.FirewallAllowed{{ 390 IPProtocol: "tcp", 391 Ports: []string{"100-120"}, 392 }}, 393 }) 394 } 395 396 func (s *connSuite) TestConnectionClosePortsRemoveCIDR(c *gc.C) { 397 s.FakeConn.Firewalls = []*compute.Firewall{{ 398 Name: "glass-onion", 399 TargetTags: []string{"spam"}, 400 SourceRanges: []string{"192.168.1.0/24", "10.0.0.0/24"}, 401 Allowed: []*compute.FirewallAllowed{{ 402 IPProtocol: "tcp", 403 Ports: []string{"80-81", "443"}, 404 }}, 405 }} 406 407 rules := corefirewall.IngressRules{ 408 corefirewall.NewIngressRule(network.MustParsePortRange("443/tcp"), "192.168.1.0/24"), 409 corefirewall.NewIngressRule(network.MustParsePortRange("80-81/tcp"), "192.168.1.0/24"), 410 } 411 err := s.Conn.ClosePorts("spam", rules) 412 c.Assert(err, jc.ErrorIsNil) 413 414 c.Check(s.FakeConn.Calls, gc.HasLen, 2) 415 c.Check(s.FakeConn.Calls[0].FuncName, gc.Equals, "GetFirewalls") 416 c.Check(s.FakeConn.Calls[1].FuncName, gc.Equals, "UpdateFirewall") 417 sort.Strings(s.FakeConn.Calls[1].Firewall.Allowed[0].Ports) 418 c.Check(s.FakeConn.Calls[1].Firewall, jc.DeepEquals, &compute.Firewall{ 419 Name: "glass-onion", 420 TargetTags: []string{"spam"}, 421 SourceRanges: []string{"10.0.0.0/24"}, 422 Allowed: []*compute.FirewallAllowed{{ 423 IPProtocol: "tcp", 424 Ports: []string{"443", "80-81"}, 425 }}, 426 }) 427 } 428 429 func (s *connSuite) TestConnectionCloseMoMatches(c *gc.C) { 430 s.FakeConn.Firewalls = []*compute.Firewall{{ 431 Name: "spam", 432 TargetTags: []string{"spam"}, 433 SourceRanges: []string{"0.0.0.0/0"}, 434 Allowed: []*compute.FirewallAllowed{{ 435 IPProtocol: "tcp", 436 Ports: []string{"80-81", "443"}, 437 }}, 438 }} 439 440 rules := corefirewall.IngressRules{ 441 corefirewall.NewIngressRule(network.MustParsePortRange("100-110/tcp"), "192.168.0.1/24"), 442 } 443 err := s.Conn.ClosePorts("spam", rules) 444 c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`closing port(s) [100-110/tcp from 192.168.0.1/24] over non-matching rules not supported`)) 445 446 c.Check(s.FakeConn.Calls, gc.HasLen, 1) 447 c.Check(s.FakeConn.Calls[0].FuncName, gc.Equals, "GetFirewalls") 448 } 449 450 func (s *connSuite) TestNetworks(c *gc.C) { 451 s.FakeConn.Networks = []*compute.Network{{ 452 Name: "kamar-taj", 453 }} 454 results, err := s.Conn.Networks() 455 c.Assert(err, jc.ErrorIsNil) 456 c.Assert(results, gc.HasLen, 1) 457 c.Assert((*results[0]).Name, gc.Equals, "kamar-taj") 458 459 c.Check(s.FakeConn.Calls, gc.HasLen, 1) 460 c.Check(s.FakeConn.Calls[0].FuncName, gc.Equals, "ListNetworks") 461 c.Check(s.FakeConn.Calls[0].ProjectID, gc.Equals, "spam") 462 } 463 464 func (s *connSuite) TestSubnetworks(c *gc.C) { 465 s.FakeConn.Subnetworks = []*compute.Subnetwork{{ 466 Name: "heptapod", 467 }} 468 results, err := s.Conn.Subnetworks("us-central1") 469 c.Assert(err, jc.ErrorIsNil) 470 c.Assert(results, gc.HasLen, 1) 471 c.Assert((*results[0]).Name, gc.Equals, "heptapod") 472 473 c.Check(s.FakeConn.Calls, gc.HasLen, 1) 474 c.Check(s.FakeConn.Calls[0].FuncName, gc.Equals, "ListSubnetworks") 475 c.Check(s.FakeConn.Calls[0].ProjectID, gc.Equals, "spam") 476 c.Check(s.FakeConn.Calls[0].Region, gc.Equals, "us-central1") 477 } 478 479 func (s *connSuite) TestRandomSuffixNamer(c *gc.C) { 480 ruleset := google.NewRuleSetFromRules(corefirewall.IngressRules{ 481 corefirewall.NewIngressRule(network.MustParsePortRange("80-90/tcp")), 482 corefirewall.NewIngressRule(network.MustParsePortRange("80-90/tcp"), "10.0.10.0/24"), 483 }) 484 i := 0 485 for _, firewall := range ruleset { 486 i++ 487 c.Logf("%#v", *firewall) 488 name, err := google.RandomSuffixNamer(firewall, "mischief", set.NewStrings()) 489 c.Assert(err, jc.ErrorIsNil) 490 if firewall.SourceCIDRs[0] == "0.0.0.0/0" { 491 c.Assert(name, gc.Equals, "mischief") 492 } else { 493 c.Assert(name, gc.Matches, "mischief-[0-9a-f]{8}") 494 } 495 } 496 c.Assert(i, gc.Equals, 2) 497 }