github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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  	jc "github.com/juju/testing/checkers"
    12  	gc "launchpad.net/gocheck"
    13  	"launchpad.net/gwacl"
    14  
    15  	"github.com/juju/juju/instance"
    16  	"github.com/juju/juju/testing"
    17  )
    18  
    19  type instanceSuite struct {
    20  	testing.BaseSuite
    21  	env        *azureEnviron
    22  	service    *gwacl.HostedService
    23  	deployment *gwacl.Deployment
    24  	role       *gwacl.Role
    25  	instance   *azureInstance
    26  }
    27  
    28  var _ = gc.Suite(&instanceSuite{})
    29  
    30  func (s *instanceSuite) SetUpTest(c *gc.C) {
    31  	s.env = makeEnviron(c)
    32  	s.service = makeDeployment(s.env, "service-name")
    33  	s.deployment = &s.service.Deployments[0]
    34  	s.deployment.Name = "deployment-one"
    35  	s.role = &s.deployment.RoleList[0]
    36  	s.role.RoleName = "role-one"
    37  	inst, err := s.env.getInstance(s.service, s.role.RoleName)
    38  	c.Assert(err, gc.IsNil)
    39  	c.Assert(inst, gc.FitsTypeOf, &azureInstance{})
    40  	s.instance = inst.(*azureInstance)
    41  }
    42  
    43  func configSetNetwork(role *gwacl.Role) *gwacl.ConfigurationSet {
    44  	for i, configSet := range role.ConfigurationSets {
    45  		if configSet.ConfigurationSetType == gwacl.CONFIG_SET_NETWORK {
    46  			return &role.ConfigurationSets[i]
    47  		}
    48  	}
    49  	return nil
    50  }
    51  
    52  // makeHostedServiceDescriptor creates a HostedServiceDescriptor with the
    53  // given service name.
    54  func makeHostedServiceDescriptor(name string) *gwacl.HostedServiceDescriptor {
    55  	labelBase64 := base64.StdEncoding.EncodeToString([]byte("label"))
    56  	return &gwacl.HostedServiceDescriptor{ServiceName: name, Label: labelBase64}
    57  }
    58  
    59  func (*instanceSuite) TestId(c *gc.C) {
    60  	azInstance := azureInstance{instanceId: "whatever"}
    61  	c.Check(azInstance.Id(), gc.Equals, instance.Id("whatever"))
    62  }
    63  
    64  func (*instanceSuite) TestStatus(c *gc.C) {
    65  	var inst azureInstance
    66  	c.Check(inst.Status(), gc.Equals, "")
    67  	inst.roleInstance = &gwacl.RoleInstance{InstanceStatus: "anyoldthing"}
    68  	c.Check(inst.Status(), gc.Equals, "anyoldthing")
    69  }
    70  
    71  func makeInputEndpoint(port int, protocol string) gwacl.InputEndpoint {
    72  	name := fmt.Sprintf("%s%d", protocol, port)
    73  	probe := &gwacl.LoadBalancerProbe{Port: port, Protocol: "TCP"}
    74  	if protocol == "udp" {
    75  		// We just use port 22 (SSH) for the
    76  		// probe when a UDP port is exposed.
    77  		probe.Port = 22
    78  	}
    79  	return gwacl.InputEndpoint{
    80  		LocalPort: port,
    81  		Name:      name,
    82  		LoadBalancedEndpointSetName: name,
    83  		LoadBalancerProbe:           probe,
    84  		Port:                        port,
    85  		Protocol:                    protocol,
    86  	}
    87  }
    88  
    89  func serialize(c *gc.C, object gwacl.AzureObject) []byte {
    90  	xml, err := object.Serialize()
    91  	c.Assert(err, gc.IsNil)
    92  	return []byte(xml)
    93  }
    94  
    95  func prepareDeploymentInfoResponse(
    96  	c *gc.C, dep gwacl.Deployment) []gwacl.DispatcherResponse {
    97  	return []gwacl.DispatcherResponse{
    98  		gwacl.NewDispatcherResponse(
    99  			serialize(c, &dep), http.StatusOK, nil),
   100  	}
   101  }
   102  
   103  func preparePortChangeConversation(c *gc.C, role *gwacl.Role) []gwacl.DispatcherResponse {
   104  	persistentRole := &gwacl.PersistentVMRole{
   105  		XMLNS:             gwacl.XMLNS,
   106  		RoleName:          role.RoleName,
   107  		ConfigurationSets: role.ConfigurationSets,
   108  	}
   109  	return []gwacl.DispatcherResponse{
   110  		// GetRole returns a PersistentVMRole.
   111  		gwacl.NewDispatcherResponse(serialize(c, persistentRole), http.StatusOK, nil),
   112  		// UpdateRole expects a 200 response, that's all.
   113  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
   114  	}
   115  }
   116  
   117  // point is 1-indexed; it represents which request should fail.
   118  func failPortChangeConversationAt(point int, responses []gwacl.DispatcherResponse) {
   119  	responses[point-1] = gwacl.NewDispatcherResponse(
   120  		nil, http.StatusInternalServerError, nil)
   121  }
   122  
   123  type expectedRequest struct {
   124  	method     string
   125  	urlpattern string
   126  }
   127  
   128  func assertPortChangeConversation(c *gc.C, record []*gwacl.X509Request, expected []expectedRequest) {
   129  	c.Assert(record, gc.HasLen, len(expected))
   130  	for index, request := range record {
   131  		c.Check(request.Method, gc.Equals, expected[index].method)
   132  		c.Check(request.URL, gc.Matches, expected[index].urlpattern)
   133  	}
   134  }
   135  
   136  func (s *instanceSuite) TestAddresses(c *gc.C) {
   137  	vnn := s.env.getVirtualNetworkName()
   138  	responses := prepareDeploymentInfoResponse(c, gwacl.Deployment{
   139  		RoleInstanceList: []gwacl.RoleInstance{{
   140  			RoleName:  s.role.RoleName,
   141  			IPAddress: "1.2.3.4",
   142  		}},
   143  		VirtualNetworkName: vnn,
   144  	})
   145  	gwacl.PatchManagementAPIResponses(responses)
   146  
   147  	expected := []instance.Address{
   148  		instance.Address{
   149  			"1.2.3.4",
   150  			instance.Ipv4Address,
   151  			vnn,
   152  			instance.NetworkCloudLocal,
   153  		},
   154  		instance.Address{
   155  			s.service.ServiceName + "." + AzureDomainName,
   156  			instance.HostName,
   157  			"",
   158  			instance.NetworkPublic,
   159  		},
   160  	}
   161  
   162  	addrs, err := s.instance.Addresses()
   163  	c.Check(err, gc.IsNil)
   164  	c.Check(addrs, jc.SameContents, expected)
   165  }
   166  
   167  func (s *instanceSuite) TestOpenPorts(c *gc.C) {
   168  	// Close the default ports.
   169  	configSetNetwork((*gwacl.Role)(s.role)).InputEndpoints = nil
   170  
   171  	responses := preparePortChangeConversation(c, s.role)
   172  	record := gwacl.PatchManagementAPIResponses(responses)
   173  	err := s.instance.OpenPorts("machine-id", []instance.Port{
   174  		{"tcp", 79}, {"tcp", 587}, {"udp", 9},
   175  	})
   176  	c.Assert(err, gc.IsNil)
   177  
   178  	assertPortChangeConversation(c, *record, []expectedRequest{
   179  		{"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole
   180  		{"PUT", ".*/deployments/deployment-one/roles/role-one"}, // UpdateRole
   181  	})
   182  
   183  	// A representative UpdateRole payload includes configuration for the
   184  	// ports requested.
   185  	role := &gwacl.PersistentVMRole{}
   186  	err = role.Deserialize((*record)[1].Payload)
   187  	c.Assert(err, gc.IsNil)
   188  	c.Check(
   189  		*configSetNetwork((*gwacl.Role)(role)).InputEndpoints,
   190  		gc.DeepEquals,
   191  		[]gwacl.InputEndpoint{
   192  			makeInputEndpoint(79, "tcp"),
   193  			makeInputEndpoint(587, "tcp"),
   194  			makeInputEndpoint(9, "udp"),
   195  		},
   196  	)
   197  }
   198  
   199  func (s *instanceSuite) TestOpenPortsFailsWhenUnableToGetRole(c *gc.C) {
   200  	responses := preparePortChangeConversation(c, s.role)
   201  	failPortChangeConversationAt(1, responses) // 1st request, GetRole
   202  	record := gwacl.PatchManagementAPIResponses(responses)
   203  	err := s.instance.OpenPorts("machine-id", []instance.Port{
   204  		{"tcp", 79}, {"tcp", 587}, {"udp", 9},
   205  	})
   206  	c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]")
   207  	c.Check(*record, gc.HasLen, 1)
   208  }
   209  
   210  func (s *instanceSuite) TestOpenPortsFailsWhenUnableToUpdateRole(c *gc.C) {
   211  	responses := preparePortChangeConversation(c, s.role)
   212  	failPortChangeConversationAt(2, responses) // 2nd request, UpdateRole
   213  	record := gwacl.PatchManagementAPIResponses(responses)
   214  	err := s.instance.OpenPorts("machine-id", []instance.Port{
   215  		{"tcp", 79}, {"tcp", 587}, {"udp", 9},
   216  	})
   217  	c.Check(err, gc.ErrorMatches, "PUT request failed [(]500: Internal Server Error[)]")
   218  	c.Check(*record, gc.HasLen, 2)
   219  }
   220  
   221  func (s *instanceSuite) TestClosePorts(c *gc.C) {
   222  	type test struct {
   223  		inputPorts  []instance.Port
   224  		removePorts []instance.Port
   225  		outputPorts []instance.Port
   226  	}
   227  
   228  	tests := []test{{
   229  		inputPorts:  []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}},
   230  		removePorts: nil,
   231  		outputPorts: []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}},
   232  	}, {
   233  		inputPorts:  []instance.Port{{"tcp", 1}},
   234  		removePorts: []instance.Port{{"udp", 1}},
   235  		outputPorts: []instance.Port{{"tcp", 1}},
   236  	}, {
   237  		inputPorts:  []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}},
   238  		removePorts: []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}},
   239  		outputPorts: []instance.Port{},
   240  	}, {
   241  		inputPorts:  []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}},
   242  		removePorts: []instance.Port{{"tcp", 99}},
   243  		outputPorts: []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}},
   244  	}}
   245  
   246  	for i, test := range tests {
   247  		c.Logf("test %d: %#v", i, test)
   248  
   249  		inputEndpoints := make([]gwacl.InputEndpoint, len(test.inputPorts))
   250  		for i, port := range test.inputPorts {
   251  			inputEndpoints[i] = makeInputEndpoint(port.Number, port.Protocol)
   252  		}
   253  		configSetNetwork(s.role).InputEndpoints = &inputEndpoints
   254  		responses := preparePortChangeConversation(c, s.role)
   255  		record := gwacl.PatchManagementAPIResponses(responses)
   256  
   257  		err := s.instance.ClosePorts("machine-id", test.removePorts)
   258  		c.Assert(err, gc.IsNil)
   259  		assertPortChangeConversation(c, *record, []expectedRequest{
   260  			{"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole
   261  			{"PUT", ".*/deployments/deployment-one/roles/role-one"}, // UpdateRole
   262  		})
   263  
   264  		// The first UpdateRole removes all endpoints from the role's
   265  		// configuration.
   266  		roleOne := &gwacl.PersistentVMRole{}
   267  		err = roleOne.Deserialize((*record)[1].Payload)
   268  		c.Assert(err, gc.IsNil)
   269  		endpoints := configSetNetwork((*gwacl.Role)(roleOne)).InputEndpoints
   270  		if len(test.outputPorts) == 0 {
   271  			c.Check(endpoints, gc.IsNil)
   272  		} else {
   273  			c.Check(endpoints, gc.NotNil)
   274  			c.Check(convertAndFilterEndpoints(*endpoints, s.env, false), gc.DeepEquals, test.outputPorts)
   275  		}
   276  	}
   277  }
   278  
   279  func (s *instanceSuite) TestClosePortsFailsWhenUnableToGetRole(c *gc.C) {
   280  	responses := preparePortChangeConversation(c, s.role)
   281  	failPortChangeConversationAt(1, responses) // 1st request, GetRole
   282  	record := gwacl.PatchManagementAPIResponses(responses)
   283  	err := s.instance.ClosePorts("machine-id", []instance.Port{
   284  		{"tcp", 79}, {"tcp", 587}, {"udp", 9},
   285  	})
   286  	c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]")
   287  	c.Check(*record, gc.HasLen, 1)
   288  }
   289  
   290  func (s *instanceSuite) TestClosePortsFailsWhenUnableToUpdateRole(c *gc.C) {
   291  	responses := preparePortChangeConversation(c, s.role)
   292  	failPortChangeConversationAt(2, responses) // 2nd request, UpdateRole
   293  	record := gwacl.PatchManagementAPIResponses(responses)
   294  	err := s.instance.ClosePorts("machine-id", []instance.Port{
   295  		{"tcp", 79}, {"tcp", 587}, {"udp", 9},
   296  	})
   297  	c.Check(err, gc.ErrorMatches, "PUT request failed [(]500: Internal Server Error[)]")
   298  	c.Check(*record, gc.HasLen, 2)
   299  }
   300  
   301  func (s *instanceSuite) TestConvertAndFilterEndpoints(c *gc.C) {
   302  	endpoints := []gwacl.InputEndpoint{
   303  		{
   304  			LocalPort: 123,
   305  			Protocol:  "udp",
   306  			Name:      "test123",
   307  			Port:      1123,
   308  		},
   309  		{
   310  			LocalPort: 456,
   311  			Protocol:  "tcp",
   312  			Name:      "test456",
   313  			Port:      44,
   314  		}}
   315  	endpoints = append(endpoints, s.env.getInitialEndpoints(true)...)
   316  	expectedPorts := []instance.Port{
   317  		{
   318  			Number:   1123,
   319  			Protocol: "udp",
   320  		},
   321  		{
   322  			Number:   44,
   323  			Protocol: "tcp",
   324  		}}
   325  	c.Check(convertAndFilterEndpoints(endpoints, s.env, true), gc.DeepEquals, expectedPorts)
   326  }
   327  
   328  func (s *instanceSuite) TestConvertAndFilterEndpointsEmptySlice(c *gc.C) {
   329  	ports := convertAndFilterEndpoints([]gwacl.InputEndpoint{}, s.env, true)
   330  	c.Check(ports, gc.HasLen, 0)
   331  }
   332  
   333  func (s *instanceSuite) TestPorts(c *gc.C) {
   334  	s.testPorts(c, false)
   335  	s.testPorts(c, true)
   336  }
   337  
   338  func (s *instanceSuite) testPorts(c *gc.C, maskStateServerPorts bool) {
   339  	// Update the role's endpoints by hand.
   340  	configSetNetwork(s.role).InputEndpoints = &[]gwacl.InputEndpoint{{
   341  		LocalPort: 223,
   342  		Protocol:  "udp",
   343  		Name:      "test223",
   344  		Port:      2123,
   345  	}, {
   346  		LocalPort: 123,
   347  		Protocol:  "udp",
   348  		Name:      "test123",
   349  		Port:      1123,
   350  	}, {
   351  		LocalPort: 456,
   352  		Protocol:  "tcp",
   353  		Name:      "test456",
   354  		Port:      4456,
   355  	}, {
   356  		LocalPort: s.env.Config().StatePort(),
   357  		Protocol:  "tcp",
   358  		Name:      "stateserver",
   359  		Port:      s.env.Config().StatePort(),
   360  	}, {
   361  		LocalPort: s.env.Config().APIPort(),
   362  		Protocol:  "tcp",
   363  		Name:      "apiserver",
   364  		Port:      s.env.Config().APIPort(),
   365  	}}
   366  
   367  	responses := preparePortChangeConversation(c, s.role)
   368  	record := gwacl.PatchManagementAPIResponses(responses)
   369  	s.instance.maskStateServerPorts = maskStateServerPorts
   370  	ports, err := s.instance.Ports("machine-id")
   371  	c.Assert(err, gc.IsNil)
   372  	assertPortChangeConversation(c, *record, []expectedRequest{
   373  		{"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole
   374  	})
   375  
   376  	expected := []instance.Port{
   377  		{Number: 4456, Protocol: "tcp"},
   378  		{Number: 1123, Protocol: "udp"},
   379  		{Number: 2123, Protocol: "udp"},
   380  	}
   381  	if !maskStateServerPorts {
   382  		expected = append(expected, instance.Port{Number: s.env.Config().StatePort(), Protocol: "tcp"})
   383  		expected = append(expected, instance.Port{Number: s.env.Config().APIPort(), Protocol: "tcp"})
   384  		instance.SortPorts(expected)
   385  	}
   386  	c.Check(ports, gc.DeepEquals, expected)
   387  }