github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/provider/azure/instance_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package azure
     5  
     6  import (
     7  	"encoding/base64"
     8  	"fmt"
     9  	"net/http"
    10  
    11  	gc "launchpad.net/gocheck"
    12  	"launchpad.net/gwacl"
    13  
    14  	"launchpad.net/juju-core/instance"
    15  	jc "launchpad.net/juju-core/testing/checkers"
    16  )
    17  
    18  type instanceSuite struct{}
    19  
    20  var _ = gc.Suite(&instanceSuite{})
    21  
    22  // makeHostedServiceDescriptor creates a HostedServiceDescriptor with the
    23  // given service name.
    24  func makeHostedServiceDescriptor(name string) *gwacl.HostedServiceDescriptor {
    25  	labelBase64 := base64.StdEncoding.EncodeToString([]byte("label"))
    26  	return &gwacl.HostedServiceDescriptor{ServiceName: name, Label: labelBase64}
    27  }
    28  
    29  func (*instanceSuite) TestId(c *gc.C) {
    30  	serviceName := "test-name"
    31  	testService := makeHostedServiceDescriptor(serviceName)
    32  	azInstance := azureInstance{*testService, nil}
    33  	c.Check(azInstance.Id(), gc.Equals, instance.Id(serviceName))
    34  }
    35  
    36  func (*instanceSuite) TestStatus(c *gc.C) {
    37  	serviceName := "test-name"
    38  	testService := makeHostedServiceDescriptor(serviceName)
    39  	testService.Status = "something"
    40  	azInstance := azureInstance{*testService, nil}
    41  	c.Check(azInstance.Status(), gc.Equals, testService.Status)
    42  }
    43  
    44  func (*instanceSuite) TestDNSName(c *gc.C) {
    45  	// An instance's DNS name is computed from its hosted-service name.
    46  	host := "hostname"
    47  	testService := makeHostedServiceDescriptor(host)
    48  	azInstance := azureInstance{*testService, nil}
    49  	dnsName, err := azInstance.DNSName()
    50  	c.Assert(err, gc.IsNil)
    51  	c.Check(dnsName, gc.Equals, host+"."+AZURE_DOMAIN_NAME)
    52  }
    53  
    54  func (*instanceSuite) TestWaitDNSName(c *gc.C) {
    55  	// An Azure instance gets its DNS name immediately, so there's no
    56  	// waiting involved.
    57  	host := "hostname"
    58  	testService := makeHostedServiceDescriptor(host)
    59  	azInstance := azureInstance{*testService, nil}
    60  	dnsName, err := azInstance.WaitDNSName()
    61  	c.Assert(err, gc.IsNil)
    62  	c.Check(dnsName, gc.Equals, host+"."+AZURE_DOMAIN_NAME)
    63  }
    64  
    65  func makeRole(name string, endpoints ...gwacl.InputEndpoint) gwacl.Role {
    66  	return gwacl.Role{
    67  		RoleName: name,
    68  		ConfigurationSets: []gwacl.ConfigurationSet{
    69  			{
    70  				ConfigurationSetType: gwacl.CONFIG_SET_NETWORK,
    71  				InputEndpoints:       &endpoints,
    72  			},
    73  		},
    74  	}
    75  }
    76  
    77  func makeDeployment(name string, roles ...gwacl.Role) gwacl.Deployment {
    78  	return gwacl.Deployment{
    79  		Name:     name,
    80  		RoleList: roles,
    81  	}
    82  }
    83  
    84  func makeInputEndpoint(port int, protocol string) gwacl.InputEndpoint {
    85  	return gwacl.InputEndpoint{
    86  		LocalPort: port,
    87  		Name:      fmt.Sprintf("%s%d", protocol, port),
    88  		Port:      port,
    89  		Protocol:  protocol,
    90  	}
    91  }
    92  
    93  func serialize(c *gc.C, object gwacl.AzureObject) []byte {
    94  	xml, err := object.Serialize()
    95  	c.Assert(err, gc.IsNil)
    96  	return []byte(xml)
    97  }
    98  
    99  func prepareDeploymentInfoResponse(
   100  	c *gc.C, dep gwacl.Deployment) []gwacl.DispatcherResponse {
   101  	return []gwacl.DispatcherResponse{
   102  		gwacl.NewDispatcherResponse(
   103  			serialize(c, &dep), http.StatusOK, nil),
   104  	}
   105  }
   106  
   107  func preparePortChangeConversation(
   108  	c *gc.C, service *gwacl.HostedServiceDescriptor,
   109  	deployments ...gwacl.Deployment) []gwacl.DispatcherResponse {
   110  	// Construct the series of responses to expected requests.
   111  	responses := []gwacl.DispatcherResponse{
   112  		// First, GetHostedServiceProperties
   113  		gwacl.NewDispatcherResponse(
   114  			serialize(c, &gwacl.HostedService{
   115  				Deployments:             deployments,
   116  				HostedServiceDescriptor: *service,
   117  				XMLNS: gwacl.XMLNS,
   118  			}),
   119  			http.StatusOK, nil),
   120  	}
   121  	for _, deployment := range deployments {
   122  		for _, role := range deployment.RoleList {
   123  			// GetRole returns a PersistentVMRole.
   124  			persistentRole := &gwacl.PersistentVMRole{
   125  				XMLNS:             gwacl.XMLNS,
   126  				RoleName:          role.RoleName,
   127  				ConfigurationSets: role.ConfigurationSets,
   128  			}
   129  			responses = append(responses, gwacl.NewDispatcherResponse(
   130  				serialize(c, persistentRole), http.StatusOK, nil))
   131  			// UpdateRole expects a 200 response, that's all.
   132  			responses = append(responses,
   133  				gwacl.NewDispatcherResponse(nil, http.StatusOK, nil))
   134  		}
   135  	}
   136  	return responses
   137  }
   138  
   139  // point is 1-indexed; it represents which request should fail.
   140  func failPortChangeConversationAt(point int, responses []gwacl.DispatcherResponse) {
   141  	responses[point-1] = gwacl.NewDispatcherResponse(
   142  		nil, http.StatusInternalServerError, nil)
   143  }
   144  
   145  type expectedRequest struct {
   146  	method     string
   147  	urlpattern string
   148  }
   149  
   150  func assertPortChangeConversation(c *gc.C, record []*gwacl.X509Request, expected []expectedRequest) {
   151  	c.Assert(record, gc.HasLen, len(expected))
   152  	for index, request := range record {
   153  		c.Check(request.Method, gc.Equals, expected[index].method)
   154  		c.Check(request.URL, gc.Matches, expected[index].urlpattern)
   155  	}
   156  }
   157  
   158  func (*instanceSuite) TestAddresses(c *gc.C) {
   159  	name := "service-name"
   160  	vnn := "Virt Net Name"
   161  	service := makeHostedServiceDescriptor(name)
   162  	responses := prepareDeploymentInfoResponse(c,
   163  		gwacl.Deployment{
   164  			RoleInstanceList: []gwacl.RoleInstance{
   165  				gwacl.RoleInstance{IPAddress: "1.2.3.4"},
   166  			},
   167  			VirtualNetworkName: vnn,
   168  		})
   169  
   170  	gwacl.PatchManagementAPIResponses(responses)
   171  	inst := azureInstance{*service, makeEnviron(c)}
   172  
   173  	expected := []instance.Address{
   174  		instance.Address{
   175  			"1.2.3.4",
   176  			instance.Ipv4Address,
   177  			vnn,
   178  			instance.NetworkCloudLocal,
   179  		},
   180  		instance.Address{
   181  			name + "." + AZURE_DOMAIN_NAME,
   182  			instance.HostName,
   183  			"",
   184  			instance.NetworkPublic,
   185  		},
   186  	}
   187  
   188  	addrs, err := inst.Addresses()
   189  	c.Check(err, gc.IsNil)
   190  
   191  	c.Check(addrs, jc.SameContents, expected)
   192  }
   193  
   194  func (*instanceSuite) TestOpenPorts(c *gc.C) {
   195  	service := makeHostedServiceDescriptor("service-name")
   196  	responses := preparePortChangeConversation(c, service,
   197  		makeDeployment("deployment-one",
   198  			makeRole("role-one"), makeRole("role-two")),
   199  		makeDeployment("deployment-two",
   200  			makeRole("role-three")))
   201  	record := gwacl.PatchManagementAPIResponses(responses)
   202  	azInstance := azureInstance{*service, makeEnviron(c)}
   203  
   204  	err := azInstance.OpenPorts("machine-id", []instance.Port{
   205  		{"tcp", 79}, {"tcp", 587}, {"udp", 9},
   206  	})
   207  
   208  	c.Assert(err, gc.IsNil)
   209  	assertPortChangeConversation(c, *record, []expectedRequest{
   210  		{"GET", ".*/services/hostedservices/service-name[?].*"},   // GetHostedServiceProperties
   211  		{"GET", ".*/deployments/deployment-one/roles/role-one"},   // GetRole
   212  		{"PUT", ".*/deployments/deployment-one/roles/role-one"},   // UpdateRole
   213  		{"GET", ".*/deployments/deployment-one/roles/role-two"},   // GetRole
   214  		{"PUT", ".*/deployments/deployment-one/roles/role-two"},   // UpdateRole
   215  		{"GET", ".*/deployments/deployment-two/roles/role-three"}, // GetRole
   216  		{"PUT", ".*/deployments/deployment-two/roles/role-three"}, // UpdateRole
   217  	})
   218  
   219  	// A representative UpdateRole payload includes configuration for the
   220  	// ports requested.
   221  	role := &gwacl.PersistentVMRole{}
   222  	err = role.Deserialize((*record)[2].Payload)
   223  	c.Assert(err, gc.IsNil)
   224  	c.Check(
   225  		*(role.ConfigurationSets[0].InputEndpoints),
   226  		gc.DeepEquals, []gwacl.InputEndpoint{
   227  			makeInputEndpoint(79, "tcp"),
   228  			makeInputEndpoint(587, "tcp"),
   229  			makeInputEndpoint(9, "udp"),
   230  		})
   231  }
   232  
   233  func (*instanceSuite) TestOpenPortsFailsWhenUnableToGetServiceProperties(c *gc.C) {
   234  	service := makeHostedServiceDescriptor("service-name")
   235  	responses := []gwacl.DispatcherResponse{
   236  		// GetHostedServiceProperties breaks.
   237  		gwacl.NewDispatcherResponse(nil, http.StatusInternalServerError, nil),
   238  	}
   239  	record := gwacl.PatchManagementAPIResponses(responses)
   240  	azInstance := azureInstance{*service, makeEnviron(c)}
   241  
   242  	err := azInstance.OpenPorts("machine-id", []instance.Port{
   243  		{"tcp", 79}, {"tcp", 587}, {"udp", 9},
   244  	})
   245  
   246  	c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]")
   247  	c.Check(*record, gc.HasLen, 1)
   248  }
   249  
   250  func (*instanceSuite) TestOpenPortsFailsWhenUnableToGetRole(c *gc.C) {
   251  	service := makeHostedServiceDescriptor("service-name")
   252  	responses := preparePortChangeConversation(c, service,
   253  		makeDeployment("deployment-one", makeRole("role-one")))
   254  	failPortChangeConversationAt(2, responses) // 2nd request, GetRole
   255  	record := gwacl.PatchManagementAPIResponses(responses)
   256  	azInstance := azureInstance{*service, makeEnviron(c)}
   257  
   258  	err := azInstance.OpenPorts("machine-id", []instance.Port{
   259  		{"tcp", 79}, {"tcp", 587}, {"udp", 9},
   260  	})
   261  
   262  	c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]")
   263  	c.Check(*record, gc.HasLen, 2)
   264  }
   265  
   266  func (*instanceSuite) TestOpenPortsFailsWhenUnableToUpdateRole(c *gc.C) {
   267  	service := makeHostedServiceDescriptor("service-name")
   268  	responses := preparePortChangeConversation(c, service,
   269  		makeDeployment("deployment-one", makeRole("role-one")))
   270  	failPortChangeConversationAt(3, responses) // 3rd request, UpdateRole
   271  	record := gwacl.PatchManagementAPIResponses(responses)
   272  	azInstance := azureInstance{*service, makeEnviron(c)}
   273  
   274  	err := azInstance.OpenPorts("machine-id", []instance.Port{
   275  		{"tcp", 79}, {"tcp", 587}, {"udp", 9},
   276  	})
   277  
   278  	c.Check(err, gc.ErrorMatches, "PUT request failed [(]500: Internal Server Error[)]")
   279  	c.Check(*record, gc.HasLen, 3)
   280  }
   281  
   282  func (*instanceSuite) TestClosePorts(c *gc.C) {
   283  	service := makeHostedServiceDescriptor("service-name")
   284  	responses := preparePortChangeConversation(c, service,
   285  		makeDeployment("deployment-one",
   286  			makeRole("role-one",
   287  				makeInputEndpoint(587, "tcp"),
   288  			),
   289  			makeRole("role-two",
   290  				makeInputEndpoint(79, "tcp"),
   291  				makeInputEndpoint(9, "udp"),
   292  			)),
   293  		makeDeployment("deployment-two",
   294  			makeRole("role-three",
   295  				makeInputEndpoint(9, "tcp"),
   296  				makeInputEndpoint(9, "udp"),
   297  			)))
   298  	record := gwacl.PatchManagementAPIResponses(responses)
   299  	azInstance := azureInstance{*service, makeEnviron(c)}
   300  
   301  	err := azInstance.ClosePorts("machine-id",
   302  		[]instance.Port{{"tcp", 587}, {"udp", 9}})
   303  
   304  	c.Assert(err, gc.IsNil)
   305  	assertPortChangeConversation(c, *record, []expectedRequest{
   306  		{"GET", ".*/services/hostedservices/service-name[?].*"},   // GetHostedServiceProperties
   307  		{"GET", ".*/deployments/deployment-one/roles/role-one"},   // GetRole
   308  		{"PUT", ".*/deployments/deployment-one/roles/role-one"},   // UpdateRole
   309  		{"GET", ".*/deployments/deployment-one/roles/role-two"},   // GetRole
   310  		{"PUT", ".*/deployments/deployment-one/roles/role-two"},   // UpdateRole
   311  		{"GET", ".*/deployments/deployment-two/roles/role-three"}, // GetRole
   312  		{"PUT", ".*/deployments/deployment-two/roles/role-three"}, // UpdateRole
   313  	})
   314  
   315  	// The first UpdateRole removes all endpoints from the role's
   316  	// configuration.
   317  	roleOne := &gwacl.PersistentVMRole{}
   318  	err = roleOne.Deserialize((*record)[2].Payload)
   319  	c.Assert(err, gc.IsNil)
   320  	c.Check(roleOne.ConfigurationSets[0].InputEndpoints, gc.IsNil)
   321  
   322  	// The second UpdateRole removes all but 79/TCP.
   323  	roleTwo := &gwacl.PersistentVMRole{}
   324  	err = roleTwo.Deserialize((*record)[4].Payload)
   325  	c.Assert(err, gc.IsNil)
   326  	c.Check(
   327  		roleTwo.ConfigurationSets[0].InputEndpoints,
   328  		gc.DeepEquals,
   329  		&[]gwacl.InputEndpoint{makeInputEndpoint(79, "tcp")})
   330  
   331  	// The third UpdateRole removes all but 9/TCP.
   332  	roleThree := &gwacl.PersistentVMRole{}
   333  	err = roleThree.Deserialize((*record)[6].Payload)
   334  	c.Assert(err, gc.IsNil)
   335  	c.Check(
   336  		roleThree.ConfigurationSets[0].InputEndpoints,
   337  		gc.DeepEquals,
   338  		&[]gwacl.InputEndpoint{makeInputEndpoint(9, "tcp")})
   339  }
   340  
   341  func (*instanceSuite) TestClosePortsFailsWhenUnableToGetServiceProperties(c *gc.C) {
   342  	service := makeHostedServiceDescriptor("service-name")
   343  	responses := []gwacl.DispatcherResponse{
   344  		// GetHostedServiceProperties breaks.
   345  		gwacl.NewDispatcherResponse(nil, http.StatusInternalServerError, nil),
   346  	}
   347  	record := gwacl.PatchManagementAPIResponses(responses)
   348  	azInstance := azureInstance{*service, makeEnviron(c)}
   349  
   350  	err := azInstance.ClosePorts("machine-id", []instance.Port{
   351  		{"tcp", 79}, {"tcp", 587}, {"udp", 9},
   352  	})
   353  
   354  	c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]")
   355  	c.Check(*record, gc.HasLen, 1)
   356  }
   357  
   358  func (*instanceSuite) TestClosePortsFailsWhenUnableToGetRole(c *gc.C) {
   359  	service := makeHostedServiceDescriptor("service-name")
   360  	responses := preparePortChangeConversation(c, service,
   361  		makeDeployment("deployment-one", makeRole("role-one")))
   362  	failPortChangeConversationAt(2, responses) // 2nd request, GetRole
   363  	record := gwacl.PatchManagementAPIResponses(responses)
   364  	azInstance := azureInstance{*service, makeEnviron(c)}
   365  
   366  	err := azInstance.ClosePorts("machine-id", []instance.Port{
   367  		{"tcp", 79}, {"tcp", 587}, {"udp", 9},
   368  	})
   369  
   370  	c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]")
   371  	c.Check(*record, gc.HasLen, 2)
   372  }
   373  
   374  func (*instanceSuite) TestClosePortsFailsWhenUnableToUpdateRole(c *gc.C) {
   375  	service := makeHostedServiceDescriptor("service-name")
   376  	responses := preparePortChangeConversation(c, service,
   377  		makeDeployment("deployment-one", makeRole("role-one")))
   378  	failPortChangeConversationAt(3, responses) // 3rd request, UpdateRole
   379  	record := gwacl.PatchManagementAPIResponses(responses)
   380  	azInstance := azureInstance{*service, makeEnviron(c)}
   381  
   382  	err := azInstance.ClosePorts("machine-id", []instance.Port{
   383  		{"tcp", 79}, {"tcp", 587}, {"udp", 9},
   384  	})
   385  
   386  	c.Check(err, gc.ErrorMatches, "PUT request failed [(]500: Internal Server Error[)]")
   387  	c.Check(*record, gc.HasLen, 3)
   388  }
   389  
   390  func (*instanceSuite) TestConvertAndFilterEndpoints(c *gc.C) {
   391  	env := makeEnviron(c)
   392  	endpoints := []gwacl.InputEndpoint{
   393  		{
   394  			LocalPort: 123,
   395  			Protocol:  "udp",
   396  			Name:      "test123",
   397  			Port:      1123,
   398  		},
   399  		{
   400  			LocalPort: 456,
   401  			Protocol:  "tcp",
   402  			Name:      "test456",
   403  			Port:      44,
   404  		}}
   405  	endpoints = append(endpoints, env.getInitialEndpoints()...)
   406  	expectedPorts := []instance.Port{
   407  		{
   408  			Number:   1123,
   409  			Protocol: "udp",
   410  		},
   411  		{
   412  			Number:   44,
   413  			Protocol: "tcp",
   414  		}}
   415  	c.Check(convertAndFilterEndpoints(endpoints, env), gc.DeepEquals, expectedPorts)
   416  }
   417  
   418  func (*instanceSuite) TestConvertAndFilterEndpointsEmptySlice(c *gc.C) {
   419  	env := makeEnviron(c)
   420  	ports := convertAndFilterEndpoints([]gwacl.InputEndpoint{}, env)
   421  	c.Check(ports, gc.HasLen, 0)
   422  }
   423  
   424  func (*instanceSuite) TestPorts(c *gc.C) {
   425  	service := makeHostedServiceDescriptor("service-name")
   426  	endpoints := []gwacl.InputEndpoint{
   427  		{
   428  			LocalPort: 223,
   429  			Protocol:  "udp",
   430  			Name:      "test223",
   431  			Port:      2123,
   432  		},
   433  		{
   434  			LocalPort: 123,
   435  			Protocol:  "udp",
   436  			Name:      "test123",
   437  			Port:      1123,
   438  		},
   439  		{
   440  			LocalPort: 456,
   441  			Protocol:  "tcp",
   442  			Name:      "test456",
   443  			Port:      4456,
   444  		}}
   445  
   446  	responses := preparePortChangeConversation(c, service,
   447  		makeDeployment("deployment-one",
   448  			makeRole("role-one", endpoints...)))
   449  	record := gwacl.PatchManagementAPIResponses(responses)
   450  	azInstance := azureInstance{*service, makeEnviron(c)}
   451  
   452  	ports, err := azInstance.Ports("machine-id")
   453  
   454  	c.Assert(err, gc.IsNil)
   455  	assertPortChangeConversation(c, *record, []expectedRequest{
   456  		{"GET", ".*/services/hostedservices/service-name[?].*"}, // GetHostedServiceProperties
   457  		{"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole
   458  	})
   459  
   460  	c.Check(
   461  		ports,
   462  		gc.DeepEquals,
   463  		// The result is sorted using instance.SortPorts() (i.e. first by protocol,
   464  		// then by number).
   465  		[]instance.Port{
   466  			{Number: 4456, Protocol: "tcp"},
   467  			{Number: 1123, Protocol: "udp"},
   468  			{Number: 2123, Protocol: "udp"},
   469  		})
   470  }
   471  
   472  func (*instanceSuite) TestPortsErrorsIfMoreThanOneRole(c *gc.C) {
   473  	service := makeHostedServiceDescriptor("service-name")
   474  	responses := preparePortChangeConversation(c, service,
   475  		makeDeployment("deployment-one",
   476  			makeRole("role-one"), makeRole("role-two")))
   477  	gwacl.PatchManagementAPIResponses(responses)
   478  	azInstance := azureInstance{*service, makeEnviron(c)}
   479  
   480  	_, err := azInstance.Ports("machine-id")
   481  
   482  	c.Check(err, gc.ErrorMatches, ".*more than one Azure role inside the deployment.*")
   483  }
   484  
   485  func (*instanceSuite) TestPortsErrorsIfMoreThanOneDeployment(c *gc.C) {
   486  	service := makeHostedServiceDescriptor("service-name")
   487  	responses := preparePortChangeConversation(c, service,
   488  		makeDeployment("deployment-one",
   489  			makeRole("role-one")),
   490  		makeDeployment("deployment-two",
   491  			makeRole("role-two")))
   492  	gwacl.PatchManagementAPIResponses(responses)
   493  	azInstance := azureInstance{*service, makeEnviron(c)}
   494  
   495  	_, err := azInstance.Ports("machine-id")
   496  
   497  	c.Check(err, gc.ErrorMatches, ".*more than one Azure deployment inside the service.*")
   498  }
   499  
   500  func (*instanceSuite) TestPortsReturnsEmptySliceIfNoDeployment(c *gc.C) {
   501  	service := makeHostedServiceDescriptor("service-name")
   502  	responses := preparePortChangeConversation(c, service)
   503  	gwacl.PatchManagementAPIResponses(responses)
   504  	azInstance := azureInstance{*service, makeEnviron(c)}
   505  
   506  	ports, err := azInstance.Ports("machine-id")
   507  
   508  	c.Assert(err, gc.IsNil)
   509  	c.Check(ports, gc.HasLen, 0)
   510  }