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