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