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