gopkg.in/goose.v2@v2.0.1/testservices/neutronservice/service.go (about) 1 // Neutron double testing service - internal direct API implementation 2 3 package neutronservice 4 5 import ( 6 "net/url" 7 "strconv" 8 "strings" 9 10 "gopkg.in/goose.v2/neutron" 11 "gopkg.in/goose.v2/testservices" 12 "gopkg.in/goose.v2/testservices/identityservice" 13 "gopkg.in/goose.v2/testservices/neutronmodel" 14 ) 15 16 var _ testservices.HttpService = (*Neutron)(nil) 17 var _ identityservice.ServiceProvider = (*Neutron)(nil) 18 19 // Neutron implements a OpenStack Neutron testing service and 20 // contains the service double's internal state. 21 type Neutron struct { 22 testservices.ServiceInstance 23 neutronModel *neutronmodel.NeutronModel 24 groups map[string]neutron.SecurityGroupV2 25 rules map[string]neutron.SecurityGroupRuleV2 26 floatingIPs map[string]neutron.FloatingIPV2 27 networks map[string]neutron.NetworkV2 28 subnets map[string]neutron.SubnetV2 29 nextGroupId int 30 nextRuleId int 31 nextPortId int 32 nextIPId int 33 } 34 35 func errorJSONEncode(err error) (int, string) { 36 serverError, ok := err.(*testservices.ServerError) 37 if !ok { 38 serverError = testservices.NewInternalServerError(err.Error()) 39 } 40 return serverError.Code(), serverError.AsJSON() 41 } 42 43 // endpoint returns endpoint URL from the given path. 44 // openstack catalog list 45 // | neutron | network | RegionOne 46 // | | | publicURL: http://<keystone ip>:9696 47 // | | | internalURL: http://<keystone ip>:9696 48 // | | | adminURL: http://<keystone ip>:9696 49 func (n *Neutron) endpointURL(version bool, path string) string { 50 ep := n.Scheme + "://" + n.Hostname 51 if version { 52 ep += n.VersionPath + "/" 53 } 54 if path != "" { 55 ep += strings.TrimLeft(path, "/") 56 } 57 return ep 58 } 59 60 func (n *Neutron) Endpoints() []identityservice.Endpoint { 61 ep := identityservice.Endpoint{ 62 AdminURL: n.endpointURL(false, ""), 63 InternalURL: n.endpointURL(false, ""), 64 PublicURL: n.endpointURL(false, ""), 65 Region: n.Region, 66 } 67 return []identityservice.Endpoint{ep} 68 } 69 70 func (n *Neutron) V3Endpoints() []identityservice.V3Endpoint { 71 url := n.endpointURL(false, "") 72 return identityservice.NewV3Endpoints(url, url, url, n.RegionID) 73 } 74 75 // New creates an instance of the Neutron object, given the parameters. 76 func New(hostURL, versionPath, tenantId, region string, identityService, fallbackIdentity identityservice.IdentityService) *Neutron { 77 URL, err := url.Parse(hostURL) 78 if err != nil { 79 panic(err) 80 } 81 hostname := URL.Host 82 if !strings.HasSuffix(hostname, "/") { 83 hostname += "/" 84 } 85 defaultSubnets := []neutron.SubnetV2{ 86 { 87 Id: "999-01", 88 NetworkId: "999", 89 Name: "subnet-999", 90 Cidr: "10.9.0.0/24", 91 }, 92 { 93 Id: "998-01", 94 NetworkId: "998", 95 Name: "subnet-998", 96 Cidr: "10.8.0.0/24", 97 }, 98 { 99 Id: "997-01", 100 NetworkId: "997", 101 Name: "subnet-997", 102 Cidr: "2001:db8::/32", 103 }, 104 } 105 neutronService := &Neutron{ 106 subnets: make(map[string]neutron.SubnetV2), 107 ServiceInstance: testservices.ServiceInstance{ 108 IdentityService: identityService, 109 FallbackIdentityService: fallbackIdentity, 110 Scheme: URL.Scheme, 111 Hostname: hostname, 112 VersionPath: versionPath, 113 TenantId: tenantId, 114 Region: region, 115 }, 116 } 117 if identityService != nil { 118 identityService.RegisterServiceProvider("neutron", "network", neutronService) 119 } 120 for _, subnet := range defaultSubnets { 121 err := neutronService.addSubnet(subnet) 122 if err != nil { 123 panic(err) 124 } 125 } 126 return neutronService 127 } 128 129 func (n *Neutron) Stop() { 130 // noop 131 } 132 133 // AddNeutronModel setups up the test double for shared data between the nova 134 // and neutron test doubles. Required for the neutron test double. 135 func (n *Neutron) AddNeutronModel(neutronModel *neutronmodel.NeutronModel) { 136 n.neutronModel = neutronModel 137 } 138 139 // NeutronModel returns the current NeutronModel, which can then be used to 140 // update internal state for neutron test doubles. 141 func (n *Neutron) NeutronModel() *neutronmodel.NeutronModel { 142 return n.neutronModel 143 } 144 145 // updateSecurityGroup updates an existing security group. 146 func (n *Neutron) updateSecurityGroup(group neutron.SecurityGroupV2) error { 147 if err := n.ProcessFunctionHook(n, group); err != nil { 148 return err 149 } 150 return n.neutronModel.UpdateSecurityGroup(group) 151 } 152 153 // addPort creates a new port. 154 func (n *Neutron) addPort(port neutron.PortV2) error { 155 if err := n.ProcessFunctionHook(n, port); err != nil { 156 return err 157 } 158 return n.neutronModel.AddPort(port) 159 } 160 161 // port retrieves an existing port by ID. 162 func (n *Neutron) port(portId string) (*neutron.PortV2, error) { 163 if err := n.ProcessFunctionHook(n, portId); err != nil { 164 return nil, err 165 } 166 return n.neutronModel.Port(portId) 167 } 168 169 // allPorts returns a list of all existing ports. 170 func (n *Neutron) allPorts() []neutron.PortV2 { 171 return n.neutronModel.AllPorts() 172 } 173 174 // removePort deletes an existing port. 175 func (n *Neutron) removePort(portId string) error { 176 if err := n.ProcessFunctionHook(n, portId); err != nil { 177 return err 178 } 179 return n.neutronModel.RemovePort(portId) 180 } 181 182 // addSecurityGroup creates a new security group. 183 func (n *Neutron) addSecurityGroup(group neutron.SecurityGroupV2) error { 184 if err := n.ProcessFunctionHook(n, group); err != nil { 185 return err 186 } 187 return n.neutronModel.AddSecurityGroup(group) 188 } 189 190 // securityGroup retrieves an existing group by ID. 191 func (n *Neutron) securityGroup(groupId string) (*neutron.SecurityGroupV2, error) { 192 if err := n.ProcessFunctionHook(n, groupId); err != nil { 193 return nil, err 194 } 195 return n.neutronModel.SecurityGroup(groupId) 196 } 197 198 // securityGroupByName retrieves an existing named group. 199 func (n *Neutron) securityGroupByName(groupName string) ([]neutron.SecurityGroupV2, error) { 200 if err := n.ProcessFunctionHook(n, groupName); err != nil { 201 return nil, err 202 } 203 return n.neutronModel.SecurityGroupByName(groupName) 204 } 205 206 // allSecurityGroups returns a list of all existing groups. 207 func (n *Neutron) allSecurityGroups() []neutron.SecurityGroupV2 { 208 return n.neutronModel.AllSecurityGroups() 209 } 210 211 // removeSecurityGroup deletes an existing group. 212 func (n *Neutron) removeSecurityGroup(groupId string) error { 213 if err := n.ProcessFunctionHook(n, groupId); err != nil { 214 return err 215 } 216 return n.neutronModel.RemoveSecurityGroup(groupId) 217 } 218 219 // addSecurityGroupRule creates a new rule in an existing group. 220 // This can be either an ingress or an egress rule (see the notes 221 // about neutron.RuleInfoV2). 222 func (n *Neutron) addSecurityGroupRule(ruleId string, rule neutron.RuleInfoV2) error { 223 if err := n.ProcessFunctionHook(n, ruleId, rule); err != nil { 224 return err 225 } 226 return n.neutronModel.AddSecurityGroupRule(ruleId, rule) 227 } 228 229 // hasSecurityGroupRule returns whether the given group contains the given rule. 230 func (n *Neutron) hasSecurityGroupRule(groupId, ruleId string) bool { 231 return n.neutronModel.HasSecurityGroupRule(groupId, ruleId) 232 } 233 234 // securityGroupRule retrieves an existing rule by ID. 235 func (n *Neutron) securityGroupRule(ruleId string) (*neutron.SecurityGroupRuleV2, error) { 236 if err := n.ProcessFunctionHook(n, ruleId); err != nil { 237 return nil, err 238 } 239 return n.neutronModel.SecurityGroupRule(ruleId) 240 } 241 242 // removeSecurityGroupRule deletes an existing rule from its group. 243 func (n *Neutron) removeSecurityGroupRule(ruleId string) error { 244 if err := n.ProcessFunctionHook(n, ruleId); err != nil { 245 return err 246 } 247 return n.neutronModel.RemoveSecurityGroupRule(ruleId) 248 } 249 250 // addFloatingIP creates a new floating IP address in the pool. 251 func (n *Neutron) addFloatingIP(ip neutron.FloatingIPV2) error { 252 if err := n.ProcessFunctionHook(n, ip); err != nil { 253 return err 254 } 255 return n.neutronModel.AddFloatingIP(ip) 256 } 257 258 // hasFloatingIP returns whether the given floating IP address exists. 259 func (n *Neutron) hasFloatingIP(address string) bool { 260 return n.neutronModel.HasFloatingIP(address) 261 } 262 263 // floatingIP retrieves the floating IP by ID. 264 func (n *Neutron) floatingIP(ipId string) (*neutron.FloatingIPV2, error) { 265 if err := n.ProcessFunctionHook(n, ipId); err != nil { 266 return nil, err 267 } 268 return n.neutronModel.FloatingIP(ipId) 269 } 270 271 // floatingIPByAddr retrieves the floating IP by address. 272 func (n *Neutron) floatingIPByAddr(address string) (*neutron.FloatingIPV2, error) { 273 if err := n.ProcessFunctionHook(n, address); err != nil { 274 return nil, err 275 } 276 return n.neutronModel.FloatingIPByAddr(address) 277 } 278 279 // matchFloatingIPs returns a list of matching FloatingIPs, after applying the 280 // given filter. Each separate filter is combined with a logical AND. 281 // Each filter can have only one value. A nil filter matches all FloatingIPs. 282 // 283 // This is tested to match OpenStack behavior. Regular expression 284 // matching is supported for FilterProjectId only, and the supported 285 // syntax is limited to whatever DB backend is used (see SQL 286 // REGEXP/RLIKE). 287 // 288 // Example: 289 // 290 // f := filter{ 291 // neutron.FilterProjectId: `foo.*`, 292 // } 293 // 294 // This will match all FloatingIPs with project_id starting 295 // with "foo". 296 func (n *Neutron) matchFloatingIPs(f filter) []neutron.FloatingIPV2 { 297 fips := n.neutronModel.AllFloatingIPs() 298 if len(f) == 0 { 299 return fips 300 } 301 if projectId := f[neutron.FilterProjectId]; projectId != "" { 302 matched := []neutron.FloatingIPV2{} 303 externalNetworks, err := n.matchNetworks(filter{neutron.FilterRouterExternal: "true"}) 304 if err != nil { 305 return fips 306 } 307 for _, fip := range fips { 308 for _, network := range externalNetworks { 309 if fip.FloatingNetworkId == network.Id && projectId == network.TenantId { 310 matched = append(matched, fip) 311 } 312 } 313 } 314 fips = matched 315 } 316 return fips 317 } 318 319 // allFloatingIPs returns a list of all created floating IPs. 320 func (n *Neutron) allFloatingIPs(f filter) []neutron.FloatingIPV2 { 321 return n.matchFloatingIPs(f) 322 } 323 324 // removeFloatingIP deletes an existing floating IP by ID. 325 func (n *Neutron) removeFloatingIP(ipId string) error { 326 if err := n.ProcessFunctionHook(n, ipId); err != nil { 327 return err 328 } 329 return n.neutronModel.RemoveFloatingIP(ipId) 330 } 331 332 // filter is used internally by matchNetworks and matchFloatingIPs. 333 type filter map[string]string 334 335 // matchNetworks returns a list of matching networks, after applying the 336 // given filter. Each separate filter is combined with a logical AND. 337 // Each filter can have only one value. A nil filter matches all networks. 338 // 339 // This is tested to match OpenStack behavior. Regular expression 340 // matching is supported for FilterNetwork only, and the supported 341 // syntax is limited to whatever DB backend is used (see SQL 342 // REGEXP/RLIKE). 343 // 344 // Example: 345 // 346 // f := filter{ 347 // neutron.FilterRouterExternal: true, 348 // neutron.FilterNetwork: `foo.*`, 349 // } 350 // 351 // This will match all external neworks with names starting 352 // with "foo". 353 func (n *Neutron) matchNetworks(f filter) ([]neutron.NetworkV2, error) { 354 networks := n.neutronModel.AllNetworks() 355 if len(f) == 0 { 356 return networks, nil 357 } 358 if external := f[neutron.FilterRouterExternal]; external != "" { 359 matched := []neutron.NetworkV2{} 360 externalBool, err := strconv.ParseBool(external) 361 if err != nil { 362 return nil, err 363 } 364 for _, network := range networks { 365 if network.External == externalBool { 366 matched = append(matched, network) 367 } 368 } 369 if len(matched) == 0 { 370 // no match, so no need to look further 371 return nil, nil 372 } 373 networks = matched 374 } 375 if name := f[neutron.FilterNetwork]; name != "" { 376 matched := []neutron.NetworkV2{} 377 for _, network := range networks { 378 if name == network.Name { 379 matched = append(matched, network) 380 } 381 } 382 if len(matched) == 0 { 383 // no match, so no need to look further 384 return nil, nil 385 } 386 networks = matched 387 } 388 return networks, nil 389 } 390 391 // allNetworks returns a list of all existing networks. 392 func (n *Neutron) allNetworks(f filter) ([]neutron.NetworkV2, error) { 393 return n.matchNetworks(f) 394 } 395 396 // network retrieves the network by ID. 397 func (n *Neutron) network(networkId string) (*neutron.NetworkV2, error) { 398 if err := n.ProcessFunctionHook(n, networkId); err != nil { 399 return nil, err 400 } 401 return n.neutronModel.Network(networkId) 402 } 403 404 // addNetwork creates a new network. 405 func (n *Neutron) addNetwork(network neutron.NetworkV2) error { 406 if err := n.ProcessFunctionHook(n, network); err != nil { 407 return err 408 } 409 return n.neutronModel.AddNetwork(network) 410 } 411 412 // removeNetwork deletes an existing group. 413 func (n *Neutron) removeNetwork(netId string) error { 414 if err := n.ProcessFunctionHook(n, netId); err != nil { 415 return err 416 } 417 return n.neutronModel.RemoveNetwork(netId) 418 } 419 420 // allSubnets returns a list of all existing subnets. 421 func (n *Neutron) allSubnets() (subnets []neutron.SubnetV2) { 422 for _, sub := range n.subnets { 423 subnets = append(subnets, sub) 424 } 425 return subnets 426 } 427 428 // subnet retrieves the subnet by ID. 429 func (n *Neutron) subnet(subnetId string) (*neutron.SubnetV2, error) { 430 if err := n.ProcessFunctionHook(n, subnetId); err != nil { 431 return nil, err 432 } 433 subnet, ok := n.subnets[subnetId] 434 if !ok { 435 return nil, testservices.NewSubnetNotFoundError(subnetId) 436 } 437 return &subnet, nil 438 } 439 440 // addSubnet creates a new subnet. 441 func (n *Neutron) addSubnet(subnet neutron.SubnetV2) error { 442 if err := n.ProcessFunctionHook(n, subnet); err != nil { 443 return err 444 } 445 if _, err := n.subnet(subnet.Id); err == nil { 446 return testservices.NewSubnetAlreadyExistsError(subnet.Id) 447 } 448 subnet.TenantId = n.TenantId 449 n.subnets[subnet.Id] = subnet 450 return nil 451 } 452 453 // removeSubnet deletes an existing subnet. 454 func (n *Neutron) removeSubnet(subnetId string) error { 455 if err := n.ProcessFunctionHook(n, subnetId); err != nil { 456 return err 457 } 458 if _, err := n.subnet(subnetId); err != nil { 459 return err 460 } 461 delete(n.subnets, subnetId) 462 return nil 463 }