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