github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/provider/azure/instance.go (about) 1 // Copyright 2011, 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package azure 5 6 import ( 7 "fmt" 8 "strings" 9 10 "launchpad.net/gwacl" 11 12 "github.com/juju/juju/instance" 13 "github.com/juju/juju/network" 14 ) 15 16 const AzureDomainName = "cloudapp.net" 17 18 type azureInstance struct { 19 environ *azureEnviron 20 hostedService *gwacl.HostedServiceDescriptor 21 roleInstance *gwacl.RoleInstance 22 instanceId instance.Id 23 deploymentName string 24 roleName string 25 maskStateServerPorts bool 26 } 27 28 // azureInstance implements Instance. 29 var _ instance.Instance = (*azureInstance)(nil) 30 31 // Id is specified in the Instance interface. 32 func (azInstance *azureInstance) Id() instance.Id { 33 return azInstance.instanceId 34 } 35 36 // supportsLoadBalancing returns true iff the instance is 37 // not a legacy instance where endpoints may have been 38 // created without load balancing set names associated. 39 func (azInstance *azureInstance) supportsLoadBalancing() bool { 40 v1Name := deploymentNameV1(azInstance.hostedService.ServiceName) 41 return azInstance.deploymentName != v1Name 42 } 43 44 // Status is specified in the Instance interface. 45 func (azInstance *azureInstance) Status() string { 46 if azInstance.roleInstance == nil { 47 return "" 48 } 49 return azInstance.roleInstance.InstanceStatus 50 } 51 52 func (azInstance *azureInstance) serviceName() string { 53 return azInstance.hostedService.ServiceName 54 } 55 56 // Addresses is specified in the Instance interface. 57 func (azInstance *azureInstance) Addresses() ([]network.Address, error) { 58 var addrs []network.Address 59 if ip := azInstance.ipAddress(); ip != "" { 60 addrs = append(addrs, network.Address{ 61 Value: ip, 62 Type: network.IPv4Address, 63 NetworkName: azInstance.environ.getVirtualNetworkName(), 64 Scope: network.ScopeCloudLocal, 65 }) 66 } 67 name := fmt.Sprintf("%s.%s", azInstance.serviceName(), AzureDomainName) 68 host := network.Address{ 69 Value: name, 70 Type: network.HostName, 71 NetworkName: "", 72 Scope: network.ScopePublic, 73 } 74 addrs = append(addrs, host) 75 return addrs, nil 76 } 77 78 func (azInstance *azureInstance) ipAddress() string { 79 if azInstance.roleInstance == nil { 80 // RoleInstance hasn't finished deploying. 81 return "" 82 } 83 return azInstance.roleInstance.IPAddress 84 } 85 86 // OpenPorts is specified in the Instance interface. 87 func (azInstance *azureInstance) OpenPorts(machineId string, portRange []network.PortRange) error { 88 return azInstance.apiCall(true, func(api *gwacl.ManagementAPI) error { 89 return azInstance.openEndpoints(api, portRange) 90 }) 91 } 92 93 // apiCall wraps a call to the azure API, optionally locking the environment. 94 func (azInstance *azureInstance) apiCall(lock bool, f func(*gwacl.ManagementAPI) error) error { 95 env := azInstance.environ 96 api := env.getSnapshot().api 97 if lock { 98 env.Lock() 99 defer env.Unlock() 100 } 101 return f(api) 102 } 103 104 // openEndpoints opens the endpoints in the Azure deployment. 105 func (azInstance *azureInstance) openEndpoints(api *gwacl.ManagementAPI, portRanges []network.PortRange) error { 106 request := &gwacl.AddRoleEndpointsRequest{ 107 ServiceName: azInstance.serviceName(), 108 DeploymentName: azInstance.deploymentName, 109 RoleName: azInstance.roleName, 110 } 111 for _, portRange := range portRanges { 112 name := fmt.Sprintf("%s%d-%d", portRange.Protocol, portRange.FromPort, portRange.ToPort) 113 for port := portRange.FromPort; port <= portRange.ToPort; port++ { 114 endpoint := gwacl.InputEndpoint{ 115 LocalPort: port, 116 Name: fmt.Sprintf("%s_range_%d", name, port), 117 Port: port, 118 Protocol: portRange.Protocol, 119 } 120 if azInstance.supportsLoadBalancing() { 121 probePort := port 122 if strings.ToUpper(endpoint.Protocol) == "UDP" { 123 // Load balancing needs a TCP port to probe, or an HTTP 124 // server port & path to query. For UDP, we just use the 125 // machine's SSH agent port to test machine liveness. 126 // 127 // It probably doesn't make sense to load balance most UDP 128 // protocols transparently, but that's an application level 129 // concern. 130 probePort = 22 131 } 132 endpoint.LoadBalancedEndpointSetName = name 133 endpoint.LoadBalancerProbe = &gwacl.LoadBalancerProbe{ 134 Port: probePort, 135 Protocol: "TCP", 136 } 137 } 138 request.InputEndpoints = append(request.InputEndpoints, endpoint) 139 } 140 } 141 return api.AddRoleEndpoints(request) 142 } 143 144 // ClosePorts is specified in the Instance interface. 145 func (azInstance *azureInstance) ClosePorts(machineId string, ports []network.PortRange) error { 146 return azInstance.apiCall(true, func(api *gwacl.ManagementAPI) error { 147 return azInstance.closeEndpoints(api, ports) 148 }) 149 } 150 151 // closeEndpoints closes the endpoints in the Azure deployment. 152 func (azInstance *azureInstance) closeEndpoints(api *gwacl.ManagementAPI, portRanges []network.PortRange) error { 153 request := &gwacl.RemoveRoleEndpointsRequest{ 154 ServiceName: azInstance.serviceName(), 155 DeploymentName: azInstance.deploymentName, 156 RoleName: azInstance.roleName, 157 } 158 for _, portRange := range portRanges { 159 name := fmt.Sprintf("%s%d-%d", portRange.Protocol, portRange.FromPort, portRange.ToPort) 160 for port := portRange.FromPort; port <= portRange.ToPort; port++ { 161 request.InputEndpoints = append(request.InputEndpoints, gwacl.InputEndpoint{ 162 LocalPort: port, 163 Name: fmt.Sprintf("%s_%d", name, port), 164 Port: port, 165 Protocol: portRange.Protocol, 166 LoadBalancedEndpointSetName: name, 167 }) 168 } 169 } 170 return api.RemoveRoleEndpoints(request) 171 } 172 173 // convertEndpointsToPorts converts a slice of gwacl.InputEndpoint into a slice of network.PortRange. 174 func convertEndpointsToPortRanges(endpoints []gwacl.InputEndpoint) []network.PortRange { 175 // group ports by prefix on the endpoint name 176 portSets := make(map[string][]network.Port) 177 otherPorts := []network.Port{} 178 for _, endpoint := range endpoints { 179 port := network.Port{ 180 Protocol: strings.ToLower(endpoint.Protocol), 181 Number: endpoint.Port, 182 } 183 if strings.Contains(endpoint.Name, "_range_") { 184 prefix := strings.Split(endpoint.Name, "_range_")[0] 185 portSets[prefix] = append(portSets[prefix], port) 186 } else { 187 otherPorts = append(otherPorts, port) 188 } 189 } 190 191 portRanges := []network.PortRange{} 192 193 // convert port sets into port ranges 194 for _, ports := range portSets { 195 portRanges = append(portRanges, network.CollapsePorts(ports)...) 196 } 197 198 portRanges = append(portRanges, network.CollapsePorts(otherPorts)...) 199 network.SortPortRanges(portRanges) 200 return portRanges 201 } 202 203 // convertAndFilterEndpoints converts a slice of gwacl.InputEndpoint into a slice of network.PortRange 204 // and filters out the initial endpoints that every instance should have opened (ssh port, etc.). 205 func convertAndFilterEndpoints(endpoints []gwacl.InputEndpoint, env *azureEnviron, stateServer bool) []network.PortRange { 206 return portRangeDiff( 207 convertEndpointsToPortRanges(endpoints), 208 convertEndpointsToPortRanges(env.getInitialEndpoints(stateServer)), 209 ) 210 } 211 212 // portRangeDiff returns all port ranges that are in a but not in b. 213 func portRangeDiff(A, B []network.PortRange) (missing []network.PortRange) { 214 next: 215 for _, a := range A { 216 for _, b := range B { 217 if a == b { 218 continue next 219 } 220 } 221 missing = append(missing, a) 222 } 223 return 224 } 225 226 // Ports is specified in the Instance interface. 227 func (azInstance *azureInstance) Ports(machineId string) (ports []network.PortRange, err error) { 228 err = azInstance.apiCall(false, func(api *gwacl.ManagementAPI) error { 229 ports, err = azInstance.listPorts(api) 230 return err 231 }) 232 if ports != nil { 233 network.SortPortRanges(ports) 234 } 235 return ports, err 236 } 237 238 // listPorts returns the slice of port ranges (network.PortRange) 239 // that this machine has opened. The returned list does not contain 240 // the "initial port ranges" (i.e. the port ranges every instance 241 // shoud have opened). 242 func (azInstance *azureInstance) listPorts(api *gwacl.ManagementAPI) ([]network.PortRange, error) { 243 endpoints, err := api.ListRoleEndpoints(&gwacl.ListRoleEndpointsRequest{ 244 ServiceName: azInstance.serviceName(), 245 DeploymentName: azInstance.deploymentName, 246 RoleName: azInstance.roleName, 247 }) 248 if err != nil { 249 return nil, err 250 } 251 ports := convertAndFilterEndpoints(endpoints, azInstance.environ, azInstance.maskStateServerPorts) 252 return ports, nil 253 }