launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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 "launchpad.net/juju-core/instance" 13 "launchpad.net/juju-core/provider/common" 14 "launchpad.net/juju-core/worker/firewaller" 15 ) 16 17 type azureInstance struct { 18 // An instance contains an Azure Service (instance==service). 19 gwacl.HostedServiceDescriptor 20 environ *azureEnviron 21 } 22 23 // azureInstance implements Instance. 24 var _ instance.Instance = (*azureInstance)(nil) 25 26 // Id is specified in the Instance interface. 27 func (azInstance *azureInstance) Id() instance.Id { 28 return instance.Id(azInstance.ServiceName) 29 } 30 31 // Status is specified in the Instance interface. 32 func (azInstance *azureInstance) Status() string { 33 return azInstance.HostedServiceDescriptor.Status 34 } 35 36 var AZURE_DOMAIN_NAME = "cloudapp.net" 37 38 // Refresh is specified in the Instance interface. 39 func (azInstance *azureInstance) Refresh() error { 40 // TODO(axw) 2013-12-16 #1261324 41 // Cache Addresses/netInfo, refresh here. 42 return nil 43 } 44 45 // Addresses is specified in the Instance interface. 46 func (azInstance *azureInstance) Addresses() ([]instance.Address, error) { 47 addrs := []instance.Address{} 48 ip, netname, err := azInstance.netInfo() 49 if err != nil { 50 return nil, err 51 } 52 if ip != "" { 53 addrs = append(addrs, instance.Address{ 54 ip, 55 instance.Ipv4Address, 56 netname, 57 instance.NetworkCloudLocal}) 58 } 59 60 name, err := azInstance.DNSName() 61 if err != nil { 62 return nil, err 63 } 64 host := instance.Address{name, instance.HostName, "", instance.NetworkPublic} 65 addrs = append(addrs, host) 66 return addrs, nil 67 } 68 69 func (azInstance *azureInstance) netInfo() (ip, netname string, err error) { 70 err = azInstance.apiCall(false, func(c *azureManagementContext) error { 71 d, err := c.GetDeployment(&gwacl.GetDeploymentRequest{ 72 ServiceName: azInstance.ServiceName, 73 DeploymentName: azInstance.ServiceName, 74 }) 75 if err != nil { 76 return err 77 } 78 switch len(d.RoleInstanceList) { 79 case 0: 80 // nothing to do, this can happen if the instances aren't finished deploying 81 return nil 82 case 1: 83 // success 84 ip = d.RoleInstanceList[0].IPAddress 85 netname = d.VirtualNetworkName 86 return nil 87 default: 88 return fmt.Errorf("Too many instances, expected one, got %d", len(d.RoleInstanceList)) 89 } 90 }) 91 if err != nil { 92 return "", "", err 93 } 94 return ip, netname, nil 95 } 96 97 // DNSName is specified in the Instance interface. 98 func (azInstance *azureInstance) DNSName() (string, error) { 99 // For deployments in the Production slot, the instance's DNS name 100 // is its service name, in the cloudapp.net domain. 101 // (For Staging deployments it's all much weirder: they get random 102 // names assigned, which somehow don't seem to resolve from the 103 // outside.) 104 name := fmt.Sprintf("%s.%s", azInstance.ServiceName, AZURE_DOMAIN_NAME) 105 return name, nil 106 } 107 108 // WaitDNSName is specified in the Instance interface. 109 func (azInstance *azureInstance) WaitDNSName() (string, error) { 110 return common.WaitDNSName(azInstance) 111 } 112 113 // OpenPorts is specified in the Instance interface. 114 func (azInstance *azureInstance) OpenPorts(machineId string, ports []instance.Port) error { 115 return azInstance.apiCall(true, func(context *azureManagementContext) error { 116 return azInstance.openEndpoints(context, ports) 117 }) 118 } 119 120 // apiCall wraps a call to the azure API to ensure it is properly disposed, optionally locking 121 // the environment 122 func (azInstance *azureInstance) apiCall(lock bool, f func(*azureManagementContext) error) error { 123 env := azInstance.environ 124 125 context, err := env.getManagementAPI() 126 if err != nil { 127 return err 128 } 129 defer env.releaseManagementAPI(context) 130 131 if lock { 132 env.Lock() 133 defer env.Unlock() 134 } 135 return f(context) 136 } 137 138 // openEndpoints opens the endpoints in the Azure deployment. The caller is 139 // responsible for locking and unlocking the environ and releasing the 140 // management context. 141 func (azInstance *azureInstance) openEndpoints(context *azureManagementContext, ports []instance.Port) error { 142 deployments, err := context.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{ 143 ServiceName: azInstance.ServiceName, 144 }) 145 if err != nil { 146 return err 147 } 148 149 for _, deployment := range deployments { 150 for _, role := range deployment.RoleList { 151 request := &gwacl.AddRoleEndpointsRequest{ 152 ServiceName: azInstance.ServiceName, 153 DeploymentName: deployment.Name, 154 RoleName: role.RoleName, 155 } 156 for _, port := range ports { 157 request.InputEndpoints = append( 158 request.InputEndpoints, gwacl.InputEndpoint{ 159 LocalPort: port.Number, 160 Name: fmt.Sprintf("%s%d", port.Protocol, port.Number), 161 Port: port.Number, 162 Protocol: port.Protocol, 163 }) 164 } 165 err := context.AddRoleEndpoints(request) 166 if err != nil { 167 return err 168 } 169 } 170 } 171 172 return nil 173 } 174 175 // ClosePorts is specified in the Instance interface. 176 func (azInstance *azureInstance) ClosePorts(machineId string, ports []instance.Port) error { 177 return azInstance.apiCall(true, func(context *azureManagementContext) error { 178 return azInstance.closeEndpoints(context, ports) 179 }) 180 } 181 182 // closeEndpoints closes the endpoints in the Azure deployment. The caller is 183 // responsible for locking and unlocking the environ and releasing the 184 // management context. 185 func (azInstance *azureInstance) closeEndpoints(context *azureManagementContext, ports []instance.Port) error { 186 deployments, err := context.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{ 187 ServiceName: azInstance.ServiceName, 188 }) 189 if err != nil { 190 return err 191 } 192 193 for _, deployment := range deployments { 194 for _, role := range deployment.RoleList { 195 request := &gwacl.RemoveRoleEndpointsRequest{ 196 ServiceName: azInstance.ServiceName, 197 DeploymentName: deployment.Name, 198 RoleName: role.RoleName, 199 } 200 for _, port := range ports { 201 request.InputEndpoints = append( 202 request.InputEndpoints, gwacl.InputEndpoint{ 203 LocalPort: port.Number, 204 Name: fmt.Sprintf("%s%d", port.Protocol, port.Number), 205 Port: port.Number, 206 Protocol: port.Protocol, 207 }) 208 } 209 err := context.RemoveRoleEndpoints(request) 210 if err != nil { 211 return err 212 } 213 } 214 } 215 216 return nil 217 } 218 219 // convertAndFilterEndpoints converts a slice of gwacl.InputEndpoint into a slice of instance.Port. 220 func convertEndpointsToPorts(endpoints []gwacl.InputEndpoint) []instance.Port { 221 ports := []instance.Port{} 222 for _, endpoint := range endpoints { 223 ports = append(ports, instance.Port{ 224 Protocol: strings.ToLower(endpoint.Protocol), 225 Number: endpoint.Port, 226 }) 227 } 228 return ports 229 } 230 231 // convertAndFilterEndpoints converts a slice of gwacl.InputEndpoint into a slice of instance.Port 232 // and filters out the initial endpoints that every instance should have opened (ssh port, etc.). 233 func convertAndFilterEndpoints(endpoints []gwacl.InputEndpoint, env *azureEnviron) []instance.Port { 234 return firewaller.Diff( 235 convertEndpointsToPorts(endpoints), 236 convertEndpointsToPorts(env.getInitialEndpoints())) 237 } 238 239 // Ports is specified in the Instance interface. 240 func (azInstance *azureInstance) Ports(machineId string) (ports []instance.Port, err error) { 241 err = azInstance.apiCall(false, func(context *azureManagementContext) error { 242 ports, err = azInstance.listPorts(context) 243 return err 244 }) 245 if ports != nil { 246 instance.SortPorts(ports) 247 } 248 return ports, err 249 } 250 251 // listPorts returns the slice of ports (instance.Port) that this machine 252 // has opened. The returned list does not contain the "initial ports" 253 // (i.e. the ports every instance shoud have opened). The caller is 254 // responsible for locking and unlocking the environ and releasing the 255 // management context. 256 func (azInstance *azureInstance) listPorts(context *azureManagementContext) ([]instance.Port, error) { 257 deployments, err := context.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{ 258 ServiceName: azInstance.ServiceName, 259 }) 260 if err != nil { 261 return nil, err 262 } 263 264 env := azInstance.environ 265 switch { 266 // Only zero or one deployment is a valid state (instance==service). 267 case len(deployments) > 1: 268 return nil, fmt.Errorf("more than one Azure deployment inside the service named %q", azInstance.ServiceName) 269 case len(deployments) == 1: 270 deployment := deployments[0] 271 switch { 272 // Only zero or one role is a valid state (instance==service). 273 case len(deployment.RoleList) > 1: 274 return nil, fmt.Errorf("more than one Azure role inside the deployment named %q", deployment.Name) 275 case len(deployment.RoleList) == 1: 276 role := deployment.RoleList[0] 277 278 endpoints, err := context.ListRoleEndpoints(&gwacl.ListRoleEndpointsRequest{ 279 ServiceName: azInstance.ServiceName, 280 DeploymentName: deployment.Name, 281 RoleName: role.RoleName, 282 }) 283 if err != nil { 284 return nil, err 285 } 286 ports := convertAndFilterEndpoints(endpoints, env) 287 return ports, nil 288 } 289 return nil, nil 290 } 291 return nil, nil 292 }