1
// Nova double testing service - internal direct API implementation
12
"gopkg.in/goose.v1/nova"
13
"gopkg.in/goose.v1/testservices"
14
"gopkg.in/goose.v1/testservices/identityservice"
17
var _ testservices.HttpService = (*Nova)(nil)
18
var _ identityservice.ServiceProvider = (*Nova)(nil)
20
// Nova implements a OpenStack Nova testing service and
21
// contains the service double's internal state.
23
testservices.ServiceInstance
24
flavors map[string]nova.FlavorDetail
25
servers map[string]nova.ServerDetail
26
groups map[string]nova.SecurityGroup
27
rules map[string]nova.SecurityGroupRule
28
floatingIPs map[string]nova.FloatingIP
29
networks map[string]nova.Network
30
serverGroups map[string][]string
31
serverIPs map[string][]string
32
availabilityZones map[string]nova.AvailabilityZone
33
serverIdToAttachedVolumes map[string][]nova.VolumeAttachment
41
func errorJSONEncode(err error) (int, string) {
42
serverError, ok := err.(*testservices.ServerError)
44
serverError = testservices.NewInternalServerError(err.Error())
46
return serverError.Code(), serverError.AsJSON()
49
// endpoint returns either a versioned or non-versioned service
50
// endpoint URL from the given path.
51
func (n *Nova) endpointURL(version bool, path string) string {
52
ep := n.Scheme + "://" + n.Hostname
54
ep += n.VersionPath + "/"
58
ep += "/" + strings.TrimLeft(path, "/")
63
func (n *Nova) Endpoints() []identityservice.Endpoint {
64
ep := identityservice.Endpoint{
65
AdminURL: n.endpointURL(true, ""),
66
InternalURL: n.endpointURL(true, ""),
67
PublicURL: n.endpointURL(true, ""),
70
return []identityservice.Endpoint{ep}
73
// New creates an instance of the Nova object, given the parameters.
74
func New(hostURL, versionPath, tenantId, region string, identityService identityservice.IdentityService) *Nova {
75
URL, err := url.Parse(hostURL)
80
if !strings.HasSuffix(hostname, "/") {
83
// Real openstack instances have flavours "out of the box". So we add some here.
84
defaultFlavors := []nova.FlavorDetail{
85
{Id: "1", Name: "m1.tiny", RAM: 512, VCPUs: 1},
86
{Id: "2", Name: "m1.small", RAM: 2048, VCPUs: 1},
87
{Id: "3", Name: "m1.medium", RAM: 4096, VCPUs: 2},
89
// Real openstack instances have a default security group "out of the box". So we add it here.
90
defaultSecurityGroups := []nova.SecurityGroup{
91
{Id: "999", Name: "default", Description: "default group"},
94
flavors: make(map[string]nova.FlavorDetail),
95
servers: make(map[string]nova.ServerDetail),
96
groups: make(map[string]nova.SecurityGroup),
97
rules: make(map[string]nova.SecurityGroupRule),
98
floatingIPs: make(map[string]nova.FloatingIP),
99
networks: make(map[string]nova.Network),
100
serverGroups: make(map[string][]string),
101
serverIPs: make(map[string][]string),
102
availabilityZones: make(map[string]nova.AvailabilityZone),
103
serverIdToAttachedVolumes: make(map[string][]nova.VolumeAttachment),
104
ServiceInstance: testservices.ServiceInstance{
105
IdentityService: identityService,
108
VersionPath: versionPath,
113
if identityService != nil {
114
identityService.RegisterServiceProvider("nova", "compute", novaService)
116
for i, flavor := range defaultFlavors {
117
novaService.buildFlavorLinks(&flavor)
118
defaultFlavors[i] = flavor
119
err := novaService.addFlavor(flavor)
124
for _, group := range defaultSecurityGroups {
125
err := novaService.addSecurityGroup(group)
130
// Add a sample default network
132
novaService.networks[netId] = nova.Network{
140
// SetAvailabilityZones sets the availability zones for setting
141
// availability zones.
143
// Note: this is implemented as a public method rather than as
144
// an HTTP API for two reasons: availability zones are created
145
// indirectly via "host aggregates", which are a cloud-provider
146
// concept that we have not implemented, and because we want to
147
// be able to synthesize zone state changes.
148
func (n *Nova) SetAvailabilityZones(zones ...nova.AvailabilityZone) {
149
n.availabilityZones = make(map[string]nova.AvailabilityZone)
150
for _, z := range zones {
151
n.availabilityZones[z.Name] = z
155
// buildFlavorLinks populates the Links field of the passed
156
// FlavorDetail as needed by OpenStack HTTP API. Call this
157
// before addFlavor().
158
func (n *Nova) buildFlavorLinks(flavor *nova.FlavorDetail) {
159
url := "/flavors/" + flavor.Id
160
flavor.Links = []nova.Link{
161
{Href: n.endpointURL(true, url), Rel: "self"},
162
{Href: n.endpointURL(false, url), Rel: "bookmark"},
166
// addFlavor creates a new flavor.
167
func (n *Nova) addFlavor(flavor nova.FlavorDetail) error {
168
if err := n.ProcessFunctionHook(n, flavor); err != nil {
171
if _, err := n.flavor(flavor.Id); err == nil {
172
return testservices.NewAddFlavorError(flavor.Id)
174
n.flavors[flavor.Id] = flavor
178
// flavor retrieves an existing flavor by ID.
179
func (n *Nova) flavor(flavorId string) (*nova.FlavorDetail, error) {
180
if err := n.ProcessFunctionHook(n, flavorId); err != nil {
183
flavor, ok := n.flavors[flavorId]
185
return nil, testservices.NewNoSuchFlavorError(flavorId)
190
// flavorAsEntity returns the stored FlavorDetail as Entity.
191
func (n *Nova) flavorAsEntity(flavorId string) (*nova.Entity, error) {
192
if err := n.ProcessFunctionHook(n, flavorId); err != nil {
195
flavor, err := n.flavor(flavorId)
206
// allFlavors returns a list of all existing flavors.
207
func (n *Nova) allFlavors() []nova.FlavorDetail {
208
var flavors []nova.FlavorDetail
209
for _, flavor := range n.flavors {
210
flavors = append(flavors, flavor)
215
// allFlavorsAsEntities returns all flavors as Entity structs.
216
func (n *Nova) allFlavorsAsEntities() []nova.Entity {
217
var entities []nova.Entity
218
for _, flavor := range n.flavors {
219
entities = append(entities, nova.Entity{
228
// removeFlavor deletes an existing flavor.
229
func (n *Nova) removeFlavor(flavorId string) error {
230
if err := n.ProcessFunctionHook(n, flavorId); err != nil {
233
if _, err := n.flavor(flavorId); err != nil {
236
delete(n.flavors, flavorId)
240
// buildServerLinks populates the Links field of the passed
241
// ServerDetail as needed by OpenStack HTTP API. Call this
242
// before addServer().
243
func (n *Nova) buildServerLinks(server *nova.ServerDetail) {
244
url := "/servers/" + server.Id
245
server.Links = []nova.Link{
246
{Href: n.endpointURL(true, url), Rel: "self"},
247
{Href: n.endpointURL(false, url), Rel: "bookmark"},
251
// addServer creates a new server.
252
func (n *Nova) addServer(server nova.ServerDetail) error {
253
if err := n.ProcessFunctionHook(n, &server); err != nil {
256
if _, err := n.server(server.Id); err == nil {
257
return testservices.NewServerAlreadyExistsError(server.Id)
259
n.servers[server.Id] = server
263
// server retrieves an existing server by ID.
264
func (n *Nova) server(serverId string) (*nova.ServerDetail, error) {
265
if err := n.ProcessFunctionHook(n, serverId); err != nil {
268
server, ok := n.servers[serverId]
270
return nil, testservices.NewServerByIDNotFoundError(serverId)
275
// serverByName retrieves the first existing server with the given name.
276
func (n *Nova) serverByName(name string) (*nova.ServerDetail, error) {
277
if err := n.ProcessFunctionHook(n, name); err != nil {
280
for _, server := range n.servers {
281
if server.Name == name {
285
return nil, testservices.NewServerByNameNotFoundError(name)
288
// serverAsEntity returns the stored ServerDetail as Entity.
289
func (n *Nova) serverAsEntity(serverId string) (*nova.Entity, error) {
290
if err := n.ProcessFunctionHook(n, serverId); err != nil {
293
server, err := n.server(serverId)
305
// filter is used internally by matchServers.
306
type filter map[string]string
308
// matchServers returns a list of matching servers, after applying the
309
// given filter. Each separate filter is combined with a logical AND.
310
// Each filter can have only one value. A nil filter matches all servers.
312
// This is tested to match OpenStack behavior. Regular expression
313
// matching is supported for FilterServer only, and the supported
314
// syntax is limited to whatever DB backend is used (see SQL
320
// nova.FilterStatus: nova.StatusActive,
321
// nova.FilterServer: `foo.*`,
324
// This will match all servers with status "ACTIVE", and names starting
326
func (n *Nova) matchServers(f filter) ([]nova.ServerDetail, error) {
327
if err := n.ProcessFunctionHook(n, f); err != nil {
330
var servers []nova.ServerDetail
331
for _, server := range n.servers {
332
servers = append(servers, server)
335
return servers, nil // empty filter matches everything
337
if status := f[nova.FilterStatus]; status != "" {
338
matched := []nova.ServerDetail{}
339
for _, server := range servers {
340
if server.Status == status {
341
matched = append(matched, server)
344
if len(matched) == 0 {
345
// no match, so no need to look further
350
if nameRex := f[nova.FilterServer]; nameRex != "" {
351
matched := []nova.ServerDetail{}
352
rex, err := regexp.Compile(nameRex)
356
for _, server := range servers {
357
if rex.MatchString(server.Name) {
358
matched = append(matched, server)
361
if len(matched) == 0 {
362
// no match, here so ignore other results
368
// TODO(dimitern) - 2013-02-11 bug=1121690
369
// implement FilterFlavor, FilterImage, FilterMarker, FilterLimit and FilterChangesSince
372
// allServers returns a list of all existing servers.
373
// Filtering is supported, see filter type for more info.
374
func (n *Nova) allServers(f filter) ([]nova.ServerDetail, error) {
375
return n.matchServers(f)
378
// allServersAsEntities returns all servers as Entity structs.
379
// Filtering is supported, see filter type for more info.
380
func (n *Nova) allServersAsEntities(f filter) ([]nova.Entity, error) {
381
var entities []nova.Entity
382
servers, err := n.matchServers(f)
386
for _, server := range servers {
387
entities = append(entities, nova.Entity{
397
// removeServer deletes an existing server.
398
func (n *Nova) removeServer(serverId string) error {
399
if err := n.ProcessFunctionHook(n, serverId); err != nil {
402
if _, err := n.server(serverId); err != nil {
405
delete(n.servers, serverId)
409
// addSecurityGroup creates a new security group.
410
func (n *Nova) addSecurityGroup(group nova.SecurityGroup) error {
411
if err := n.ProcessFunctionHook(n, group); err != nil {
414
if _, err := n.securityGroup(group.Id); err == nil {
415
return testservices.NewSecurityGroupAlreadyExistsError(group.Id)
417
group.TenantId = n.TenantId
418
if group.Rules == nil {
419
group.Rules = []nova.SecurityGroupRule{}
421
n.groups[group.Id] = group
425
// securityGroup retrieves an existing group by ID.
426
func (n *Nova) securityGroup(groupId string) (*nova.SecurityGroup, error) {
427
if err := n.ProcessFunctionHook(n, groupId); err != nil {
430
group, ok := n.groups[groupId]
432
return nil, testservices.NewSecurityGroupByIDNotFoundError(groupId)
437
// securityGroupByName retrieves an existing named group.
438
func (n *Nova) securityGroupByName(groupName string) (*nova.SecurityGroup, error) {
439
if err := n.ProcessFunctionHook(n, groupName); err != nil {
442
for _, group := range n.groups {
443
if group.Name == groupName {
447
return nil, testservices.NewSecurityGroupByNameNotFoundError(groupName)
450
// allSecurityGroups returns a list of all existing groups.
451
func (n *Nova) allSecurityGroups() []nova.SecurityGroup {
452
var groups []nova.SecurityGroup
453
for _, group := range n.groups {
454
groups = append(groups, group)
459
// removeSecurityGroup deletes an existing group.
460
func (n *Nova) removeSecurityGroup(groupId string) error {
461
if err := n.ProcessFunctionHook(n, groupId); err != nil {
464
if _, err := n.securityGroup(groupId); err != nil {
467
delete(n.groups, groupId)
471
// addSecurityGroupRule creates a new rule in an existing group.
472
// This can be either an ingress or a group rule (see the notes
473
// about nova.RuleInfo).
474
func (n *Nova) addSecurityGroupRule(ruleId string, rule nova.RuleInfo) error {
475
if err := n.ProcessFunctionHook(n, ruleId, rule); err != nil {
478
if _, err := n.securityGroupRule(ruleId); err == nil {
479
return testservices.NewSecurityGroupRuleAlreadyExistsError(ruleId)
481
group, err := n.securityGroup(rule.ParentGroupId)
485
for _, ru := range group.Rules {
487
return testservices.NewCannotAddTwiceRuleToGroupError(ru.Id, group.Id)
490
var zeroSecurityGroupRef nova.SecurityGroupRef
491
newrule := nova.SecurityGroupRule{
492
ParentGroupId: rule.ParentGroupId,
494
Group: zeroSecurityGroupRef,
496
if rule.GroupId != nil {
497
sourceGroup, err := n.securityGroup(*rule.GroupId)
499
return testservices.NewUnknownSecurityGroupError(*rule.GroupId)
501
newrule.Group = nova.SecurityGroupRef{
502
TenantId: sourceGroup.TenantId,
503
Name: sourceGroup.Name,
505
} else if rule.Cidr == "" {
506
// http://pad.lv/1226996
507
// It seems that if you don't supply Cidr or GroupId then
508
// Openstack treats the Cidr as 0.0.0.0/0
509
// However, since that is not clearly specified we just panic()
510
// because we don't want to rely on that behavior
511
panic(fmt.Sprintf("Neither Cidr nor GroupId are set for this SecurityGroup Rule: %v", rule))
513
if rule.FromPort != 0 {
514
newrule.FromPort = &rule.FromPort
516
if rule.ToPort != 0 {
517
newrule.ToPort = &rule.ToPort
519
if rule.IPProtocol != "" {
520
newrule.IPProtocol = &rule.IPProtocol
523
newrule.IPRange = make(map[string]string)
524
newrule.IPRange["cidr"] = rule.Cidr
527
group.Rules = append(group.Rules, newrule)
528
n.groups[group.Id] = *group
529
n.rules[newrule.Id] = newrule
533
// hasSecurityGroupRule returns whether the given group contains the given rule,
534
// or (when groupId="-1") whether the given rule exists.
535
func (n *Nova) hasSecurityGroupRule(groupId, ruleId string) bool {
536
rule, ok := n.rules[ruleId]
537
_, err := n.securityGroup(groupId)
538
return ok && (groupId == "-1" || (err == nil && rule.ParentGroupId == groupId))
541
// securityGroupRule retrieves an existing rule by ID.
542
func (n *Nova) securityGroupRule(ruleId string) (*nova.SecurityGroupRule, error) {
543
if err := n.ProcessFunctionHook(n, ruleId); err != nil {
546
rule, ok := n.rules[ruleId]
548
return nil, testservices.NewSecurityGroupRuleNotFoundError(ruleId)
553
// removeSecurityGroupRule deletes an existing rule from its group.
554
func (n *Nova) removeSecurityGroupRule(ruleId string) error {
555
if err := n.ProcessFunctionHook(n, ruleId); err != nil {
558
rule, err := n.securityGroupRule(ruleId)
562
if group, err := n.securityGroup(rule.ParentGroupId); err == nil {
564
for ri, ru := range group.Rules {
571
group.Rules = append(group.Rules[:idx], group.Rules[idx+1:]...)
572
n.groups[group.Id] = *group
574
// Silently ignore missing rules...
577
delete(n.rules, ruleId)
581
// addServerSecurityGroup attaches an existing server to a group.
582
func (n *Nova) addServerSecurityGroup(serverId string, groupId string) error {
583
if err := n.ProcessFunctionHook(n, serverId, groupId); err != nil {
586
if _, err := n.server(serverId); err != nil {
589
if _, err := n.securityGroup(groupId); err != nil {
592
groups, ok := n.serverGroups[serverId]
594
for _, gid := range groups {
596
return testservices.NewServerBelongsToGroupError(serverId, groupId)
600
groups = append(groups, groupId)
601
n.serverGroups[serverId] = groups
605
// hasServerSecurityGroup returns whether the given server belongs to the group.
606
func (n *Nova) hasServerSecurityGroup(serverId string, groupId string) bool {
607
if _, err := n.server(serverId); err != nil {
610
if _, err := n.securityGroup(groupId); err != nil {
613
groups, ok := n.serverGroups[serverId]
617
for _, gid := range groups {
625
// allServerSecurityGroups returns all security groups attached to the
627
func (n *Nova) allServerSecurityGroups(serverId string) []nova.SecurityGroup {
628
var groups []nova.SecurityGroup
629
for _, gid := range n.serverGroups[serverId] {
630
group, err := n.securityGroup(gid)
634
groups = append(groups, *group)
639
// removeServerSecurityGroup detaches an existing server from a group.
640
func (n *Nova) removeServerSecurityGroup(serverId string, groupId string) error {
641
if err := n.ProcessFunctionHook(n, serverId, groupId); err != nil {
644
if _, err := n.server(serverId); err != nil {
647
if _, err := n.securityGroup(groupId); err != nil {
650
groups, ok := n.serverGroups[serverId]
652
return testservices.NewServerDoesNotBelongToGroupsError(serverId)
655
for gi, gid := range groups {
662
return testservices.NewServerDoesNotBelongToGroupError(serverId, groupId)
664
groups = append(groups[:idx], groups[idx+1:]...)
665
n.serverGroups[serverId] = groups
669
// addFloatingIP creates a new floating IP address in the pool.
670
func (n *Nova) addFloatingIP(ip nova.FloatingIP) error {
671
if err := n.ProcessFunctionHook(n, ip); err != nil {
674
if _, err := n.floatingIP(ip.Id); err == nil {
675
return testservices.NewFloatingIPExistsError(ip.Id)
677
n.floatingIPs[ip.Id] = ip
681
// hasFloatingIP returns whether the given floating IP address exists.
682
func (n *Nova) hasFloatingIP(address string) bool {
683
if len(n.floatingIPs) == 0 {
686
for _, fip := range n.floatingIPs {
687
if fip.IP == address {
694
// floatingIP retrieves the floating IP by ID.
695
func (n *Nova) floatingIP(ipId string) (*nova.FloatingIP, error) {
696
if err := n.ProcessFunctionHook(n, ipId); err != nil {
699
ip, ok := n.floatingIPs[ipId]
701
return nil, testservices.NewFloatingIPNotFoundError(ipId)
706
// floatingIPByAddr retrieves the floating IP by address.
707
func (n *Nova) floatingIPByAddr(address string) (*nova.FloatingIP, error) {
708
if err := n.ProcessFunctionHook(n, address); err != nil {
711
for _, fip := range n.floatingIPs {
712
if fip.IP == address {
716
return nil, testservices.NewFloatingIPNotFoundError(address)
719
// allFloatingIPs returns a list of all created floating IPs.
720
func (n *Nova) allFloatingIPs() []nova.FloatingIP {
721
var fips []nova.FloatingIP
722
for _, fip := range n.floatingIPs {
723
fips = append(fips, fip)
728
// removeFloatingIP deletes an existing floating IP by ID.
729
func (n *Nova) removeFloatingIP(ipId string) error {
730
if err := n.ProcessFunctionHook(n, ipId); err != nil {
733
if _, err := n.floatingIP(ipId); err != nil {
736
delete(n.floatingIPs, ipId)
740
// addServerFloatingIP attaches an existing floating IP to a server.
741
func (n *Nova) addServerFloatingIP(serverId string, ipId string) error {
742
if err := n.ProcessFunctionHook(n, serverId, ipId); err != nil {
745
if _, err := n.server(serverId); err != nil {
748
if fip, err := n.floatingIP(ipId); err != nil {
751
fixedIP := "4.3.2.1" // not important really, unused
752
fip.FixedIP = &fixedIP
753
fip.InstanceId = &serverId
754
n.floatingIPs[ipId] = *fip
756
fips, ok := n.serverIPs[serverId]
758
for _, fipId := range fips {
760
return testservices.NewServerHasFloatingIPError(serverId, ipId)
764
fips = append(fips, ipId)
765
n.serverIPs[serverId] = fips
769
// hasServerFloatingIP verifies the given floating IP belongs to a server.
770
func (n *Nova) hasServerFloatingIP(serverId, address string) bool {
771
if _, err := n.server(serverId); err != nil || !n.hasFloatingIP(address) {
774
fips, ok := n.serverIPs[serverId]
778
for _, fipId := range fips {
779
fip := n.floatingIPs[fipId]
780
if fip.IP == address {
787
// removeServerFloatingIP deletes an attached floating IP from a server.
788
func (n *Nova) removeServerFloatingIP(serverId string, ipId string) error {
789
if err := n.ProcessFunctionHook(n, serverId); err != nil {
792
if _, err := n.server(serverId); err != nil {
795
if fip, err := n.floatingIP(ipId); err != nil {
800
n.floatingIPs[ipId] = *fip
802
fips, ok := n.serverIPs[serverId]
804
return testservices.NewNoFloatingIPsToRemoveError(serverId)
807
for fi, fipId := range fips {
814
return testservices.NewNoFloatingIPsError(serverId, ipId)
816
fips = append(fips[:idx], fips[idx+1:]...)
817
n.serverIPs[serverId] = fips
821
// allNetworks returns a list of all existing networks.
822
func (n *Nova) allNetworks() (networks []nova.Network) {
823
for _, net := range n.networks {
824
networks = append(networks, net)
829
// allAvailabilityZones returns a list of all existing availability zones,
831
func (n *Nova) allAvailabilityZones() (zones []nova.AvailabilityZone) {
832
for _, zone := range n.availabilityZones {
833
zones = append(zones, zone)
835
sort.Sort(azByName(zones))
839
type azByName []nova.AvailabilityZone
841
func (a azByName) Len() int {
845
func (a azByName) Less(i, j int) bool {
846
return a[i].Name < a[j].Name
849
func (a azByName) Swap(i, j int) {
850
a[i], a[j] = a[j], a[i]
853
// setServerMetadata sets metadata on a server.
854
func (n *Nova) setServerMetadata(serverId string, metadata map[string]string) error {
855
if err := n.ProcessFunctionHook(n, serverId, metadata); err != nil {
858
server, err := n.server(serverId)
862
if server.Metadata == nil {
863
server.Metadata = make(map[string]string)
865
for k, v := range metadata {
866
server.Metadata[k] = v
868
n.servers[serverId] = *server