github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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/worker/firewaller" 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(c *azureManagementContext) error { 65 d, err := c.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() ([]instance.Address, error) { 87 var addrs []instance.Address 88 for i := 0; i < 2; i++ { 89 if ip := azInstance.ipAddress(); ip != "" { 90 addrs = append(addrs, instance.Address{ 91 Value: ip, 92 Type: instance.Ipv4Address, 93 NetworkName: azInstance.environ.getVirtualNetworkName(), 94 NetworkScope: instance.NetworkCloudLocal, 95 }) 96 break 97 } 98 if err := azInstance.Refresh(); err != nil { 99 return nil, err 100 } 101 } 102 host := instance.Address{ 103 Value: fmt.Sprintf("%s.%s", azInstance.serviceName(), AzureDomainName), 104 Type: instance.HostName, 105 NetworkName: "", 106 NetworkScope: instance.NetworkPublic, 107 } 108 addrs = append(addrs, host) 109 return addrs, nil 110 } 111 112 func (azInstance *azureInstance) ipAddress() string { 113 azInstance.mu.Lock() 114 defer azInstance.mu.Unlock() 115 if azInstance.roleInstance == nil { 116 // RoleInstance hasn't finished deploying. 117 return "" 118 } 119 return azInstance.roleInstance.IPAddress 120 } 121 122 // OpenPorts is specified in the Instance interface. 123 func (azInstance *azureInstance) OpenPorts(machineId string, ports []instance.Port) error { 124 return azInstance.apiCall(true, func(context *azureManagementContext) error { 125 return azInstance.openEndpoints(context, ports) 126 }) 127 } 128 129 // apiCall wraps a call to the azure API to ensure it is properly disposed, optionally locking 130 // the environment 131 func (azInstance *azureInstance) apiCall(lock bool, f func(*azureManagementContext) error) error { 132 env := azInstance.environ 133 context, err := env.getManagementAPI() 134 if err != nil { 135 return err 136 } 137 defer env.releaseManagementAPI(context) 138 if lock { 139 env.Lock() 140 defer env.Unlock() 141 } 142 return f(context) 143 } 144 145 // openEndpoints opens the endpoints in the Azure deployment. The caller is 146 // responsible for locking and unlocking the environ and releasing the 147 // management context. 148 func (azInstance *azureInstance) openEndpoints(context *azureManagementContext, ports []instance.Port) error { 149 request := &gwacl.AddRoleEndpointsRequest{ 150 ServiceName: azInstance.serviceName(), 151 DeploymentName: azInstance.deploymentName, 152 RoleName: azInstance.roleName, 153 } 154 for _, port := range ports { 155 name := fmt.Sprintf("%s%d", port.Protocol, port.Number) 156 endpoint := gwacl.InputEndpoint{ 157 LocalPort: port.Number, 158 Name: name, 159 Port: port.Number, 160 Protocol: port.Protocol, 161 } 162 if azInstance.supportsLoadBalancing() { 163 probePort := port.Number 164 if strings.ToUpper(endpoint.Protocol) == "UDP" { 165 // Load balancing needs a TCP port to probe, or an HTTP 166 // server port & path to query. For UDP, we just use the 167 // machine's SSH agent port to test machine liveness. 168 // 169 // It probably doesn't make sense to load balance most UDP 170 // protocols transparently, but that's an application level 171 // concern. 172 probePort = 22 173 } 174 endpoint.LoadBalancedEndpointSetName = name 175 endpoint.LoadBalancerProbe = &gwacl.LoadBalancerProbe{ 176 Port: probePort, 177 Protocol: "TCP", 178 } 179 } 180 request.InputEndpoints = append(request.InputEndpoints, endpoint) 181 } 182 return context.AddRoleEndpoints(request) 183 } 184 185 // ClosePorts is specified in the Instance interface. 186 func (azInstance *azureInstance) ClosePorts(machineId string, ports []instance.Port) error { 187 return azInstance.apiCall(true, func(context *azureManagementContext) error { 188 return azInstance.closeEndpoints(context, ports) 189 }) 190 } 191 192 // closeEndpoints closes the endpoints in the Azure deployment. The caller is 193 // responsible for locking and unlocking the environ and releasing the 194 // management context. 195 func (azInstance *azureInstance) closeEndpoints(context *azureManagementContext, ports []instance.Port) error { 196 request := &gwacl.RemoveRoleEndpointsRequest{ 197 ServiceName: azInstance.serviceName(), 198 DeploymentName: azInstance.deploymentName, 199 RoleName: azInstance.roleName, 200 } 201 for _, port := range ports { 202 name := fmt.Sprintf("%s%d", port.Protocol, port.Number) 203 request.InputEndpoints = append(request.InputEndpoints, gwacl.InputEndpoint{ 204 LocalPort: port.Number, 205 Name: name, 206 Port: port.Number, 207 Protocol: port.Protocol, 208 LoadBalancedEndpointSetName: name, 209 }) 210 } 211 return context.RemoveRoleEndpoints(request) 212 } 213 214 // convertEndpointsToPorts converts a slice of gwacl.InputEndpoint into a slice of instance.Port. 215 func convertEndpointsToPorts(endpoints []gwacl.InputEndpoint) []instance.Port { 216 ports := []instance.Port{} 217 for _, endpoint := range endpoints { 218 ports = append(ports, instance.Port{ 219 Protocol: strings.ToLower(endpoint.Protocol), 220 Number: endpoint.Port, 221 }) 222 } 223 return ports 224 } 225 226 // convertAndFilterEndpoints converts a slice of gwacl.InputEndpoint into a slice of instance.Port 227 // and filters out the initial endpoints that every instance should have opened (ssh port, etc.). 228 func convertAndFilterEndpoints(endpoints []gwacl.InputEndpoint, env *azureEnviron, stateServer bool) []instance.Port { 229 return firewaller.Diff( 230 convertEndpointsToPorts(endpoints), 231 convertEndpointsToPorts(env.getInitialEndpoints(stateServer)), 232 ) 233 } 234 235 // Ports is specified in the Instance interface. 236 func (azInstance *azureInstance) Ports(machineId string) (ports []instance.Port, err error) { 237 err = azInstance.apiCall(false, func(context *azureManagementContext) error { 238 ports, err = azInstance.listPorts(context) 239 return err 240 }) 241 if ports != nil { 242 instance.SortPorts(ports) 243 } 244 return ports, err 245 } 246 247 // listPorts returns the slice of ports (instance.Port) that this machine 248 // has opened. The returned list does not contain the "initial ports" 249 // (i.e. the ports every instance shoud have opened). The caller is 250 // responsible for locking and unlocking the environ and releasing the 251 // management context. 252 func (azInstance *azureInstance) listPorts(context *azureManagementContext) ([]instance.Port, error) { 253 endpoints, err := context.ListRoleEndpoints(&gwacl.ListRoleEndpointsRequest{ 254 ServiceName: azInstance.serviceName(), 255 DeploymentName: azInstance.deploymentName, 256 RoleName: azInstance.roleName, 257 }) 258 if err != nil { 259 return nil, err 260 } 261 ports := convertAndFilterEndpoints(endpoints, azInstance.environ, azInstance.maskStateServerPorts) 262 return ports, nil 263 }