github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/provider/azure/instance.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package azure 5 6 import ( 7 "fmt" 8 "net/http" 9 "strings" 10 11 "github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest" 12 "github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest/to" 13 "github.com/Azure/azure-sdk-for-go/arm/compute" 14 "github.com/Azure/azure-sdk-for-go/arm/network" 15 16 "github.com/juju/errors" 17 "github.com/juju/juju/instance" 18 jujunetwork "github.com/juju/juju/network" 19 "github.com/juju/juju/status" 20 "github.com/juju/names" 21 ) 22 23 type azureInstance struct { 24 compute.VirtualMachine 25 env *azureEnviron 26 networkInterfaces []network.Interface 27 publicIPAddresses []network.PublicIPAddress 28 } 29 30 // Id is specified in the Instance interface. 31 func (inst *azureInstance) Id() instance.Id { 32 // Note: we use Name and not Id, since all VM operations are in 33 // terms of the VM name (qualified by resource group). The ID is 34 // an internal detail. 35 return instance.Id(to.String(inst.VirtualMachine.Name)) 36 } 37 38 // Status is specified in the Instance interface. 39 func (inst *azureInstance) Status() instance.InstanceStatus { 40 // NOTE(axw) ideally we would use the power state, but that is only 41 // available when using the "instance view". Instance view is only 42 // delivered when explicitly requested, and you can only request it 43 // when querying a single VM. This means the results of AllInstances 44 // or Instances would have the instance view missing. 45 return instance.InstanceStatus{ 46 Status: status.StatusEmpty, 47 Message: to.String(inst.Properties.ProvisioningState), 48 } 49 50 } 51 52 // setInstanceAddresses queries Azure for the NICs and public IPs associated 53 // with the given set of instances. This assumes that the instances' 54 // VirtualMachines are up-to-date, and that there are no concurrent accesses 55 // to the instances. 56 func setInstanceAddresses( 57 pipClient network.PublicIPAddressesClient, 58 resourceGroup string, 59 instances []*azureInstance, 60 nicsResult network.InterfaceListResult, 61 ) (err error) { 62 63 instanceNics := make(map[instance.Id][]network.Interface) 64 instancePips := make(map[instance.Id][]network.PublicIPAddress) 65 for _, inst := range instances { 66 instanceNics[inst.Id()] = nil 67 instancePips[inst.Id()] = nil 68 } 69 70 // When setAddresses returns without error, update each 71 // instance's network interfaces and public IP addresses. 72 setInstanceFields := func(inst *azureInstance) { 73 inst.networkInterfaces = instanceNics[inst.Id()] 74 inst.publicIPAddresses = instancePips[inst.Id()] 75 } 76 defer func() { 77 if err != nil { 78 return 79 } 80 for _, inst := range instances { 81 setInstanceFields(inst) 82 } 83 }() 84 85 // We do not rely on references because of how StopInstances works. 86 // In order to not leak resources we must not delete the virtual 87 // machine until after all of its dependencies are deleted. 88 // 89 // NICs and PIPs cannot be deleted until they have no references. 90 // Thus, we cannot delete a PIP until there is no reference to it 91 // in any NICs, and likewise we cannot delete a NIC until there 92 // is no reference to it in any virtual machine. 93 94 if nicsResult.Value != nil { 95 for _, nic := range *nicsResult.Value { 96 instanceId := instance.Id(toTags(nic.Tags)[jujuMachineNameTag]) 97 if _, ok := instanceNics[instanceId]; !ok { 98 continue 99 } 100 instanceNics[instanceId] = append(instanceNics[instanceId], nic) 101 } 102 } 103 104 pipsResult, err := pipClient.List(resourceGroup) 105 if err != nil { 106 return errors.Annotate(err, "listing public IP addresses") 107 } 108 if pipsResult.Value != nil { 109 for _, pip := range *pipsResult.Value { 110 instanceId := instance.Id(toTags(pip.Tags)[jujuMachineNameTag]) 111 if _, ok := instanceNics[instanceId]; !ok { 112 continue 113 } 114 instancePips[instanceId] = append(instancePips[instanceId], pip) 115 } 116 } 117 118 // Fields will be assigned to instances by the deferred call. 119 return nil 120 } 121 122 // Addresses is specified in the Instance interface. 123 func (inst *azureInstance) Addresses() ([]jujunetwork.Address, error) { 124 addresses := make([]jujunetwork.Address, 0, len(inst.networkInterfaces)+len(inst.publicIPAddresses)) 125 for _, nic := range inst.networkInterfaces { 126 if nic.Properties.IPConfigurations == nil { 127 continue 128 } 129 for _, ipConfiguration := range *nic.Properties.IPConfigurations { 130 privateIpAddress := ipConfiguration.Properties.PrivateIPAddress 131 if privateIpAddress == nil { 132 continue 133 } 134 addresses = append(addresses, jujunetwork.NewScopedAddress( 135 to.String(privateIpAddress), 136 jujunetwork.ScopeCloudLocal, 137 )) 138 } 139 } 140 for _, pip := range inst.publicIPAddresses { 141 if pip.Properties.IPAddress == nil { 142 continue 143 } 144 addresses = append(addresses, jujunetwork.NewScopedAddress( 145 to.String(pip.Properties.IPAddress), 146 jujunetwork.ScopePublic, 147 )) 148 } 149 return addresses, nil 150 } 151 152 // internalNetworkAddress returns the instance's jujunetwork.Address for the 153 // internal virtual network. This address is used to identify the machine in 154 // network security rules. 155 func (inst *azureInstance) internalNetworkAddress() (jujunetwork.Address, error) { 156 inst.env.mu.Lock() 157 subscriptionId := inst.env.config.subscriptionId 158 resourceGroup := inst.env.resourceGroup 159 inst.env.mu.Unlock() 160 internalSubnetId := internalSubnetId(resourceGroup, subscriptionId) 161 162 for _, nic := range inst.networkInterfaces { 163 if nic.Properties.IPConfigurations == nil { 164 continue 165 } 166 for _, ipConfiguration := range *nic.Properties.IPConfigurations { 167 if ipConfiguration.Properties.Subnet == nil { 168 continue 169 } 170 if strings.ToLower(to.String(ipConfiguration.Properties.Subnet.ID)) != strings.ToLower(internalSubnetId) { 171 continue 172 } 173 privateIpAddress := ipConfiguration.Properties.PrivateIPAddress 174 if privateIpAddress == nil { 175 continue 176 } 177 return jujunetwork.NewScopedAddress( 178 to.String(privateIpAddress), 179 jujunetwork.ScopeCloudLocal, 180 ), nil 181 } 182 } 183 return jujunetwork.Address{}, errors.NotFoundf("internal network address") 184 } 185 186 // OpenPorts is specified in the Instance interface. 187 func (inst *azureInstance) OpenPorts(machineId string, ports []jujunetwork.PortRange) error { 188 inst.env.mu.Lock() 189 nsgClient := network.SecurityGroupsClient{inst.env.network} 190 securityRuleClient := network.SecurityRulesClient{inst.env.network} 191 inst.env.mu.Unlock() 192 internalNetworkAddress, err := inst.internalNetworkAddress() 193 if err != nil { 194 return errors.Trace(err) 195 } 196 197 securityGroupName := internalSecurityGroupName 198 var nsg network.SecurityGroup 199 if err := inst.env.callAPI(func() (autorest.Response, error) { 200 var err error 201 nsg, err = nsgClient.Get(inst.env.resourceGroup, securityGroupName) 202 return nsg.Response, err 203 }); err != nil { 204 return errors.Annotate(err, "querying network security group") 205 } 206 207 var securityRules []network.SecurityRule 208 if nsg.Properties.SecurityRules != nil { 209 securityRules = *nsg.Properties.SecurityRules 210 } else { 211 nsg.Properties.SecurityRules = &securityRules 212 } 213 214 // Create rules one at a time; this is necessary to avoid trampling 215 // on changes made by the provisioner. We still record rules in the 216 // NSG in memory, so we can easily tell which priorities are available. 217 vmName := resourceName(names.NewMachineTag(machineId)) 218 prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName)) 219 for _, ports := range ports { 220 ruleName := securityRuleName(prefix, ports) 221 222 // Check if the rule already exists; OpenPorts must be idempotent. 223 var found bool 224 for _, rule := range securityRules { 225 if to.String(rule.Name) == ruleName { 226 found = true 227 break 228 } 229 } 230 if found { 231 logger.Debugf("security rule %q already exists", ruleName) 232 continue 233 } 234 logger.Debugf("creating security rule %q", ruleName) 235 236 priority, err := nextSecurityRulePriority(nsg, securityRuleInternalMax+1, securityRuleMax) 237 if err != nil { 238 return errors.Annotatef(err, "getting security rule priority for %s", ports) 239 } 240 241 var protocol network.SecurityRuleProtocol 242 switch ports.Protocol { 243 case "tcp": 244 protocol = network.TCP 245 case "udp": 246 protocol = network.UDP 247 default: 248 return errors.Errorf("invalid protocol %q", ports.Protocol) 249 } 250 251 var portRange string 252 if ports.FromPort != ports.ToPort { 253 portRange = fmt.Sprintf("%d-%d", ports.FromPort, ports.ToPort) 254 } else { 255 portRange = fmt.Sprint(ports.FromPort) 256 } 257 258 rule := network.SecurityRule{ 259 Properties: &network.SecurityRulePropertiesFormat{ 260 Description: to.StringPtr(ports.String()), 261 Protocol: protocol, 262 SourcePortRange: to.StringPtr("*"), 263 DestinationPortRange: to.StringPtr(portRange), 264 SourceAddressPrefix: to.StringPtr("*"), 265 DestinationAddressPrefix: to.StringPtr(internalNetworkAddress.Value), 266 Access: network.Allow, 267 Priority: to.IntPtr(priority), 268 Direction: network.Inbound, 269 }, 270 } 271 if err := inst.env.callAPI(func() (autorest.Response, error) { 272 result, err := securityRuleClient.CreateOrUpdate( 273 inst.env.resourceGroup, securityGroupName, ruleName, rule, 274 ) 275 return result.Response, err 276 }); err != nil { 277 return errors.Annotatef(err, "creating security rule for %s", ports) 278 } 279 securityRules = append(securityRules, rule) 280 } 281 return nil 282 } 283 284 // ClosePorts is specified in the Instance interface. 285 func (inst *azureInstance) ClosePorts(machineId string, ports []jujunetwork.PortRange) error { 286 inst.env.mu.Lock() 287 securityRuleClient := network.SecurityRulesClient{inst.env.network} 288 inst.env.mu.Unlock() 289 securityGroupName := internalSecurityGroupName 290 291 // Delete rules one at a time; this is necessary to avoid trampling 292 // on changes made by the provisioner. 293 vmName := resourceName(names.NewMachineTag(machineId)) 294 prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName)) 295 for _, ports := range ports { 296 ruleName := securityRuleName(prefix, ports) 297 logger.Debugf("deleting security rule %q", ruleName) 298 var result autorest.Response 299 if err := inst.env.callAPI(func() (autorest.Response, error) { 300 var err error 301 result, err = securityRuleClient.Delete( 302 inst.env.resourceGroup, securityGroupName, ruleName, 303 ) 304 return result, err 305 }); err != nil { 306 if result.Response == nil || result.StatusCode != http.StatusNotFound { 307 return errors.Annotatef(err, "deleting security rule %q", ruleName) 308 } 309 } 310 } 311 return nil 312 } 313 314 // Ports is specified in the Instance interface. 315 func (inst *azureInstance) Ports(machineId string) (ports []jujunetwork.PortRange, err error) { 316 inst.env.mu.Lock() 317 nsgClient := network.SecurityGroupsClient{inst.env.network} 318 inst.env.mu.Unlock() 319 320 securityGroupName := internalSecurityGroupName 321 var nsg network.SecurityGroup 322 if err := inst.env.callAPI(func() (autorest.Response, error) { 323 var err error 324 nsg, err = nsgClient.Get(inst.env.resourceGroup, securityGroupName) 325 return nsg.Response, err 326 }); err != nil { 327 return nil, errors.Annotate(err, "querying network security group") 328 } 329 if nsg.Properties.SecurityRules == nil { 330 return nil, nil 331 } 332 333 vmName := resourceName(names.NewMachineTag(machineId)) 334 prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName)) 335 for _, rule := range *nsg.Properties.SecurityRules { 336 if rule.Properties.Direction != network.Inbound { 337 continue 338 } 339 if rule.Properties.Access != network.Allow { 340 continue 341 } 342 if to.Int(rule.Properties.Priority) <= securityRuleInternalMax { 343 continue 344 } 345 if !strings.HasPrefix(to.String(rule.Name), prefix) { 346 continue 347 } 348 349 var portRange jujunetwork.PortRange 350 if *rule.Properties.DestinationPortRange == "*" { 351 portRange.FromPort = 0 352 portRange.ToPort = 65535 353 } else { 354 portRange, err = jujunetwork.ParsePortRange( 355 *rule.Properties.DestinationPortRange, 356 ) 357 if err != nil { 358 return nil, errors.Annotatef( 359 err, "parsing port range for security rule %q", 360 to.String(rule.Name), 361 ) 362 } 363 } 364 365 var protocols []string 366 switch rule.Properties.Protocol { 367 case network.TCP: 368 protocols = []string{"tcp"} 369 case network.UDP: 370 protocols = []string{"udp"} 371 default: 372 protocols = []string{"tcp", "udp"} 373 } 374 for _, protocol := range protocols { 375 portRange.Protocol = protocol 376 ports = append(ports, portRange) 377 } 378 } 379 return ports, nil 380 } 381 382 // deleteInstanceNetworkSecurityRules deletes network security rules in the 383 // internal network security group that correspond to the specified machine. 384 // 385 // This is expected to delete *all* security rules related to the instance, 386 // i.e. both the ones opened by OpenPorts above, and the ones opened for API 387 // access. 388 func deleteInstanceNetworkSecurityRules( 389 resourceGroup string, id instance.Id, 390 nsgClient network.SecurityGroupsClient, 391 securityRuleClient network.SecurityRulesClient, 392 callAPI callAPIFunc, 393 ) error { 394 var nsg network.SecurityGroup 395 if err := callAPI(func() (autorest.Response, error) { 396 var err error 397 nsg, err = nsgClient.Get(resourceGroup, internalSecurityGroupName) 398 return nsg.Response, err 399 }); err != nil { 400 return errors.Annotate(err, "querying network security group") 401 } 402 if nsg.Properties.SecurityRules == nil { 403 return nil 404 } 405 prefix := instanceNetworkSecurityRulePrefix(id) 406 for _, rule := range *nsg.Properties.SecurityRules { 407 ruleName := to.String(rule.Name) 408 if !strings.HasPrefix(ruleName, prefix) { 409 continue 410 } 411 result, err := securityRuleClient.Delete( 412 resourceGroup, 413 internalSecurityGroupName, 414 ruleName, 415 ) 416 if err != nil { 417 if result.Response == nil || result.StatusCode != http.StatusNotFound { 418 return errors.Annotatef(err, "deleting security rule %q", ruleName) 419 } 420 } 421 } 422 return nil 423 } 424 425 // instanceNetworkSecurityRulePrefix returns the unique prefix for network 426 // security rule names that relate to the instance with the given ID. 427 func instanceNetworkSecurityRulePrefix(id instance.Id) string { 428 return string(id) + "-" 429 } 430 431 // securityRuleName returns the security rule name for the given port range, 432 // and prefix returned by instanceNetworkSecurityRulePrefix. 433 func securityRuleName(prefix string, ports jujunetwork.PortRange) string { 434 ruleName := fmt.Sprintf("%s%s-%d", prefix, ports.Protocol, ports.FromPort) 435 if ports.FromPort != ports.ToPort { 436 ruleName += fmt.Sprintf("-%d", ports.ToPort) 437 } 438 return ruleName 439 }