// listPorts returns the slice of port ranges (network.PortRange) // that this machine has opened. The returned list does not contain // the "initial port ranges" (i.e. the port ranges every instance // shoud have opened). func (azInstance *azureInstance) listPorts(api *gwacl.ManagementAPI) ([]network.PortRange, error) { endpoints, err := api.ListRoleEndpoints(&gwacl.ListRoleEndpointsRequest{ ServiceName: azInstance.serviceName(), DeploymentName: azInstance.deploymentName, RoleName: azInstance.roleName, }) if err != nil { return nil, err } ports := convertAndFilterEndpoints(endpoints, azInstance.environ, azInstance.maskStateServerPorts) return ports, nil }
// newHostedService creates a hosted service. It will make up a unique name, // starting with the given prefix. func newHostedService(azure *gwacl.ManagementAPI, prefix, affinityGroupName, label string) (*gwacl.HostedService, error) { var err error var createdService *gwacl.CreateHostedService for tries := 10; tries > 0 && err == nil && createdService == nil; tries-- { createdService, err = attemptCreateService(azure, prefix, affinityGroupName, label) } if err != nil { return nil, errors.Annotate(err, "could not create hosted service") } if createdService == nil { return nil, fmt.Errorf("could not come up with a unique hosted service name - is your randomizer initialized?") } return azure.GetHostedServiceProperties(createdService.ServiceName, true) }
// attemptCreateService tries to create a new hosted service on Azure, with a // name it chooses (based on the given prefix), but recognizes that the name // may not be available. If the name is not available, it does not treat that // as an error but just returns nil. func attemptCreateService(azure *gwacl.ManagementAPI, prefix string, affinityGroupName string, location string) (*gwacl.CreateHostedService, error) { var err error name := gwacl.MakeRandomHostedServiceName(prefix) err = azure.CheckHostedServiceNameAvailability(name) if err != nil { // The calling function should retry. return nil, nil } req := gwacl.NewCreateHostedServiceWithLocation(name, name, location) req.AffinityGroup = affinityGroupName err = azure.AddHostedService(req) if err != nil { return nil, err } return req, nil }
// attemptCreateService tries to create a new hosted service on Azure, with a // name it chooses (based on the given prefix), but recognizes that the name // may not be available. If the name is not available, it does not treat that // as an error but just returns nil. func attemptCreateService(azure *gwacl.ManagementAPI, prefix string, affinityGroupName string) (*gwacl.CreateHostedService, error) { name := gwacl.MakeRandomHostedServiceName(prefix) req := gwacl.NewCreateHostedServiceWithLocation(name, name, serviceLocation) req.AffinityGroup = affinityGroupName err := azure.AddHostedService(req) azErr, isAzureError := err.(*gwacl.AzureError) if isAzureError && azErr.HTTPStatus == http.StatusConflict { // Conflict. As far as we can see, this only happens if the // name was already in use. It's still dangerous to assume // that we know it can't be anything else, but there's nothing // else in the error that we can use for closer identifcation. return nil, nil } if err != nil { return nil, err } return req, nil }
// closeEndpoints closes the endpoints in the Azure deployment. func (azInstance *azureInstance) closeEndpoints(api *gwacl.ManagementAPI, portRanges []network.PortRange) error { request := &gwacl.RemoveRoleEndpointsRequest{ ServiceName: azInstance.serviceName(), DeploymentName: azInstance.deploymentName, RoleName: azInstance.roleName, } for _, portRange := range portRanges { name := fmt.Sprintf("%s%d-%d", portRange.Protocol, portRange.FromPort, portRange.ToPort) for port := portRange.FromPort; port <= portRange.ToPort; port++ { request.InputEndpoints = append(request.InputEndpoints, gwacl.InputEndpoint{ LocalPort: port, Name: fmt.Sprintf("%s_%d", name, port), Port: port, Protocol: portRange.Protocol, LoadBalancedEndpointSetName: name, }) } } return api.RemoveRoleEndpoints(request) }
// openEndpoints opens the endpoints in the Azure deployment. func (azInstance *azureInstance) openEndpoints(api *gwacl.ManagementAPI, portRanges []network.PortRange) error { request := &gwacl.AddRoleEndpointsRequest{ ServiceName: azInstance.serviceName(), DeploymentName: azInstance.deploymentName, RoleName: azInstance.roleName, } for _, portRange := range portRanges { name := fmt.Sprintf("%s%d-%d", portRange.Protocol, portRange.FromPort, portRange.ToPort) for port := portRange.FromPort; port <= portRange.ToPort; port++ { endpoint := gwacl.InputEndpoint{ LocalPort: port, Name: fmt.Sprintf("%s_range_%d", name, port), Port: port, Protocol: portRange.Protocol, } if azInstance.supportsLoadBalancing() { probePort := port if strings.ToUpper(endpoint.Protocol) == "UDP" { // Load balancing needs a TCP port to probe, or an HTTP // server port & path to query. For UDP, we just use the // machine's SSH agent port to test machine liveness. // // It probably doesn't make sense to load balance most UDP // protocols transparently, but that's an application level // concern. probePort = 22 } endpoint.LoadBalancedEndpointSetName = name endpoint.LoadBalancerProbe = &gwacl.LoadBalancerProbe{ Port: probePort, Protocol: "TCP", } } request.InputEndpoints = append(request.InputEndpoints, endpoint) } } return api.AddRoleEndpoints(request) }
// createInstance creates all of the Azure entities necessary for a // new instance. This includes Cloud Service, Deployment and Role. // // If serviceName is non-empty, then createInstance will assign to // the Cloud Service with that name. Otherwise, a new Cloud Service // will be created. func (env *azureEnviron) createInstance(azure *gwacl.ManagementAPI, role *gwacl.Role, serviceName string, stateServer bool) (resultInst instance.Instance, resultErr error) { var inst instance.Instance defer func() { if inst != nil && resultErr != nil { if err := env.StopInstances(inst.Id()); err != nil { // Failure upon failure. Log it, but return the original error. logger.Errorf("error releasing failed instance: %v", err) } } }() var err error var service *gwacl.HostedService if serviceName != "" { logger.Debugf("creating instance in existing cloud service %q", serviceName) service, err = azure.GetHostedServiceProperties(serviceName, true) } else { logger.Debugf("creating instance in new cloud service") // If we're creating a cloud service for state servers, // we will want to open additional ports. We need to // record this against the cloud service, so we use a // special label for the purpose. var label string if stateServer { label = stateServerLabel } service, err = newHostedService(azure, env.getEnvPrefix(), env.getAffinityGroupName(), label) } if err != nil { return nil, err } if len(service.Deployments) == 0 { // This is a newly created cloud service, so we // should destroy it if anything below fails. defer func() { if resultErr != nil { azure.DeleteHostedService(service.ServiceName) // Destroying the hosted service destroys the instance, // so ensure StopInstances isn't called. inst = nil } }() // Create an initial deployment. deployment := gwacl.NewDeploymentForCreateVMDeployment( deploymentNameV2(service.ServiceName), deploymentSlot, deploymentNameV2(service.ServiceName), []gwacl.Role{*role}, env.getVirtualNetworkName(), ) if err := azure.AddDeployment(deployment, service.ServiceName); err != nil { return nil, errors.Annotate(err, "error creating VM deployment") } service.Deployments = append(service.Deployments, *deployment) } else { // Update the deployment. deployment := &service.Deployments[0] if err := azure.AddRole(&gwacl.AddRoleRequest{ ServiceName: service.ServiceName, DeploymentName: deployment.Name, PersistentVMRole: (*gwacl.PersistentVMRole)(role), }); err != nil { return nil, err } deployment.RoleList = append(deployment.RoleList, *role) } return env.getInstance(service, role.RoleName) }